Repository: ibc/mediasoup Branch: v3 Commit: f14a6d4d16ca Files: 1274 Total size: 6.5 MB Directory structure: gitextract_nw9bolxa/ ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── Bug_Report.md │ │ ├── Feature_Request.md │ │ └── config.yml │ └── workflows/ │ ├── mediasoup-codeql.yaml │ ├── mediasoup-node.yaml │ ├── mediasoup-rust.yaml │ ├── mediasoup-worker-clang-tidy.yaml │ ├── mediasoup-worker-fuzzer.yaml │ ├── mediasoup-worker-prebuild.yaml │ └── mediasoup-worker.yaml ├── .gitignore ├── .npmrc ├── .prettierrc.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── doc/ │ ├── Building.md │ ├── Charts.md │ ├── Closures.md │ ├── Fuzzer.md │ ├── README.md │ ├── RTCP.md │ └── Rust-crates.md ├── eslint.config.mjs ├── jest.config.mjs ├── knip.config.mjs ├── node/ │ └── src/ │ ├── ActiveSpeakerObserver.ts │ ├── ActiveSpeakerObserverTypes.ts │ ├── AudioLevelObserver.ts │ ├── AudioLevelObserverTypes.ts │ ├── Channel.ts │ ├── Consumer.ts │ ├── ConsumerTypes.ts │ ├── DataConsumer.ts │ ├── DataConsumerTypes.ts │ ├── DataProducer.ts │ ├── DataProducerTypes.ts │ ├── DirectTransport.ts │ ├── DirectTransportTypes.ts │ ├── Logger.ts │ ├── PipeTransport.ts │ ├── PipeTransportTypes.ts │ ├── PlainTransport.ts │ ├── PlainTransportTypes.ts │ ├── Producer.ts │ ├── ProducerTypes.ts │ ├── Router.ts │ ├── RouterTypes.ts │ ├── RtpObserver.ts │ ├── RtpObserverTypes.ts │ ├── Transport.ts │ ├── TransportTypes.ts │ ├── WebRtcServer.ts │ ├── WebRtcServerTypes.ts │ ├── WebRtcTransport.ts │ ├── WebRtcTransportTypes.ts │ ├── Worker.ts │ ├── WorkerTypes.ts │ ├── enhancedEvents.ts │ ├── errors.ts │ ├── extras.ts │ ├── fbsUtils.ts │ ├── index.ts │ ├── indexTypes.ts │ ├── ortc.ts │ ├── rtpParametersFbsUtils.ts │ ├── rtpParametersTypes.ts │ ├── rtpStreamStatsFbsUtils.ts │ ├── rtpStreamStatsTypes.ts │ ├── scalabilityModesTypes.ts │ ├── scalabilityModesUtils.ts │ ├── sctpParametersFbsUtils.ts │ ├── sctpParametersTypes.ts │ ├── srtpParametersFbsUtils.ts │ ├── srtpParametersTypes.ts │ ├── supportedRtpCapabilities.ts │ ├── test/ │ │ ├── data/ │ │ │ ├── dtls-cert.pem │ │ │ └── dtls-key.pem │ │ ├── test-ActiveSpeakerObserver.ts │ │ ├── test-AudioLevelObserver.ts │ │ ├── test-Consumer.ts │ │ ├── test-DataConsumer.ts │ │ ├── test-DataProducer.ts │ │ ├── test-DirectTransport.ts │ │ ├── test-PipeTransport.ts │ │ ├── test-PlainTransport.ts │ │ ├── test-Producer.ts │ │ ├── test-Router.ts │ │ ├── test-WebRtcServer.ts │ │ ├── test-WebRtcTransport.ts │ │ ├── test-Worker.ts │ │ ├── test-mediasoup.ts │ │ ├── test-multiopus.ts │ │ ├── test-ortc.ts │ │ └── test-werift-sctp.ts │ ├── types.ts │ └── utils.ts ├── npm-scripts.mjs ├── package.json ├── rust/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── benches/ │ │ ├── direct_data.rs │ │ └── producer.rs │ ├── examples/ │ │ ├── echo.rs │ │ ├── multiopus.rs │ │ ├── readme.md │ │ ├── svc-simulcast.rs │ │ └── videoroom.rs │ ├── examples-frontend/ │ │ ├── echo/ │ │ │ ├── .eslintrc.js │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ ├── tsconfig.json │ │ │ └── webpack.config.js │ │ ├── multiopus/ │ │ │ ├── .eslintrc.js │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ ├── tsconfig.json │ │ │ └── webpack.config.js │ │ ├── readme.md │ │ ├── svc-simulcast/ │ │ │ ├── .eslintrc.js │ │ │ ├── index.html │ │ │ ├── package.json │ │ │ ├── src/ │ │ │ │ └── index.ts │ │ │ ├── tsconfig.json │ │ │ └── webpack.config.js │ │ └── videoroom/ │ │ ├── .eslintrc.js │ │ ├── index.html │ │ ├── package.json │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── webpack.config.js │ ├── src/ │ │ ├── data_structures.rs │ │ ├── fbs.rs │ │ ├── lib.rs │ │ ├── macros.rs │ │ ├── messages.rs │ │ ├── ortc/ │ │ │ └── tests.rs │ │ ├── ortc.rs │ │ ├── prelude.rs │ │ ├── router/ │ │ │ ├── active_speaker_observer/ │ │ │ │ └── tests.rs │ │ │ ├── active_speaker_observer.rs │ │ │ ├── audio_level_observer/ │ │ │ │ └── tests.rs │ │ │ ├── audio_level_observer.rs │ │ │ ├── consumer/ │ │ │ │ └── tests.rs │ │ │ ├── consumer.rs │ │ │ ├── data_consumer/ │ │ │ │ └── tests.rs │ │ │ ├── data_consumer.rs │ │ │ ├── data_producer/ │ │ │ │ └── tests.rs │ │ │ ├── data_producer.rs │ │ │ ├── direct_transport/ │ │ │ │ └── tests.rs │ │ │ ├── direct_transport.rs │ │ │ ├── pipe_transport/ │ │ │ │ └── tests.rs │ │ │ ├── pipe_transport.rs │ │ │ ├── plain_transport/ │ │ │ │ └── tests.rs │ │ │ ├── plain_transport.rs │ │ │ ├── producer/ │ │ │ │ └── tests.rs │ │ │ ├── producer.rs │ │ │ ├── rtp_observer.rs │ │ │ ├── tests.rs │ │ │ ├── transport.rs │ │ │ ├── webrtc_transport/ │ │ │ │ └── tests.rs │ │ │ └── webrtc_transport.rs │ │ ├── router.rs │ │ ├── rtp_parameters.rs │ │ ├── scalability_modes.rs │ │ ├── sctp_parameters.rs │ │ ├── srtp_parameters.rs │ │ ├── supported_rtp_capabilities.rs │ │ ├── webrtc_server/ │ │ │ └── tests.rs │ │ ├── webrtc_server.rs │ │ ├── worker/ │ │ │ ├── channel.rs │ │ │ ├── common.rs │ │ │ ├── utils/ │ │ │ │ ├── channel_read_fn.rs │ │ │ │ └── channel_write_fn.rs │ │ │ └── utils.rs │ │ ├── worker.rs │ │ ├── worker_manager/ │ │ │ └── tests.rs │ │ └── worker_manager.rs │ ├── tests/ │ │ └── integration/ │ │ ├── active_speaker_observer.rs │ │ ├── audio_level_observer.rs │ │ ├── consumer.rs │ │ ├── data/ │ │ │ ├── dtls-cert.pem │ │ │ └── dtls-key.pem │ │ ├── data_consumer.rs │ │ ├── data_producer.rs │ │ ├── direct_transport.rs │ │ ├── main.rs │ │ ├── multiopus.rs │ │ ├── pipe_transport.rs │ │ ├── plain_transport.rs │ │ ├── producer.rs │ │ ├── router.rs │ │ ├── smoke.rs │ │ ├── webrtc_server.rs │ │ ├── webrtc_transport.rs │ │ └── worker.rs │ └── types/ │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── data_structures/ │ │ └── tests.rs │ ├── data_structures.rs │ ├── lib.rs │ ├── rtp_parameters/ │ │ └── tests.rs │ ├── rtp_parameters.rs │ ├── scalability_modes/ │ │ └── tests.rs │ ├── scalability_modes.rs │ ├── sctp_parameters.rs │ └── srtp_parameters.rs ├── rust-toolchain.toml ├── tsconfig.json └── worker/ ├── .clang-format ├── .clang-tidy ├── .clangd ├── Cargo.toml ├── Dockerfile ├── Dockerfile.386 ├── Dockerfile.alpine ├── Makefile ├── build.rs ├── deps/ │ ├── libwebrtc/ │ │ ├── LICENSE │ │ ├── PATENTS │ │ ├── README.md │ │ ├── libwebrtc/ │ │ │ ├── api/ │ │ │ │ ├── bitrate_constraints.h │ │ │ │ ├── network_state_predictor.cc │ │ │ │ ├── network_state_predictor.h │ │ │ │ ├── transport/ │ │ │ │ │ ├── bitrate_settings.cc │ │ │ │ │ ├── bitrate_settings.h │ │ │ │ │ ├── field_trial_based_config.cc │ │ │ │ │ ├── field_trial_based_config.h │ │ │ │ │ ├── goog_cc_factory.cc │ │ │ │ │ ├── goog_cc_factory.h │ │ │ │ │ ├── network_control.h │ │ │ │ │ ├── network_types.cc │ │ │ │ │ ├── network_types.h │ │ │ │ │ └── webrtc_key_value_config.h │ │ │ │ └── units/ │ │ │ │ ├── data_rate.cc │ │ │ │ ├── data_rate.h │ │ │ │ ├── data_size.cc │ │ │ │ ├── data_size.h │ │ │ │ ├── frequency.cc │ │ │ │ ├── frequency.h │ │ │ │ ├── time_delta.cc │ │ │ │ ├── time_delta.h │ │ │ │ ├── timestamp.cc │ │ │ │ └── timestamp.h │ │ │ ├── call/ │ │ │ │ ├── rtp_transport_controller_send.cc │ │ │ │ ├── rtp_transport_controller_send.h │ │ │ │ └── rtp_transport_controller_send_interface.h │ │ │ ├── mediasoup_helpers.h │ │ │ ├── modules/ │ │ │ │ ├── bitrate_controller/ │ │ │ │ │ ├── loss_based_bandwidth_estimation.cc │ │ │ │ │ ├── loss_based_bandwidth_estimation.h │ │ │ │ │ ├── send_side_bandwidth_estimation.cc │ │ │ │ │ └── send_side_bandwidth_estimation.h │ │ │ │ ├── congestion_controller/ │ │ │ │ │ ├── goog_cc/ │ │ │ │ │ │ ├── acknowledged_bitrate_estimator.cc │ │ │ │ │ │ ├── acknowledged_bitrate_estimator.h │ │ │ │ │ │ ├── alr_detector.cc │ │ │ │ │ │ ├── alr_detector.h │ │ │ │ │ │ ├── bitrate_estimator.cc │ │ │ │ │ │ ├── bitrate_estimator.h │ │ │ │ │ │ ├── congestion_window_pushback_controller.cc │ │ │ │ │ │ ├── congestion_window_pushback_controller.h │ │ │ │ │ │ ├── delay_based_bwe.cc │ │ │ │ │ │ ├── delay_based_bwe.h │ │ │ │ │ │ ├── delay_increase_detector_interface.h │ │ │ │ │ │ ├── goog_cc_network_control.cc │ │ │ │ │ │ ├── goog_cc_network_control.h │ │ │ │ │ │ ├── link_capacity_estimator.cc │ │ │ │ │ │ ├── link_capacity_estimator.h │ │ │ │ │ │ ├── median_slope_estimator.cc │ │ │ │ │ │ ├── median_slope_estimator.h │ │ │ │ │ │ ├── probe_bitrate_estimator.cc │ │ │ │ │ │ ├── probe_bitrate_estimator.h │ │ │ │ │ │ ├── probe_controller.cc │ │ │ │ │ │ ├── probe_controller.h │ │ │ │ │ │ ├── trendline_estimator.cc │ │ │ │ │ │ └── trendline_estimator.h │ │ │ │ │ └── rtp/ │ │ │ │ │ ├── control_handler.cc │ │ │ │ │ ├── control_handler.h │ │ │ │ │ ├── send_time_history.cc │ │ │ │ │ ├── send_time_history.h │ │ │ │ │ ├── transport_feedback_adapter.cc │ │ │ │ │ └── transport_feedback_adapter.h │ │ │ │ ├── include/ │ │ │ │ │ └── module_common_types_public.h │ │ │ │ ├── pacing/ │ │ │ │ │ ├── bitrate_prober.cc │ │ │ │ │ ├── bitrate_prober.h │ │ │ │ │ ├── interval_budget.cc │ │ │ │ │ ├── interval_budget.h │ │ │ │ │ ├── paced_sender.cc │ │ │ │ │ ├── paced_sender.h │ │ │ │ │ └── packet_router.h │ │ │ │ ├── remote_bitrate_estimator/ │ │ │ │ │ ├── aimd_rate_control.cc │ │ │ │ │ ├── aimd_rate_control.h │ │ │ │ │ ├── bwe_defines.cc │ │ │ │ │ ├── include/ │ │ │ │ │ │ ├── bwe_defines.h │ │ │ │ │ │ └── remote_bitrate_estimator.h │ │ │ │ │ ├── inter_arrival.cc │ │ │ │ │ ├── inter_arrival.h │ │ │ │ │ ├── overuse_detector.cc │ │ │ │ │ ├── overuse_detector.h │ │ │ │ │ ├── overuse_estimator.cc │ │ │ │ │ ├── overuse_estimator.h │ │ │ │ │ ├── remote_bitrate_estimator_abs_send_time.cc │ │ │ │ │ └── remote_bitrate_estimator_abs_send_time.h │ │ │ │ └── rtp_rtcp/ │ │ │ │ ├── include/ │ │ │ │ │ ├── rtp_rtcp_defines.cc │ │ │ │ │ └── rtp_rtcp_defines.h │ │ │ │ └── source/ │ │ │ │ └── rtp_packet/ │ │ │ │ └── transport_feedback.h │ │ │ ├── rtc_base/ │ │ │ │ ├── constructor_magic.h │ │ │ │ ├── experiments/ │ │ │ │ │ ├── alr_experiment.cc │ │ │ │ │ ├── alr_experiment.h │ │ │ │ │ ├── field_trial_parser.cc │ │ │ │ │ ├── field_trial_parser.h │ │ │ │ │ ├── field_trial_units.cc │ │ │ │ │ ├── field_trial_units.h │ │ │ │ │ ├── rate_control_settings.cc │ │ │ │ │ └── rate_control_settings.h │ │ │ │ ├── network/ │ │ │ │ │ ├── sent_packet.cc │ │ │ │ │ └── sent_packet.h │ │ │ │ ├── numerics/ │ │ │ │ │ ├── percentile_filter.h │ │ │ │ │ ├── safe_compare.h │ │ │ │ │ ├── safe_conversions.h │ │ │ │ │ ├── safe_conversions_impl.h │ │ │ │ │ └── safe_minmax.h │ │ │ │ ├── rate_statistics.cc │ │ │ │ ├── rate_statistics.h │ │ │ │ ├── system/ │ │ │ │ │ └── unused.h │ │ │ │ ├── type_traits.h │ │ │ │ └── units/ │ │ │ │ └── unit_base.h │ │ │ └── system_wrappers/ │ │ │ └── source/ │ │ │ ├── field_trial.cc │ │ │ └── field_trial.h │ │ ├── libwebrtc.gyp │ │ └── meson.build │ └── webrtc-fuzzer-corpora/ │ ├── README.md │ ├── add_sha1.sh │ ├── corpora/ │ │ ├── agc-corpus/ │ │ │ ├── agc-1 │ │ │ ├── agc-2 │ │ │ ├── agc-3 │ │ │ └── agc-4 │ │ ├── audio_processing-corpus/ │ │ │ ├── audio-processing-0 │ │ │ ├── audio-processing-1 │ │ │ ├── audio-processing-2 │ │ │ └── audio-processing-3 │ │ ├── mdns-corpus/ │ │ │ ├── 1.mdns │ │ │ ├── 10.mdns │ │ │ ├── 11.mdns │ │ │ ├── 12.mdns │ │ │ ├── 13.mdns │ │ │ ├── 14.mdns │ │ │ ├── 15.mdns │ │ │ ├── 16.mdns │ │ │ ├── 17.mdns │ │ │ ├── 18.mdns │ │ │ ├── 19.mdns │ │ │ ├── 2.mdns │ │ │ ├── 20.mdns │ │ │ ├── 3.mdns │ │ │ ├── 4.mdns │ │ │ ├── 5.mdns │ │ │ ├── 6.mdns │ │ │ ├── 7.mdns │ │ │ ├── 8.mdns │ │ │ └── 9.mdns │ │ ├── pseudotcp-corpus/ │ │ │ └── 785b96587d0eb44dd5d75b7a886f37e2ac504511 │ │ ├── rtcp-corpus/ │ │ │ ├── 0.rtcp │ │ │ ├── 1.rtcp │ │ │ ├── 10.rtcp │ │ │ ├── 11.rtcp │ │ │ ├── 12.rtcp │ │ │ ├── 13.rtcp │ │ │ ├── 14.rtcp │ │ │ ├── 15.rtcp │ │ │ ├── 16.rtcp │ │ │ ├── 17.rtcp │ │ │ ├── 18.rtcp │ │ │ ├── 19.rtcp │ │ │ ├── 2.rtcp │ │ │ ├── 20.rtcp │ │ │ ├── 21.rtcp │ │ │ ├── 22.rtcp │ │ │ ├── 23.rtcp │ │ │ ├── 24.rtcp │ │ │ ├── 25.rtcp │ │ │ ├── 26.rtcp │ │ │ ├── 27.rtcp │ │ │ ├── 28.rtcp │ │ │ ├── 29.rtcp │ │ │ ├── 3.rtcp │ │ │ ├── 30.rtcp │ │ │ ├── 31.rtcp │ │ │ ├── 32.rtcp │ │ │ ├── 33.rtcp │ │ │ ├── 34.rtcp │ │ │ ├── 35.rtcp │ │ │ ├── 36.rtcp │ │ │ ├── 37.rtcp │ │ │ ├── 38.rtcp │ │ │ ├── 39.rtcp │ │ │ ├── 4.rtcp │ │ │ ├── 40.rtcp │ │ │ ├── 41.rtcp │ │ │ ├── 42.rtcp │ │ │ ├── 43.rtcp │ │ │ ├── 44.rtcp │ │ │ ├── 45.rtcp │ │ │ ├── 46.rtcp │ │ │ ├── 47.rtcp │ │ │ ├── 48.rtcp │ │ │ ├── 49.rtcp │ │ │ ├── 5.rtcp │ │ │ ├── 50.rtcp │ │ │ ├── 51.rtcp │ │ │ ├── 52.rtcp │ │ │ ├── 53.rtcp │ │ │ ├── 54.rtcp │ │ │ ├── 55.rtcp │ │ │ ├── 56.rtcp │ │ │ ├── 57.rtcp │ │ │ ├── 58.rtcp │ │ │ ├── 59.rtcp │ │ │ ├── 6.rtcp │ │ │ ├── 60.rtcp │ │ │ ├── 61.rtcp │ │ │ ├── 62.rtcp │ │ │ ├── 63.rtcp │ │ │ ├── 64.rtcp │ │ │ ├── 65.rtcp │ │ │ ├── 66.rtcp │ │ │ ├── 7.rtcp │ │ │ ├── 8.rtcp │ │ │ ├── 9.rtcp │ │ │ ├── fir.rtcp │ │ │ ├── pli.rtcp │ │ │ ├── remb.rtcp │ │ │ ├── rr-remb.rtcp │ │ │ ├── rr-sdes-bye.rtcp │ │ │ ├── rr.rtcp │ │ │ ├── sr-sdes.rtcp │ │ │ └── twcc-feedback.rtcp │ │ ├── rtp-corpus/ │ │ │ ├── rtp-0 │ │ │ ├── rtp-1 │ │ │ ├── rtp-2 │ │ │ ├── rtp-3 │ │ │ ├── rtp-4 │ │ │ ├── rtp-5 │ │ │ ├── rtp-6 │ │ │ └── rtp-7 │ │ ├── sdp-corpus/ │ │ │ ├── 10.sdp │ │ │ ├── 11.sdp │ │ │ ├── 12.sdp │ │ │ ├── 13.sdp │ │ │ ├── 14.sdp │ │ │ ├── 15.sdp │ │ │ ├── 16.sdp │ │ │ ├── 17.sdp │ │ │ ├── 18.sdp │ │ │ ├── 19.sdp │ │ │ ├── 2.sdp │ │ │ ├── 20.sdp │ │ │ ├── 21.sdp │ │ │ ├── 22.sdp │ │ │ ├── 23.sdp │ │ │ ├── 24.sdp │ │ │ ├── 25.sdp │ │ │ ├── 26.sdp │ │ │ ├── 27.sdp │ │ │ ├── 28.sdp │ │ │ ├── 29.sdp │ │ │ ├── 3.sdp │ │ │ ├── 30.sdp │ │ │ ├── 31.sdp │ │ │ ├── 32.sdp │ │ │ ├── 33.sdp │ │ │ ├── 34.sdp │ │ │ ├── 35.sdp │ │ │ ├── 36.sdp │ │ │ ├── 37.sdp │ │ │ ├── 38.sdp │ │ │ ├── 39.sdp │ │ │ ├── 4.sdp │ │ │ ├── 40.sdp │ │ │ ├── 41.sdp │ │ │ ├── 42.sdp │ │ │ ├── 43.sdp │ │ │ ├── 44.sdp │ │ │ ├── 45.sdp │ │ │ ├── 46.sdp │ │ │ ├── 47.sdp │ │ │ ├── 48.sdp │ │ │ ├── 49.sdp │ │ │ ├── 5.sdp │ │ │ ├── 50.sdp │ │ │ ├── 51.sdp │ │ │ ├── 52.sdp │ │ │ ├── 53.sdp │ │ │ ├── 54.sdp │ │ │ ├── 55.sdp │ │ │ ├── 6.sdp │ │ │ ├── 7.sdp │ │ │ ├── 8.sdp │ │ │ ├── 9.sdp │ │ │ ├── firefox-1.sdp │ │ │ ├── firefox-2.sdp │ │ │ ├── opera-1.sdp │ │ │ ├── opera-2.sdp │ │ │ ├── unittest-1.sdp │ │ │ ├── unittest-2.sdp │ │ │ ├── unittest-3.sdp │ │ │ ├── unittest-4.sdp │ │ │ ├── unittest-5.sdp │ │ │ ├── unittest-6.sdp │ │ │ ├── unittest-7.sdp │ │ │ ├── unittest-8.sdp │ │ │ └── unittest-9.sdp │ │ └── stun-corpus/ │ │ ├── 0.stun │ │ ├── 1.stun │ │ ├── 10.stun │ │ ├── 11.stun │ │ ├── 12.stun │ │ ├── 13.stun │ │ ├── 14.stun │ │ ├── 15.stun │ │ ├── 16.stun │ │ ├── 17.stun │ │ ├── 2.stun │ │ ├── 3.stun │ │ ├── 4.stun │ │ ├── 5.stun │ │ ├── 6.stun │ │ ├── 7.stun │ │ ├── 8.stun │ │ ├── 9.stun │ │ └── validator-crash-1.stun │ └── reports/ │ ├── crashes/ │ │ ├── .placeholder │ │ ├── rtcp/ │ │ │ ├── crash-1640b2f21ba20409d930e9653052d579d450073a │ │ │ ├── crash-16b8706455b637b7696aeb56ed40dad8f90d81d0 │ │ │ ├── crash-17ccf761d298d6a703f71627197c5f1adcf57140 │ │ │ ├── crash-3762f3b9f11328e939e577de46e20a3fb0ccc324 │ │ │ ├── crash-461a0e9201a7ea5ea6a43511571bdafce10b8185 │ │ │ ├── crash-7257232c6e9efe6362b921117ad3cd5d8170ec0d │ │ │ ├── crash-75a5a7739802ac20cbe2937d6206348ffde23605 │ │ │ ├── crash-85b6e5d82d61837df67df0e333cb2392044a47c6 │ │ │ ├── crash-c7a1f348bd6f9422caeb41079e46331551fd2587 │ │ │ └── crash-daf57e58c2552e5cf091b0b92aa9f4ab2d4a5b4a │ │ └── rtp/ │ │ ├── crash-15d89650c0a728a6431c10c3adeb8e9a2484af83 │ │ ├── crash-1e7e56a8894a7dc2c735a0732429930f4e4a9232 │ │ ├── crash-42df1e99b29f5c2e2a881c257a31ab3a3e76c650 │ │ ├── crash-5b35219e5f366ac6577599c82bc54cc33f7bbba4 │ │ ├── crash-6d9f1846dbb7acbe5dcb70623e5ac9ade871de1a │ │ ├── crash-7e2d460edd5d5d7f5548922f10489f468d1638bf │ │ ├── crash-7e3b3351f85a64bf0932a9ec7faca66717991c0f │ │ ├── crash-9b6dfaedb1a8baca07492a8be205582db2e81ae3 │ │ ├── crash-af6dff495b4c6b06250561160b019a33de972478 │ │ ├── crash-b9a848d594a7b1d0a0d698d10d2fed4f2f96c038 │ │ ├── crash-ba25a83b65e08f750cf832e205e16f1d298ea614 │ │ └── crash-c14c0b082685dee2e98ab564c39883932f869cdd │ ├── memory-leaks/ │ │ └── .placeholder │ └── timeouts/ │ └── .placeholder ├── fbs/ │ ├── activeSpeakerObserver.fbs │ ├── audioLevelObserver.fbs │ ├── common.fbs │ ├── consumer.fbs │ ├── dataConsumer.fbs │ ├── dataProducer.fbs │ ├── directTransport.fbs │ ├── liburing.fbs │ ├── log.fbs │ ├── meson.build │ ├── message.fbs │ ├── notification.fbs │ ├── pipeTransport.fbs │ ├── plainTransport.fbs │ ├── producer.fbs │ ├── request.fbs │ ├── response.fbs │ ├── router.fbs │ ├── rtpObserver.fbs │ ├── rtpPacket.fbs │ ├── rtpParameters.fbs │ ├── rtpStream.fbs │ ├── rtxStream.fbs │ ├── sctpAssociation.fbs │ ├── sctpParameters.fbs │ ├── srtpParameters.fbs │ ├── transport.fbs │ ├── webRtcServer.fbs │ ├── webRtcTransport.fbs │ └── worker.fbs ├── fuzzer/ │ ├── include/ │ │ ├── FuzzerUtils.hpp │ │ └── RTC/ │ │ ├── FuzzerDtlsTransport.hpp │ │ ├── FuzzerRateCalculator.hpp │ │ ├── FuzzerSeqManager.hpp │ │ ├── FuzzerTrendCalculator.hpp │ │ ├── ICE/ │ │ │ └── FuzzerStunPacket.hpp │ │ ├── RTCP/ │ │ │ ├── FuzzerBye.hpp │ │ │ ├── FuzzerFeedbackPs.hpp │ │ │ ├── FuzzerFeedbackPsAfb.hpp │ │ │ ├── FuzzerFeedbackPsFir.hpp │ │ │ ├── FuzzerFeedbackPsLei.hpp │ │ │ ├── FuzzerFeedbackPsPli.hpp │ │ │ ├── FuzzerFeedbackPsRemb.hpp │ │ │ ├── FuzzerFeedbackPsRpsi.hpp │ │ │ ├── FuzzerFeedbackPsSli.hpp │ │ │ ├── FuzzerFeedbackPsTst.hpp │ │ │ ├── FuzzerFeedbackPsVbcm.hpp │ │ │ ├── FuzzerFeedbackRtp.hpp │ │ │ ├── FuzzerFeedbackRtpEcn.hpp │ │ │ ├── FuzzerFeedbackRtpNack.hpp │ │ │ ├── FuzzerFeedbackRtpSrReq.hpp │ │ │ ├── FuzzerFeedbackRtpTllei.hpp │ │ │ ├── FuzzerFeedbackRtpTmmb.hpp │ │ │ ├── FuzzerFeedbackRtpTransport.hpp │ │ │ ├── FuzzerPacket.hpp │ │ │ ├── FuzzerReceiverReport.hpp │ │ │ ├── FuzzerSdes.hpp │ │ │ ├── FuzzerSenderReport.hpp │ │ │ └── FuzzerXr.hpp │ │ ├── RTP/ │ │ │ ├── Codecs/ │ │ │ │ ├── FuzzerAV1.hpp │ │ │ │ ├── FuzzerDependencyDescriptor.hpp │ │ │ │ ├── FuzzerH264.hpp │ │ │ │ ├── FuzzerOpus.hpp │ │ │ │ ├── FuzzerVP8.hpp │ │ │ │ └── FuzzerVP9.hpp │ │ │ ├── FuzzerPacket.hpp │ │ │ ├── FuzzerProbationGenerator.hpp │ │ │ ├── FuzzerRetransmissionBuffer.hpp │ │ │ └── FuzzerRtpStreamSend.hpp │ │ └── SCTP/ │ │ ├── FuzzerStateCookie.hpp │ │ └── packet/ │ │ └── FuzzerPacket.hpp │ ├── new-corpus/ │ │ └── .placeholder │ ├── reports/ │ │ ├── .placeholder │ │ ├── crash-058a4c1cc23eb6852d2d239a5596b10c572faa7b │ │ ├── crash-082d8c1a727f57b415a81ea68d8dd0c21804a4a1 │ │ ├── crash-2d204ea940d313bbdb69874f035ec5e7664b7181 │ │ ├── crash-572450c78229fbb005891235ad020c4374d44f82 │ │ ├── crash-6cfd7698f02bb829e64f926e2d8ce86596562843 │ │ ├── crash-732ced16c38f8a29b621a590e243782f2102e771 │ │ ├── crash-7e7caf72377ad55d353719f28febb5238eadfc9e │ │ ├── crash-89d745213ab3a50a7bd6c0bfaa3caf3dd5b81c96 │ │ ├── crash-91572165de5ef12fe8415b150e40457eccca0362 │ │ ├── crash-92e8ac42827b4da15e94289879d56a44c70ee4ec │ │ ├── crash-9401450d2dad5c11b31f93d7c69660e28ae6a1d6 │ │ ├── crash-9cc885b84ba02d766f422c6512ead3808ded0189 │ │ ├── crash-ac5d03e5d918b7f714c0452a59ad9c0e1ca3e501 │ │ ├── crash-b1dc07dd80d43d55c727cbf97e3db1285502bc39 │ │ ├── crash-b75c1208384621922270e954b4902442592c3ca9 │ │ ├── crash-c079dea0539424a1e986de88801b6a11005509c6 │ │ ├── crash-cb8c8802d60e3501cbf36d5e88b6882b8dfb76c9 │ │ ├── crash-cfd568c4ff6705458b0f431bf5e6fc89d7aa6883 │ │ ├── crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 │ │ ├── crash-dcfd05592934ab472c98a1813256aabb9bb43bfb │ │ ├── crash-ddfab2c0dd845e8d3e8f8d27e1f4cb49d92d279a │ │ └── crash-f7c53b8c190ca20d0a3ab04b51a075e2e29bb4c6 │ └── src/ │ ├── FuzzerUtils.cpp │ ├── RTC/ │ │ ├── FuzzerDtlsTransport.cpp │ │ ├── FuzzerRateCalculator.cpp │ │ ├── FuzzerSeqManager.cpp │ │ ├── FuzzerTrendCalculator.cpp │ │ ├── ICE/ │ │ │ └── FuzzerStunPacket.cpp │ │ ├── RTCP/ │ │ │ ├── FuzzerBye.cpp │ │ │ ├── FuzzerFeedbackPs.cpp │ │ │ ├── FuzzerFeedbackPsAfb.cpp │ │ │ ├── FuzzerFeedbackPsFir.cpp │ │ │ ├── FuzzerFeedbackPsLei.cpp │ │ │ ├── FuzzerFeedbackPsPli.cpp │ │ │ ├── FuzzerFeedbackPsRemb.cpp │ │ │ ├── FuzzerFeedbackPsRpsi.cpp │ │ │ ├── FuzzerFeedbackPsSli.cpp │ │ │ ├── FuzzerFeedbackPsTst.cpp │ │ │ ├── FuzzerFeedbackPsVbcm.cpp │ │ │ ├── FuzzerFeedbackRtp.cpp │ │ │ ├── FuzzerFeedbackRtpEcn.cpp │ │ │ ├── FuzzerFeedbackRtpNack.cpp │ │ │ ├── FuzzerFeedbackRtpSrReq.cpp │ │ │ ├── FuzzerFeedbackRtpTllei.cpp │ │ │ ├── FuzzerFeedbackRtpTmmb.cpp │ │ │ ├── FuzzerFeedbackRtpTransport.cpp │ │ │ ├── FuzzerPacket.cpp │ │ │ ├── FuzzerReceiverReport.cpp │ │ │ ├── FuzzerSdes.cpp │ │ │ ├── FuzzerSenderReport.cpp │ │ │ └── FuzzerXr.cpp │ │ ├── RTP/ │ │ │ ├── Codecs/ │ │ │ │ ├── FuzzerAV1.cpp │ │ │ │ ├── FuzzerDependencyDescriptor.cpp │ │ │ │ ├── FuzzerH264.cpp │ │ │ │ ├── FuzzerOpus.cpp │ │ │ │ ├── FuzzerVP8.cpp │ │ │ │ └── FuzzerVP9.cpp │ │ │ ├── FuzzerPacket.cpp │ │ │ ├── FuzzerProbationGenerator.cpp │ │ │ ├── FuzzerRetransmissionBuffer.cpp │ │ │ └── FuzzerRtpStreamSend.cpp │ │ └── SCTP/ │ │ ├── association/ │ │ │ └── FuzzerStateCookie.cpp │ │ └── packet/ │ │ └── FuzzerPacket.cpp │ └── fuzzer.cpp ├── include/ │ ├── Channel/ │ │ ├── ChannelMessageRegistrator.hpp │ │ ├── ChannelMessageRegistratorInterface.hpp │ │ ├── ChannelNotification.hpp │ │ ├── ChannelNotifier.hpp │ │ ├── ChannelRequest.hpp │ │ └── ChannelSocket.hpp │ ├── DepLibSRTP.hpp │ ├── DepLibUV.hpp │ ├── DepLibUring.hpp │ ├── DepLibWebRTC.hpp │ ├── DepOpenSSL.hpp │ ├── DepUsrSCTP.hpp │ ├── LogLevel.hpp │ ├── Logger.hpp │ ├── MediaSoupErrors.hpp │ ├── RTC/ │ │ ├── ActiveSpeakerObserver.hpp │ │ ├── AudioLevelObserver.hpp │ │ ├── BweType.hpp │ │ ├── Consts.hpp │ │ ├── Consumer.hpp │ │ ├── ConsumerTypes.hpp │ │ ├── DataConsumer.hpp │ │ ├── DataProducer.hpp │ │ ├── DirectTransport.hpp │ │ ├── DtlsTransport.hpp │ │ ├── ICE/ │ │ │ ├── IceCandidate.hpp │ │ │ ├── IceServer.hpp │ │ │ └── StunPacket.hpp │ │ ├── KeyFrameRequestManager.hpp │ │ ├── NackGenerator.hpp │ │ ├── Parameters.hpp │ │ ├── PipeConsumer.hpp │ │ ├── PipeTransport.hpp │ │ ├── PlainTransport.hpp │ │ ├── PortManager.hpp │ │ ├── Producer.hpp │ │ ├── RTCP/ │ │ │ ├── Bye.hpp │ │ │ ├── CompoundPacket.hpp │ │ │ ├── Feedback.hpp │ │ │ ├── FeedbackItem.hpp │ │ │ ├── FeedbackPs.hpp │ │ │ ├── FeedbackPsAfb.hpp │ │ │ ├── FeedbackPsFir.hpp │ │ │ ├── FeedbackPsLei.hpp │ │ │ ├── FeedbackPsPli.hpp │ │ │ ├── FeedbackPsRemb.hpp │ │ │ ├── FeedbackPsRpsi.hpp │ │ │ ├── FeedbackPsSli.hpp │ │ │ ├── FeedbackPsTst.hpp │ │ │ ├── FeedbackPsVbcm.hpp │ │ │ ├── FeedbackRtp.hpp │ │ │ ├── FeedbackRtpEcn.hpp │ │ │ ├── FeedbackRtpNack.hpp │ │ │ ├── FeedbackRtpSrReq.hpp │ │ │ ├── FeedbackRtpTllei.hpp │ │ │ ├── FeedbackRtpTmmb.hpp │ │ │ ├── FeedbackRtpTransport.hpp │ │ │ ├── Packet.hpp │ │ │ ├── ReceiverReport.hpp │ │ │ ├── Sdes.hpp │ │ │ ├── SenderReport.hpp │ │ │ ├── XR.hpp │ │ │ ├── XrDelaySinceLastRr.hpp │ │ │ └── XrReceiverReferenceTime.hpp │ │ ├── RTP/ │ │ │ ├── Codecs/ │ │ │ │ ├── AV1.hpp │ │ │ │ ├── DependencyDescriptor.hpp │ │ │ │ ├── H264.hpp │ │ │ │ ├── Opus.hpp │ │ │ │ ├── PayloadDescriptorHandler.hpp │ │ │ │ ├── Tools.hpp │ │ │ │ ├── VP8.hpp │ │ │ │ └── VP9.hpp │ │ │ ├── HeaderExtensionIds.hpp │ │ │ ├── Packet.hpp │ │ │ ├── ProbationGenerator.hpp │ │ │ ├── RetransmissionBuffer.hpp │ │ │ ├── RtpStream.hpp │ │ │ ├── RtpStreamRecv.hpp │ │ │ ├── RtpStreamSend.hpp │ │ │ ├── RtxStream.hpp │ │ │ └── SharedPacket.hpp │ │ ├── RateCalculator.hpp │ │ ├── Router.hpp │ │ ├── RtcLogger.hpp │ │ ├── RtpDictionaries.hpp │ │ ├── RtpListener.hpp │ │ ├── RtpObserver.hpp │ │ ├── SCTP/ │ │ │ ├── TODO_SCTP.md │ │ │ ├── association/ │ │ │ │ ├── Association.hpp │ │ │ │ ├── AssociationListenerDeferrer.hpp │ │ │ │ ├── HeartbeatHandler.hpp │ │ │ │ ├── NegotiatedCapabilities.hpp │ │ │ │ ├── PacketSender.hpp │ │ │ │ ├── StateCookie.hpp │ │ │ │ ├── StreamResetHandler.hpp │ │ │ │ ├── TransmissionControlBlock.hpp │ │ │ │ └── TransmissionControlBlockContextInterface.hpp │ │ │ ├── packet/ │ │ │ │ ├── Chunk.hpp │ │ │ │ ├── ErrorCause.hpp │ │ │ │ ├── Packet.hpp │ │ │ │ ├── Parameter.hpp │ │ │ │ ├── TLV.hpp │ │ │ │ ├── UserData.hpp │ │ │ │ ├── chunks/ │ │ │ │ │ ├── AbortAssociationChunk.hpp │ │ │ │ │ ├── AnyDataChunk.hpp │ │ │ │ │ ├── AnyForwardTsnChunk.hpp │ │ │ │ │ ├── AnyInitChunk.hpp │ │ │ │ │ ├── CookieAckChunk.hpp │ │ │ │ │ ├── CookieEchoChunk.hpp │ │ │ │ │ ├── DataChunk.hpp │ │ │ │ │ ├── ForwardTsnChunk.hpp │ │ │ │ │ ├── HeartbeatAckChunk.hpp │ │ │ │ │ ├── HeartbeatRequestChunk.hpp │ │ │ │ │ ├── IDataChunk.hpp │ │ │ │ │ ├── IForwardTsnChunk.hpp │ │ │ │ │ ├── InitAckChunk.hpp │ │ │ │ │ ├── InitChunk.hpp │ │ │ │ │ ├── OperationErrorChunk.hpp │ │ │ │ │ ├── ReConfigChunk.hpp │ │ │ │ │ ├── SackChunk.hpp │ │ │ │ │ ├── ShutdownAckChunk.hpp │ │ │ │ │ ├── ShutdownChunk.hpp │ │ │ │ │ ├── ShutdownCompleteChunk.hpp │ │ │ │ │ └── UnknownChunk.hpp │ │ │ │ ├── errorCauses/ │ │ │ │ │ ├── CookieReceivedWhileShuttingDownErrorCause.hpp │ │ │ │ │ ├── InvalidMandatoryParameterErrorCause.hpp │ │ │ │ │ ├── InvalidStreamIdentifierErrorCause.hpp │ │ │ │ │ ├── MissingMandatoryParameterErrorCause.hpp │ │ │ │ │ ├── NoUserDataErrorCause.hpp │ │ │ │ │ ├── OutOfResourceErrorCause.hpp │ │ │ │ │ ├── ProtocolViolationErrorCause.hpp │ │ │ │ │ ├── RestartOfAnAssociationWithNewAddressesErrorCause.hpp │ │ │ │ │ ├── StaleCookieErrorCause.hpp │ │ │ │ │ ├── UnknownErrorCause.hpp │ │ │ │ │ ├── UnrecognizedChunkTypeErrorCause.hpp │ │ │ │ │ ├── UnrecognizedParametersErrorCause.hpp │ │ │ │ │ ├── UnresolvableAddressErrorCause.hpp │ │ │ │ │ └── UserInitiatedAbortErrorCause.hpp │ │ │ │ └── parameters/ │ │ │ │ ├── AddIncomingStreamsRequestParameter.hpp │ │ │ │ ├── AddOutgoingStreamsRequestParameter.hpp │ │ │ │ ├── CookiePreservativeParameter.hpp │ │ │ │ ├── ForwardTsnSupportedParameter.hpp │ │ │ │ ├── HeartbeatInfoParameter.hpp │ │ │ │ ├── IPv4AddressParameter.hpp │ │ │ │ ├── IPv6AddressParameter.hpp │ │ │ │ ├── IncomingSsnResetRequestParameter.hpp │ │ │ │ ├── OutgoingSsnResetRequestParameter.hpp │ │ │ │ ├── ReconfigurationResponseParameter.hpp │ │ │ │ ├── SsnTsnResetRequestParameter.hpp │ │ │ │ ├── StateCookieParameter.hpp │ │ │ │ ├── SupportedAddressTypesParameter.hpp │ │ │ │ ├── SupportedExtensionsParameter.hpp │ │ │ │ ├── UnknownParameter.hpp │ │ │ │ ├── UnrecognizedParameterParameter.hpp │ │ │ │ └── ZeroChecksumAcceptableParameter.hpp │ │ │ ├── public/ │ │ │ │ ├── AssociationInterface.hpp │ │ │ │ ├── AssociationListenerInterface.hpp │ │ │ │ ├── AssociationMetrics.hpp │ │ │ │ ├── Message.hpp │ │ │ │ ├── SctpOptions.hpp │ │ │ │ └── SctpTypes.hpp │ │ │ ├── rx/ │ │ │ │ ├── DataTracker.hpp │ │ │ │ ├── InterleavedReassemblyStreams.hpp │ │ │ │ ├── ReassemblyQueue.hpp │ │ │ │ ├── ReassemblyStreamsInterface.hpp │ │ │ │ └── TraditionalReassemblyStreams.hpp │ │ │ └── tx/ │ │ │ ├── OutstandingData.hpp │ │ │ ├── RetransmissionErrorCounter.hpp │ │ │ ├── RetransmissionQueue.hpp │ │ │ ├── RetransmissionTimeout.hpp │ │ │ ├── RoundRobinSendQueue.hpp │ │ │ ├── SendQueueInterface.hpp │ │ │ └── StreamScheduler.hpp │ │ ├── SctpAssociation.hpp │ │ ├── SctpDictionaries.hpp │ │ ├── SctpListener.hpp │ │ ├── SenderBandwidthEstimator.hpp │ │ ├── SeqManager.hpp │ │ ├── Serializable.hpp │ │ ├── SimpleConsumer.hpp │ │ ├── SimulcastConsumer.hpp │ │ ├── SrtpSession.hpp │ │ ├── SvcConsumer.hpp │ │ ├── TcpConnection.hpp │ │ ├── TcpServer.hpp │ │ ├── Transport.hpp │ │ ├── TransportCongestionControlClient.hpp │ │ ├── TransportCongestionControlServer.hpp │ │ ├── TransportTuple.hpp │ │ ├── TrendCalculator.hpp │ │ ├── UdpSocket.hpp │ │ ├── WebRtcServer.hpp │ │ └── WebRtcTransport.hpp │ ├── Settings.hpp │ ├── Shared.hpp │ ├── SharedInterface.hpp │ ├── Utils/ │ │ └── UnwrappedSequenceNumber.hpp │ ├── Utils.hpp │ ├── Worker.hpp │ ├── common.hpp │ ├── handles/ │ │ ├── BackoffTimerHandle.hpp │ │ ├── BackoffTimerHandleInterface.hpp │ │ ├── SignalHandle.hpp │ │ ├── TcpConnectionHandle.hpp │ │ ├── TcpServerHandle.hpp │ │ ├── TimerHandle.hpp │ │ ├── TimerHandleInterface.hpp │ │ ├── UdpSocketHandle.hpp │ │ └── UnixStreamSocketHandle.hpp │ └── lib.hpp ├── meson.build ├── meson_options.txt ├── mocks/ │ ├── include/ │ │ ├── Channel/ │ │ │ └── MockChannelMessageRegistrator.hpp │ │ ├── MockShared.hpp │ │ ├── RTC/ │ │ │ └── SCTP/ │ │ │ ├── association/ │ │ │ │ ├── MockAssociationListener.hpp │ │ │ │ └── MockTransmissionControlBlockContext.hpp │ │ │ └── tx/ │ │ │ └── MockSendQueue.hpp │ │ ├── handles/ │ │ │ ├── MockBackoffTimerHandle.hpp │ │ │ └── MockTimerHandle.hpp │ │ └── mockTypes.hpp │ └── src/ │ ├── Channel/ │ │ └── MockChannelMessageRegistrator.cpp │ ├── MockShared.cpp │ ├── RTC/ │ │ └── SCTP/ │ │ └── association/ │ │ └── MockTransmissionControlBlockContext.cpp │ └── handles/ │ └── MockBackoffTimerHandle.cpp ├── scripts/ │ ├── .npmrc │ ├── clang-scripts.mjs │ ├── get-dep.sh │ ├── package.json │ └── run-fuzzer.sh ├── src/ │ ├── Channel/ │ │ ├── ChannelMessageRegistrator.cpp │ │ ├── ChannelNotification.cpp │ │ ├── ChannelNotifier.cpp │ │ ├── ChannelRequest.cpp │ │ └── ChannelSocket.cpp │ ├── DepLibSRTP.cpp │ ├── DepLibUV.cpp │ ├── DepLibUring.cpp │ ├── DepLibWebRTC.cpp │ ├── DepOpenSSL.cpp │ ├── DepUsrSCTP.cpp │ ├── Logger.cpp │ ├── MediaSoupErrors.cpp │ ├── RTC/ │ │ ├── ActiveSpeakerObserver.cpp │ │ ├── AudioLevelObserver.cpp │ │ ├── Consumer.cpp │ │ ├── DataConsumer.cpp │ │ ├── DataProducer.cpp │ │ ├── DirectTransport.cpp │ │ ├── DtlsTransport.cpp │ │ ├── ICE/ │ │ │ ├── IceCandidate.cpp │ │ │ ├── IceServer.cpp │ │ │ └── StunPacket.cpp │ │ ├── KeyFrameRequestManager.cpp │ │ ├── NackGenerator.cpp │ │ ├── PipeConsumer.cpp │ │ ├── PipeTransport.cpp │ │ ├── PlainTransport.cpp │ │ ├── PortManager.cpp │ │ ├── Producer.cpp │ │ ├── RTCP/ │ │ │ ├── Bye.cpp │ │ │ ├── CompoundPacket.cpp │ │ │ ├── Feedback.cpp │ │ │ ├── FeedbackPs.cpp │ │ │ ├── FeedbackPsAfb.cpp │ │ │ ├── FeedbackPsFir.cpp │ │ │ ├── FeedbackPsLei.cpp │ │ │ ├── FeedbackPsPli.cpp │ │ │ ├── FeedbackPsRemb.cpp │ │ │ ├── FeedbackPsRpsi.cpp │ │ │ ├── FeedbackPsSli.cpp │ │ │ ├── FeedbackPsTst.cpp │ │ │ ├── FeedbackPsVbcm.cpp │ │ │ ├── FeedbackRtp.cpp │ │ │ ├── FeedbackRtpEcn.cpp │ │ │ ├── FeedbackRtpNack.cpp │ │ │ ├── FeedbackRtpSrReq.cpp │ │ │ ├── FeedbackRtpTllei.cpp │ │ │ ├── FeedbackRtpTmmb.cpp │ │ │ ├── FeedbackRtpTransport.cpp │ │ │ ├── Packet.cpp │ │ │ ├── ReceiverReport.cpp │ │ │ ├── Sdes.cpp │ │ │ ├── SenderReport.cpp │ │ │ ├── XR.cpp │ │ │ ├── XrDelaySinceLastRr.cpp │ │ │ └── XrReceiverReferenceTime.cpp │ │ ├── RTP/ │ │ │ ├── Codecs/ │ │ │ │ ├── AV1.cpp │ │ │ │ ├── DependencyDescriptor.cpp │ │ │ │ ├── H264.cpp │ │ │ │ ├── Opus.cpp │ │ │ │ ├── VP8.cpp │ │ │ │ └── VP9.cpp │ │ │ ├── Packet.cpp │ │ │ ├── ProbationGenerator.cpp │ │ │ ├── RetransmissionBuffer.cpp │ │ │ ├── RtpStream.cpp │ │ │ ├── RtpStreamRecv.cpp │ │ │ ├── RtpStreamSend.cpp │ │ │ ├── RtxStream.cpp │ │ │ └── SharedPacket.cpp │ │ ├── RateCalculator.cpp │ │ ├── Router.cpp │ │ ├── RtcLogger.cpp │ │ ├── RtpDictionaries/ │ │ │ ├── Parameters.cpp │ │ │ ├── RtcpFeedback.cpp │ │ │ ├── RtcpParameters.cpp │ │ │ ├── RtpCodecMimeType.cpp │ │ │ ├── RtpCodecParameters.cpp │ │ │ ├── RtpEncodingParameters.cpp │ │ │ ├── RtpHeaderExtensionParameters.cpp │ │ │ ├── RtpHeaderExtensionUri.cpp │ │ │ ├── RtpParameters.cpp │ │ │ └── RtpRtxParameters.cpp │ │ ├── RtpListener.cpp │ │ ├── RtpObserver.cpp │ │ ├── SCTP/ │ │ │ ├── association/ │ │ │ │ ├── Association.cpp │ │ │ │ ├── AssociationListenerDeferrer.cpp │ │ │ │ ├── HeartbeatHandler.cpp │ │ │ │ ├── NegotiatedCapabilities.cpp │ │ │ │ ├── PacketSender.cpp │ │ │ │ ├── StateCookie.cpp │ │ │ │ ├── StreamResetHandler.cpp │ │ │ │ └── TransmissionControlBlock.cpp │ │ │ ├── packet/ │ │ │ │ ├── Chunk.cpp │ │ │ │ ├── ErrorCause.cpp │ │ │ │ ├── Packet.cpp │ │ │ │ ├── Parameter.cpp │ │ │ │ ├── TLV.cpp │ │ │ │ ├── UserData.cpp │ │ │ │ ├── chunks/ │ │ │ │ │ ├── AbortAssociationChunk.cpp │ │ │ │ │ ├── CookieAckChunk.cpp │ │ │ │ │ ├── CookieEchoChunk.cpp │ │ │ │ │ ├── DataChunk.cpp │ │ │ │ │ ├── ForwardTsnChunk.cpp │ │ │ │ │ ├── HeartbeatAckChunk.cpp │ │ │ │ │ ├── HeartbeatRequestChunk.cpp │ │ │ │ │ ├── IDataChunk.cpp │ │ │ │ │ ├── IForwardTsnChunk.cpp │ │ │ │ │ ├── InitAckChunk.cpp │ │ │ │ │ ├── InitChunk.cpp │ │ │ │ │ ├── OperationErrorChunk.cpp │ │ │ │ │ ├── ReConfigChunk.cpp │ │ │ │ │ ├── SackChunk.cpp │ │ │ │ │ ├── ShutdownAckChunk.cpp │ │ │ │ │ ├── ShutdownChunk.cpp │ │ │ │ │ ├── ShutdownCompleteChunk.cpp │ │ │ │ │ └── UnknownChunk.cpp │ │ │ │ ├── errorCauses/ │ │ │ │ │ ├── CookieReceivedWhileShuttingDownErrorCause.cpp │ │ │ │ │ ├── InvalidMandatoryParameterErrorCause.cpp │ │ │ │ │ ├── InvalidStreamIdentifierErrorCause.cpp │ │ │ │ │ ├── MissingMandatoryParameterErrorCause.cpp │ │ │ │ │ ├── NoUserDataErrorCause.cpp │ │ │ │ │ ├── OutOfResourceErrorCause.cpp │ │ │ │ │ ├── ProtocolViolationErrorCause.cpp │ │ │ │ │ ├── RestartOfAnAssociationWithNewAddressesErrorCause.cpp │ │ │ │ │ ├── StaleCookieErrorCause.cpp │ │ │ │ │ ├── UnknownErrorCause.cpp │ │ │ │ │ ├── UnrecognizedChunkTypeErrorCause.cpp │ │ │ │ │ ├── UnrecognizedParametersErrorCause.cpp │ │ │ │ │ ├── UnresolvableAddressErrorCause.cpp │ │ │ │ │ └── UserInitiatedAbortErrorCause.cpp │ │ │ │ └── parameters/ │ │ │ │ ├── AddIncomingStreamsRequestParameter.cpp │ │ │ │ ├── AddOutgoingStreamsRequestParameter.cpp │ │ │ │ ├── CookiePreservativeParameter.cpp │ │ │ │ ├── ForwardTsnSupportedParameter.cpp │ │ │ │ ├── HeartbeatInfoParameter.cpp │ │ │ │ ├── IPv4AddressParameter.cpp │ │ │ │ ├── IPv6AddressParameter.cpp │ │ │ │ ├── IncomingSsnResetRequestParameter.cpp │ │ │ │ ├── OutgoingSsnResetRequestParameter.cpp │ │ │ │ ├── ReconfigurationResponseParameter.cpp │ │ │ │ ├── SsnTsnResetRequestParameter.cpp │ │ │ │ ├── StateCookieParameter.cpp │ │ │ │ ├── SupportedAddressTypesParameter.cpp │ │ │ │ ├── SupportedExtensionsParameter.cpp │ │ │ │ ├── UnknownParameter.cpp │ │ │ │ ├── UnrecognizedParameterParameter.cpp │ │ │ │ └── ZeroChecksumAcceptableParameter.cpp │ │ │ ├── public/ │ │ │ │ ├── AssociationMetrics.cpp │ │ │ │ └── Message.cpp │ │ │ ├── rx/ │ │ │ │ ├── DataTracker.cpp │ │ │ │ ├── InterleavedReassemblyStreams.cpp │ │ │ │ ├── ReassemblyQueue.cpp │ │ │ │ └── TraditionalReassemblyStreams.cpp │ │ │ └── tx/ │ │ │ ├── OutstandingData.cpp │ │ │ ├── RetransmissionErrorCounter.cpp │ │ │ ├── RetransmissionQueue.cpp │ │ │ ├── RetransmissionTimeout.cpp │ │ │ ├── RoundRobinSendQueue.cpp │ │ │ └── StreamScheduler.cpp │ │ ├── SctpAssociation.cpp │ │ ├── SctpDictionaries/ │ │ │ └── SctpStreamParameters.cpp │ │ ├── SctpListener.cpp │ │ ├── SenderBandwidthEstimator.cpp │ │ ├── SeqManager.cpp │ │ ├── Serializable.cpp │ │ ├── SimpleConsumer.cpp │ │ ├── SimulcastConsumer.cpp │ │ ├── SrtpSession.cpp │ │ ├── SvcConsumer.cpp │ │ ├── TcpConnection.cpp │ │ ├── TcpServer.cpp │ │ ├── Transport.cpp │ │ ├── TransportCongestionControlClient.cpp │ │ ├── TransportCongestionControlServer.cpp │ │ ├── TransportTuple.cpp │ │ ├── TrendCalculator.cpp │ │ ├── UdpSocket.cpp │ │ ├── WebRtcServer.cpp │ │ └── WebRtcTransport.cpp │ ├── Settings.cpp │ ├── Shared.cpp │ ├── Utils/ │ │ ├── BitStream.cpp │ │ ├── Crypto.cpp │ │ ├── File.cpp │ │ ├── IP.cpp │ │ ├── README_BASE64_UTILS │ │ └── String.cpp │ ├── Worker.cpp │ ├── handles/ │ │ ├── BackoffTimerHandle.cpp │ │ ├── SignalHandle.cpp │ │ ├── TcpConnectionHandle.cpp │ │ ├── TcpServerHandle.cpp │ │ ├── TimerHandle.cpp │ │ ├── UdpSocketHandle.cpp │ │ └── UnixStreamSocketHandle.cpp │ ├── lib.cpp │ ├── lib.rs │ └── main.cpp ├── subprojects/ │ ├── .clang-tidy │ ├── abseil-cpp.wrap │ ├── catch2.wrap │ ├── flatbuffers.wrap │ ├── libsrtp3.wrap │ ├── liburing.wrap │ ├── libuv.wrap │ ├── openssl.wrap │ ├── usrsctp.wrap │ └── wingetopt.wrap ├── tasks.py ├── test/ │ ├── data/ │ │ ├── H264_SVC/ │ │ │ └── naluInfo/ │ │ │ ├── naluInfo.264 │ │ │ └── naluInfo.csv │ │ ├── packet1.info │ │ ├── packet1.raw │ │ ├── packet2.info │ │ ├── packet2.raw │ │ ├── packet3.info │ │ ├── packet3.raw │ │ └── rtp-stream-1.txt │ ├── include/ │ │ ├── RTC/ │ │ │ ├── ICE/ │ │ │ │ └── iceCommon.hpp │ │ │ ├── RTP/ │ │ │ │ └── rtpCommon.hpp │ │ │ └── SCTP/ │ │ │ └── sctpCommon.hpp │ │ ├── catch2Macros.hpp │ │ └── testHelpers.hpp │ └── src/ │ ├── RTC/ │ │ ├── ICE/ │ │ │ ├── TestStunPacket.cpp │ │ │ └── iceCommon.cpp │ │ ├── RTCP/ │ │ │ ├── TestBye.cpp │ │ │ ├── TestFeedbackPsAfb.cpp │ │ │ ├── TestFeedbackPsFir.cpp │ │ │ ├── TestFeedbackPsLei.cpp │ │ │ ├── TestFeedbackPsPli.cpp │ │ │ ├── TestFeedbackPsRemb.cpp │ │ │ ├── TestFeedbackPsRpsi.cpp │ │ │ ├── TestFeedbackPsSli.cpp │ │ │ ├── TestFeedbackPsTst.cpp │ │ │ ├── TestFeedbackPsVbcm.cpp │ │ │ ├── TestFeedbackRtpEcn.cpp │ │ │ ├── TestFeedbackRtpNack.cpp │ │ │ ├── TestFeedbackRtpSrReq.cpp │ │ │ ├── TestFeedbackRtpTllei.cpp │ │ │ ├── TestFeedbackRtpTmmb.cpp │ │ │ ├── TestFeedbackRtpTransport.cpp │ │ │ ├── TestPacket.cpp │ │ │ ├── TestReceiverReport.cpp │ │ │ ├── TestSdes.cpp │ │ │ ├── TestSenderReport.cpp │ │ │ └── TestXr.cpp │ │ ├── RTP/ │ │ │ ├── Codecs/ │ │ │ │ ├── TestDependencyDescriptor.cpp │ │ │ │ ├── TestH264.cpp │ │ │ │ ├── TestVP8.cpp │ │ │ │ └── TestVP9.cpp │ │ │ ├── TestPacket.cpp │ │ │ ├── TestProbationGenerator.cpp │ │ │ ├── TestRetransmissionBuffer.cpp │ │ │ ├── TestRtpStreamRecv.cpp │ │ │ ├── TestRtpStreamSend.cpp │ │ │ ├── TestSharedPacket.cpp │ │ │ └── rtpCommon.cpp │ │ ├── SCTP/ │ │ │ ├── association/ │ │ │ │ ├── TestHeartbeatHandler.cpp │ │ │ │ ├── TestNegotiatedCapabilities.cpp │ │ │ │ └── TestStateCookie.cpp │ │ │ ├── packet/ │ │ │ │ ├── TestChunk.cpp │ │ │ │ ├── TestErrorCause.cpp │ │ │ │ ├── TestPacket.cpp │ │ │ │ ├── TestParameter.cpp │ │ │ │ ├── chunks/ │ │ │ │ │ ├── TestAbortAssociationChunk.cpp │ │ │ │ │ ├── TestCookieAckChunk.cpp │ │ │ │ │ ├── TestCookieEchoChunk.cpp │ │ │ │ │ ├── TestDataChunk.cpp │ │ │ │ │ ├── TestForwardTsnChunk.cpp │ │ │ │ │ ├── TestHeartbeatAckChunk.cpp │ │ │ │ │ ├── TestHeartbeatRequestChunk.cpp │ │ │ │ │ ├── TestIDataChunk.cpp │ │ │ │ │ ├── TestIForwardTsnChunk.cpp │ │ │ │ │ ├── TestInitAckChunk.cpp │ │ │ │ │ ├── TestInitChunk.cpp │ │ │ │ │ ├── TestOperationErrorChunk.cpp │ │ │ │ │ ├── TestReConfigChunk.cpp │ │ │ │ │ ├── TestSackChunk.cpp │ │ │ │ │ ├── TestShutdownAckChunk.cpp │ │ │ │ │ ├── TestShutdownChunk.cpp │ │ │ │ │ ├── TestShutdownCompleteChunk.cpp │ │ │ │ │ └── TestUnknownChunk.cpp │ │ │ │ ├── errorCauses/ │ │ │ │ │ ├── TestCookieReceivedWhileShuttingDownErrorCause.cpp │ │ │ │ │ ├── TestInvalidMandatoryParameterErrorCause.cpp │ │ │ │ │ ├── TestInvalidStreamIdentifierErrorCause.cpp │ │ │ │ │ ├── TestMissingMandatoryParameterErrorCause.cpp │ │ │ │ │ ├── TestNoUserDataErrorCause.cpp │ │ │ │ │ ├── TestOutOfResourceErrorCause.cpp │ │ │ │ │ ├── TestProtocolViolationErrorCause.cpp │ │ │ │ │ ├── TestRestartOfAnAssociationWithNewAddressesErrorCause.cpp │ │ │ │ │ ├── TestStaleCookieErrorCause.cpp │ │ │ │ │ ├── TestUnknownErrorCause.cpp │ │ │ │ │ ├── TestUnrecognizedChunkTypeErrorCause.cpp │ │ │ │ │ ├── TestUnrecognizedParametersErrorCause.cpp │ │ │ │ │ ├── TestUnresolvableAddressErrorCause.cpp │ │ │ │ │ └── TestUserInitiatedAbortErrorCause.cpp │ │ │ │ └── parameters/ │ │ │ │ ├── TestAddIncomingStreamsRequestParameter.cpp │ │ │ │ ├── TestAddOutgoingStreamsRequestParameter.cpp │ │ │ │ ├── TestCookiePreservativeParameter.cpp │ │ │ │ ├── TestForwardTsnSupportedParameter.cpp │ │ │ │ ├── TestHeartbeatInfoParameter.cpp │ │ │ │ ├── TestIPv4AddressParameter.cpp │ │ │ │ ├── TestIPv6AddressParameter.cpp │ │ │ │ ├── TestIncomingSsnResetRequestParameter.cpp │ │ │ │ ├── TestOutgoingSsnResetRequestParameter.cpp │ │ │ │ ├── TestReconfigurationResponseParameter.cpp │ │ │ │ ├── TestSsnTsnResetRequestParameter.cpp │ │ │ │ ├── TestStateCookieParameter.cpp │ │ │ │ ├── TestSupportedAddressTypesParameter.cpp │ │ │ │ ├── TestSupportedExtensionsParameter.cpp │ │ │ │ ├── TestUnknownParameter.cpp │ │ │ │ ├── TestUnrecognizedParameterParameter.cpp │ │ │ │ └── TestZeroChecksumAcceptableParameter.cpp │ │ │ ├── rx/ │ │ │ │ ├── TestDataTracker.cpp │ │ │ │ ├── TestInterleavedReassemblyStreams.cpp │ │ │ │ ├── TestReassemblyQueue.cpp │ │ │ │ └── TestTraditionalReassemblyStreams.cpp │ │ │ ├── sctpCommon.cpp │ │ │ └── tx/ │ │ │ ├── TestOutstandingData.cpp │ │ │ ├── TestRetransmissionErrorCounter.cpp │ │ │ ├── TestRetransmissionQueue.cpp │ │ │ ├── TestRetransmissionTimeout.cpp │ │ │ ├── TestRoundRobinSendQueue.cpp │ │ │ └── TestStreamScheduler.cpp │ │ ├── TestKeyFrameRequestManager.cpp │ │ ├── TestNackGenerator.cpp │ │ ├── TestRateCalculator.cpp │ │ ├── TestRtpEncodingParameters.cpp │ │ ├── TestSeqManager.cpp │ │ ├── TestSimpleConsumer.cpp │ │ ├── TestTransportCongestionControlServer.cpp │ │ ├── TestTransportTuple.cpp │ │ └── TestTrendCalculator.cpp │ ├── Utils/ │ │ ├── TestBits.cpp │ │ ├── TestByte.cpp │ │ ├── TestCrypto.cpp │ │ ├── TestIP.cpp │ │ ├── TestNumber.cpp │ │ ├── TestString.cpp │ │ ├── TestTime.cpp │ │ └── TestUnwrappedSequenceNumber.cpp │ ├── testHelpers.cpp │ └── tests.cpp └── ubsan_suppressions.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: https://editorconfig.org root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.{ts,mts,mjs,cjs,js}] indent_style = tab [*.json] indent_style = tab [*.md] indent_style = tab [*.toml] indent_style = space indent_size = 4 ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: mediasoup ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with a single custom sponsorship URL ================================================ FILE: .github/ISSUE_TEMPLATE/Bug_Report.md ================================================ --- name: 🐍 Bug Report about: Report a bug in mediasoup labels: bug --- # Bug Report **IMPORTANT:** We primarily use GitHub as an issue tracker. Just open an issue here if you have encountered a bug in mediasoup. If you have questions or doubts about mediasoup or need support, please use the mediasoup Discourse Group instead: https://mediasoup.discourse.group If you got a crash in mediasoup, please try to provide a core dump into the issue report: https://mediasoup.org/support/#crashes-in-mediasoup-get-a-core-dump ## Your environment - Operating system: - gcc/clang version: - mediasoup (Node) version: - mediasoup (Rust) version: - mediasoup-client version: ## Issue description ================================================ FILE: .github/ISSUE_TEMPLATE/Feature_Request.md ================================================ --- name: 🚀 Feature Request about: Suggest an idea or improvement for mediasoup labels: feature --- # Feature Request ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: 🙈 Support Question url: https://mediasoup.discourse.group about: | We primarily use GitHub as an issue tracker. Please, use the mediasoup Discourse Group if you have questions or doubts or if you need support about mediasoup and its ecosystem. Before asking any questions, please check the mediasoup official documentation at https://mediasoup.org/documentation ================================================ FILE: .github/workflows/mediasoup-codeql.yaml ================================================ name: mediasoup-codeql on: push: branches: [v3] paths: - 'worker/**' - 'node/src/**' - 'rust/src/**' pull_request: paths: - 'worker/**' - 'node/src/**' - 'rust/src/**' workflow_dispatch: concurrency: # Cancel a currently running workflow from the same PR, branch or tag when a # new workflow is triggered. group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: analyze: name: Analyze runs-on: ubuntu-24.04 timeout-minutes: 120 permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: ['c-cpp', 'javascript-typescript', 'python'] env: MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' MEDIASOUP_LOCAL_DEV: 'true' MEDIASOUP_BUILDTYPE: 'Release' steps: - name: Checkout repository uses: actions/checkout@v6 - name: Node.js uses: actions/setup-node@v6 with: node-version: 24 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} config: | paths-ignore: - 'art/**' - 'doc/**' - 'node_modules/**' - 'node/lib/**' - 'node/src/fbs/**' - 'rust/benches/**' - 'rust/examples/**' - 'rust/examples-frontend/**' - 'worker/deps/**' - 'worker/subprojects/**' - 'worker/fuzzer/new-corpus/**' - 'worker/fuzzer/reports/**' - 'worker/out/**' # If you wish to specify custom queries, you can do so here or in a # config file. By default, queries listed here will override any # specified in a config file. Prefix the list here with "+" to use # these queries and those in the config file. # # Details on CodeQL's query packs refer to: # https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # # queries: security-extended,security-and-quality # Use `npm ci` to build mediasoup Node and worker instead of relying on # built-in Autobuild. - name: npm ci run: npm ci --foreground-scripts - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: '/language:${{matrix.language}}' ================================================ FILE: .github/workflows/mediasoup-node.yaml ================================================ name: mediasoup-node on: push: branches: [v3] paths: - 'node/**' - 'worker/**' - 'package.json' - 'package-lock.json' - 'tsconfig.json' - 'npm-scripts.mjs' - '.github/workflows/mediasoup-node.yaml' pull_request: paths: - 'node/**' - 'worker/**' - 'package.json' - 'package-lock.json' - 'tsconfig.json' - 'npm-scripts.mjs' - '.github/workflows/mediasoup-node.yaml' workflow_dispatch: concurrency: # Cancel a currently running workflow from the same PR, branch or tag when a # new workflow is triggered. group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: ci: strategy: # Here we want to see all errors, not just the first one. fail-fast: false matrix: build: - os: ubuntu-24.04 node: 22 cc: gcc cxx: g++ - os: ubuntu-24.04 node: 24 cc: gcc cxx: g++ meson_args: '-Db_sanitize=address' npm-audit: true run-lint: true - os: ubuntu-24.04 node: 24 cc: gcc cxx: g++ - os: ubuntu-24.04 node: 24 cc: clang cxx: clang++ meson_args: '-Db_sanitize=undefined' - os: ubuntu-24.04-arm node: 22 cc: gcc cxx: g++ - os: ubuntu-24.04-arm node: 24 cc: gcc cxx: g++ meson_args: '-Db_sanitize=address' # In Ubuntu 24.04 ARM in Debug build type with ASAN, createWorker() # takes too long. # See https://github.com/versatica/mediasoup/pull/1503. skip-test-in-debug-build-type: true - os: macos-15 node: 24 cc: clang cxx: clang++ - os: windows-2022 node: 22 cc: cl cxx: cl - os: windows-2025 node: 24 cc: cl cxx: cl build-type: - Release - Debug runs-on: ${{ matrix.build.os }} timeout-minutes: 60 env: CC: ${{ matrix.build.cc }} CXX: ${{ matrix.build.cxx }} MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' MEDIASOUP_LOCAL_DEV: 'true' MEDIASOUP_BUILDTYPE: ${{ matrix.build-type }} MESON_ARGS: ${{ matrix.build.meson_args }} # Disable leak detection because it's detected by the tool flatc uses to build. # NOTE: This env only affects when 'b_sanitize' args are given in `meson_args`. ASAN_OPTIONS: 'detect_leaks=0 symbolize=1 detect_stack_use_after_return=1 strict_init_order=1 check_initialization_order=1 detect_container_overflow=1' steps: - name: Checkout uses: actions/checkout@v6 - name: Node.js uses: actions/setup-node@v6 with: node-version: ${{ matrix.build.node }} - name: Configure cache uses: actions/cache@v5 with: path: | ~/.npm key: ${{ matrix.build.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ matrix.build.os }}-node- - name: npm ci --foreground-scripts run: npm ci --foreground-scripts - if: ${{ matrix.build.npm-audit }} name: npm audit --omit dev run: npm audit --omit dev - if: ${{ matrix.build.npm-audit }} name: npm audit --prefix worker/scripts run: npm audit --prefix worker/scripts - if: ${{ matrix.build-type == 'Release' }} name: npm run worker:prebuild run: npm run worker:prebuild - name: npm run worker:prebuild-name run: npm run worker:prebuild-name - if: ${{ matrix.build.run-lint }} name: npm run lint:node run: npm run lint:node - if: ${{ !matrix.build.skip-test-in-debug-build-type || matrix.build-type != 'Debug' }} name: npm run test:node run: npm run test:node ================================================ FILE: .github/workflows/mediasoup-rust.yaml ================================================ name: mediasoup-rust on: push: branches: [v3] paths: - 'rust/**' - 'worker/**' - 'Cargo.toml' - 'Cargo.lock' - 'rust-toolchain.toml' - '.github/workflows/mediasoup-rust.yaml' pull_request: paths: - 'rust/**' - 'worker/**' - 'Cargo.toml' - 'Cargo.lock' - 'rust-toolchain.toml' - '.github/workflows/mediasoup-rust.yaml' workflow_dispatch: concurrency: # Cancel a currently running workflow from the same PR, branch or tag when a # new workflow is triggered. group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true env: CARGO_TERM_COLOR: always jobs: ci: strategy: # Here we want to see all errors, not just the first one. fail-fast: false matrix: build: - os: ubuntu-24.04 run-cargo-fmt: true - os: ubuntu-24.04-arm - os: macos-15 - os: windows-2022 - os: windows-2025 runs-on: ${{ matrix.build.os }} timeout-minutes: 60 env: KEEP_BUILD_ARTIFACTS: '1' steps: - name: Checkout uses: actions/checkout@v6 - name: Configure cache uses: actions/cache@v5 with: path: | ~/.cargo/registry ~/.cargo/git key: ${{ matrix.build.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} restore-keys: | ${{ matrix.build.os }}-cargo- - if: ${{ matrix.build.run-cargo-fmt }} name: cargo fmt --all -- --check run: cargo fmt --all -- --check - name: cargo clippy --all-targets -- -D warnings run: cargo clippy --all-targets -- -D warnings # NOTE: In Windows this will build and test libmediasoupworker in release # mode twice since build.rs doesn't allow debug mode on Windows. - name: cargo test run: | cargo test --verbose cargo test --release --verbose - name: cargo doc --locked --all --no-deps --lib run: cargo doc --locked --all --no-deps --lib env: DOCS_RS: '1' RUSTDOCFLAGS: '-D rustdoc::broken-intra-doc-links -D rustdoc::private_intra_doc_links' ================================================ FILE: .github/workflows/mediasoup-worker-clang-tidy.yaml ================================================ name: mediasoup-worker-clang-tidy on: pull_request: paths: - 'worker/src/**/*.cpp' - 'worker/include/**/*.hpp' - 'worker/test/src/**/*.cpp' - 'worker/test/include/**/*.hpp' - 'worker/fuzzer/src/**/*.cpp' - 'worker/fuzzer/include/**/*.hpp' - 'worker/meson.build' - 'worker/tasks.py' - 'worker/.clang-format' - 'worker/.clang-tidy' - '.github/workflows/mediasoup-worker-clang-tidy.yaml' concurrency: # Cancel a currently running workflow from the same PR, branch or tag when a # new workflow is triggered. group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: clang-tidy: runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v6 - name: Install dependencies run: pip3 install --break-system-packages invoke - name: Setup Meson build (generates compile_commands.json) working-directory: worker env: MESON_ARGS: '-Dms_build_tests=true' run: invoke setup - name: Compile flatbuffers FBS files working-directory: worker run: invoke flatc - name: Install NPM worker development tools run: npm ci --prefix worker/scripts --foreground-scripts - name: Normalize compile_commands.json paths run: npm run normalize-compile-commands --prefix worker/scripts --foreground-scripts - uses: ZedThree/clang-tidy-review@v0.23.1 id: review with: apt_packages: 'libclang-rt-21-dev' build_dir: 'worker/out/Release/build' config_file: 'worker/.clang-tidy' clang_tidy_version: '21' lgtm_comment_body: '' extra_arguments: --quiet include: 'worker/src/**/*.cpp,worker/test/src/**/*.cpp,worker/fuzzer/src/**/*.cpp' # Uploads an artifact containing clang_fixes.json. - uses: ZedThree/clang-tidy-review/upload@v0.23.1 id: upload-review # If there are any comments, fail the check. - if: steps.review.outputs.total_comments > 0 run: exit 1 ================================================ FILE: .github/workflows/mediasoup-worker-fuzzer.yaml ================================================ name: mediasoup-worker-fuzzer on: push: branches: [v3] paths: - 'worker/**' - '.github/workflows/mediasoup-worker-fuzzer.yaml' pull_request: paths: - 'worker/**' - '.github/workflows/mediasoup-worker-fuzzer.yaml' workflow_dispatch: concurrency: # Cancel a currently running workflow from the same PR, branch or tag when a # new workflow is triggered. group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: ci: strategy: matrix: build: - os: ubuntu-24.04 cc: clang cxx: clang++ pip-break-system-packages: true - os: ubuntu-24.04-arm cc: clang cxx: clang++ pip-break-system-packages: true build-type: - Release - Debug runs-on: ${{ matrix.build.os }} timeout-minutes: 60 env: CC: ${{ matrix.build.cc }} CXX: ${{ matrix.build.cxx }} MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' MEDIASOUP_LOCAL_DEV: 'true' MEDIASOUP_BUILDTYPE: ${{ matrix.build-type }} steps: - name: Checkout uses: actions/checkout@v6 # We need to install pip invoke manually. - if: ${{ !matrix.build.pip-break-system-packages }} name: pip3 install invoke run: pip3 install invoke # In modern OSs we need to run pip with this option. - if: ${{ matrix.build.pip-break-system-packages }} name: pip3 install --break-system-packages invoke run: pip3 install --break-system-packages invoke # Build the mediasoup-worker-fuzzer binary (which uses libFuzzer). - name: invoke -r worker fuzzer run: invoke -r worker fuzzer # Run mediasoup-worker-fuzzer for 5 minutes. - name: run-fuzzer.sh 300 run: cd worker && ./scripts/run-fuzzer.sh 300 ================================================ FILE: .github/workflows/mediasoup-worker-prebuild.yaml ================================================ name: mediasoup-worker-prebuild # Only trigger on GitHub releases. on: release: types: [published] jobs: ci: strategy: # Here we want to see all errors, not just the first one. fail-fast: false matrix: build: # Worker prebuild for Linux with kernel version 6 Ubuntu (22.04). # Let's use Ubuntu 22.04 instead of 24.04 so that it builds the # mediasoup-worker binary using an old version of GLib that will work # on Linux hosts running more modern GLib versions. # See https://github.com/versatica/mediasoup/issues/1089. - os: ubuntu-22.04 cc: gcc cxx: g++ - os: ubuntu-22.04-arm cc: gcc cxx: g++ - os: macos-15 cc: clang cxx: clang++ - os: windows-2025 cc: cl cxx: cl runs-on: ${{ matrix.build.os }} timeout-minutes: 90 env: CC: ${{ matrix.build.cc }} CXX: ${{ matrix.build.cxx }} MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' MEDIASOUP_LOCAL_DEV: 'true' MEDIASOUP_BUILDTYPE: 'Release' steps: - name: Checkout uses: actions/checkout@v6 - name: Node.js uses: actions/setup-node@v6 with: node-version: 24 # We need to install some NPM production deps for npm-scripts.mjs to # work. - name: npm ci --ignore-scripts --omit=dev --foreground-scripts run: npm ci --ignore-scripts --omit=dev --foreground-scripts - name: npm run worker:prebuild-name run: npm run worker:prebuild-name - name: npm run worker:build run: npm run worker:build # Publish prebuild binaries on tag. - name: npm run worker:prebuild run: npm run worker:prebuild - name: Upload mediasoup-worker prebuilt binary uses: softprops/action-gh-release@v2 with: files: worker/prebuild/mediasoup-worker-*.tgz ================================================ FILE: .github/workflows/mediasoup-worker.yaml ================================================ name: mediasoup-worker on: push: branches: [v3] paths: - 'worker/**' - 'worker/scripts/package.json' - 'worker/scripts/package-lock.json' - '.github/workflows/mediasoup-worker.yaml' pull_request: paths: - 'worker/**' - 'worker/scripts/package.json' - 'worker/scripts/package-lock.json' - '.github/workflows/mediasoup-worker.yaml' workflow_dispatch: concurrency: # Cancel a currently running workflow from the same PR, branch or tag when a # new workflow is triggered. group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: ci: strategy: # Here we want to see all errors, not just the first one. fail-fast: false matrix: build: - os: ubuntu-22.04 cc: gcc cxx: g++ build-type: Release run-lint: false run-test: true run-test-asan-address: false run-test-asan-undefined: false - os: ubuntu-22.04 cc: clang cxx: clang++ build-type: Release run-lint: false run-test: true run-test-asan-address: false run-test-asan-undefined: false - os: ubuntu-22.04-arm cc: gcc cxx: g++ build-type: Release # No clang-format for ARM. run-lint: false run-test: true run-test-asan-address: false run-test-asan-undefined: false - os: ubuntu-24.04 cc: gcc cxx: g++ build-type: Release pip-break-system-packages: true run-lint: false run-test: true run-test-asan-address: false run-test-asan-undefined: false # Compile with all Meson option flags enabled once with gcc in # Release mode. meson_args: '-Dms_log_trace=true -Dms_log_file_line=true -Dms_rtc_logger_rtp=true -Dms_dump_rtp_payload_descriptor=true -Dms_dump_rtp_shared_packet_memory_usage=true' - os: ubuntu-24.04 cc: gcc cxx: g++ build-type: Debug pip-break-system-packages: true run-lint: false run-test: true run-test-asan-address: false run-test-asan-undefined: false # Compile with all Meson option flags enabled once with gcc in # Debug mode. meson_args: '-Dms_log_trace=true -Dms_log_file_line=true -Dms_rtc_logger_rtp=true -Dms_dump_rtp_payload_descriptor=true -Dms_dump_rtp_shared_packet_memory_usage=true -Dms_disable_liburing=true' - os: ubuntu-24.04 cc: clang cxx: clang++ build-type: Release pip-break-system-packages: true run-lint: false run-test: true run-test-asan-address: true run-test-asan-undefined: true # Let's just compile with all Meson option flags enabled once with # clang. meson_args: '-Dms_log_trace=true -Dms_log_file_line=true -Dms_rtc_logger_rtp=true -Dms_dump_rtp_payload_descriptor=true -Dms_dump_rtp_shared_packet_memory_usage=true' - os: ubuntu-24.04-arm cc: gcc cxx: g++ build-type: Release pip-break-system-packages: true # No clang-format for ARM. run-lint: false run-test: true run-test-asan-address: false run-test-asan-undefined: false - os: ubuntu-24.04-arm cc: clang cxx: clang++ build-type: Release pip-break-system-packages: true # No clang-format for ARM. run-lint: false run-test: true run-test-asan-address: false run-test-asan-undefined: false - os: macos-15 cc: clang cxx: clang++ build-type: Release pip-break-system-packages: true # Run lint for the latest macos. run-lint: true run-test: true run-test-asan-address: false run-test-asan-undefined: false - os: windows-2022 cc: cl cxx: cl build-type: Release # No clang-format for Windows. run-lint: false # Maybe some day we fix this. run-test: false # Address Sanitizer does not work on Windows. run-test-asan-address: false run-test-asan-undefined: false - os: windows-2025 cc: cl cxx: cl build-type: Release # No clang-format for Windows. run-lint: false # Maybe some day we fix this. run-test: false # Address Sanitizer does not work on Windows. run-test-asan-address: false run-test-asan-undefined: false runs-on: ${{ matrix.build.os }} timeout-minutes: 90 env: CC: ${{ matrix.build.cc }} CXX: ${{ matrix.build.cxx }} MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD: 'true' MEDIASOUP_LOCAL_DEV: 'false' MEDIASOUP_BUILDTYPE: ${{ matrix.build.build-type }} MESON_ARGS: ${{ matrix.build.meson_args }} steps: - name: Checkout uses: actions/checkout@v6 - name: Node.js uses: actions/setup-node@v6 with: node-version: 24 - name: Configure cache uses: actions/cache@v5 with: path: | ~/.npm key: ${{ matrix.build.os }}-node-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ matrix.build.os }}-node- # We need to install pip invoke manually. - if: ${{ !matrix.build.pip-break-system-packages }} name: pip3 install invoke run: pip3 install invoke # In modern OSs we need to run pip with this option. - if: ${{ matrix.build.pip-break-system-packages }} name: pip3 install --break-system-packages invoke run: pip3 install --break-system-packages invoke # Fail if run-lint is set for non macos. - if: ${{ matrix.build.run-lint && !startsWith(matrix.build.os, 'macos') }} name: fail if run-lint is set for non macos run: | echo "run-lint set for non macos" exit 1 # Install clang-format on macos. - if: ${{ matrix.build.run-lint && startsWith(matrix.build.os, 'macos') }} name: brew install clang-format@22 run: | brew install clang-format@22 # We need to install npm deps of worker/scripts/package.json. - if: ${{ matrix.build.run-lint }} name: npm ci --prefix worker/scripts run: npm ci --prefix worker/scripts --foreground-scripts - if: ${{ matrix.build.run-lint }} name: invoke -r worker lint run: invoke -r worker lint - name: invoke -r worker mediasoup-worker run: invoke -r worker mediasoup-worker - if: ${{ matrix.build.run-test }} name: invoke -r worker test run: invoke -r worker test # Let's clean everything before rebuilding worker tests with ASAN. - if: ${{ matrix.build.run-test-asan-address }} name: invoke -r worker test-asan-address run: invoke -r worker clean-all && invoke -r worker test-asan-address # Let's clean everything before rebuilding worker tests with ASAN. - if: ${{ matrix.build.run-test-asan-undefined }} name: invoke -r worker test-asan-undefined run: invoke -r worker clean-all && invoke -r worker test-asan-undefined ================================================ FILE: .gitignore ================================================ ## Meson. /worker/out /worker/subprojects/* !/worker/subprojects/*.wrap !/worker/subprojects/.clang-tidy ## Node. /node_modules /node/lib # flatc generated files. /node/src/fbs ## Rust. /rust/examples-frontend/*/node_modules /rust/examples-frontend/*/package-lock.json /target ## Worker. /worker/scripts/node_modules # Flatc generated files. /worker/include/FBS /worker/prebuild # Python invoke. /worker/pip_invoke # Build artifacts. /worker/**/Debug /worker/**/Release # clang-fuzzer stuff is too big. /worker/deps/clang-fuzzer # Ignore all fuzzer generated test inputs. /worker/fuzzer/new-corpus/* !/worker/fuzzer/new-corpus/.placeholder ## Others. /coverage /.cache # Packaged module. *.tgz ## Text editors' config files. /.zed ================================================ FILE: .npmrc ================================================ # Generate package-lock.json. package-lock=true # For bad node/npm version to throw actual error. engine-strict=true ================================================ FILE: .prettierrc.json ================================================ { "useTabs": true, "tabWidth": 2, "arrowParens": "avoid", "bracketSpacing": true, "semi": true, "singleQuote": true, "trailingComma": "es5", "endOfLine": "auto" } ================================================ FILE: CHANGELOG.md ================================================ # Changelog ### NEXT - Node: Update TypeScript to v6 ([PR #1790](https://github.com/versatica/mediasoup/pull/1790)). ### 3.19.22 - Node: Avoid "worker died" event when the Node application is closed via signal without calling `worker.close()` ([PR #1788](https://github.com/versatica/mediasoup/pull/1788)). ### 3.19.21 - Worker: Fix regression in `DirectTransport` when closing a `DataProducer` or `DataConsumer` ([PR #1780](https://github.com/versatica/mediasoup/pull/1780)). ### 3.19.20 - Worker: Add `useBuiltInSctpStack` setting (defaults to `false`) to enable mediasoup built-in SCTP stack ([PR #1777](https://github.com/versatica/mediasoup/pull/1777)). ### 3.19.19 - Worker: Ensure 4-byte alignment for network packet receive buffers and test buffers to avoid undefined behavior ([PR #1756](https://github.com/versatica/mediasoup/pull/1756)). - Worker: Update liburing from 2.12-1 to 2.14-1 ([PR #1761](https://github.com/versatica/mediasoup/pull/1761)). ### 3.19.18 - Worker: Improve `Utils::Crypto::GetRandomUInt()` ([PR #1725](https://github.com/versatica/mediasoup/pull/1725)). - Convert `WORKER_CLOSE` into a notification ([PR #1729](https://github.com/versatica/mediasoup/pull/1729)). - Node tests: Replace `sctp` unmaintained library with `werift-sctp` ([PR #1732](https://github.com/versatica/mediasoup/pull/1732), thanks to @shinyoshiaki for his help with `werift-sctp`). - Worker: Require C++20 ([PR #1741](https://github.com/versatica/mediasoup/pull/1741)). - Fix "SCTP failed" if no DataChannel is created on a Transport with `enableSctp: true` ([PR #1749](https://github.com/versatica/mediasoup/pull/1749)). ### 3.19.17 - `ICE::StunPacket`: Fix wrong memory access in `GetXorMappedAddress()` method ([08c1ec9](https://github.com/versatica/mediasoup/commit/ea464d40ef77247c3ff7acd10e4a0118665fdd14)). ### 3.19.16 - `RTP::ProbationGenerator`: Remove wrong warning log ([PR #1703](https://github.com/versatica/mediasoup/pull/1703)). ### 3.19.15 - `RtpStreamSend`: duplicated packets are discarded ([PR #1683](https://github.com/versatica/mediasoup/pull/1683)). - Worker: Update liburing from 2.5-2 to 2.12-1 ([PR #1686](https://github.com/versatica/mediasoup/pull/1686)). - Worker: Use the new `RTP::Packet` class ([PR #1689](https://github.com/versatica/mediasoup/pull/1689)). - Worker: Use the new `ICE::StunPacket` class ([PR #1697](https://github.com/versatica/mediasoup/pull/1697)). - Node: Expose `ortc` functions in `exports` in `package.json` and main module ([PR #1698](https://github.com/versatica/mediasoup/pull/1698)). ### 3.19.14 - Worker: Fix missing system header include, which fails in GCC 15 ([PR #1679](https://github.com/versatica/mediasoup/pull/1679), credits to @upisfree). ### 3.19.13 - `RtxStream`: Don't check if RTP timestamp moved backwards ([PR #1668](https://github.com/versatica/mediasoup/pull/1668), credits to @Lynnworld). - Fix RTX packets containing non yet seen RTP packets being discarded ([PR #1653](https://github.com/versatica/mediasoup/pull/1653), credits to @penguinol, @mengbieting and @Lynnworld). ### 3.19.12 - Only look up the RTP packet’s RID extension if the packet doesn’t have MID extension ([PR #1666](https://github.com/versatica/mediasoup/pull/1666)). ### 3.19.11 - Node: Add `workerBin` optional field in `createWorker()` ([PR #1660](https://github.com/versatica/mediasoup/pull/1660)). ### 3.19.10 - Add `jitter` in `Consumer` 'outbound-rtp' stats ([PR #1654](https://github.com/versatica/mediasoup/pull/1654)). ### 3.19.9 - Fix RTCP packets lost in stats ([PR #1651](https://github.com/versatica/mediasoup/pull/1651)). ### 3.19.8 - Fix RTCP cumulative total lost computation ([PR #1650](https://github.com/versatica/mediasoup/pull/1650)). ### 3.19.7 - Bump up Meson from 1.5.0 to 1.9.1 ([PR #1634](https://github.com/versatica/mediasoup/pull/1634)). - `SeqManager`: Fix, properly account out of order drops until an input is forwarded ([#1635](https://github.com/versatica/mediasoup/pull/1635)), thanks to @pnts-se-whereby for reporting. - `RtpParameters`: Add `msid` optional field ([PR #1634](https://github.com/versatica/mediasoup/pull/1634)). ### 3.19.6 - AV1: Set DependencyDescriptor Header Extension to 'recvonly' but forward it between pipe transports ([#1632](https://github.com/versatica/mediasoup/pull/1632)). ### 3.19.5 - Add custom 'urn:mediasoup:params:rtp-hdrext:packet-id' (mediasoup-packet-id) header extension ([#1631](https://github.com/versatica/mediasoup/pull/1631)). ### 3.19.4 - AV1: Add support for DD extension header forwarding ([#1610](https://github.com/versatica/mediasoup/pull/1610)). - DependencyDescriptor: Update listener on RtpPacket clone ([#1618](https://github.com/versatica/mediasoup/pull/1618)). ### 3.19.3 - CI: Remove `macos-13` hosts. - VP8: Fix keyframe detection if "extended" bit is not set ([PR #1612](https://github.com/versatica/mediasoup/pull/1612), credits to @nifigase). - CI: Remove `node-20` GitHub actions. - Require Node.js >= 22 ([PR #1614](https://github.com/versatica/mediasoup/pull/1614)). ### 3.19.2 - `IceServer`: Fix active tuple selection when in "completed" state ([PR #1608](https://github.com/versatica/mediasoup/pull/1608), credits to @pangsimon). ### 3.19.1 - Worker: Fix retransmissions, set proper marker bit ([PR #1606](https://github.com/versatica/mediasoup/pull/1606)). ### 3.19.0 - Node: Improve worker binary location detection ([PR #1603](https://github.com/versatica/mediasoup/pull/1603)). - `router.pipeToRouter()` can now connect two `Routers` in the same `Worker` if `keepId` is set to `false` ([PR #1604](https://github.com/versatica/mediasoup/pull/1604)). ### 3.18.1 - `TransportTuple`: Generate hash based not only on remote IP:port but also on local IP:port ([PR #1586](https://github.com/versatica/mediasoup/pull/1586)). - `IceServer`: Only update selected tuple if the new Binding request has ICE renomination ([PR #1587](https://github.com/versatica/mediasoup/pull/1587), credits to @pangsimon). - Fix installation in paths with spaces ([PR #1596](https://github.com/versatica/mediasoup/pull/1596), thanks to @ShuzhaoFeng for reporting and helping with this issue). ### 3.18.0 - Node: Make `RtpCodecCapability.preferredPayloadType` mandatory and add `RouterRtpCodecCapability` type (in which `preferredPayloadType` is optional) and `RouterRtpCapabilities` type ([PR #1584](https://github.com/versatica/mediasoup/pull/1584)). ### 3.17.1 - `WebRtcServer`: Remove the limit of 8 `listenInfos`. ### 3.17.0 - Worker: Update Meson subprojects ([PR #1582](https://github.com/versatica/mediasoup/pull/1582)). - `TransportListenInfo`: Add `exposeInternalIp` which, if set to `true` and `announcedAddress` is set, exposes an additional ICE candidate in `WebRtcTransport` whose IP is `listenInfo.ip` rather than `listenInfo.announcedAddress` ([PR #1583](https://github.com/versatica/mediasoup/pull/1583)). ### 3.16.8 - Node: Fix `PipeConsumerOptions` TypeScript type (make `ConsumerAppData` TS argument optional) ([PR #1581](https://github.com/versatica/mediasoup/pull/1581)). ### 3.16.7 - `Router`: Add `updateMediaCodecs()` method to dynamically change Router's RTP capabilities ([PR #1571](https://github.com/versatica/mediasoup/pull/1571)). - `RtpStream`: Update `maxPacketTs` if RTP timestamp moved backwards despite in-order RTP sequence number ([PR #1574](https://github.com/versatica/mediasoup/pull/1574), credits to @oppolixiang). - `RtpStream`: Ignore padding only RTP packets in RTP data counters ([PR #1580](https://github.com/versatica/mediasoup/pull/1580), thanks to @quanli168 for reporting the issue). ### 3.16.6 - Remove H265 codec and deprecated frame-marking RTP extension ([PR #1564](https://github.com/versatica/mediasoup/pull/1564)). - `SimulcastConsumer`: Fix selecting spatial layer higher than preferred one ([PR #1565](https://github.com/versatica/mediasoup/pull/1565)). - Remove H264-SVC codec ([PR #1568](https://github.com/versatica/mediasoup/pull/1568)). - `RateCalculator`: Fix crash due to buffer overflow and avoid time overflow ([PR #1570](https://github.com/versatica/mediasoup/pull/1570)). ### 3.16.5 - `Consumer` classes: Really fix target layer retransmission buffer ([PR #1558](https://github.com/versatica/mediasoup/pull/1558)). ### 3.16.4 - `Consumer` classes: Disable target layer retransmission buffer until [issue #1554] (https://github.com/versatica/mediasoup/issues/1554) is really fixed. ### 3.16.3 - `Consumer` classes: Fix target layer retransmission buffer ([PR #1555](https://github.com/versatica/mediasoup/pull/1555)). ### 3.16.2 - `Consumer` classes: Disable target layer retransmission buffer until [issue #1554] (https://github.com/versatica/mediasoup/issues/1554) is fixed. ### 3.16.1 - libuv: Update to v1.51.0 ([PR #1543](https://github.com/versatica/mediasoup/pull/1543)). - libsrtp: Update to v3.0.0-beta version in our fork ([PR #1544](https://github.com/versatica/mediasoup/pull/1544)). - `Consumer` classes: Only drop packets in RTP sequence manager when they belong to current spatial layer ([PR #1549](https://github.com/versatica/mediasoup/pull/1549)). - `Consumer` classes: Add target layer retransmission buffer to avoid PLIs/FIRs when RTP packets containing a key frame arrive out of order ([PR #1550](https://github.com/versatica/mediasoup/pull/1550)). ### 3.16.0 - Node: Make `worker.close()` close the worker process by sending a `WORKER_CLOSE` request through the channel instead of by sending a SIGINT signal ([PR #1534](https://github.com/versatica/mediasoup/pull/1534)). - Worker: Add initial AV1 codec support ([PR #1508](https://github.com/versatica/mediasoup/pull/1508)). - `SvcConsumer`: Fix K-SVC bitrate in `IncreaseLayer()` method ([PR #1535](https://github.com/versatica/mediasoup/pull/1535) by @vpalmisano). - Node: Require Node >= 20 (drop support for Node 18) ([PR #1536](https://github.com/versatica/mediasoup/pull/1536)). ### 3.15.8 - Worker: Fix encode retransmitted packets with the corresponding data ([PR #1527](https://github.com/versatica/mediasoup/pull/1527)). ### 3.15.7 - CI: Remove redundant hosts `macos-14` and `windows-2022` from `mediasoup-worker-prebuild` job ([PR #1506](https://github.com/versatica/mediasoup/pull/1506)). - Node: Modernize code ([PR #1513](https://github.com/versatica/mediasoup/pull/1513)). - Fix wrong SCTP stream parameters in SCTP `DataConsumer` that consumes from a direct `DataProducer` ([PR #1516](https://github.com/versatica/mediasoup/pull/1516)). ### 3.15.6 - CI: Remove deprecated `ubuntu-20.04` host and add `windows-2025`, `ubuntu-22.04-arm` and `ubuntu-24.04-arm` hosts ([PR #1500](https://github.com/versatica/mediasoup/pull/1500)). ### 3.15.5 - `Consumer`: Fix sequence number gap ([PR #1494](https://github.com/versatica/mediasoup/pull/1494)). - Fix VP9 out of order packets forwarding ([PR #1486](https://github.com/versatica/mediasoup/pull/1486) by @vpalmisano). ### 3.15.4 - Worker: Drop VP8 packets with a higher temporal layer than the current one ([PR #1009](https://github.com/versatica/mediasoup/pull/1009)). - Fix the problem of the TCC package being omitted from being sent ([PR #1492](https://github.com/versatica/mediasoup/pull/1492) by @penguinol). ### 3.15.3 - Node: Expose `Index` interface in `types.indexTypes` or via `import { Index as MediasoupIndex } from 'mediasoup/lib/indexTypes'` ([PR #1485](https://github.com/versatica/mediasoup/pull/1485)). ### 3.15.2 - Worker: Fix crash when using colliding `portRange` values in different transports ([PR #1469](https://github.com/versatica/mediasoup/pull/1469)). ### 3.15.1 - Expose `extras` namespace which exports `EnhancedEventEmitter` and `enhancedOnce()` for now ([PR #1464](https://github.com/versatica/mediasoup/pull/1464)). ### 3.15.0 - Node: Add TypeScript interfaces for all exported classes ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)). - Node: Add new `transport.type` getter than returns `'webrtc' | 'plain' | 'pipe' | 'direct'` ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)). - Node: Add new `rtpObserver.type` getter than returns `'activespeaker' | 'audiolevel'` ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)). ### 3.14.16 - `SimulcastConsumer`: Fix cannot switch layers if initial `tsReferenceSpatialLayer disappears` disappears ([PR #1459](https://github.com/versatica/mediasoup/pull/1459) by @Lynnworld). ### 3.14.15 - Update worker abseil-cpp dependency to 20240722.0 LTS (fixes compilation for FreeBSD systems) ([PR #1457](https://github.com/versatica/mediasoup/pull/1457), credits to @garrettboone). ### 3.14.14 - Sign self generated DTLS certificate with SHA256 ([PR #1450](https://github.com/versatica/mediasoup/pull/1450)). - Node: Fix `mediasoup.types` exported types are empty ([PR #1453](https://github.com/versatica/mediasoup/pull/1453)). ### 3.14.13 - Node: Fix regression in exported `mediasoup.types` (classes are now exported as classes instead of types). ### 3.14.12 - Worker: Fix `io_uring` support detection ([PR #1445](https://github.com/versatica/mediasoup/pull/1445)). - Mitigate libsrtp wraparound with loss decryption failure ([PR #1438](https://github.com/versatica/mediasoup/pull/1438)). - Node: New `setLogEventListeners()` utility to get log events ([PR #1448](https://github.com/versatica/mediasoup/pull/1448)). ### 3.14.11 - Worker: Fix `disableLiburing` option in `WorkerSettings` ([PR #1444](https://github.com/versatica/mediasoup/pull/1444)). ### 3.14.10 - CI: Support Node 22 ([PR #1434](https://github.com/versatica/mediasoup/pull/1434)). - Update ESLint to version 9 ([PR #1435](https://github.com/versatica/mediasoup/pull/1435)). - Worker: Add `disableLiburing` boolean option (`false` by default) to disable `io_uring` even if it's supported by the prebuilt `mediasoup-worker` and by current host ([PR #1442](https://github.com/versatica/mediasoup/pull/1442)). ### 3.14.9 - Worker: Test, fix buffer overflow ([PR #1419](https://github.com/versatica/mediasoup/pull/1419)). - Bump up Meson from 1.3.0 to 1.5.0 ([PR #1424](https://github.com/versatica/mediasoup/pull/1424)). - Node: Export new `WorkerObserver`, `ProducerObserver`, etc. TypeScript types ([PR #1430](https://github.com/versatica/mediasoup/pull/1430)). - Fix frozen video in simulcast due to wrong dropping of padding only packets ([PR #1431](https://github.com/versatica/mediasoup/pull/1431), thanks to @quanli168). ### 3.14.8 - Add support for 'playout-delay' RTP extension ([PR #1412](https://github.com/versatica/mediasoup/pull/1412) by @DavidNegro). ### 3.14.7 - `SimulcastConsumer`: Fix increase layer when current layer has not receive SR ([PR #1098](https://github.com/versatica/mediasoup/pull/1098) by @penguinol). - Ignore RTP packets with empty payload ([PR #1403](https://github.com/versatica/mediasoup/pull/1403), credits to @ggarber). ### 3.14.6 - Worker: Fix potential double free when ICE consent check fails ([PR #1393](https://github.com/versatica/mediasoup/pull/1393)). ### 3.14.5 - Worker: Fix memory leak when using `WebRtcServer` with TCP enabled ([PR #1389](https://github.com/versatica/mediasoup/pull/1389)). - Worker: Fix crash when closing `WebRtcServer` with active `WebRtcTransports` ([PR #1390](https://github.com/versatica/mediasoup/pull/1390)). ### 3.14.4 - Worker: Fix crash. `RtcpFeedback` parameter is optional ([PR #1387](https://github.com/versatica/mediasoup/pull/1387), credits to @Lynnworld). ### 3.14.3 - Worker: Fix possible value overflow in `FeedbackRtpTransport.cpp` ([PR #1386](https://github.com/versatica/mediasoup/pull/1386), credits to @Lynnworld). ### 3.14.2 - Update worker subprojects ([PR #1376](https://github.com/versatica/mediasoup/pull/1376)). - OPUS: Fix DTX detection ([PR #1357](https://github.com/versatica/mediasoup/pull/1357)). - Worker: Fix sending callback leaks ([PR #1383](https://github.com/versatica/mediasoup/pull/1383), credits to @Lynnworld). ### 3.14.1 - Node: Bring transport `rtpPacketLossReceived` and `rtpPacketLossSent` stats back ([PR #1371](https://github.com/versatica/mediasoup/pull/1371)). ### 3.14.0 - `TransportListenInfo`: Add `portRange` (deprecate worker port range) ([PR #1365](https://github.com/versatica/mediasoup/pull/1365)). - Require Node.js >= 18 ([PR #1365](https://github.com/versatica/mediasoup/pull/1365)). ### 3.13.24 - Node: Fix missing `bitrateByLayer` field in stats of `RtpRecvStream` in Node ([PR #1349](https://github.com/versatica/mediasoup/pull/1349)). - Update worker dependency libuv to 1.48.0. - Update worker FlatBuffers to 24.3.6-1 (fix cannot set temporal layer 0) ([PR #1348](https://github.com/versatica/mediasoup/pull/1348)). ### 3.13.23 - Fix DTLS packets do not honor configured DTLS MTU (attempt 3) ([PR #1345](https://github.com/versatica/mediasoup/pull/1345)). ### 3.13.22 - Fix wrong publication of mediasoup NPM 3.13.21. ### 3.13.21 - Revert ([PR #1156](https://github.com/versatica/mediasoup/pull/1156)) "Make DTLS fragment stay within MTU size range" because it causes a memory leak ([PR #1342](https://github.com/versatica/mediasoup/pull/1342)). ### 3.13.20 - Add server side ICE consent checks to detect silent WebRTC disconnections ([PR #1332](https://github.com/versatica/mediasoup/pull/1332)). - Fix regression (crash) in transport-cc feedback generation ([PR #1339](https://github.com/versatica/mediasoup/pull/1339)). ### 3.13.19 - Node: Fix `router.createWebRtcTransport()` with `listenIps` ([PR #1330](https://github.com/versatica/mediasoup/pull/1330)). ### 3.13.18 - Make transport-cc feedback work similarly to libwebrtc ([PR #1088](https://github.com/versatica/mediasoup/pull/1088) by @penguinol). - `TransportListenInfo`: "announced ip" can also be a hostname ([PR #1322](https://github.com/versatica/mediasoup/pull/1322)). - `TransportListenInfo`: Rename "announced ip" to "announced address" ([PR #1324](https://github.com/versatica/mediasoup/pull/1324)). - CI: Add `macos-14`. ### 3.13.17 - Fix prebuilt worker download ([PR #1319](https://github.com/versatica/mediasoup/pull/1319) by @brynrichards). - libsrtp: Update to v3.0-alpha version in our fork. ### 3.13.16 - Node: Add new `worker.on('subprocessclose')` event ([PR #1307](https://github.com/versatica/mediasoup/pull/1307)). ### 3.13.15 - Add worker prebuild binary for Linux kernel 6 ([PR #1300](https://github.com/versatica/mediasoup/pull/1300)). ### 3.13.14 - Avoid modification of user input data ([PR #1285](https://github.com/versatica/mediasoup/pull/1285)). - `TransportListenInfo`: Add transport socket flags ([PR #1291](https://github.com/versatica/mediasoup/pull/1291)). - Note that `flags.ipv6Only` is `false` by default. - `TransportListenInfo`: Ignore given socket flags if not suitable for given IP family or transport ([PR #1294](https://github.com/versatica/mediasoup/pull/1294)). - Meson: Remove `-Db_pie=true -Db_staticpic=true` args ([PR #1293](https://github.com/versatica/mediasoup/pull/1293)). - Add RTCP Sender Report trace event ([PR #1267](https://github.com/versatica/mediasoup/pull/1267) by @GithubUser8080). ### 3.13.13 - Worker: Do not use references for async callbacks ([PR #1274](https://github.com/versatica/mediasoup/pull/1274)). - liburing: Enable zero copy ([PR #1273](https://github.com/versatica/mediasoup/pull/1273)). - Fix build on musl based systems (such as Alpine Linux) ([PR #1279](https://github.com/versatica/mediasoup/pull/1279)). ### 3.13.12 - Worker: Disable `RtcLogger` usage if not enabled ([PR #1264](https://github.com/versatica/mediasoup/pull/1264)). - npm installation: Don't require Python if valid worker prebuilt binary is fetched ([PR #1265](https://github.com/versatica/mediasoup/pull/1265)). - Update h264-profile-level-id NPM dependency to 1.1.0. ### 3.13.11 - liburing: Avoid extra memcpy on RTP ([PR #1258](https://github.com/versatica/mediasoup/pull/1258)). - libsrtp: Use our own fork with performance gain ([PR #1260](https://github.com/versatica/mediasoup/pull/1260)). - `DataConsumer`: Add `addSubchannel()` and `removeSubchannel()` methods ([PR #1263](https://github.com/versatica/mediasoup/pull/1263)). - Fix Rust `DataConsumer` ([PR #1262](https://github.com/versatica/mediasoup/pull/1262)). ### 3.13.10 - `tasks.py`: Always include `--no-user` in `pip install` commands to avoid the "can not combine --user and --target" error in Windows ([PR #1257](https://github.com/versatica/mediasoup/pull/1257)). ### 3.13.9 - Update worker liburing dependency to 2.4-2 ([PR #1254](https://github.com/versatica/mediasoup/pull/1254)). - liburing: Enable by default ([PR 1255](https://github.com/versatica/mediasoup/pull/1255)). ### 3.13.8 - liburing: Enable liburing usage for SCTP data delivery ([PR 1249](https://github.com/versatica/mediasoup/pull/1249)). - liburing: Disable by default ([PR 1253](https://github.com/versatica/mediasoup/pull/1253)). ### 3.13.7 - Update worker dependencies ([PR #1201](https://github.com/versatica/mediasoup/pull/1201)): - abseil-cpp 20230802.0-2 - libuv 1.47.0-1 - OpenSSL 3.0.8-2 - usrsctp snapshot ebb18adac6501bad4501b1f6dccb67a1c85cc299 - Enable `liburing` usage for Linux (kernel versions >= 6) ([PR #1218](https://github.com/versatica/mediasoup/pull/1218)). ### 3.13.6 - Replace make + Makefile with Python Invoke library + tasks.py (also fix installation under path with whitespaces) ([PR #1239](https://github.com/versatica/mediasoup/pull/1239)). ### 3.13.5 - Fix RTCP SDES packet size calculation ([PR #1236](https://github.com/versatica/mediasoup/pull/1236) based on PR [PR #1234](https://github.com/versatica/mediasoup/pull/1234) by @ybybwdwd). - RTCP Compound Packet: Use a single DLRR report to hold all ssrc info sub-blocks ([PR #1237](https://github.com/versatica/mediasoup/pull/1237)). ### 3.13.4 - Fix RTCP DLRR (Delay Since Last Receiver Report) block parsing ([PR #1234](https://github.com/versatica/mediasoup/pull/1234)). ### 3.13.3 - Node: Fix issue when 'pause'/'resume' events are not emitted ([PR #1231](https://github.com/versatica/mediasoup/pull/1231) by @douglaseel). ### 3.13.2 - FBS: `LayersChangeNotification` body must be optional (fixes a crash) ([PR #1227](https://github.com/versatica/mediasoup/pull/1227)). ### 3.13.1 - Node: Extract version from `package.json` using `require()` ([PR #1217](https://github.com/versatica/mediasoup/pull/1217) by @arcinston). ### 3.13.0 - Switch from JSON based messages to FlatBuffers ([PR #1064](https://github.com/versatica/mediasoup/pull/1064)). - Add `TransportListenInfo` in all transports and send/recv buffer size options ([PR #1084](https://github.com/versatica/mediasoup/pull/1084)). - Add optional `rtcpListenInfo` in `PlainTransportOptions` ([PR #1099](https://github.com/versatica/mediasoup/pull/1099)). - Add pause/resume API in `DataProducer` and `DataConsumer` ([PR #1104](https://github.com/versatica/mediasoup/pull/1104)). - DataChannel subchannels feature ([PR #1152](https://github.com/versatica/mediasoup/pull/1152)). - Worker: Make DTLS fragment stay within MTU size range ([PR #1156](https://github.com/versatica/mediasoup/pull/1156), based on [PR #1143](https://github.com/versatica/mediasoup/pull/1143) by @vpnts-se). ### 3.12.16 - Fix `IceServer` crash when client uses ICE renomination ([PR #1182](https://github.com/versatica/mediasoup/pull/1182)). ### 3.12.15 - Fix NPM "postinstall" task in Windows ([PR #1187](https://github.com/versatica/mediasoup/pull/1187)). ### 3.12.14 - CI: Use Node.js version 20 ([PR #1177](https://github.com/versatica/mediasoup/pull/1177)). - Use given `PYTHON` environment variable (if given) when running `worker/scripts/getmake.py` ([PR #1186](https://github.com/versatica/mediasoup/pull/1186)). ### 3.12.13 - Bump up Meson from 1.1.0 to 1.2.1 (fixes Xcode 15 build issues) ([PR #1163](https://github.com/versatica/mediasoup/pull/1163) by @arcinston). ### 3.12.12 - Support C++20 ([PR #1150](https://github.com/versatica/mediasoup/pull/1150) by @o-u-p). ### 3.12.11 - Google Transport Feedback: Read Reference Time field as 24bits signed as per spec ([PR #1145](https://github.com/versatica/mediasoup/pull/1145)). ### 3.12.10 - Node: Rename `WebRtcTransport.webRtcServerClosed()` to `listenServerClosed()` ([PR #1141](https://github.com/versatica/mediasoup/pull/1141) by @piranna). ### 3.12.9 - Fix RTCP SDES ([PR #1139](https://github.com/versatica/mediasoup/pull/1139)). ### 3.12.8 - Export `workerBin` absolute path ([PR #1123](https://github.com/versatica/mediasoup/pull/1123)). ### 3.12.7 - `SimulcastConsumer`: Fix lack of "layerschange" event when all streams in the producer die ([PR #1122](https://github.com/versatica/mediasoup/pull/1122)). ### 3.12.6 - Worker: Add `Transport::Destroying()` protected method ([PR #1114](https://github.com/versatica/mediasoup/pull/1114)). - `RtpStreamRecv`: Fix jitter calculation ([PR #1117](https://github.com/versatica/mediasoup/pull/1117), thanks to @penguinol). - Revert "Node: make types.ts only export types rather than the entire class/code" ([PR #1109](https://github.com/versatica/mediasoup/pull/1109)) because it requires `typescript` >= 5 in the apps that import mediasoup and we don't want to be that strict yet. ### 3.12.5 - `DataConsumer`: Fix removed 'bufferedamountlow' notification ([PR #1113](https://github.com/versatica/mediasoup/pull/1113)). ### 3.12.4 - Fix downloaded prebuilt binary check on Windows ([PR #1105](https://github.com/versatica/mediasoup/pull/1105) by @woodfe). ### 3.12.3 Migrate `npm-scripts.js` to `npm-scripts.mjs` (ES Module) ([PR #1093](https://github.com/versatica/mediasoup/pull/1093)). ### 3.12.2 - CI: Use `ubuntu-20.04` to build `mediasoup-worker` prebuilt on Linux ([PR #1092](https://github.com/versatica/mediasoup/pull/1092)). ### 3.12.1 - `mediasoup-worker` prebuild: Fallback to local building if fetched binary doesn't run on current host ([PR #1090](https://github.com/versatica/mediasoup/pull/1090)). ### 3.12.0 - Automate and publish prebuilt `mediasoup-worker` binaries ([PR #1087](https://github.com/versatica/mediasoup/pull/1087), thanks to @barlock for his work in ([PR #1083](https://github.com/versatica/mediasoup/pull/1083)). ### 3.11.26 - Worker: Fix NACK timer and avoid negative RTT ([PR #1082](https://github.com/versatica/mediasoup/pull/1082), thanks to @o-u-p for his work in ([PR #1076](https://github.com/versatica/mediasoup/pull/1076)). ### 3.11.25 - Worker: Require C++17, Meson >= 1.1.0 and update subprojects ([PR #1081](https://github.com/versatica/mediasoup/pull/1081)). ### 3.11.24 - `SeqManager`: Fix performance regression ([PR #1068](https://github.com/versatica/mediasoup/pull/1068), thanks to @vpalmisano for properly reporting). ### 3.11.23 - Node: Fix `appData` for `Transport` and `RtpObserver` parent classes ([PR #1066](https://github.com/versatica/mediasoup/pull/1066)). ### 3.11.22 - `RtpStreamRecv`: Only perform RTP inactivity check on simulcast streams ([PR #1061](https://github.com/versatica/mediasoup/pull/1061)). - `SeqManager`: Properly remove old dropped entries ([PR #1054](https://github.com/versatica/mediasoup/pull/1054)). - libwebrtc: Upgrade trendline estimator to improve low bandwidth conditions ([PR #1055](https://github.com/versatica/mediasoup/pull/1055) by @ggarber). - libwebrtc: Fix bandwidth probation dead state ([PR #1031](https://github.com/versatica/mediasoup/pull/1031) by @vpalmisano). ### 3.11.21 - Fix check division by zero in transport congestion control ([PR #1049](https://github.com/versatica/mediasoup/pull/1049) by @ggarber). - Fix lost pending statuses in transport CC feedback ([PR #1050](https://github.com/versatica/mediasoup/pull/1050) by @ggarber). ### 3.11.20 - `RtpStreamSend`: Reset RTP retransmission buffer upon RTP sequence number reset ([PR #1041](https://github.com/versatica/mediasoup/pull/1041)). - `RtpRetransmissionBuffer`: Handle corner case in which received packet has lower seq than newest packet in the buffer but higher timestamp ([PR #1044](https://github.com/versatica/mediasoup/pull/1044)). - `SeqManager`: Fix crash and add fuzzer ([PR #1045](https://github.com/versatica/mediasoup/pull/1045)). - Node: Make `appData` TS typed and writable ([PR #1046](https://github.com/versatica/mediasoup/pull/1046), credits to @mango-martin). ### 3.11.19 - `SvcConsumer`: Properly handle VP9 K-SVC bandwidth allocation ([PR #1036](https://github.com/versatica/mediasoup/pull/1036) by @vpalmisano). ### 3.11.18 - `RtpRetransmissionBuffer`: Consider the case of packet with newest timestamp but "old" seq number ([PR #1039](https://github.com/versatica/mediasoup/pull/1039)). ### 3.11.17 - Add `transport.setMinOutgoingBitrate()` method ([PR #1038](https://github.com/versatica/mediasoup/pull/1038), credits to @ jcague). - `RTC::RetransmissionBuffer`: Increase `RetransmissionBufferMaxItems` from 2500 to 5000. ### 3.11.16 - Fix `SeqManager`: Properly consider previous cycle dropped inputs ([PR #1032](https://github.com/versatica/mediasoup/pull/1032)). - `RtpRetransmissionBuffer`: Get rid of not necessary `startSeq` private member ([PR #1029](https://github.com/versatica/mediasoup/pull/1029)). - Node: Upgrade TypeScript to 5.0.2. ### 3.11.15 - `RtpRetransmissionBuffer`: Fix crash and add fuzzer ([PR #1028](https://github.com/versatica/mediasoup/pull/1028)). ### 3.11.14 - Refactor RTP retransmission buffer in a separate and testable `RTC::RetransmissionBuffer` class ([PR #1023](https://github.com/versatica/mediasoup/pull/1023)). ### 3.11.13 - `AudioLevelObserver`: Use multimap rather than map to avoid conflict if various Producers generate same audio level ([PR #1021](https://github.com/versatica/mediasoup/pull/1021), issue reported by @buptlsp). ### 3.11.12 - Fix jitter calculation ([PR #1019](https://github.com/versatica/mediasoup/pull/1019), credits to @alexciarlillo and @snnz). ### 3.11.11 - Add support for RTCP NACK in OPUS ([PR #1015](https://github.com/versatica/mediasoup/pull/1015)). ### 3.11.10 - Download and use MSYS/make locally for Windows postinstall ([PR #792](https://github.com/versatica/mediasoup/pull/792) by @snnz). ### 3.11.9 - Allow simulcast with a single encoding (and N temporal layers) ([PR #1013](https://github.com/versatica/mediasoup/pull/1013)). - Update libsrtp to 2.5.0. ### 3.11.8 - `SimulcastConsumer::GetDesiredBitrate()`: Choose the highest bitrate among all Producer streams ([PR #992](https://github.com/versatica/mediasoup/pull/992)). - `SimulcastConsumer`: Fix frozen video when syncing keyframe is discarded due to too high RTP timestamp extra offset needed ([PR #999](https://github.com/versatica/mediasoup/pull/999), thanks to @satoren for properly reporting the issue and helping with the solution). ### 3.11.7 - libwebrtc: Fix crash due to invalid `arrival_time` value ([PR #985](https://github.com/versatica/mediasoup/pull/985) by @ggarber). - libwebrtc: Replace `MS_ASSERT()` with `MS_ERROR()` ([PR #988](https://github.com/versatica/mediasoup/pull/988)). ### 3.11.6 - Fix wrong `PictureID` rolling over in VP9 and VP8 ([PR #984](https://github.com/versatica/mediasoup/pull/984) by @jcague). ### 3.11.5 - Require Node.js >= 16 ([PR #973](https://github.com/versatica/mediasoup/pull/973)). - Fix wrong `Consumer` bandwidth estimation under `Producer` packet loss ([PR #962](https://github.com/versatica/mediasoup/pull/962) by @ggarber). ### 3.11.4 - Node: Migrate tests to TypeScript ([PR #958](https://github.com/versatica/mediasoup/pull/958)). - Node: Remove compiled JavaScript from repository and compile TypeScript code on NPM `prepare` script on demand when installed via git ([PR #954](https://github.com/versatica/mediasoup/pull/954)). - Worker: Add `RTC::Shared` singleton for RTC entities ([PR #953](https://github.com/versatica/mediasoup/pull/953)). - Update OpenSSL to 3.0.7. ### 3.11.3 - `ChannelMessageHandlers`: Make `RegisterHandler()` not remove the existing handler if another one with same `id` is given ([PR #952](https://github.com/versatica/mediasoup/pull/952)). ### 3.11.2 - Fix installation issue in Linux due to a bug in ninja latest version 1.11.1 ([PR #948](https://github.com/versatica/mediasoup/pull/948)). ### 3.11.1 - `ActiveSpeakerObserver`: Revert 'dominantspeaker' event changes in [PR #941](https://github.com/versatica/mediasoup/pull/941) to avoid breaking changes ([PR #947](https://github.com/versatica/mediasoup/pull/947)). ### 3.11.0 - `Transport`: Remove duplicate call to method ([PR #931](https://github.com/versatica/mediasoup/pull/931)). - RTCP: Adjust maximum compound packet size ([PR #934](https://github.com/versatica/mediasoup/pull/934)). - `DataConsumer`: Fix `bufferedAmount` type to be a number again ([PR #936](https://github.com/versatica/mediasoup/pull/936)). - `ActiveSpeakerObserver`: Fix 'dominantspeaker' event by having a single `Producer` as argument rather than an array with a single `Producer` into it ([PR #941](https://github.com/versatica/mediasoup/pull/941)). - `ActiveSpeakerObserver`: Fix memory leak ([PR #942](https://github.com/versatica/mediasoup/pull/942)). - Fix some libwebrtc issues ([PR #944](https://github.com/versatica/mediasoup/pull/944)). - Tests: Normalize hexadecimal data representation ([PR #945](https://github.com/versatica/mediasoup/pull/945)). - `SctpAssociation`: Fix memory violation ([PR #943](https://github.com/versatica/mediasoup/pull/943)). ### 3.10.12 - Fix worker crash due to `std::out_of_range` exception ([PR #933](https://github.com/versatica/mediasoup/pull/933)). ### 3.10.11 - RTCP: Fix trailing space needed by `srtp_protect_rtcp()` ([PR #929](https://github.com/versatica/mediasoup/pull/929)). ### 3.10.10 - Fix the JSON serialization for the payload channel `rtp` event ([PR #926](https://github.com/versatica/mediasoup/pull/926) by @mhammo). ### 3.10.9 - RTCP enhancements ([PR #914](https://github.com/versatica/mediasoup/pull/914)). ### 3.10.8 - `Consumer`: use a bitset instead of a set for supported payload types ([PR #919](https://github.com/versatica/mediasoup/pull/919)). - RtpPacket: optimize UpdateMid() ([PR #920](https://github.com/versatica/mediasoup/pull/920)). - Little optimizations and modernization ([PR #916](https://github.com/versatica/mediasoup/pull/916)). - Fix SIGSEGV at `RTC::WebRtcTransport::OnIceServerTupleRemoved()` ([PR #915](https://github.com/versatica/mediasoup/pull/915), credits to @ybybwdwd). - `WebRtcServer`: Make `port` optional (if not given, a random available port from the `Worker` port range is used) ([PR #908](https://github.com/versatica/mediasoup/pull/908) by @satoren). ### 3.10.7 - Forward `abs-capture-time` RTP extension also for audio packets ([PR #911](https://github.com/versatica/mediasoup/pull/911)). ### 3.10.6 - Node: Define TypeScript types for `internal` and `data` objects ([PR #891](https://github.com/versatica/mediasoup/pull/891)). - `Channel` and `PayloadChannel`: Refactor `internal` with a single `handlerId` ([PR #889](https://github.com/versatica/mediasoup/pull/889)). - `Channel` and `PayloadChannel`: Optimize message format and JSON generation ([PR #893](https://github.com/versatica/mediasoup/pull/893)). - New C++ `ChannelMessageHandlers` class ([PR #894](https://github.com/versatica/mediasoup/pull/894)). - Fix Rust support after recent changes ([PR #898](https://github.com/versatica/mediasoup/pull/898)). - Modify `FeedbackRtpTransport` and tests to be compliant with latest libwebrtc code, make reference time to be unsigned ([PR #899](https://github.com/versatica/mediasoup/pull/899) by @penguinol and @sarumjanuch). ### 3.10.5 - `RtpStreamSend`: Do not store too old RTP packets ([PR #885](https://github.com/versatica/mediasoup/pull/885)). - Log error details in channel socket. ([PR #875](https://github.com/versatica/mediasoup/pull/875) by @mstyura). ### 3.10.4 - Do not clone RTP packets if not needed ([PR #850](https://github.com/versatica/mediasoup/pull/850)). - Fix DTLS related crash ([PR #867](https://github.com/versatica/mediasoup/pull/867)). ### 3.10.3 - `SimpleConsumer`: Fix. Only process Opus codec ([PR #865](https://github.com/versatica/mediasoup/pull/865)). - TypeScript: Improve `WebRtcTransportOptions` type to not allow `webRtcServer` and `listenIps`options at the same time ([PR #852](https://github.com/versatica/mediasoup/pull/852)). ### 3.10.2 - Fix release contents by including `meson_options.txt` ([PR #863](https://github.com/versatica/mediasoup/pull/863)). ### 3.10.1 - `RtpStreamSend`: Memory optimizations ([PR #840](https://github.com/versatica/mediasoup/pull/840)). Extracted from #675, by @nazar-pc. - `SimpleConsumer`: Opus DTX ignore capabilities ([PR #846](https://github.com/versatica/mediasoup/pull/846)). - Update `libuv` to 1.44.1: Fixes `libuv` build ([PR #857](https://github.com/versatica/mediasoup/pull/857)). ### 3.10.0 - `WebRtcServer`: A new class that brings to `WebRtcTransports` the ability to listen on a single UDP/TCP port ([PR #834](https://github.com/versatica/mediasoup/pull/834)). - More SRTP crypto suites ([PR #837](https://github.com/versatica/mediasoup/pull/837)). - Improve `EnhancedEventEmitter` ([PR #836](https://github.com/versatica/mediasoup/pull/836)). - `TransportCongestionControlClient`: Allow setting max outgoing bitrate before `tccClient` is created ([PR #833](https://github.com/versatica/mediasoup/pull/833)). - Update TypeScript version. ### 3.9.17 - `RateCalculator`: Fix old buffer items cleanup ([PR #830](https://github.com/versatica/mediasoup/pull/830) by @dsdolzhenko). - Update TypeScript version. ### 3.9.16 - `SimulcastConsumer`: Fix spatial layer switch with unordered packets ([PR #823](https://github.com/versatica/mediasoup/pull/823) by @jcague). - Update TypeScript version. ### 3.9.15 - `RateCalculator`: Revert Fix old buffer items cleanup ([PR #819](https://github.com/versatica/mediasoup/pull/819) by @dsdolzhenko). ### 3.9.14 - `NackGenerator`: Add a configurable delay before sending NACK ([PR #827](https://github.com/versatica/mediasoup/pull/827), credits to @penguinol). - `SimulcastConsumer`: Fix a race condition in SimulcastConsumer ([PR #825](https://github.com/versatica/mediasoup/pull/825) by @dsdolzhenko). - Add support for H264 SVC (#798 by @prtmD). - `RtpStreamSend`: Support receive RTCP-XR RRT and send RTCP-XR DLRR ([PR #781](https://github.com/versatica/mediasoup/pull/781) by @aggresss). - `RateCalculator`: Fix old buffer items cleanup ([PR #819](https://github.com/versatica/mediasoup/pull/819) by @dsdolzhenko). - `DirectTransport`: Create a buffer to process RTP packets ([PR #730](https://github.com/versatica/mediasoup/pull/730) by @rtctt). - Node: Improve `appData` TypeScript syntax and initialization. - Allow setting max outgoing bitrate below the initial value ([PR #826](https://github.com/versatica/mediasoup/pull/826) by @ggarber). - Update TypeScript version. ### 3.9.13 - `VP8`: Do not discard `TL0PICIDX` from Temporal Layers higher than 0 (PR @817 by @jcague). - Update TypeScript version. ### 3.9.12 - `DtlsTransport`: Make DTLS negotiation run immediately ([PR #815](https://github.com/versatica/mediasoup/pull/815)). - Update TypeScript version. ### 3.9.11 - Modify `SimulcastConsumer` to keep using layers without good scores ([PR #804](https://github.com/versatica/mediasoup/pull/804) by @ggarber). ### 3.9.10 - Update worker dependencies: - OpenSSL 3.0.2. - abseil-cpp 20211102.0. - nlohmann_json 3.10.5. - usrsctp snapshot 4e06feb01cadcd127d119486b98a4bd3d64aa1e7. - wingetopt 1.00. - Update TypeScript version. - Fix RTP marker bit not being reseted after mangling in each `Consumer` ([PR #811](https://github.com/versatica/mediasoup/pull/811) by @ggarber). ### 3.9.9 - Optimize RTP header extension handling ([PR #786](https://github.com/versatica/mediasoup/pull/786)). - `RateCalculator`: Reset optimization ([PR #785](https://github.com/versatica/mediasoup/pull/785)). - Fix frozen video due to double call to `Consumer::UserOnTransportDisconnected()` ([PR #788](https://github.com/versatica/mediasoup/pull/788), thanks to @ggarber for exposing this issue in [PR #787](https://github.com/versatica/mediasoup/pull/787)). ### 3.9.8 - Fix VP9 kSVC forwarding logic to not forward lower unneded layers ([PR #778](https://github.com/versatica/mediasoup/pull/778) by @ggarber). - Fix update bandwidth estimation configuration and available bitrate when updating max outgoing bitrate ([PR #779](https://github.com/versatica/mediasoup/pull/779) by @ggarber). - Replace outdated `random-numbers` package by native `crypto.randomInt()` ([PR #776](https://github.com/versatica/mediasoup/pull/776) by @piranna). - Update TypeScript version. ### 3.9.7 - Typing event emitters in mediasoup Node ([PR #764](https://github.com/versatica/mediasoup/pull/764) by @unao). ### 3.9.6 - TCC client optimizations for faster and more stable BWE ([PR #712](https://github.com/versatica/mediasoup/pull/712) by @ggarber). - Added support for RTP abs-capture-time header ([PR #761](https://github.com/versatica/mediasoup/pull/761) by @oto313). ### 3.9.5 - ICE renomination support ([PR #756](https://github.com/versatica/mediasoup/pull/756)). - Update `libuv` to 1.43.0. ### 3.9.4 - Worker: Fix bad printing of error messages from Worker ([PR #750](https://github.com/versatica/mediasoup/pull/750) by @j1elo). ### 3.9.3 - Single H264/H265 codec configuration in `supportedRtpCapabilities` ([PR #718](https://github.com/versatica/mediasoup/pull/718)). - Improve Windows support by not requiring MSVC configuration ([PR #741](https://github.com/versatica/mediasoup/pull/741)). ### 3.9.2 - `pipeToRouter()`: Reuse same `PipeTransport` when possible ([PR #697](https://github.com/versatica/mediasoup/pull/697)). - Add `worker.died` boolean getter. - Update TypeScript version to 4.X.X and use `target: "esnext"` so transpilation of ECMAScript private fields (`#xxxxx`) don't use `WeakMaps` tricks but use standard syntax instead. - Use more than one core for compilation on Windows ([PR #709](https://github.com/versatica/mediasoup/pull/709)). - `Consumer`: Modification of bitrate allocation algorithm ([PR #708](https://github.com/versatica/mediasoup/pull/708)). ### 3.9.1 - NixOS friendly build process ([PR #683](https://github.com/versatica/mediasoup/pull/683)). - Worker: Emit "died" event before observer "close" ([PR #684](https://github.com/versatica/mediasoup/pull/684)). - Transport: Hide debug message for RTX RTCP-RR packets ([PR #688](https://github.com/versatica/mediasoup/pull/688)). - Update `libuv` to 1.42.0. - Improve Windows support ([PR #692](https://github.com/versatica/mediasoup/pull/692)). - Avoid build commands when MEDIASOUP_WORKER_BIN is set ([PR #695](https://github.com/versatica/mediasoup/pull/695)). ### 3.9.0 - Replaces GYP build system with fully-functional Meson build system ([PR #622](https://github.com/versatica/mediasoup/pull/622)). - Worker communication optimization (aka removing netstring dependency) ([PR #644](https://github.com/versatica/mediasoup/pull/644)). - Move TypeScript and compiled JavaScript code to a new `node` folder. - Use ES6 private fields. - Require Node.js version >= 12. ### 3.8.4 - OPUS multi-channel (Surround sound) support ([PR #647](https://github.com/versatica/mediasoup/pull/647)). - Add `packetLoss` stats to transport ([PR #648](https://github.com/versatica/mediasoup/pull/648) by @ggarber). - Fixes for active speaker observer ([PR #655](https://github.com/versatica/mediasoup/pull/655) by @ggarber). - Fix big endian issues ([PR #639](https://github.com/versatica/mediasoup/pull/639)). ### 3.8.3 - Fix wrong `size_t*` to `int*` conversion in 64bit Big-Endian hosts ([PR #637](https://github.com/versatica/mediasoup/pull/637)). ### 3.8.2 - `ActiveSpeakerObserver`: Fix crash due to a `nullptr` ([PR #634](https://github.com/versatica/mediasoup/pull/634)). ### 3.8.1 - `SimulcastConsumer`: Fix RTP timestamp when switching layers ([PR #626](https://github.com/versatica/mediasoup/pull/626) by @penguinol). ### 3.8.0 - Update `libuv` to 1.42.0. - Use non-ASM OpenSSL on Windows ([PR #614](https://github.com/versatica/mediasoup/pull/614)). - Fix minor memory leak caused by non-virtual destructor ([PR #625](https://github.com/versatica/mediasoup/pull/625)). - Dominant Speaker Event ([PR #603](https://github.com/versatica/mediasoup/pull/603) by @SteveMcFarlin). ### 3.7.19 - Update `libuv` to 1.41.0. - C++: - Move header includes ([PR #608](https://github.com/versatica/mediasoup/pull/608)). - Enhance debugging on channel request/notification error ([PR #607](https://github.com/versatica/mediasoup/pull/607)). ### 3.7.18 - Support for optional fixed port on transports ([PR #593](https://github.com/versatica/mediasoup/pull/593) by @nazar-pc). - Upgrade and optimize OpenSSL dependency ([PR #598](https://github.com/versatica/mediasoup/pull/598) by @vpalmisano): - OpenSSL upgraded to version 1.1.1k. - Enable the compilation of assembly extensions for OpenSSL. - Optimize the worker build (`-O3`) and disable the debug flag (`-g`). ### 3.7.17 - Introduce `PipeConsumerOptions` to avoid incorrect type information on `PipeTransport.consume()` arguments. - Make `ConsumerOptions.rtpCapabilities` field required as it should have always been. ### 3.7.16 - Add `mid` option in `ConsumerOptions` to provide way to override MID ([PR #586](https://github.com/versatica/mediasoup/pull/586) by @mstyura). ### 3.7.15 - `kind` field of `RtpHeaderExtension` is no longer optional. It must be 'audio' or 'video'. - Refactor API inconsistency in internal RTP Observer communication with worker. ### 3.7.14 - Update `usrsctp` to include a "possible use after free bug" fix (commit [here](https://github.com/sctplab/usrsctp/commit/0f8d58300b1fdcd943b4a9dd3fbd830825390d4d)). ### 3.7.13 - Fix build on FreeBSD ([PR #585](https://github.com/versatica/mediasoup/pull/585) by @smortex). ### 3.7.12 - `mediasoup-worker`: Fix memory leaks on error exit ([PR #581](https://github.com/versatica/mediasoup/pull/581)). ### 3.7.11 - Fix `DepUsrSCTP::Checker::timer` not being freed on `Worker` close ([PR #576](https://github.com/versatica/mediasoup/pull/576)). Thanks @nazar-pc for discovering this. ### 3.7.10 - Remove clang tools binaries from regular installation. ### 3.7.9 - Code clean up. ### 3.7.8 - `PayloadChannel`: Copy received messages into a separate buffer to avoid memory corruption if the message is later modified ([PR #570](https://github.com/versatica/mediasoup/pull/570) by @aggresss). ### 3.7.7 - Thread and memory safety fixes needed for mediasoup-rust ([PR #562](https://github.com/versatica/mediasoup/pull/562) by @nazar-pc). - mediasoup-rust support on macOS ([PR #567](https://github.com/versatica/mediasoup/pull/567) by @nazar-pc). - mediasoup-rust release 0.7.2. ### 3.7.6 - `Transport`: Implement new `setMaxOutgoingBitrate()` method ([PR #555](https://github.com/versatica/mediasoup/pull/555) by @t-mullen). - `SctpAssociation`: Don't warn if SCTP send buffer is full. - Rust: Update modules structure and other minor improvements for Rust version ([PR #558](https://github.com/versatica/mediasoup/pull/558)). - `mediasoup-worker`: Avoid duplicated basenames so that `libmediasoup-worker` is compilable on macOS ([PR #557](https://github.com/versatica/mediasoup/pull/557)). ### 3.7.5 - SctpAssociation: provide 'sctpsendbufferfull' reason on send error (#552). ### 3.7.4 - Improve `RateCalculator` ([PR #547](https://github.com/versatica/mediasoup/pull/547) by @vpalmisano). ### 3.7.3 - Make worker M1 compilable. ### 3.7.2 - `RateCalculator` optimization ([PR #538](https://github.com/versatica/mediasoup/pull/538) by @vpalmisano). ### 3.7.1 - `SimulcastConsumer`: Fix miscalculation when increasing layer ([PR #541](https://github.com/versatica/mediasoup/pull/541) by @penguinol). - Rust version with thread-based worker ([PR #540](https://github.com/versatica/mediasoup/pull/540)). ### 3.7.0 - Welcome to `mediasoup-rust`! Authored by @nazar-pc (PRs #518 and #533). - Update `usrsctp`. ### 3.6.37 - Fix crash if empty `fingerprints` array is given in `webrtcTransport.connect()` (issue #537). ### 3.6.36 - `Producer`: Add new stats field 'rtxPacketsDiscarded' ([PR #536](https://github.com/versatica/mediasoup/pull/536)). ### 3.6.35 - `Consumer` classes: make `IsActive()` return `true` (even if `Producer`'s score is 0) when DTX is enabled ([PR #534](https://github.com/versatica/mediasoup/pull/534) due to issue #532). ### 3.6.34 - Fix crash (regression, issue #529). ### 3.6.33 - Add missing `delete cb` that otherwise would leak ([PR #527](https://github.com/versatica/mediasoup/pull/527) based on [PR #526](https://github.com/versatica/mediasoup/pull/526) by @vpalmisano). - `router.pipeToRouter()`: Fix possible inconsistency in `pipeProducer.paused` status (as discussed in this [thread](https://mediasoup.discourse.group/t/concurrency-architecture/2515/) in the mediasoup forum). - Update `nlohmann/json` to 3.9.1. - Update `usrsctp`. - Enhance Jitter calculation. ### 3.6.32 - Fix notifications from `mediasoup-worker` being processed before responses received before them (issue #501). ### 3.6.31 - Move `bufferedAmount` from `dataConsumer.dump()` to `dataConsumer.getStats()`. ### 3.6.30 - Add `pipe` option to `transport.consume()`([PR #494](https://github.com/versatica/mediasoup/pull/494)). - So the receiver will get all streams from the `Producer`. - It works for any kind of transport (but `PipeTransport` which is always like this). - Add `LICENSE` and `PATENTS` files in `libwebrtc` dependency (issue #495). - Added `worker/src/Utils/README_BASE64_UTILS` (issue #497). - Update `usrsctp`. ### 3.6.29 - Fix wrong message about `rtcMinPort` and `rtcMaxPort`. - Update deps. - Improve `EnhancedEventEmitter.safeAsPromise()` (although not used). ### 3.6.28 - Fix replacement of `__MEDIASOUP_VERSION__` in `lib/index.d.ts` (issue #483). - `worker/scripts/configure.py`: Handle 'mips64' ([PR #485](https://github.com/versatica/mediasoup/pull/485)). ### 3.6.27 - Allow the `mediasoup-worker` process to inherit all environment variables (issue #480). ### 3.6.26 - BWE tweaks and debug logs. ### 3.6.25 - SCTP fixes ([PR #479](https://github.com/versatica/mediasoup/pull/479)). ### 3.6.24 - Update `awaitqueue` dependency. ### 3.6.23 - Fix yet another memory leak in Node.js layer due to `PayloadChannel` event listener not being removed. ### 3.6.22 - `Transport.cpp`: Provide transport congestion client with RTCP Receiver Reports (#464). - Update `libuv` to 1.40.0. - Update Node deps. - `SctpAssociation.cpp`: increase `sctpBufferedAmount` before sending any data (#472). ### 3.6.21 - Fix memory leak in Node.js layer due to `PayloadChannel` event listener not being removed (related to #463). ### 3.6.20 - Remove `-fwrapv` when building `mediasoup-worker` in `Debug` mode (issue #460). - Add `MEDIASOUP_MAX_CORES` to limit `NUM_CORES` during `mediasoup-worker` build ([PR #462](https://github.com/versatica/mediasoup/pull/462)). ### 3.6.19 - Update `usrsctp` dependency. - Update `typescript-eslint` deps. - Update Node deps. ### 3.6.18 - Fix `ortc.getConsumerRtpParameters()` RTX codec comparison issue ([PR #453](https://github.com/versatica/mediasoup/pull/453)). - RtpObserver: expose `RtpObserverAddRemoveProducerOptions` for `addProducer()` and `removeProducer()` methods. ### 3.6.17 - Update `libuv` to 1.39.0. - Update Node deps. - SimulcastConsumer: Prefer the highest spatial layer initially ([PR #450](https://github.com/versatica/mediasoup/pull/450)). - RtpStreamRecv: Set RtpDataCounter window size to 6 secs if DTX (#451) ### 3.6.16 - `SctpAssociation.cpp`: Fix `OnSctpAssociationBufferedAmount()` call. - Update deps. - New API to send data from Node throught SCTP DataConsumer. ### 3.6.15 - Avoid SRTP leak by deleting invalid SSRCs after STRP decryption (issue #437, thanks to @penguinol for reporting). - Update `usrsctp` dep. - DataConsumer 'bufferedAmount' implementation ([PR #442](https://github.com/versatica/mediasoup/pull/442)). ### 3.6.14 - Fix `usrsctp` vulnerability ([PR #439](https://github.com/versatica/mediasoup/pull/439)). - Fix issue #435 (thanks to @penguinol for reporting). - `TransportCongestionControlClient.cpp`: Enable periodic ALR probing to recover faster from network issues. - Update `nlohmann::json` C++ dep to 3.9.0. ### 3.6.13 - RTP on `DirectTransport` (issue #433, [PR #434](https://github.com/versatica/mediasoup/pull/434)): - New API `producer.send(rtpPacket: Buffer)`. - New API `consumer.on('rtp', (rtpPacket: Buffer)`. - New API `directTransport.sendRtcp(rtcpPacket: Buffer)`. - New API `directTransport.on('rtcp', (rtpPacket: Buffer)`. ### 3.6.12 - Release script. ### 3.6.11 - `Transport`: rename `maxSctpSendBufferSize` to `sctpSendBufferSize`. ### 3.6.10 - `Transport`: Implement `maxSctpSendBufferSize`. - Update `libuv` to 1.38.1. ### 3.6.9 - `Transport::ReceiveRtpPacket()`: Call `RecvStreamClosed(packet->GetSsrc())` if received RTP packet does not match any `Producer`. - `Transport::HandleRtcpPacket()`: Ensure `Consumer` is found for received NACK Feedback packets. - Fix issue #408. ### 3.6.8 - Fix SRTP leak due to streams not being removed when a `Producer` or `Consumer` is closed. - [PR #428](https://github.com/versatica/mediasoup/pull/428) (fixes issues #426). - Credits to credits to @penguinol for reporting and initial work at [PR #427](https://github.com/versatica/mediasoup/pull/427). - Update `nlohmann::json` C++ dep to 3.8.0. - C++: Enhance `const` correctness. ### 3.6.7 - `ConsumerScore`: Add `producerScores`, scores of all RTP streams in the producer ordered by encoding (just useful when the producer uses simulcast). - [PR #421](https://github.com/versatica/mediasoup/pull/421) (fixes issues #420). - Hide worker executable console in Windows. - [PR #419](https://github.com/versatica/mediasoup/pull/419) (credits to @BlueMagnificent). - `RtpStream.cpp`: Fix wrong `std::round()` usage. - Issue #423. ### 3.6.6 - Update `usrsctp` library. - Update ESLint and TypeScript related dependencies. ### 3.6.5 - Set `score:0` when `dtx:true` is set in an `encoding` and there is no RTP for some seconds for that RTP stream. - Fixes #415. ### 3.6.4 - `gyp`: Fix CLT version detection in OSX Catalina when XCode app is not installed. - [PR #413](https://github.com/versatica/mediasoup/pull/413) (credits to @enimo). ### 3.6.3 - Modernize TypeScript. ### 3.6.2 - Fix crash in `Transport.ts` when closing a `DataConsumer` created on a `DirectTransport`. ### 3.6.1 - Export new `DirectTransport` in `types`. - Make `DataProducerOptions` optional (not needed when in a `DirectTransport`). ### 3.6.0 - SCTP/DataChannel termination: - [PR #409](https://github.com/versatica/mediasoup/pull/409) - Allow the Node application to directly send text/binary messages to `mediasoup-worker` C++ process so others can consume them using `DataConsumers`. - And vice-versa: allow the Node application to directly consume in Node messages send by `DataProducers`. - Add `WorkerLogTag` TypeScript enum and also add a new 'message' tag into it. ### 3.5.15 - Simulcast and SVC: Better computation of desired bitrate based on `maxBitrate` field in the `producer.rtpParameters.encodings`. ### 3.5.14 - Update deps, specially `uuid` and `@types/uuid` that had a TypeScript related bug. - `TransportCongestionClient.cpp`: Improve sender side bandwidth estimation by do not reporting `this->initialAvailableBitrate` as available bitrate due to strange behavior in the algorithm. ### 3.5.13 - Simplify `GetDesiredBitrate()` in `SimulcastConsumer` and `SvcConsumer`. - Update `libuv` to 1.38.0. ### 3.5.12 - `SeqManager.cpp`: Improve performance. - [PR #398](https://github.com/versatica/mediasoup/pull/398) (credits to @penguinol). ### 3.5.11 - `SeqManager.cpp`: Fix a bug and improve performance. - Fixes issue #395 via [PR #396](https://github.com/versatica/mediasoup/pull/396) (credits to @penguinol). - Drop Node.js 8 support. Minimum supported Node.js version is now 10. - Upgrade `eslint` and `jest` major versions. ### 3.5.10 - `SimulcastConsumer.cpp`: Fix `IncreaseLayer()` method (fixes #394). - Udpate Node deps. ### 3.5.9 - `libwebrtc`: Apply patch by @sspanak and @Ivaka to avoid crash. Related issue: #357. - `PortManager.cpp`: Do not use `UV_UDP_RECVMMSG` in Windows due to a bug in `libuv` 1.37.0. - Update Node deps. ### 3.5.8 - Enable `UV_UDP_RECVMMSG`: - Upgrade `libuv` to 1.37.0. - Use `uv_udp_init_ex()` with `UV_UDP_RECVMMSG` flag. - Add our own `uv.gyp` now that `libuv` has removed support for GYP (fixes #384). ### 3.5.7 - Fix crash in `mediasoup-worker` due to conversion from `uint64_t` to `int64_t` (used within `libwebrtc` code. Fixes #357. - Update `usrsctp` library. - Update Node deps. ### 3.5.6 - `SeqManager.cpp`: Fix video lag after a long time. - Fixes #372 (thanks @penguinol for reporting it and giving the solution). ### 3.5.5 - `UdpSocket.cpp`: Revert `uv__udp_recvmmsg()` usage since it notifies about received UDP packets in reverse order. Feature on hold until fixed. ### 3.5.4 - `Transport.cpp`: Enable transport congestion client for the first video Consumer, no matter it's uses simulcast, SVC or a single stream. - Update `libuv` to 1.35.0. - `UdpSocket.cpp`: Ensure the new libuv's `uv__udp_recvmmsg()` is used, which is more efficient. ### 3.5.3 - `PlainTransport`: Remove `multiSource` option. It was a hack nobody should use. ### 3.5.2 - Enable MID RTP extension in mediasoup to receivers direction (for consumers). - This **requires** mediasoup-client 3.5.2 to work. ### 3.5.1 - `PlainTransport`: Fix event name: 'rtcpTuple' => 'rtcptuple'. ### 3.5.0 - `PipeTransport`: Add support for SRTP and RTP retransmission (RTX + NACK). Useful when connecting two mediasoup servers running in different hosts via pipe transports. - `PlainTransport`: Add support for SRTP. - Rename `PlainRtpTransport` to `PlainTransport` everywhere (classes, methods, TypeScript types, etc). Keep previous names and mark them as DEPRECATED. - Fix vulnarability in IPv6 parser. ### 3.4.13 - Update `uuid` dep to 7.0.X (new API). - Fix crash due wrong array index in `PipeConsumer::FillJson()`. - Fixes #364 ### 3.4.12 - TypeScript: generate `es2020` instead of `es6`. - Update `usrsctp` library. - Fixes #362 (thanks @chvarlam for reporting it). ### 3.4.11 - `IceServer.cpp`: Reject received STUN Binding request with 487 if remote peer indicates ICE-CONTROLLED into it. ### 3.4.10 - `ProducerOptions`: Rename `keyFrameWaitTime` option to `keyFrameRequestDelay` and make it work as expected. ### 3.4.9 - Add `Utils::Json::IsPositiveInteger()` to not rely on `is_number_unsigned()` of json lib, which is unreliable due to its design. - Avoid ES6 `export default` and always use named `export`. - `router.pipeToRouter()`: Ensure a single `PipeTransport` pair is created between `router1` and `router2`. - Since the operation is async, it may happen that two simultaneous calls to `router1.pipeToRouter({ producerId: xxx, router: router2 })` would end up generating two pairs of `PipeTranports`. To prevent that, let's use an async queue. - Add `keyFrameWaitTime` option to `ProducerOptions`. - Update Node and C++ deps. ### 3.4.8 - `libsrtp.gyp`: Fix regression in mediasoup for Windows. - `libsrtp.gyp`: Modernize it based on the new `BUILD.gn` in Chromium. - `libsrtp.gyp`: Don't include "test" and other targets. - Assume `HAVE_INTTYPES_H`, `HAVE_INT8_T`, etc. in Windows. - Issue details: https://github.com/sctplab/usrsctp/issues/353 - `gyp` dependency: Add support for Microsoft Visual Studio 2019. - Modify our own `gyp` sources to fix the issue. - CL uploaded to GYP project with the fix. - Issue details: https://github.com/sctplab/usrsctp/issues/347 ### 3.4.7 - `PortManager.cpp`: Do not limit the number of failed `bind()` attempts to 20 since it does not work well in scenarios that launch tons of `Workers` with same port range. Instead iterate all ports in the range given to the Worker. - Do not copy `catch.hpp` into `test/include/` but make the GYP `mediasoup-worker-test` target include the corresponding folder in `deps/catch`. ### 3.4.6 - Update libsrtp to 2.3.0. - Update ESLint and TypeScript deps. ### 3.4.5 - Update deps. - Fix text in `./github/Bug_Report.md` so it no longer references the deprecated mailing list. ### 3.4.4 - `Transport.cpp`: Ignore RTCP SDES packets (we don't do anything with them anyway). - `Producer` and `Consumer` stats: Always show `roundTripTime` (even if calculated value is 0) after a `roundTripTime` > 0 has been seen. ### 3.4.3 - `Transport.cpp`: Fix RTCP FIR processing: - Instead of looking at the media ssrc in the common header, iterate FIR items and look for associated `Consumers` based on ssrcs in each FIR item. - Fixes #350 (thanks @j1elo for reporting and documenting the issue). ### 3.4.2 - `SctpAssociation.cpp`: Improve/fix logs. - Improve Node `EventEmitter` events inline documentation. - `test-node-sctp.js`: Wait for SCTP association to be open before sending data. ### 3.4.1 - Improve `mediasoup-worker` build system by using `sh` instead of `bash` and default to 4 cores (thanks @smoke, [PR #349](https://github.com/versatica/mediasoup/pull/349)). ### 3.4.0 - Add `worker.getResourceUsage()` API. - Update OpenSSL to 1.1.1d. - Update `libuv` to 1.34.0. - Update TypeScript version. ### 3.3.8 - Update usrsctp dependency (it fixes a potential wrong memory access). - More details in the reported issue: https://github.com/sctplab/usrsctp/issues/408 ### 3.3.7 - Fix `version` getter. ### 3.3.6 - `SctpAssociation.cpp`: Initialize the `usrsctp` socket in the class constructor. Fixes #348. ### 3.3.5 - Fix usage of a deallocated `RTC::TcpConnection` instance under heavy CPU usage due to mediasoup deleting the instance in the middle of a receiving iteration. Fixes #333. - More details in the commit: https://github.com/versatica/mediasoup/commit/49824baf102ab6d2b01e5bca565c29b8ac0fec22 ### 3.3.4 - IPv6 fix: Use `INET6_ADDRSTRLEN` instead of `INET_ADDRSTRLEN`. ### 3.3.3 - Add `consumer.setPriority()` and `consumer.priority` API to prioritize how the estimated outgoing bitrate in a transport is distributed among all video consumers (in case there is not enough bitrate to satisfy them). - Make video `SimpleConsumers` play the BWE game by helping in probation generation and bitrate distribution. - Add `consumer.preferredLayers` getter. - Rename `enablePacketEvent()` and "packet" event to `enableTraceEvent()` and "trace" event (sorry SEMVER). - Transport: Add a new "trace" event of type "bwe" with detailed information about bitrates. ### 3.3.2 - Improve "packet" event by not firing both "keyframe" and "rtp" types for the same RTP packet. ### 3.3.1 - Add type "keyframe" as a valid type for "packet" event in `Producers` and `Consumers`. ### 3.3.0 - Add transport-cc bandwidth estimation and congestion control in sender and receiver side. - Run in Windows. - Rewrite to TypeScript. - Tons of improvements. ### 3.2.5 - Fix TCP leak (#325). ### 3.2.4 - `PlainRtpTransport`: Fix comedia mode. ### 3.2.3 - `RateCalculator`: improve efficiency in `GetRate()` method (#324). ### 3.2.2 - `RtpDataCounter`: use window size of 2500 ms instead of 1000 ms. - Fixes false "lack of RTP" detection in some screen sharing usages with simulcast. - Fixes #312. ### 3.2.1 - Add RTCP Extended Reports for RTT calculation on receiver RTP stream (thanks @yangjinechofor for initial pull request #314). - Make `mediasoup-worker` compile in Armbian Debian Buster (thanks @krishisola, fixes #321). ### 3.2.0 - Add DataChannel support via DataProducers and DataConsumers (#10). - SRTP: Add support for AEAD GCM (#320). ### 3.1.7 - `PipeConsumer.cpp`: Fix RTCP generation (thanks @vpalmisano). ### 3.1.6 - VP8 and H264: Fix regression in 3.1.5 that produces lot of changes in current temporal layer detection. ### 3.1.5 - VP8 and H264: Allow packets without temporal layer information even if N temporal layers were announced. ### 3.1.4 - Add `-fPIC` in `cflags` to compile in x86-64. Fixes #315. ### 3.1.3 - Set the sender SSRC on PLI and FIR requests [related thread](https://mediasoup.discourse.group/t/broadcasting-a-vp8-rtp-stream-from-gstreamer/93). ### 3.1.2 - Workaround to detect H264 key frames when Chrome uses external encoder (related [issue](https://bugs.chromium.org/p/webrtc/issues/detail?id=10746)). Fixes #313. ### 3.1.1 - Improve `GetBitratePriority()` method in `SimulcastConsumer` and `SvcConsumer` by checking the total bitrate of all temporal layers in a given producer stream or spatial layer. ### 3.1.0 - Add SVC support. It includes VP9 full SVC and VP9 K-SVC as implemented by libwebrtc. - Prefer Python 2 (if available) over Python 3. This is because there are yet pending issues with gyp + Python 3. ### 3.0.12 - Do not require Python 2 to compile mediasoup worker (#207). Both Python 2 and 3 can now be used. ### 3.0.11 - Codecs: Improve temporal layer switching in VP8 and H264. - Skip worker compilation if `MEDIASOUP_WORKER_BIN` environment variable is given (#309). This makes it possible to install mediasoup in platforms in which, somehow, gcc > 4.8 is not available during `npm install mediasoup` but it's available later. - Fix `RtpStreamRecv::TransmissionCounter::GetBitrate()`. ### 3.0.10 - `parseScalabilityMode()`: allow "S" as spatial layer (and not just "L"). "L" means "dependent spatial layer" while "S" means "independent spatial layer", which is used in K-SVC (VP9, AV1, etc). ### 3.0.9 - `RtpStreamSend::ReceiveRtcpReceiverReport()`: improve `rtt` calculation if no Sender Report info is reported in received Received Report. - Update `libuv` to version 1.29.1. ### 3.0.8 - VP8 & H264: Improve temporal layer switching. ### 3.0.7 - RTP frame-marking: Add some missing checks. ### 3.0.6 - Fix regression in proxied RTP header extensions. ### 3.0.5 - Add support for frame-marking RTP extension and use it to enable temporal layers switching in H264 codec (#305). ### 3.0.4 - Improve RTP probation for simulcast/svc consumers by using proper RTP retransmission with increasing sequence number. ### 3.0.3 - Simulcast: Improve timestamps extra offset handling by having a map of extra offsets indexed by received timestamps. This helps in case of packet retransmission. ### 3.0.2 - Simulcast: proper RTP stream switching by rewriting packet timestamp with a new timestamp calculated from the SenderReports' NTP relationship. ### 3.0.1 - Fix crash in `SimulcastConsumer::IncreaseLayer()` with Safari and H264 (#300). ### 3.0.0 - v3 is here! ### 2.6.19 - `RtpStreamSend.cpp`: Fix a crash in `StorePacket()` when it receives an old packet and there is no space left in the storage buffer (thanks to zkfun for reporting it and providing us with the solution). - Update deps. ### 2.6.18 - Fix usage of a deallocated `RTC::TcpConnection` instance under heavy CPU usage due to mediasoup deleting the instance in the middle of a receiving iteration. ### 2.6.17 - Improve build system by using all available CPU cores in parallel. ### 2.6.16 - Don't mandate server port range to be >= 99. ### 2.6.15 - Fix NACK retransmissions. ### 2.6.14 - Fix TCP leak (#325). ### 2.6.13 - Make `mediasoup-worker` compile in Armbian Debian Buster (thanks @krishisola, fixes #321). - Update deps. ### 2.6.12 - Fix RTCP Receiver Report handling. ### 2.6.11 - Update deps. - Simulcast: Increase profiles one by one unless explicitly forced (fixes #188). ### 2.6.10 - `PlainRtpTransport.js`: Add missing methods and events. ### 2.6.9 - Remove a potential crash if a single `encoding` is given in the Producer `rtpParameters` and it has a `profile` value. ### 2.6.8 - C++: Verify in libuv static callbacks that the associated C++ instance has not been deallocated (thanks @artushin and @mariat-atg for reporting and providing valuable help in #258). ### 2.6.7 - Fix wrong destruction of Transports in Router.cpp that generates 100% CPU usage in `mediasoup-worker` processes. ### 2.6.6 - Fix a port leak when a WebRtcTransport is remotely closed due to a DTLS close alert (thanks @artushin for reporting it in #259). ### 2.6.5 - RtpPacket: Fix Two-Byte header extensions parsing. ### 2.6.4 - Upgrade again to OpenSSL 1.1.0j (20 Nov 2018) after adding a workaround for issue [#257](https://github.com/versatica/mediasoup/issues/257). ### 2.6.3 - Downgrade OpenSSL to version 1.1.0h (27 Mar 2018) until issue [#257](https://github.com/versatica/mediasoup/issues/257) is fixed. ### 2.6.2 - C++: Remove all `Destroy()` class methods and no longer do `delete this`. - Update libuv to 1.24.1. - Update OpenSSL to 1.1.0g. ### 2.6.1 - worker: Internal refactor and code cleanup. - Remove announced support for certain RTCP feedback types that mediasoup does nothing with (and avoid forwarding them to the remote RTP sender). - fuzzer: fix some wrong memory access in `RtpPacket::Dump()` and `StunMessage::Dump()` (just used during development). ### 2.6.0 - Integrate [libFuzzer](http://llvm.org/docs/LibFuzzer.html) into mediasoup (documentation in the `doc` folder). Extensive testing done. Several heap-buffer-overflow and memory leaks fixed. ### 2.5.6 - `Producer.cpp`: Remove `UpdateRtpParameters()`. It was broken since Consumers were not notified about profile removed and so on, so they may crash. - `Producer.cpp: Remove some maps and simplify streams handling by having a single `mapSsrcRtpStreamInfo`. Just keep `mapActiveProfiles`because`GetActiveProfiles()` method needs it. - `Producer::MayNeedNewStream()`: Ignore new media streams with new SSRC if its RID is already in use by other media stream (fixes #235). - Fix a bad memory access when using two byte RTP header extensions. ### 2.5.5 - `Server.js`: If a worker crashes make sure `_latestWorkerIdx` becomes 0. ### 2.5.4 - `server.Room()`: Assign workers incrementally or explicitly via new `workerIdx` argument. - Add `server.numWorkers` getter. ### 2.5.3 - Don't announce `muxId` nor RTP MID extension support in `Consumer` RTP parameters. ### 2.5.2 - Enable RTP MID extension again. ### 2.5.1 - Disable RTP MID extension until [#230](https://github.com/versatica/mediasoup/issues/230) is fixed. ### 2.5.0 - Add RTP MID extension support. ### 2.4.6 - Do not close `Transport` on ICE disconnected (as it would prevent ICE restart on "recv" TCP transports). ### 2.4.5 - Improve codec matching. ### 2.4.4 - Fix audio codec matching when `channels` parameter is not given. ### 2.4.3 - Make `PlainRtpTransport` not leak if port allocation fails (related issue [#224](https://github.com/versatica/mediasoup/issues/224)). ### 2.4.2 - Fix a crash in when no more RTP ports were available (see related issue [#222](https://github.com/versatica/mediasoup/issues/222)). ### 2.4.1 - Update dependencies. ### 2.4.0 - Allow non WebRTC peers to create plain RTP transports (no ICE/DTLS/SRTP but just plain RTP and RTCP) for sending and receiving media. ### 2.3.3 - Fix C++ syntax to avoid an error when building the worker with clang 8.0.0 (OSX 10.11.6). ### 2.3.2 - `Channel.js`: Upgrade `REQUEST_TIMEOUT` to 20 seconds to avoid timeout errors when the Node or worker thread usage is too high (related to this [issue](https://github.com/versatica/mediasoup-client/issues/48)). ### 2.3.1 - H264: Check if there is room for the indicated NAL unit size (thanks @ggarber). - H264: Code cleanup. ### 2.3.0 - Add new "spy" feature. A "spy" peer cannot produce media and is invisible for other peers in the room. ### 2.2.7 - Fix H264 simulcast by properly detecting when the profile switching should be done. - Fix a crash in `Consumer::GetStats()` (see related issue [#196](https://github.com/versatica/mediasoup/issues/196)). ### 2.2.6 - Add H264 simulcast capability. ### 2.2.5 - Avoid calling deprecated (NOOP) `SSL_CTX_set_ecdh_auto()` function in OpenSSL >= 1.1.0. ### 2.2.4 - [Fix #4](https://github.com/versatica/mediasoup/issues/4): Avoid DTLS handshake fragmentation. ### 2.2.3 - [Fix #196](https://github.com/versatica/mediasoup/issues/196): Crash in `Consumer::getStats()` due to wrong `targetProfile`. ### 2.2.2 - Improve [issue #209](https://github.com/versatica/mediasoup/issues/209). ### 2.2.1 - [Fix #209](https://github.com/versatica/mediasoup/issues/209): `DtlsTransport`: don't crash when signaled fingerprint and DTLS fingerprint do not match (thanks @yangjinecho for reporting it). ### 2.2.0 - Update Node and C/C++ dependencies. ### 2.1.0 - Add `localIP` option for `room.createRtpStreamer()` and `transport.startMirroring()` [[PR #199](https://github.com/versatica/mediasoup/pull/199)](https://github.com/versatica/mediasoup/pull/199). ### 2.0.16 - Improve C++ usage (remove "warning: missing initializer for member" [-Wmissing-field-initializers]). - Update Travis-CI settings. ### 2.0.15 - Make `PlainRtpTransport` also send RTCP SR/RR reports (thanks @artushin for reporting). ### 2.0.14 - [Fix #193](https://github.com/versatica/mediasoup/issues/193): `preferTcp` not honored (thanks @artushin). ### 2.0.13 - Avoid crash when no remote IP/port is given. ### 2.0.12 - Add `handled` and `unhandled` events to `Consumer`. ### 2.0.11 - [Fix #185](https://github.com/versatica/mediasoup/issues/185): Consumer: initialize effective profile to 'NONE' (thanks @artushin). - [Fix #186](https://github.com/versatica/mediasoup/issues/186): NackGenerator code being executed after instance deletion (thanks @baiyufei). ### 2.0.10 - [Fix #183](https://github.com/versatica/mediasoup/issues/183): Always reset the effective `Consumer` profile when removed (thanks @thehappycoder). ### 2.0.9 - Make ICE+DTLS more flexible by allowing sending DTLS handshake when ICE is just connected. ### 2.0.8 - Disable stats periodic retrieval also on remote closure of `Producer` and `WebRtcTransport`. ### 2.0.7 - [Fix #180](https://github.com/versatica/mediasoup/issues/180): Added missing include `cmath` so that `std::round` can be used (thanks @jacobEAdamson). ### 2.0.6 - [Fix #173](https://github.com/versatica/mediasoup/issues/173): Avoid buffer overflow in `()` (thanks @lightmare). - Improve stream layers management in `Consumer` by using the new `RtpMonitor` class. ### 2.0.5 - [Fix #164](https://github.com/versatica/mediasoup/issues/164): Sometimes video freezes forever (no RTP received in browser at all). - [Fix #160](https://github.com/versatica/mediasoup/issues/160): Assert error in `RTC::Consumer::GetStats()`. ### 2.0.4 - [Fix #159](https://github.com/versatica/mediasoup/issues/159): Don’t rely on VP8 payload descriptor flags to assure the existence of data. - [Fix #160](https://github.com/versatica/mediasoup/issues/160): Reset `targetProfile` when the corresponding profile is removed. ### 2.0.3 - worker: Fix crash when VP8 payload has no `PictureId`. ### 2.0.2 - worker: Remove wrong `assert` on `Producer::DeactivateStreamProfiles()`. ### 2.0.1 - Update README file. ### 2.0.0 - New design based on `Producers` and `Consumer` plus a mediasoup protocol and the **mediasoup-client** client side SDK. ### 1.2.8 - Fix a crash due to RTX packet processing while the associated `NackGenerator` is not yet created. ### 1.2.7 - Habemus RTX ([RFC 4588](https://tools.ietf.org/html/rfc4588)) for proper RTP retransmission. ### 1.2.6 - Fix an issue in `buffer.toString()` that makes mediasoup fail in Node 8. - Update libuv to version 1.12.0. ### 1.2.5 - Add support for [ICE renomination](https://tools.ietf.org/html/draft-thatcher-ice-renomination). ### 1.2.4 - Fix a SDP negotiation issue when the remote peer does not have compatible codecs. ### 1.2.3 - Add video codecs supported by Microsoft Edge. ### 1.2.2 - `RtpReceiver`: generate RTCP PLI when "rtpraw" or "rtpobject" event listener is set. ### 1.2.1 - `RtpReceiver`: fix an error producing packets when "rtpobject" event is set. ### 1.2.0 - `RtpSender`: allow `disable()`/`enable()` without forcing SDP renegotiation (#114). ### 1.1.0 - Add `Room.on('audiolevels')` event. ### 1.0.2 - Set a maximum value of 1500 bytes for packet storage in `RtpStreamSend`. ### 1.0.1 - Avoid possible segfault if `RemoteBitrateEstimator` generates a bandwidth estimation with zero SSRCs. ### 1.0.0 - First stable release. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to mediasoup Thanks for taking the time to contribute to mediasoup! 🎉👍 ## License By contributing to mediasoup, you agree that your contributions will be licensed under its ISC License. ## Reporting Bugs We primarily use GitHub as an issue tracker. Just open an issue in GitHub if you have encountered a bug in mediasoup. If you have questions or doubts about mediasoup or need support, please use the mediasoup Discourse Group instead: - https://mediasoup.discourse.group If you got a crash in mediasoup, please try to provide a core dump into the issue report: - https://mediasoup.org/support/#crashes-in-mediasoup-get-a-core-dump ## Pull Request Process When creating a Pull Request for mediasoup: - Ensure that changes/additions done in TypeScript files (in `node/src` folder) are also applied to the Rust layer (in `rust` folder), and vice-versa. - Test units must be added for both Node.js and Rust. - Changes/additions in C++ code may need tests in `worker/test` folder. Once all changes are done, run the following commands to verify that the code in your PR conforms to the code syntax of the project, it does not break existing funtionality and tests pass: - `npm run lint`: Check TypeScript and C++ linting rules. Formating errors can be automatically fixed by running `npm run format`. - `npm run typescript:build`: Compile TypeScript code (under `src` folder) into JavaScript code (under `lib` folder). - `npm run test`: Run JavaScript and C++ test units. - Instead, you can run `npm run release:check` which will run all those steps. - `cargo fmt`, `cargo clippy` and `cargo test` to ensure that everything is good in Rust side. The full list of `npm` scripts, `invoke` tasks and `cargo` commands is available in the [doc/Building.md](/doc/Building.md) file. ## Coding Style In adition to automatic checks performed by commands above, we also enforce other minor things related to coding style: ### Comments in TypeScript and C++ We use `//` for inline comments in both JavaScript and C++ source files. - Comments must start with upercase letter. - Comments must not exceed 80 columns (split into different lines if necessary). - Comments must end with a dot. Example (good): ```ts // Calculate foo based on bar value. const foo = bar / 2; ``` Example (bad): ```ts // calculate foo based on bar value const foo = bar / 2; ``` When adding inline documentation for methods or functions, we use `/** */` syntax. Example: ```ts /** * Calculates current score for foo and bar. */ function calculateScore(): number { // [...] } ``` ================================================ FILE: Cargo.toml ================================================ [workspace] resolver = "2" members = [ "rust", "rust/types", "worker" ] ================================================ FILE: LICENSE ================================================ ISC License Copyright © 2015, Iñaki Baz Castillo Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ================================================ FILE: README.md ================================================ # mediasoup v3 [![][mediasoup-banner]][mediasoup-website] [![][npm-shield-mediasoup]][npm-mediasoup] [![][crates-shield-mediasoup]][crates-mediasoup] [![][opencollective-shield-mediasoup]][opencollective-mediasoup] --- [![][github-actions-shield-mediasoup-node]][github-actions-mediasoup-node] [![][github-actions-shield-mediasoup-worker]][github-actions-mediasoup-worker] [![][github-actions-shield-mediasoup-rust]][github-actions-mediasoup-rust] [![][github-actions-shield-mediasoup-worker-fuzzer]][github-actions-mediasoup-worker-fuzzer] [![][github-actions-shield-mediasoup-worker-prebuild]][github-actions-mediasoup-worker-prebuild] [![][github-actions-mediasoup-codeql-shield-mediasoup]][github-actions-mediasoup-codeql-mediasoup] ## Website and Documentation - [mediasoup.org][mediasoup-website] ## Support Forum - [mediasoup.discourse.group][mediasoup-discourse] ## Design Goals mediasoup and its client side libraries are designed to accomplish with the following goals: - Be a SFU (Selective Forwarding Unit). - Support both WebRTC and plain RTP input and output. - Be a Node.js module or Rust crate in server side. - Be a tiny TypeScript and C++ libraries in client side. - Be minimalist: just handle the media layer. - Be signaling agnostic: do not mandate any signaling protocol. - Be super low level API. - Support all existing WebRTC endpoints. - Enable integration with well known multimedia libraries/tools. ## Architecture ![][mediasoup-architecture] ## Use Cases mediasoup and its client side libraries provide a super low level API. They are intended to enable different use cases and scenarios, without any constraint or assumption. Some of these use cases are: - Group video chat applications. - One-to-many (or few-to-many) broadcasting applications in real-time. - RTP streaming. ## Features - ECMAScript 6/Idiomatic Rust low level API. - Multi-stream: multiple audio/video streams over a single ICE + DTLS transport. - IPv6 ready. - ICE / DTLS / RTP / RTCP over UDP and TCP. - Simulcast and SVC support. - Congestion control. - Sender and receiver bandwidth estimation with spatial/temporal layers distribution algorithm. - Data message exchange (via WebRTC DataChannels, SCTP over plain UDP, and direct termination in Node.js/Rust). - Extremely powerful (media worker thread/subprocess coded in C++ on top of [libuv](https://libuv.org)). ## Demo Online [![][mediasoup-demo-screenshot]][mediasoup-demo] Try it at [v3demo.mediasoup.org](https://v3demo.mediasoup.org) ([source code](https://github.com/versatica/mediasoup-demo)). ## Authors - Iñaki Baz Castillo [[website](https://inakibaz.me)|[github](https://github.com/ibc/)] - José Luis Millán [[github](https://github.com/jmillan/)] - Nazar Mokynskyi [[github](https://github.com/nazar-pc/)] ## Social - Bluesky: [@mediasoup-sfu.bsky.social](https://bsky.app/profile/mediasoup-sfu.bsky.social) ## Sponsor You can support mediasoup by [sponsoring][sponsor] it. Thanks! ## License [ISC](./LICENSE) [mediasoup-banner]: /art/mediasoup-banner.png [mediasoup-website]: https://mediasoup.org [mediasoup-discourse]: https://mediasoup.discourse.group [npm-shield-mediasoup]: https://img.shields.io/npm/v/mediasoup.svg [npm-mediasoup]: https://npmjs.org/package/mediasoup [crates-shield-mediasoup]: https://img.shields.io/crates/v/mediasoup.svg [crates-mediasoup]: https://crates.io/crates/mediasoup [opencollective-shield-mediasoup]: https://img.shields.io/opencollective/all/mediasoup.svg [opencollective-mediasoup]: https://opencollective.com/mediasoup [github-actions-shield-mediasoup-node]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-node.yaml/badge.svg?branch=v3 [github-actions-mediasoup-node]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-node.yaml?query=branch%3Av3 [github-actions-shield-mediasoup-worker]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker.yaml/badge.svg?branch=v3 [github-actions-mediasoup-worker]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker.yaml?query=branch%3Av3 [github-actions-shield-mediasoup-rust]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-rust.yaml/badge.svg?branch=v3 [github-actions-mediasoup-rust]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-rust.yaml?query=branch%3Av3 [github-actions-shield-mediasoup-worker-fuzzer]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker-fuzzer.yaml/badge.svg?branch=v3 [github-actions-mediasoup-worker-fuzzer]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker-fuzzer.yaml?query=branch%3Av3 [github-actions-shield-mediasoup-worker-prebuild]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker-prebuild.yaml/badge.svg?event=release [github-actions-mediasoup-worker-prebuild]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-worker-prebuild.yaml?query=event%3Arelease [github-actions-mediasoup-codeql-shield-mediasoup]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-codeql.yaml/badge.svg?branch=v3 [github-actions-mediasoup-codeql-mediasoup]: https://github.com/versatica/mediasoup/actions/workflows/mediasoup-codeql.yaml?query=branch%3Av3 [sponsor]: https://mediasoup.org/sponsor [mediasoup-architecture]: /art/mediasoup-v3-architecture-02.png [mediasoup-demo-screenshot]: /art/mediasoup-v3.png [mediasoup-demo]: https://v3demo.mediasoup.org ================================================ FILE: doc/Building.md ================================================ # Building This document is intended for mediasoup developers. ## NPM scripts The `package.json` file in the main folder includes the following scripts: ### `npm run typescript:build` Compiles mediasoup TypeScript code (`node/src` folder) JavaScript and places it into the `node/lib` directory. ### `npm run typescript:watch` Compiles mediasoup TypeScript code (`node/src` folder) JavaScript, places it into the `node/lib` directory an watches for changes in the TypeScript files. ### `npm run worker:build` Builds the `mediasoup-worker` binary. It invokes `invoke`below. ### `npm run worker:prebuild-name` Prints the name of the corresponding `mediasoup-worker` prebuild tar file. ### `npm run worker:prebuild` Creates a prebuilt of `mediasoup-worker` binary in the `worker/prebuild` folder. ### `npm run lint` Runs both `lint:node` and `lint:worker` tasks. ### `npm run lint:node` Validates mediasoup TypeScript files using [ESLint](https://eslint.org), [Prettier](https://prettier.io) and [Knip](https://knip.dev/). ### `npm run lint:worker` Validates mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). It invokes `invoke lint` below. See [Install clang-format](#install-clang-format) for requirements. ### `npm run format` Runs both `format:node` and `format:worker` tasks. ### `npm run format:node` Format TypeScript and JavaScript code using [Prettier](https://prettier.io). ### `npm run format:worker` Rewrites mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). It invokes `invoke format` below. See [Install clang-format](#install-clang-format) for requirements. ### `npm run tidy:worker` Runs [clang-tidy](http://clang.llvm.org/extra/clang-tidy) and performs C++ code checks following `worker/.clang-tidy` rules. It invokes `invoke tidy` below. See [Install clang-tidy](#install-clang-tidy) for requirements. ### `npm run tidy:worker:fix` Same as `npm run tidy:worker` but it also applies fixes. ### `npm run flatc` Runs both `flatc:node` and `flatc:worker` tasks. ### `npm run flatc:node` Compiles [FlatBuffers](https://github.com/google/flatbuffers) `.fbs` files in `worker/fbs` to TypeScript code. ### `npm run flatc:worker` Compiles [FlatBuffers](https://github.com/google/flatbuffers) `.fbs` files in `worker/fbs` to C++ code. ### `npm run test` Runs both `test:node` and `test:worker` tasks. ### `npm run test:node` Runs [Jest](https://jestjs.io) test units located at `node/test` folder. Jest command arguments can be given using `--` as follows: ```bash npm run test:node -- --testPathPatterns "node/src/test/test-Worker.ts" --testNamePattern "createWorker" ``` ### `npm run test:worker` Runs [Catch2](https://github.com/catchorg/Catch2) test units located at `worker/test` folder. It invokes `invoke test` below. ### `npm run coverage` Runs `coverage:node` task. ### `npm run coverage:node` Same as `test:node` task but it also opens a browser window with TypeScript coverage results. ### `npm run release:check` Runs linters and tests in Node and C++ code. ### `npm run release` Publishes a new NPM version of mediasoup. Requirements for it to work: - "version" field in `package.json` must have been incremented (and not commited to Git). - `CHANGELOG.md` file must have been updated with an entry matching the new version. - Of course, permissions to publish in NPM registry are required. ## Rust The only special feature in Rust case is special environment variable "KEEP_BUILD_ARTIFACTS", that when set to "1" will allow incremental recompilation of changed C++ sources during hacking on mediasoup. It is not necessary for normal usage of mediasoup as a dependency. ## Python Invoke and `tasks.py` file mediasoup uses Python [Invoke](https://www.pyinvoke.org) library for managing and organizing tasks in the `worker` folder (mediasoup worker C++ subproject). `Invoke` is basically a replacemente of `make` + `Makefile` written in Python. mediasoup automatically installs `Invoke` in a local custom path during the installation process (in both Node and Rust) so the user doesn't need to worry about it. Tasks are defined in `worker/tasks.py`. For development purposes, developers or contributors can install `Invoke` using `pip3 install invoke` and run tasks below within the `worker` folder. See all the tasks by running `invoke --list` within the `worker` folder. _NOTE:_ For some of these tasks to work, npm dependencies of `worker/scripts/package.json` must be installed: ```bash npm ci --prefix worker/scripts ``` ### `invoke` (default task) Alias of `invoke mediasoup-worker` task below. ### `invoke meson-ninja` Installs `meson` and `ninja` into a local custom path. ### `invoke clean` Cleans built objects and binaries. ### `invoke clean-build` Cleans built objects and other artifacts, but keeps `mediasoup-worker` binary in place. ### `invoke clean-pip` Cleans `meson` and `ninja` installed in local prefix with pip. ### `invoke clean-subprojects` Cleans subprojects downloaded with Meson. ### `invoke clean-all` Cleans built objects and binaries, `meson` and `ninja` installed in local prefix with pip and all subprojects downloaded with Meson. ### `invoke update-wrap-file [subproject]` Updates the wrap file of a subproject (those in `worker/subprojects` folder) with Meson. After updating it, `invoke setup` must be called by passing `MESON_ARGS="--reconfigure"` environment variable. Usage example: ```bash cd worker invoke update-wrap-file openssl MESON_ARGS="--reconfigure" invoke setup ``` ### `invoke mediasoup-worker` Builds the `mediasoup-worker` binary at `worker/out/Release`. If the "MEDIASOUP_MAX_CORES" environment variable is set, the build process will use that number of CPU cores. Otherwise it will auto-detect the number of cores in the machine. "MEDIASOUP_BUILDTYPE" environment variable controls build types, "Release" and "Debug" are presets optimized for those use cases. Other build types are possible too, but they are not presets and will require "MESON_ARGS" use to customize build configuration. Check the meaning of useful macros in the `worker/include/Logger.hpp` header file if you want to enable tracing or other debug information. Binary is built at `worker/out/MEDIASOUP_BUILDTYPE/build`. In order to instruct the mediasoup Node.js module to use the "Debug" mediasoup-worker` binary, an environment variable must be set before running the Node.js application: ```bash MEDIASOUP_BUILDTYPE=Debug node myapp.js ``` If the "MEDIASOUP_WORKER_BIN" environment variable is set (it must be an absolute file path), mediasoup will use the it as `mediasoup-worker` binary and **won't** compile the binary: ```bash MEDIASOUP_WORKER_BIN="/home/xxx/src/foo/mediasoup-worker" node myapp.js ``` ### `invoke libmediasoup-worker` Builds the `libmediasoup-worker` static library at `worker/out/Release`. "MEDIASOUP_MAX_CORES"` and "MEDIASOUP_BUILDTYPE" environment variables from above still apply for static library build. ### `invoke xcode` Builds a Xcode project for the mediasoup worker subproject. ### `invoke lint` Validates mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html) and rules in `worker/.clang-format`. **Requirements:** - A specific version of `clang-format`is required. See [Install clang-format](#install-clang-format). - `clang-format-VERSION` or `clang-format` (corresponding to the required version) must be in the `PATH`. If not, add it before running the command. ### `invoke format` Rewrites mediasoup worker C++ files using [clang-format](https://clang.llvm.org/docs/ClangFormat.html). **Requirements:** - A specific version of `clang-format`is required. See [Install clang-format](#install-clang-format). - `clang-format-VERSION` or `clang-format` (corresponding to the required version) must be in the `PATH`. If not, add it before running the command. ### `invoke tidy` Runs [clang-tidy](http://clang.llvm.org/extra/clang-tidy) and performs C++ code checks following `worker/.clang-tidy` rules. **Requirements:** - `invoke clean` must have been called first. - A specific version of `clang-tidy`is required. See [Install clang-tidy](#install-clang-tidy). - `clang-tidy-VERSION` or `clang-tidy` (corresponding to the required version) must be in the `PATH`. If not, add it before running the command. Same for other `clang-tidy` related executables such as `run-clang-tidy` and `clang-apply-replacements`, **Environment variables:** - "MEDIASOUP_TIDY_CHECKS": Optional. Comma separated list of checks. Overrides the checks defined in `worker/.clang-tidy` file. - "MEDIASOUP_TIDY_FILES": Optional. Space separated source file paths to process. All `.cpp` files will be processes by default. - File paths must be relative to `worker/` folder. - File paths can use [glob](https://github.com/isaacs/node-glob) syntax. Example: `"src/RTC/SCTP/**/*.cpp"`. **Usage example in macOS:** ```bash PATH="/opt/homebrew/opt/llvm/bin/:$PATH" invoke tidy ``` It may happens that `clang-tidy` doesn't know where C++ standard libraries are so it shows lot of warnings about them. Depending on your local setup this may work: ```bash PATH="/opt/homebrew/opt/llvm/bin/:$PATH" CPATH=/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1 invoke tidy ``` ### `invoke tidy-fix` Same as `invoke tidy` but it also applies fixes. ### `invoke test` Builds and runs the `mediasoup-worker-test` binary at `worker/out/Release` (or at `worker/out/Debug` if the "MEDIASOUP_BUILDTYPE" environment variable is set to "Debug"), which uses [Catch2](https://github.com/catchorg/Catch2) to run test units located at `worker/test` folder. ### `invoke test-asan-address` Run test with Address Sanitizer with `-fsanitize=address`. ### `invoke test-asan-undefined` Run test with Address Sanitizer with `-fsanitize=undefined`. ### `invoke fuzzer` Builds the `mediasoup-worker-fuzzer` binary (which uses [libFuzzer](http://llvm.org/docs/LibFuzzer.html)) at `worker/out/Release` (or at `worker/out/Debug/` if the "MEDIASOUP_BUILDTYPE" environment variable is set to "Debug"). **Requirements:** - Linux with fuzzer capable clang++. - "CC" environment variable must point to `clang`. - "CXX" environment variable must point to `clang++`. Read the [Fuzzer](Fuzzer.md) documentation for detailed information. ### `invoke fuzzer-run-all` Runs all fuzzer cases. ### `invoke docker` Builds a Linux Ubuntu Docker image with fuzzer capable clang++ and all dependencies to run mediasoup. ### `invoke docker-run` Runs a container of the Ubuntu Docker image created with `invoke docker`. It automatically executes a `bash` session in the mediasoup directory, which is a Docker volume that points to the mediasoup root folder. **NOTE:** To install and run mediasoup in the container, previous installation (if any) must be properly cleaned by entering the `worker` directory and running `invoke clean-all`. ### `invoke docker-alpine` Builds a Linux Alpine Docker image with all dependencies to run mediasoup. ### `invoke docker-alpine-run` Runs a container of the Alpine Docker image created with `invoke docker-alpine`. It automatically executes an `ash` session in the mediasoup directory, which is a Docker volume that points to the mediasoup root folder. **NOTE:** To install and run mediasoup in the container, previous installation (if any) must be properly cleaned by entering the `worker` directory and running `invoke clean-all`. ### `invoke docker-386` Builds a 386 Linux Debian (32 bits arch) Docker image with all dependencies to run mediasoup. ### `invoke docker-alpine-386` Runs a container of the 386 Linux Debian (32 bits arch) Docker image created with `invoke docker-386`. It automatically executes an `ash` session in the mediasoup directory, which is a Docker volume that points to the mediasoup root folder. **NOTE:** To install and run mediasoup in the container, previous installation (if any) must be properly cleaned by entering the `worker` directory and running `invoke clean-all`. **NOTE:** Due to the very old Node v18 in this image, in order to run mediasoup Node tests, `npm ci` must be executed with `--ignore-scripts --engine-strict=false` arguments. ## Makefile The `worker` folder contains a `Makefile` file for the mediasoup worker C++ subproject. It acts as a proxy to the `Invoke` tasks defined in `tasks.py`. The `Makefile` file exists to help developers or contributors that prefer keep using `make` commands. All tasks defined in `tasks.py` (see above) are available in `Makefile`. There is only one exception: - The `update-wrap-file` needs a "SUBPROJECT" environment variable indicating the subproject to update. Usage example: ```bash cd worker make update-wrap-file SUBPROJECT=openssl ``` ## Install clang-format A specific `clang-format` version is required to be installed in the system, which is defined in [clang-scripts.mjs](../worker/scripts/clang-scripts.mjs). macOS: ```bash brew install clang-format@VERSION ``` Linux: ```bash apt-get install clang-format-VERSION ``` ## Install clang-tidy A specific `clang-tidy` version is required to be installed in the system, which is defined in [clang-scripts.mjs](../worker/scripts/clang-scripts.mjs). macOS: ```bash brew install clang-tidy@VERSION ``` Linux: ```bash apt-get install clang-tidy-VERSION ``` ================================================ FILE: doc/Charts.md ================================================ # Charts ## Broadcasting mediasoup **v2** (a room uses a single media worker subprocess by design, so a single CPU). Charts provided by [CoSMo](https://www.cosmosoftware.io) team. Scenario: - 1 peer producing audio and video tracks. - N spy peers receiving them. #### Bandwidth out (Mbps) / number of viewers ![](charts/mediasoup_SFU_BW_out.png) #### Packets per second / number of viewers ![](charts/mediasoup_SFU_packetspersec.png) #### Average bitrate (bps) and googRTT (ms) / number of viewers ![](charts/mediasoup_clients_getstats.png) #### CPU usage / number of viewers ![](charts/mediasoup_SFU_cpu.png) ================================================ FILE: doc/Closures.md ================================================ # Closures Some considerations: - Any JS `xxxxx.yyyyyClosed()` method is equivalent to the corresponding C++ `~Xxxxx()` destructor. They both silently destroy things without generating any internal notifications/events. - _NOTE:_ Yes, the JS `xxxxx.yyyyyClosed()` produces **public** JS event `xxxxx.on('yyyyyclose')`, but that's not internal stuff. ## JS worker.close() - Public API. - Kills the mediasoup-worker process (if not already died) via signal. - Iterates all JS Routers and calls `router.workerClosed()`. ## mediasoup-worker process dies unexpectely - The JS Worker emits public JS `worker.on('died')`. - Iterates all JS Routers and calls `router.workerClosed()`. ## C++ Worker::Close() - Called when the mediasoup-worker process is killed. - Iterates all C++ Routers and calls `delete router`. ## JS router.workerClosed() - Private API. - Emits public JS `router.on('workerclose')`. ## JS router.close() - Public API. - Sends channel request `WORKER_CLOSE_ROUTER`: - Processed by the C++ Worker. - It removes the C++ Router from its map. - It calls C++ `delete router`. - Iterates all JS Transports and calls `transport.routerClosed()`. - Emits private JS `router.on('@close')` (so the JS Worker cleans its map). ## C++ ~Router() destructor - Iterates all C++ Transports and calls `delete transport`. ## JS transport.routerClosed() - Private API. - Iterates all JS Producers and calls `producer.transportClosed()`. - Iterates all JS Consumers and calls `consumer.transportClosed()`. - Emits public JS `transport.on('routerclose')`. ## JS transport.close() - Public API. - Sends channel request `ROUTER_CLOSE_TRANSPORT`. - Processed by the C++ Router. - It calls C++ `transport->Close()` (so the C++ Transport will notify the C++ Router about closed Producers and Consumers in that Transport). - It removes the C++ Transport from its map. - It calls C++ `delete transport`. - Iterates all JS Producers and calls `producer.transportClosed()`. - For each JS Producer, the JS Transport emits private JS `transport.on('@producerclose')` (so the JS Router cleans its maps). - Iterates all JS Consumers and calls `consumer.transportClosed()`. - Emits private JS `transport.on('@close')` (so the JS Router cleans its map). ## C++ ~Transport() destructor - Iterates all C++ Producers and calls `delete producer`. - Iterates all C++ Consumer and calls `delete consumer`. ## C++ Transport::Close() - Iterates all C++ Producers. For each Producer: - Removes it from its map of Producers. - Calls its `listener->OnTransportProducerClosed(this, producer)` (so the C++ Router cleans its maps and calls `consumer->ProducerClosed()` on its associated Consumers). - Calls `delete producer`. - It clears its map of C++ Producers. - Iterates all C++ Consumer. For each Consumer: - Removes it from its map of Consumers. - Call its `listener->OnTransportConsumerClosed(this, consumer)` (so the C++ Router cleans its maps). - Calls `delete consumer`. - It clears its map of C++ Consumers. _NOTE:_ If a Transport holds a Producer and a Consumer associated to that Producer, ugly things may happen when calling `Transport::Close()`: - While iterating the C++ Producers as above, the C++ Consumer would be deleted (via `Consumer::ProducerClosed()`). - As far as it's properly removed from the `Transport::mapConsumers` everything would be ok when later iterating the map of Consumers. - Must ensure that, in this scenario, the JS event `consumer.on('producerclose')` is not called since `consumer.on('transportclose')` is supposed to happen before. - This won't happen since the JS `Consumer` has removed its channel notifications within its `transportClosed()` method. ## C++ Router::OnTransportProducerClosed(transport, producer) - Gets the set of C++ Consumers associated to the closed Producer in its `mapProducerConsumers`. For each Consumer: - Calls `consumer->ProducerClosed()`. - Deletes the entry in `mapProducerConsumers` with key `producer`. - Deletes the entry in `mapProducers` with key `producer->id`. ## C++ Router::OnTransportConsumerClosed(transport, consumer) - Get the associated C++ Producer from `mapConsumerProducer`. - Remove the closed C++ Consumer from the set of Consumers in the corresponding `mapProducerConsumers` entry for the given Producer. - Deletes the entry in `mapConsumerProducer` with key `consumer`. ## JS producer.transportClosed() - Private API. - Emits public JS `producer.on('transportclose')`. ## JS producer.close() - Public API. - Sends channel request `TRANSPORT_CLOSE_PRODUCER`. - Processed by the C++ Transport. - Removes it from its map of Producers. - Calls its `listener->OnTransportProducerClosed(this, producer)` (so the C++ Router cleans its maps and calls `consumer->ProducerClose()` on its associated Consumers). - Calls `delete producer`. - Emits private JS `producer.on('@close')` (so the JS Transport cleans its map and will also emit private JS `transport.on('@producerclose')` so the JS Router cleans its map). ## C++ ~Producer() destructor - Destroys its stuff. ## JS consumer.transportClosed() - Private API. - Emits public JS `consumer.on('transportclose')`. ## JS consumer.close() - Public API. - Sends channel request `TRANSPORT_CLOSE_CONSUMER`. - Processed by the C++ Transport. - Removes it from its map of Consumers. - Calls its `listener->OnTransportConsumerClosed(this, consumer)` (so the C++ Router cleans its maps). - Calls `delete consumer`. - Emits private JS `consumer.on('@close')` (so the JS Transport cleans its map). ## C++ ~Consumer() destructor - Destroys its stuff. ## C++ Consumer::ProducerClosed() - Called from the C++ Router within the `Router::OnTransportProducerClosed()` listener. - Send a channel notification `producerclose` to the JS Consumer. - The JS Consumer emits private JS `consumer.on('@produceclose')` (so the JS Transport cleans its map). - The JS Consumer emits public JS `consumer.on('produceclose')`. - Notifies its C++ Transport via `listener->onConsumerProducerClosed()` which: - cleans its map of Consumers, - notifies the Router via `listener->OnTransportConsumerClosed()`), and - deletes the Consumer. ================================================ FILE: doc/Fuzzer.md ================================================ # Fuzzer Once we have built the `mediasoup-worker-fuzzer` target in a Linux environment with `fuzzer` capable clang (see `make fuzzer` documentation in [Building](Building.md)) we can then execute the fuzzer binary at `worker/out/Release/mediasoup-worker-fuzzer`. **NOTE:** From now on, we assume we are in the mediasoup `worker` directory. ## Related documentation - [libFuzzer documentation](http://llvm.org/docs/LibFuzzer.html) - [libFuzzer Tutorial](https://github.com/google/fuzzer-test-suite/blob/master/tutorial/libFuzzerTutorial.md) - [webrtcH4cKS ~ Lets get better at fuzzing in 2019](https://webrtchacks.com/lets-get-better-at-fuzzing-in-2019-heres-how/) - [OSS-Fuzz](https://github.com/google/oss-fuzz) - Continuous fuzzing of open source software ("fuzz for me") ## Corpus files The `deps/webrtc-fuzzer-corpora/corpora` directory has corpus directories taken from the [webrtc-fuzzer-corpora](https://github.com/RTC-Cartel/webrtc-fuzzer-corpora) project. They should be use to feed fuzzer with appropriate input. However, given how `libFuzzer` [works](http://llvm.org/docs/LibFuzzer.html#options), the first directory given as command line parameter is not just used for reading corpus files, but also to store newly generated ones. So, it's recommended to pass `fuzzer/new-corpus` as first directory. Such a directory is gitignored. ## Crash reports When the fuzzer detects an issue it generates a crash report file which contains the bytes given as input. Those files can be individually used later (instead of passing corpus directories) to the fuzzer to verify that the issue has been fixed. The `fuzzer/reports` directory should be used to store those new crash reports. In order to use it, the following option must be given to the fuzzer executable: ```bash -artifact_prefix=fuzzer/reports/ ``` ## Others It's recommended to (also) pass the following options to the fuzzer: - `-max_len=1400`: We don't need much more input size. For memory leak detection enable the following environment variable: - `LSAN_OPTIONS=verbosity=1:log_threads=1` The mediasoup-worker fuzzer reads some custom environment variables to decide which kind of fuzzing perform: - `MS_FUZZ_STUN=1`: Enable STUN fuzzer. - `MS_FUZZ_DTLS=1`: Enable DTLS fuzzer. - `MS_FUZZ_SCTP=1`: Enable SCTP fuzzer. - `MS_FUZZ_RTP=1`: Enable RTP fuzzer. - `MS_FUZZ_RTCP=1`: Enable RTCP fuzzer. - `MS_FUZZ_CODECS=1`: Enable audio/video codecs fuzzer. - `MS_FUZZ_UTILS=1`: Enable C++ utils fuzzer. - If none of them is given, then **all** fuzzers are enabled. The log level can also be set by setting the `MS_FUZZ_LOG_LEVEL` environment variable to "debug", "warn" or "error" (it is "none" if unset). ## Usage examples - Detect memory leaks and just fuzz STUN: ```bash MS_FUZZ_STUN=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus ``` - Detect memory leaks and just fuzz DTLS: ```bash MS_FUZZ_DTLS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus ``` - Detect memory leaks and just fuzz SCTP: ```bash MS_FUZZ_SCTP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus ``` - Detect memory leaks and just fuzz RTP: ```bash MS_FUZZ_RTP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus ``` - Detect memory leaks and just fuzz RTCP: ```bash MS_FUZZ_RTCP=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus ``` - Detect memory leaks and just fuzz audio/video codecs: ```bash MS_FUZZ_CODECS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus ``` - Detect memory leaks and just fuzz mediasoup-worker C++ utils: ```bash MS_FUZZ_UTILS=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=2000 fuzzer/new-corpus ``` - Detect memory leaks and fuzz everything with log level "warn" and enable log tags "rtp" and "rtcp": ```bash MS_FUZZ_LOG_LEVEL=warn MS_FUZZ_LOG_TAGS="rtp rtcp" LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus ``` - Verify that a specific crash is fixed: ```bash LSAN_OPTIONS=verbosity=1:log_threads=1 ./out/Release/mediasoup-worker-fuzzer fuzzer/reports/crash-f39771f7a03c0e7e539d4e52f48f7adad8976404 ``` ================================================ FILE: doc/README.md ================================================ # Internal Documentation **NOTE:** Internal documentation for developing purposes. Get the mediasoup public documentation at [mediasoup.org](https://mediasoup.org). - [Building](Building.md) - [Fuzzer](Fuzzer.md) - [Rust-crates](Rust-crates.md) - [RTCP](RTCP.md) - [Closures](Closures.md) - [Charts](Charts.md) ================================================ FILE: doc/RTCP.md ================================================ # RTCP This documentation describes how RTCP packets are processed in mediasoup. Being a SFU, some received RTCP packets are locally consumed and others are ignored. This document also describes which RTCP packets are locally generated by mediasoup. mediasoup does not forward feedback information from a remote RTP receiver, which could incur in the remote sender modifying the transmition rate in a way that it would affect the reception quality for the overall participants in a router (i.e. limiting the remote RTP sender transmition rate). ### Sender Reports mediasoup locally generates the Sender Reports of the streams it sends and processes the Sender Reports is receives from producer endpoints. ### Receiver Reports mediasoup locally generates the Receiver Reports of the streams it receives and consumes the Receiver Reports from every remote RTP receivers. The combination of Sender and Receiver Reports are used to determine the quality of each link. Information that is available at the JavaScript API level in order to determine the quality of each participant in the router. ### SDES SDES information is locally consumed. ### BYE This information is ignored. ### APP This information is ignored. ### XR Currently not implemented (ignored). The same logic should be applied as to Receiver Reports. ## RTP Feedback ### NACK Received NACK requests are locally consumed. The solicited RTP packets are re-sent to the remote RTP receiver that requested them. mediasoup locally generate NACK requests for remote senders. ### TMMBR / TMMBN ``` RFC 5104: A receiver, translator, or mixer uses the Temporary Maximum Media Stream Bit Rate Request (TMMBR, "timber") to request a sender to limit the maximum bit rate for a media stream (see section 2.2) to, or below, the provided value. ``` This information is ignored. As for now, we do not limit the bit rate for the media streams as we are relying on locally generated reception reports (RR) to make the remote RTP senders adjust their transmition rates given such values. ### RTCP-SR-REQ ``` RFC 6051: This memo outlines how RTP sessions are synchronised, and discusses how rapidly such synchronisation can occur. We show that most RTP sessions can be synchronised immediately, but that the use of video switching multipoint conference units (MCUs) or large source-specific multicast (SSM) groups can greatly increase the synchronisation delay. This increase in delay can be unacceptable to some applications that use layered and/or multi-description codecs. ``` This information is ignored. ### RAMS This information is ignored. ### TLLEI This information is ignored. In future it could be useful to acknowledge remote RTP receivers when mediasoup generates NACK request in order to avoid them sending them, which could generate the so called "feedback storm" or "NACK storm". ``` RFC 6642: The RTCP TPLR message can be used by the intermediaries to inform the receiver that the sender of the RTCP TPLR has received reports that the indicated packets were lost and ask the receiver not to send feedback to it regarding these packets. ``` ### RTCP-ECN-FB This information is ignored. ### PAUSE-RESUME This information is ignored (and not even implemented in any RTP/WebRTC endpoint). ### Transport-wide Congestion Control (TCC) Planned for v3. ## PS Feedback ### PLI This information is locally consumed and generates a PLI request to the corresponding RTP sender. Also it is locally sent when required. This is: a new participant joins the conference for a fast rendering by the rest of participants. ### SLI This information is ignored. ### RPSI This information is ignored. ### FIR This information is locally consumed and generates a PLI request to the corresponding RTP sender. ### TSTR/TSTN This information is ignored. ``` RFC 5104: The Temporal-Spatial Trade-off Request (TSTR) instructs the video encoder to change its trade-off between temporal and spatial resolution. Index values from 0 to 31 indicate monotonically a desire for higher frame rate. ``` ### VBCM This information is ignored. ### PSLEI Same applicability as **TLLEI**. ### AFB This information is ignored except for the REMB messages, which is locally consumed. ### REMB This information is locally consumed to perform sender side bandwidth estimation. REMB RTCP is generated locally based on the remote bitrate estimation. ## Mediasoup internal behaviour for each type of RTCP ### Generic RTCP | | SR | RR | SDES | BYE | APP | | -------- | --- | --- | ---- | --- | --- | | Consumer | G | C | | | | | Producer | C | G | C | I | I | ### RTP Feedback RTCP | | NACK | TMMBR | TMMBN | TLLEI | ECN-FB | PAUSE-RESUME | TCC | | -------- | ---- | ----- | ----- | ----- | ------ | ------------ | --- | | Consumer | C | I | I | I | I | I | | | Producer | G | | I | I | | | | ## PS Feedback RTCP | | PLI | SLI | RPSI | FIR | TSTR | TSTN | VBCM | PSLI | AFB | REMB | | --------- | --- | --- | ---- | --- | ---- | ---- | ---- | ---- | --- | ---- | | Consumer | C | I | I | C | I | | I | | I | C | | Producer | G | | | | | I | | I | | | | Transport | | | | | | | | | | G | ( ): Does not apply. (I): Ignore. (C): Consume locally. (B): Bypass. (G): Generate locally. ================================================ FILE: doc/Rust-crates.md ================================================ # Rust crates There are 3 crates: `mediasoup`, `mediasoup-sys` and `mediasoup-types`: - `mediasoup-sys` crate wraps C++ worker into Rust. - `mediasoup-types` crate defines and exposes mediasoup Rust types. - `mediasoup` crate uses `mediasoup-sys` and `mediasoup-types` and it exposes nice user API in idiomatic Rust. - `mediasoup-sys` is the only one that needs updating if changes are purely inside the worker or inside the `mediasoup-sys` crate. You can bump them all, but it is not required. - If `mediasoup-sys`'s API changes in a breaking way, then its minor version needs to be changed, otherwise patch version needs to be changed. Same for `mediasoup-types` crate. **Important:** Adding new APIs that `mediasoup` crate has to understand to continue working normally is a breaking change because it'll start crashing/printing errors if unexpected things happen. ## Steps to publish new mediasoup crates 1. Update versions in `worker/Cargo.toml` (for `mediasoup-sys` crate), `rust/types/Cargo.toml` (for `mediasoup-types` crate) and `rust/Cargo.toml` (for `mediasoup` crate). Note that in `rust/Cargo.toml` you may need to update the version of `[dependencies.mediasoup-sys]` and/or `[dependencies.mediasoup-types]` if it also changed. 2. Update `rust/CHANGELOG.md`. 3. Run `cargo build` to reflect changes in `Cargo.lock`. 4. Create PR and have it merged in mediasoup main branch. 5. Upload Git tags (the new one in `rust/CHANGELOG.md`, so the new `mediasoup` crate version): ```sh git tag -a rust-X.X.X -m rust-X.X.X git push origin rust-X.X.X ``` 6. Publish crates (you need an account and permissions and so on): ```sh cd rust/types cargo publish cd worker cargo publish cd rust cargo publish ``` ## Notes - Depending on the state in `worker` directory you may need to run `invoke clean-all` or `make clean-all` in `worker` directory first. - `cargo publish` will create the crate package, check if all necessary dependencies are already present on [crates.io](https://crates.io/), will then compile the package (to ensure that you don't publish a broken version) and will upload it to [crates.io](https://crates.io/). - Never publish from random branches or local state that is not on GitHub. If you have local files modified Cargo will refuse to publish until you commit all the changes. ## Extras ### Check crate without publishing If you want to do everything except publishing itself, `cargo package` command exists. You can also run `cargo package --dry-run` to avoid package generation or `cargo publish --dry-run`. ### Update required Rust version Using `rustup` command: ```sh rustup update ``` ================================================ FILE: eslint.config.mjs ================================================ import eslint from '@eslint/js'; import tsEslint from 'typescript-eslint'; import jestEslint from 'eslint-plugin-jest'; import prettierRecommendedEslint from 'eslint-plugin-prettier/recommended'; import globals from 'globals'; const config = tsEslint.config( { languageOptions: { sourceType: 'module', globals: { ...globals.node }, }, linterOptions: { noInlineConfig: false, reportUnusedDisableDirectives: 'error', }, }, eslint.configs.recommended, prettierRecommendedEslint, { rules: { 'constructor-super': 2, curly: [2, 'all'], // Unfortunatelly `curly` does not apply to blocks in `switch` cases so // this is needed. 'no-restricted-syntax': [ 2, { selector: 'SwitchCase > *.consequent[type!="BlockStatement"]', message: 'Switch cases without blocks are disallowed', }, ], 'guard-for-in': 2, 'newline-after-var': 2, 'newline-before-return': 2, 'no-alert': 2, 'no-caller': 2, 'no-case-declarations': 2, 'no-catch-shadow': 2, 'no-class-assign': 2, 'no-console': 2, 'no-const-assign': 2, 'no-debugger': 2, 'no-dupe-args': 2, 'no-dupe-keys': 2, 'no-duplicate-case': 2, 'no-div-regex': 2, 'no-empty': [2, { allowEmptyCatch: true }], 'no-empty-pattern': 2, 'no-eval': 2, 'no-extend-native': 2, 'no-ex-assign': 2, 'no-extra-bind': 2, 'no-extra-boolean-cast': 2, 'no-extra-label': 2, 'no-fallthrough': 2, 'no-func-assign': 2, 'no-global-assign': 2, 'no-implicit-coercion': 2, 'no-implicit-globals': 2, 'no-inner-declarations': 2, 'no-invalid-regexp': 2, 'no-invalid-this': 2, 'no-irregular-whitespace': 2, 'no-lonely-if': 2, 'no-multi-str': 2, 'no-native-reassign': 2, 'no-negated-in-lhs': 2, 'no-new': 2, 'no-new-func': 2, 'no-new-wrappers': 2, 'no-obj-calls': 2, 'no-proto': 2, 'no-prototype-builtins': 0, 'no-redeclare': 2, 'no-regex-spaces': 2, 'no-restricted-imports': 2, 'no-return-assign': 2, 'no-self-assign': 2, 'no-self-compare': 2, 'no-sequences': 2, 'no-shadow': 2, 'no-shadow-restricted-names': 2, 'no-sparse-arrays': 2, 'no-this-before-super': 2, 'no-throw-literal': 2, 'no-undef': 2, 'no-unmodified-loop-condition': 2, 'no-unreachable': 2, 'no-unused-vars': [ 2, { vars: 'all', args: 'after-used', caughtErrors: 'none' }, ], 'no-unused-private-class-members': 2, 'no-use-before-define': 0, 'no-useless-call': 2, 'no-useless-computed-key': 2, 'no-useless-concat': 2, 'no-useless-rename': 2, 'no-var': 2, 'object-curly-newline': 0, 'prefer-const': 2, 'prefer-rest-params': 2, 'prefer-spread': 2, 'prefer-template': 2, 'spaced-comment': [2, 'always'], strict: 2, 'valid-typeof': 2, yoda: 2, }, }, // NOTE: We need to apply this only to .ts source files (and not to .mjs // files). ...tsEslint.configs.recommendedTypeChecked.map(item => ({ ...item, files: ['node/src/**/*.ts'], })), // NOTE: We need to apply this only to .ts source files (and not to .mjs // files). ...tsEslint.configs.stylisticTypeChecked.map(item => ({ ...item, files: ['node/src/**/*.ts'], })), { name: '.ts source files', files: ['node/src/**/*.ts'], languageOptions: { parserOptions: { project: 'tsconfig.json', }, }, rules: { '@typescript-eslint/class-literal-property-style': [2, 'getters'], '@typescript-eslint/consistent-generic-constructors': [ 2, 'type-annotation', ], '@typescript-eslint/dot-notation': 0, '@typescript-eslint/no-unused-vars': [ 2, { vars: 'all', args: 'after-used', caughtErrors: 'none', ignoreRestSiblings: false, }, ], // We want to use `type` instead of `interface`. '@typescript-eslint/consistent-type-definitions': 0, '@typescript-eslint/prefer-nullish-coalescing': [ 0, { ignorePrimitives: { string: true }, }, ], '@typescript-eslint/explicit-function-return-type': [ 2, { allowExpressions: true }, ], '@typescript-eslint/no-unsafe-member-access': 0, '@typescript-eslint/no-unsafe-assignment': 0, '@typescript-eslint/no-unsafe-call': 0, '@typescript-eslint/no-unsafe-return': 0, '@typescript-eslint/no-unsafe-argument': 0, '@typescript-eslint/consistent-indexed-object-style': 0, '@typescript-eslint/no-empty-function': 0, '@typescript-eslint/restrict-template-expressions': 0, '@typescript-eslint/no-duplicate-type-constituents': [ 2, { ignoreUnions: true }, ], '@typescript-eslint/no-redundant-type-constituents': 0, }, }, { name: '.ts test files', ...jestEslint.configs['flat/recommended'], files: ['node/src/test/**/*.ts'], rules: { ...jestEslint.configs['flat/recommended'].rules, 'jest/no-disabled-tests': 2, 'jest/prefer-expect-assertions': 0, '@typescript-eslint/no-unnecessary-type-assertion': 0, }, } ); export default config; ================================================ FILE: jest.config.mjs ================================================ const config = { verbose: true, testEnvironment: 'node', testRegex: 'node/src/test/test-.*\\.ts', transform: { // transpilation: true is needed to avoid warnigns. However we lose TS // checks. We don't care since we have TS tasks for that. // See https://kulshekhar.github.io/ts-jest/docs/getting-started/options/transpilation '^.+\\.ts?$': ['ts-jest', { transpilation: true }], }, coveragePathIgnorePatterns: [ 'node/src/Logger.ts', 'node/src/enhancedEvents.ts', 'node/src/fbs', 'node/src/test', ], modulePathIgnorePatterns: ['worker', 'rust', 'target'], cacheDirectory: '.cache/jest', }; export default config; ================================================ FILE: knip.config.mjs ================================================ const config = { $schema: 'https://unpkg.com/knip@5/schema.json', project: ['node/src/**/*.ts'], ignore: [ 'node/src/fbsUtils.ts', 'node/src/rtpParametersFbsUtils.ts', 'node/src/rtpStreamStatsFbsUtils.ts', 'node/src/srtpParametersFbsUtils.ts', ], ignoreDependencies: ['open-cli', 'supports-color'], typescript: { config: ['tsconfig.json'], }, jest: { config: ['jest.config.mjs'], entry: ['node/src/test/**/*.ts'], }, }; export default config; ================================================ FILE: node/src/ActiveSpeakerObserver.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { ActiveSpeakerObserver, ActiveSpeakerObserverDominantSpeaker, ActiveSpeakerObserverEvents, ActiveSpeakerObserverObserver, ActiveSpeakerObserverObserverEvents, } from './ActiveSpeakerObserverTypes'; import type { RtpObserver } from './RtpObserverTypes'; import { RtpObserverImpl, RtpObserverConstructorOptions } from './RtpObserver'; import type { AppData } from './types'; import { Event, Notification } from './fbs/notification'; import * as FbsActiveSpeakerObserver from './fbs/active-speaker-observer'; type RtpObserverObserverConstructorOptions = RtpObserverConstructorOptions; const logger = new Logger('ActiveSpeakerObserver'); export class ActiveSpeakerObserverImpl< ActiveSpeakerObserverAppData extends AppData = AppData, > extends RtpObserverImpl< ActiveSpeakerObserverAppData, ActiveSpeakerObserverEvents, ActiveSpeakerObserverObserver > implements RtpObserver, ActiveSpeakerObserver { constructor( options: RtpObserverObserverConstructorOptions ) { const observer: ActiveSpeakerObserverObserver = new EnhancedEventEmitter(); super(options, observer); this.handleWorkerNotifications(); this.handleListenerError(); } get type(): 'activespeaker' { return 'activespeaker'; } override get observer(): ActiveSpeakerObserverObserver { return super.observer; } private handleWorkerNotifications(): void { this.channel.on( this.internal.rtpObserverId, (event: Event, data?: Notification) => { switch (event) { case Event.ACTIVESPEAKEROBSERVER_DOMINANT_SPEAKER: { const notification = new FbsActiveSpeakerObserver.DominantSpeakerNotification(); data!.body(notification); const producer = this.getProducerById(notification.producerId()!); if (!producer) { break; } const dominantSpeaker: ActiveSpeakerObserverDominantSpeaker = { producer, }; this.safeEmit('dominantspeaker', dominantSpeaker); this.observer.safeEmit('dominantspeaker', dominantSpeaker); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } } ); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } ================================================ FILE: node/src/ActiveSpeakerObserverTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { RtpObserver, RtpObserverEvents, RtpObserverObserverEvents, } from './RtpObserverTypes'; import type { Producer } from './ProducerTypes'; import type { AppData } from './types'; export type ActiveSpeakerObserverOptions< ActiveSpeakerObserverAppData extends AppData = AppData, > = { interval?: number; /** * Custom application data. */ appData?: ActiveSpeakerObserverAppData; }; export type ActiveSpeakerObserverDominantSpeaker = { /** * The audio Producer instance. */ producer: Producer; }; export type ActiveSpeakerObserverEvents = RtpObserverEvents & { dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; }; export type ActiveSpeakerObserverObserver = EnhancedEventEmitter; export type ActiveSpeakerObserverObserverEvents = RtpObserverObserverEvents & { dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; }; export interface ActiveSpeakerObserver< ActiveSpeakerObserverAppData extends AppData = AppData, > extends RtpObserver< ActiveSpeakerObserverAppData, ActiveSpeakerObserverEvents, ActiveSpeakerObserverObserver > { /** * RtpObserver type. * * @override */ get type(): 'activespeaker'; /** * Observer. * * @override */ get observer(): ActiveSpeakerObserverObserver; } ================================================ FILE: node/src/AudioLevelObserver.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { AudioLevelObserver, AudioLevelObserverVolume, AudioLevelObserverEvents, AudioLevelObserverObserver, AudioLevelObserverObserverEvents, } from './AudioLevelObserverTypes'; import type { RtpObserver } from './RtpObserverTypes'; import { RtpObserverImpl, RtpObserverConstructorOptions } from './RtpObserver'; import type { Producer } from './ProducerTypes'; import type { AppData } from './types'; import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; import * as FbsAudioLevelObserver from './fbs/audio-level-observer'; type AudioLevelObserverConstructorOptions = RtpObserverConstructorOptions; const logger = new Logger('AudioLevelObserver'); export class AudioLevelObserverImpl< AudioLevelObserverAppData extends AppData = AppData, > extends RtpObserverImpl< AudioLevelObserverAppData, AudioLevelObserverEvents, AudioLevelObserverObserver > implements RtpObserver, AudioLevelObserver { constructor( options: AudioLevelObserverConstructorOptions ) { const observer: AudioLevelObserverObserver = new EnhancedEventEmitter(); super(options, observer); this.handleWorkerNotifications(); this.handleListenerError(); } get type(): 'audiolevel' { return 'audiolevel'; } override get observer(): AudioLevelObserverObserver { return super.observer; } private handleWorkerNotifications(): void { this.channel.on( this.internal.rtpObserverId, (event: Event, data?: Notification) => { switch (event) { case Event.AUDIOLEVELOBSERVER_VOLUMES: { const notification = new FbsAudioLevelObserver.VolumesNotification(); data!.body(notification); // Get the corresponding Producer instance and remove entries with // no Producer (it may have been closed in the meanwhile). const volumes: AudioLevelObserverVolume[] = fbsUtils .parseVector(notification, 'volumes', parseVolume) .map( ({ producerId, volume, }: { producerId: string; volume: number; }) => ({ producer: this.getProducerById(producerId)!, volume, }) ) .filter(({ producer }: { producer: Producer }) => producer); if (volumes.length > 0) { this.safeEmit('volumes', volumes); // Emit observer event. this.observer.safeEmit('volumes', volumes); } break; } case Event.AUDIOLEVELOBSERVER_SILENCE: { this.safeEmit('silence'); // Emit observer event. this.observer.safeEmit('silence'); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } } ); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } function parseVolume(binary: FbsAudioLevelObserver.Volume): { producerId: string; volume: number; } { return { producerId: binary.producerId()!, volume: binary.volume(), }; } ================================================ FILE: node/src/AudioLevelObserverTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { RtpObserver, RtpObserverEvents, RtpObserverObserverEvents, } from './RtpObserverTypes'; import type { Producer } from './ProducerTypes'; import type { AppData } from './types'; export type AudioLevelObserverOptions< AudioLevelObserverAppData extends AppData = AppData, > = { /** * Maximum number of entries in the 'volumes”' event. Default 1. */ maxEntries?: number; /** * Minimum average volume (in dBvo from -127 to 0) for entries in the * 'volumes' event. Default -80. */ threshold?: number; /** * Interval in ms for checking audio volumes. Default 1000. */ interval?: number; /** * Custom application data. */ appData?: AudioLevelObserverAppData; }; export type AudioLevelObserverVolume = { /** * The audio Producer instance. */ producer: Producer; /** * The average volume (in dBvo from -127 to 0) of the audio Producer in the * last interval. */ volume: number; }; export type AudioLevelObserverEvents = RtpObserverEvents & { volumes: [AudioLevelObserverVolume[]]; silence: []; }; export type AudioLevelObserverObserver = EnhancedEventEmitter; export type AudioLevelObserverObserverEvents = RtpObserverObserverEvents & { volumes: [AudioLevelObserverVolume[]]; silence: []; }; export interface AudioLevelObserver< AudioLevelObserverAppData extends AppData = AppData, > extends RtpObserver< AudioLevelObserverAppData, AudioLevelObserverEvents, AudioLevelObserverObserver > { /** * RtpObserver type. * * @override */ get type(): 'audiolevel'; /** * Observer. * * @override */ get observer(): AudioLevelObserverObserver; } ================================================ FILE: node/src/Channel.ts ================================================ import * as os from 'node:os'; import type { Duplex } from 'node:stream'; import { info, warn } from 'node:console'; import * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import { InvalidStateError } from './errors'; import { Body as RequestBody, Method, Request } from './fbs/request'; import { Response } from './fbs/response'; import { Message, Body as MessageBody } from './fbs/message'; import { Notification, Body as NotificationBody, Event, } from './fbs/notification'; import { Log } from './fbs/log'; const IS_LITTLE_ENDIAN = os.endianness() === 'LE'; const logger = new Logger('Channel'); type Sent = { id: number; method: string; resolve: (data: Response | PromiseLike) => void; reject: (error: Error) => void; close: () => void; }; // Binary length for a 4194304 bytes payload. const MESSAGE_MAX_LEN = 4194308; const PAYLOAD_MAX_LEN = 4194304; export class Channel extends EnhancedEventEmitter { // Closed flag. #closed = false; // Unix Socket instance for sending messages to the worker process. readonly #producerSocket: Duplex; // Unix Socket instance for receiving messages to the worker process. readonly #consumerSocket: Duplex; // Next id for messages sent to the worker process. #nextId = 0; // Map of pending sent requests. readonly #sents: Map = new Map(); // Buffer for reading messages from the worker. #recvBuffer: Buffer = Buffer.alloc(0); // flatbuffers builder. #bufferBuilder: flatbuffers.Builder = new flatbuffers.Builder(1024); constructor({ producerSocket, consumerSocket, pid, }: { producerSocket: Duplex; consumerSocket: Duplex; pid: number; }) { super(); logger.debug('constructor()'); this.#producerSocket = producerSocket; this.#consumerSocket = consumerSocket; // Read Channel responses/notifications from the worker. this.#consumerSocket.on('data', (buffer: Buffer) => { if (!this.#recvBuffer.length) { this.#recvBuffer = buffer; } else { this.#recvBuffer = Buffer.concat( [this.#recvBuffer, buffer], this.#recvBuffer.length + buffer.length ); } if (this.#recvBuffer.length > PAYLOAD_MAX_LEN) { logger.error('receiving buffer is full, discarding all data in it'); // Reset the buffer and exit. this.#recvBuffer = Buffer.alloc(0); return; } let msgStart = 0; while (true) { const readLen = this.#recvBuffer.length - msgStart; if (readLen < 4) { // Incomplete data. break; } const dataView = new DataView( this.#recvBuffer.buffer, this.#recvBuffer.byteOffset + msgStart ); const msgLen = dataView.getUint32(0, IS_LITTLE_ENDIAN); if (readLen < 4 + msgLen) { // Incomplete data. break; } const payload = this.#recvBuffer.subarray( msgStart + 4, msgStart + 4 + msgLen ); msgStart += 4 + msgLen; const buf = new flatbuffers.ByteBuffer(new Uint8Array(payload)); const message = Message.getRootAsMessage(buf); try { switch (message.dataType()) { case MessageBody.Response: { const response = new Response(); message.data(response); this.processResponse(response); break; } case MessageBody.Notification: { const notification = new Notification(); message.data(notification); this.processNotification(notification); break; } case MessageBody.Log: { const log = new Log(); message.data(log); this.processLog(pid, log); break; } default: { warn( `worker[pid:${pid}] unexpected data: ${payload.toString( 'utf8', 1 )}` ); } } } catch (error) { logger.error( `received invalid message from the worker process: ${error}` ); } } if (msgStart != 0) { this.#recvBuffer = this.#recvBuffer.slice(msgStart); } }); this.#consumerSocket.on('end', () => { logger.debug('Consumer Channel ended by the worker process'); }); this.#consumerSocket.on('error', error => { logger.error(`Consumer Channel error: ${error}`); }); this.#producerSocket.on('end', () => { logger.debug('Producer Channel ended by the worker process'); }); this.#producerSocket.on('error', error => { logger.error(`Producer Channel error: ${error}`); }); } /** * flatbuffer builder. */ get bufferBuilder(): flatbuffers.Builder { return this.#bufferBuilder; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; // Close every pending sent. for (const sent of this.#sents.values()) { sent.close(); } // Remove event listeners but leave a fake 'error' hander to avoid // propagation. this.#consumerSocket.removeAllListeners('end'); this.#consumerSocket.removeAllListeners('error'); this.#consumerSocket.on('error', () => {}); this.#producerSocket.removeAllListeners('end'); this.#producerSocket.removeAllListeners('error'); this.#producerSocket.on('error', () => {}); // Destroy the sockets. try { this.#producerSocket.destroy(); } catch (error) {} try { this.#consumerSocket.destroy(); } catch (error) {} } notify( event: Event, bodyType?: NotificationBody, bodyOffset?: number, handlerId?: string ): void { logger.debug(`notify() [event:${Event[event]}]`); if (this.#closed) { throw new InvalidStateError( `Channel closed, cannot send notification [event:${Event[event]}]` ); } const handlerIdOffset = this.#bufferBuilder.createString(handlerId ?? ''); let notificationOffset: number; if (bodyType && bodyOffset) { notificationOffset = Notification.createNotification( this.#bufferBuilder, handlerIdOffset, event, bodyType, bodyOffset ); } else { notificationOffset = Notification.createNotification( this.#bufferBuilder, handlerIdOffset, event, NotificationBody.NONE, 0 ); } const messageOffset = Message.createMessage( this.#bufferBuilder, MessageBody.Notification, notificationOffset ); // Finalizes the buffer and adds a 4 byte prefix with the size of the buffer. this.#bufferBuilder.finishSizePrefixed(messageOffset); // Create a new buffer with this data so multiple contiguous flatbuffers // do not point to the builder buffer overriding others info. const buffer = new Uint8Array(this.#bufferBuilder.asUint8Array()); // Clear the buffer builder so it's reused for the next request. this.#bufferBuilder.clear(); if (buffer.byteLength > MESSAGE_MAX_LEN) { logger.error(`notify() | notification too big [event:${Event[event]}]`); return; } try { // This may throw if closed or remote side ended. this.#producerSocket.write(buffer, 'binary'); } catch (error) { logger.error(`notify() | sending notification failed: ${error}`); return; } } async request( method: Method, bodyType?: RequestBody, bodyOffset?: number, handlerId?: string ): Promise { logger.debug(`request() [method:${Method[method]}]`); if (this.#closed) { throw new InvalidStateError( `Channel closed, cannot send request [method:${Method[method]}]` ); } if (this.#nextId < 4294967295) { ++this.#nextId; } else { this.#nextId = 1; } const id = this.#nextId; const handlerIdOffset = this.#bufferBuilder.createString(handlerId ?? ''); let requestOffset: number; if (bodyType && bodyOffset) { requestOffset = Request.createRequest( this.#bufferBuilder, id, method, handlerIdOffset, bodyType, bodyOffset ); } else { requestOffset = Request.createRequest( this.#bufferBuilder, id, method, handlerIdOffset, RequestBody.NONE, 0 ); } const messageOffset = Message.createMessage( this.#bufferBuilder, MessageBody.Request, requestOffset ); // Finalizes the buffer and adds a 4 byte prefix with the size of the buffer. this.#bufferBuilder.finishSizePrefixed(messageOffset); // Create a new buffer with this data so multiple contiguous flatbuffers // do not point to the builder buffer overriding others info. const buffer = new Uint8Array(this.#bufferBuilder.asUint8Array()); // Clear the buffer builder so it's reused for the next request. this.#bufferBuilder.clear(); if (buffer.byteLength > MESSAGE_MAX_LEN) { throw new Error(`request too big [method:${Method[method]}]`); } // This may throw if closed or remote side ended. this.#producerSocket.write(buffer, 'binary'); return new Promise((pResolve, pReject) => { const sent: Sent = { id: id, method: Method[method], resolve: data2 => { if (!this.#sents.delete(id)) { return; } pResolve(data2); }, reject: error => { if (!this.#sents.delete(id)) { return; } pReject(error); }, close: () => { pReject( new InvalidStateError( `Channel closed, pending request aborted [method:${Method[method]}, id:${id}]` ) ); }, }; // Add sent stuff to the map. this.#sents.set(id, sent); }); } private processResponse(response: Response): void { const sent = this.#sents.get(response.id()); if (!sent) { logger.error( `received response does not match any sent request [id:${response.id()}]` ); return; } if (response.accepted()) { logger.debug(`request succeeded [method:${sent.method}, id:${sent.id}]`); sent.resolve(response); } else if (response.error()) { logger.warn( `request failed [method:${sent.method}, id:${ sent.id }]: ${response.reason()}` ); switch (response.error()!) { case 'TypeError': { sent.reject(new TypeError(response.reason()!)); break; } default: { sent.reject(new Error(response.reason()!)); } } } else { logger.error( `received response is not accepted nor rejected [method:${sent.method}, id:${sent.id}]` ); } } private processNotification(notification: Notification): void { // Due to how Promises work, it may happen that we receive a response // from the worker followed by a notification from the worker. If we // emit the notification immediately it may reach its target **before** // the response, destroying the ordered delivery. So we must wait a bit // here. // See https://github.com/versatica/mediasoup/issues/510 setImmediate(() => this.emit(notification.handlerId()!, notification.event(), notification) ); } private processLog(pid: number, log: Log): void { const logData = log.data()!; switch (logData[0]) { // 'D' (a debug log). case 'D': { logger.debug(`[pid:${pid}] ${logData.slice(1)}`); break; } // 'W' (a warn log). case 'W': { logger.warn(`[pid:${pid}] ${logData.slice(1)}`); break; } // 'E' (a error log). case 'E': { logger.error(`[pid:${pid}] ${logData.slice(1)}`); break; } // 'X' (a dump log). case 'X': { info(logData.slice(1)); break; } } } } ================================================ FILE: node/src/Consumer.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { Consumer, ConsumerType, ConsumerScore, ConsumerLayers, ConsumerDump, SimpleConsumerDump, SimulcastConsumerDump, SvcConsumerDump, PipeConsumerDump, BaseConsumerDump, RtpStreamDump, RtpStreamParametersDump, RtxStreamDump, RtxStreamParameters, ConsumerStat, ConsumerTraceEventType, ConsumerTraceEventData, ConsumerEvents, ConsumerObserver, ConsumerObserverEvents, } from './ConsumerTypes'; import { Channel } from './Channel'; import type { TransportInternal } from './Transport'; import type { ProducerStat } from './ProducerTypes'; import type { MediaKind, RtpParameters } from './rtpParametersTypes'; import { parseRtpEncodingParameters, parseRtpParameters, } from './rtpParametersFbsUtils'; import { parseRtpStreamStats } from './rtpStreamStatsFbsUtils'; import type { AppData } from './types'; import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; import { TraceDirection as FbsTraceDirection } from './fbs/common'; import * as FbsRequest from './fbs/request'; import * as FbsTransport from './fbs/transport'; import * as FbsConsumer from './fbs/consumer'; import * as FbsConsumerTraceInfo from './fbs/consumer/trace-info'; import * as FbsRtpStream from './fbs/rtp-stream'; import * as FbsRtxStream from './fbs/rtx-stream'; import { Type as FbsRtpParametersType } from './fbs/rtp-parameters'; import * as FbsRtpParameters from './fbs/rtp-parameters'; type ConsumerInternal = TransportInternal & { consumerId: string; }; type ConsumerData = { producerId: string; kind: MediaKind; rtpParameters: RtpParameters; type: ConsumerType; }; const logger = new Logger('Consumer'); export class ConsumerImpl extends EnhancedEventEmitter implements Consumer { // Internal data. readonly #internal: ConsumerInternal; // Consumer data. readonly #data: ConsumerData; // Channel instance. readonly #channel: Channel; // Closed flag. #closed = false; // Custom app data. #appData: ConsumerAppData; // Paused flag. #paused = false; // Associated Producer paused flag. #producerPaused = false; // Current priority. #priority = 1; // Current score. #score: ConsumerScore; // Preferred layers. #preferredLayers?: ConsumerLayers; // Curent layers. #currentLayers?: ConsumerLayers; // Observer instance. readonly #observer: ConsumerObserver = new EnhancedEventEmitter(); constructor({ internal, data, channel, appData, paused, producerPaused, score = { score: 10, producerScore: 10, producerScores: [] }, preferredLayers, }: { internal: ConsumerInternal; data: ConsumerData; channel: Channel; appData?: ConsumerAppData; paused: boolean; producerPaused: boolean; score?: ConsumerScore; preferredLayers?: ConsumerLayers; }) { super(); logger.debug('constructor()'); this.#internal = internal; this.#data = data; this.#channel = channel; this.#paused = paused; this.#producerPaused = producerPaused; this.#score = score; this.#preferredLayers = preferredLayers; this.#appData = appData ?? ({} as ConsumerAppData); this.handleWorkerNotifications(); this.handleListenerError(); } get id(): string { return this.#internal.consumerId; } get producerId(): string { return this.#data.producerId; } get closed(): boolean { return this.#closed; } get kind(): MediaKind { return this.#data.kind; } get rtpParameters(): RtpParameters { return this.#data.rtpParameters; } get type(): ConsumerType { return this.#data.type; } get paused(): boolean { return this.#paused; } get producerPaused(): boolean { return this.#producerPaused; } get priority(): number { return this.#priority; } get score(): ConsumerScore { return this.#score; } get preferredLayers(): ConsumerLayers | undefined { return this.#preferredLayers; } get currentLayers(): ConsumerLayers | undefined { return this.#currentLayers; } get appData(): ConsumerAppData { return this.#appData; } set appData(appData: ConsumerAppData) { this.#appData = appData; } get observer(): ConsumerObserver { return this.#observer; } /** * Just for testing purposes. * * @private */ get channelForTesting(): Channel { return this.#channel; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.consumerId); /* Build Request. */ const requestOffset = new FbsTransport.CloseConsumerRequestT( this.#internal.consumerId ).pack(this.#channel.bufferBuilder); this.#channel .request( FbsRequest.Method.TRANSPORT_CLOSE_CONSUMER, FbsRequest.Body.Transport_CloseConsumerRequest, requestOffset, this.#internal.transportId ) .catch(() => {}); this.emit('@close'); // Emit observer event. this.#observer.safeEmit('close'); } transportClosed(): void { if (this.#closed) { return; } logger.debug('transportClosed()'); this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.consumerId); this.safeEmit('transportclose'); // Emit observer event. this.#observer.safeEmit('close'); } async dump(): Promise { logger.debug('dump()'); const response = await this.#channel.request( FbsRequest.Method.CONSUMER_DUMP, undefined, undefined, this.#internal.consumerId ); /* Decode Response. */ const data = new FbsConsumer.DumpResponse(); response.body(data); return parseConsumerDumpResponse(data); } async getStats(): Promise<(ConsumerStat | ProducerStat)[]> { logger.debug('getStats()'); const response = await this.#channel.request( FbsRequest.Method.CONSUMER_GET_STATS, undefined, undefined, this.#internal.consumerId ); /* Decode Response. */ const data = new FbsConsumer.GetStatsResponse(); response.body(data); return parseConsumerStats(data); } async pause(): Promise { logger.debug('pause()'); await this.#channel.request( FbsRequest.Method.CONSUMER_PAUSE, undefined, undefined, this.#internal.consumerId ); const wasPaused = this.#paused; this.#paused = true; // Emit observer event. if (!wasPaused && !this.#producerPaused) { this.#observer.safeEmit('pause'); } } async resume(): Promise { logger.debug('resume()'); await this.#channel.request( FbsRequest.Method.CONSUMER_RESUME, undefined, undefined, this.#internal.consumerId ); const wasPaused = this.#paused; this.#paused = false; // Emit observer event. if (wasPaused && !this.#producerPaused) { this.#observer.safeEmit('resume'); } } async setPreferredLayers({ spatialLayer, temporalLayer, }: ConsumerLayers): Promise { logger.debug('setPreferredLayers()'); if (typeof spatialLayer !== 'number') { throw new TypeError('spatialLayer must be a number'); } if (temporalLayer && typeof temporalLayer !== 'number') { throw new TypeError('if given, temporalLayer must be a number'); } const builder = this.#channel.bufferBuilder; const preferredLayersOffset = FbsConsumer.ConsumerLayers.createConsumerLayers( builder, spatialLayer, temporalLayer ?? null ); const requestOffset = FbsConsumer.SetPreferredLayersRequest.createSetPreferredLayersRequest( builder, preferredLayersOffset ); const response = await this.#channel.request( FbsRequest.Method.CONSUMER_SET_PREFERRED_LAYERS, FbsRequest.Body.Consumer_SetPreferredLayersRequest, requestOffset, this.#internal.consumerId ); /* Decode Response. */ const data = new FbsConsumer.SetPreferredLayersResponse(); let preferredLayers: ConsumerLayers | undefined; // Response is empty for non Simulcast Consumers. if (response.body(data)) { const status = data.unpack(); if (status.preferredLayers) { preferredLayers = { spatialLayer: status.preferredLayers.spatialLayer, temporalLayer: status.preferredLayers.temporalLayer ?? undefined, }; } } this.#preferredLayers = preferredLayers; } async setPriority(priority: number): Promise { logger.debug('setPriority()'); if (typeof priority !== 'number' || priority < 0) { throw new TypeError('priority must be a positive number'); } const requestOffset = FbsConsumer.SetPriorityRequest.createSetPriorityRequest( this.#channel.bufferBuilder, priority ); const response = await this.#channel.request( FbsRequest.Method.CONSUMER_SET_PRIORITY, FbsRequest.Body.Consumer_SetPriorityRequest, requestOffset, this.#internal.consumerId ); const data = new FbsConsumer.SetPriorityResponse(); response.body(data); const status = data.unpack(); this.#priority = status.priority; } async unsetPriority(): Promise { logger.debug('unsetPriority()'); await this.setPriority(1); } async requestKeyFrame(): Promise { logger.debug('requestKeyFrame()'); await this.#channel.request( FbsRequest.Method.CONSUMER_REQUEST_KEY_FRAME, undefined, undefined, this.#internal.consumerId ); } async enableTraceEvent(types: ConsumerTraceEventType[] = []): Promise { logger.debug('enableTraceEvent()'); if (!Array.isArray(types)) { throw new TypeError('types must be an array'); } if (types.find(type => typeof type !== 'string')) { throw new TypeError('every type must be a string'); } // Convert event types. const fbsEventTypes: FbsConsumer.TraceEventType[] = []; for (const eventType of types) { try { fbsEventTypes.push(consumerTraceEventTypeToFbs(eventType)); } catch (error) { logger.warn('enableTraceEvent() | [error:${error}]'); } } /* Build Request. */ const requestOffset = new FbsConsumer.EnableTraceEventRequestT( fbsEventTypes ).pack(this.#channel.bufferBuilder); await this.#channel.request( FbsRequest.Method.CONSUMER_ENABLE_TRACE_EVENT, FbsRequest.Body.Consumer_EnableTraceEventRequest, requestOffset, this.#internal.consumerId ); } private handleWorkerNotifications(): void { this.#channel.on( this.#internal.consumerId, (event: Event, data?: Notification) => { switch (event) { case Event.CONSUMER_PRODUCER_CLOSE: { if (this.#closed) { break; } this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.consumerId); this.emit('@producerclose'); this.safeEmit('producerclose'); // Emit observer event. this.#observer.safeEmit('close'); break; } case Event.CONSUMER_PRODUCER_PAUSE: { if (this.#producerPaused) { break; } this.#producerPaused = true; this.safeEmit('producerpause'); // Emit observer event. if (!this.#paused) { this.#observer.safeEmit('pause'); } break; } case Event.CONSUMER_PRODUCER_RESUME: { if (!this.#producerPaused) { break; } this.#producerPaused = false; this.safeEmit('producerresume'); // Emit observer event. if (!this.#paused) { this.#observer.safeEmit('resume'); } break; } case Event.CONSUMER_SCORE: { const notification = new FbsConsumer.ScoreNotification(); data!.body(notification); const score: ConsumerScore = notification.score()!.unpack(); this.#score = score; this.safeEmit('score', score); // Emit observer event. this.#observer.safeEmit('score', score); break; } case Event.CONSUMER_LAYERS_CHANGE: { const notification = new FbsConsumer.LayersChangeNotification(); data!.body(notification); const layers: ConsumerLayers | undefined = notification.layers() ? parseConsumerLayers(notification.layers()!) : undefined; this.#currentLayers = layers; this.safeEmit('layerschange', layers); // Emit observer event. this.#observer.safeEmit('layerschange', layers); break; } case Event.CONSUMER_TRACE: { const notification = new FbsConsumer.TraceNotification(); data!.body(notification); const trace: ConsumerTraceEventData = parseTraceEventData(notification); this.safeEmit('trace', trace); // Emit observer event. this.observer.safeEmit('trace', trace); this.safeEmit('trace', trace); // Emit observer event. this.#observer.safeEmit('trace', trace); break; } case Event.CONSUMER_RTP: { if (this.#closed) { break; } const notification = new FbsConsumer.RtpNotification(); data!.body(notification); this.safeEmit('rtp', Buffer.from(notification.dataArray()!)); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } } ); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } function parseTraceEventData( trace: FbsConsumer.TraceNotification ): ConsumerTraceEventData { // eslint-disable-next-line @typescript-eslint/no-explicit-any let info: any; if (trace.infoType() !== FbsConsumer.TraceInfo.NONE) { const accessor = trace.info.bind(trace); info = FbsConsumerTraceInfo.unionToTraceInfo(trace.infoType(), accessor); trace.info(info); } return { type: consumerTraceEventTypeFromFbs(trace.type()), timestamp: Number(trace.timestamp()), direction: trace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out', info: info?.unpack(), }; } function consumerTraceEventTypeToFbs( eventType: ConsumerTraceEventType ): FbsConsumer.TraceEventType { switch (eventType) { case 'keyframe': { return FbsConsumer.TraceEventType.KEYFRAME; } case 'fir': { return FbsConsumer.TraceEventType.FIR; } case 'nack': { return FbsConsumer.TraceEventType.NACK; } case 'pli': { return FbsConsumer.TraceEventType.PLI; } case 'rtp': { return FbsConsumer.TraceEventType.RTP; } default: { throw new TypeError(`invalid ConsumerTraceEventType: ${eventType}`); } } } function consumerTraceEventTypeFromFbs( traceType: FbsConsumer.TraceEventType ): ConsumerTraceEventType { switch (traceType) { case FbsConsumer.TraceEventType.KEYFRAME: { return 'keyframe'; } case FbsConsumer.TraceEventType.FIR: { return 'fir'; } case FbsConsumer.TraceEventType.NACK: { return 'nack'; } case FbsConsumer.TraceEventType.PLI: { return 'pli'; } case FbsConsumer.TraceEventType.RTP: { return 'rtp'; } default: { throw new TypeError(`invalid FbsConsumer.TraceEventType: ${traceType}`); } } } function parseConsumerLayers(data: FbsConsumer.ConsumerLayers): ConsumerLayers { const spatialLayer = data.spatialLayer(); const temporalLayer = data.temporalLayer() !== null ? data.temporalLayer()! : undefined; return { spatialLayer, temporalLayer, }; } function parseRtpStream(data: FbsRtpStream.Dump): RtpStreamDump { const params = parseRtpStreamParameters(data.params()!); let rtxStream: RtxStreamDump | undefined; if (data.rtxStream()) { rtxStream = parseRtxStream(data.rtxStream()!); } return { params, score: data.score(), rtxStream, }; } function parseRtpStreamParameters( data: FbsRtpStream.Params ): RtpStreamParametersDump { return { encodingIdx: data.encodingIdx(), ssrc: data.ssrc(), payloadType: data.payloadType(), mimeType: data.mimeType()!, clockRate: data.clockRate(), rid: data.rid()!.length > 0 ? data.rid()! : undefined, cname: data.cname()!, rtxSsrc: data.rtxSsrc() !== null ? data.rtxSsrc()! : undefined, rtxPayloadType: data.rtxPayloadType() !== null ? data.rtxPayloadType()! : undefined, useNack: data.useNack(), usePli: data.usePli(), useFir: data.useFir(), useInBandFec: data.useInBandFec(), useDtx: data.useDtx(), spatialLayers: data.spatialLayers(), temporalLayers: data.temporalLayers(), }; } function parseRtxStream(data: FbsRtxStream.RtxDump): RtxStreamDump { const params = parseRtxStreamParameters(data.params()!); return { params, }; } function parseRtxStreamParameters( data: FbsRtxStream.Params ): RtxStreamParameters { return { ssrc: data.ssrc(), payloadType: data.payloadType(), mimeType: data.mimeType()!, clockRate: data.clockRate(), rrid: data.rrid()!.length > 0 ? data.rrid()! : undefined, cname: data.cname()!, }; } function parseBaseConsumerDump( data: FbsConsumer.BaseConsumerDump ): BaseConsumerDump { return { id: data.id()!, producerId: data.producerId()!, kind: data.kind() === FbsRtpParameters.MediaKind.AUDIO ? 'audio' : 'video', rtpParameters: parseRtpParameters(data.rtpParameters()!), consumableRtpEncodings: data.consumableRtpEncodingsLength() > 0 ? fbsUtils.parseVector( data, 'consumableRtpEncodings', parseRtpEncodingParameters ) : undefined, traceEventTypes: fbsUtils.parseVector( data, 'traceEventTypes', consumerTraceEventTypeFromFbs ), supportedCodecPayloadTypes: fbsUtils.parseVector( data, 'supportedCodecPayloadTypes' ), paused: data.paused(), producerPaused: data.producerPaused(), priority: data.priority(), }; } function parseSimpleConsumerDump( data: FbsConsumer.ConsumerDump ): SimpleConsumerDump { const base = parseBaseConsumerDump(data.base()!); const rtpStream = parseRtpStream(data.rtpStreams(0)!); return { ...base, type: 'simple', rtpStream, }; } function parseSimulcastConsumerDump( data: FbsConsumer.ConsumerDump ): SimulcastConsumerDump { const base = parseBaseConsumerDump(data.base()!); const rtpStream = parseRtpStream(data.rtpStreams(0)!); return { ...base, type: 'simulcast', rtpStream, preferredSpatialLayer: data.preferredSpatialLayer()!, targetSpatialLayer: data.targetSpatialLayer()!, currentSpatialLayer: data.currentSpatialLayer()!, preferredTemporalLayer: data.preferredTemporalLayer()!, targetTemporalLayer: data.targetTemporalLayer()!, currentTemporalLayer: data.currentTemporalLayer()!, }; } function parseSvcConsumerDump(data: FbsConsumer.ConsumerDump): SvcConsumerDump { const dump = parseSimulcastConsumerDump(data); dump.type = 'svc'; return dump; } function parsePipeConsumerDump( data: FbsConsumer.ConsumerDump ): PipeConsumerDump { const base = parseBaseConsumerDump(data.base()!); const rtpStreams = fbsUtils.parseVector(data, 'rtpStreams', parseRtpStream); return { ...base, type: 'pipe', rtpStreams, }; } function parseConsumerDumpResponse( data: FbsConsumer.DumpResponse ): ConsumerDump { const type = data.data()!.base()!.type(); switch (type) { case FbsRtpParametersType.SIMPLE: { const dump = new FbsConsumer.ConsumerDump(); data.data(dump); return parseSimpleConsumerDump(dump); } case FbsRtpParametersType.SIMULCAST: { const dump = new FbsConsumer.ConsumerDump(); data.data(dump); return parseSimulcastConsumerDump(dump); } case FbsRtpParametersType.SVC: { const dump = new FbsConsumer.ConsumerDump(); data.data(dump); return parseSvcConsumerDump(dump); } case FbsRtpParametersType.PIPE: { const dump = new FbsConsumer.ConsumerDump(); data.data(dump); return parsePipeConsumerDump(dump); } default: { throw new TypeError(`invalid Consumer type: ${type}`); } } } function parseConsumerStats( binary: FbsConsumer.GetStatsResponse ): (ConsumerStat | ProducerStat)[] { return fbsUtils.parseVector(binary, 'stats', parseRtpStreamStats); } ================================================ FILE: node/src/ConsumerTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { ProducerStat } from './ProducerTypes'; import type { MediaKind, RtpCapabilities, RtpEncodingParameters, RtpParameters, } from './rtpParametersTypes'; import type { RtpStreamSendStats } from './rtpStreamStatsTypes'; import type { AppData } from './types'; export type ConsumerOptions = { /** * The id of the Producer to consume. */ producerId: string; /** * RTP capabilities of the consuming endpoint. */ rtpCapabilities: RtpCapabilities; /** * Whether the consumer must start in paused mode. Default false. * * When creating a video Consumer, it's recommended to set paused to true, * then transmit the Consumer parameters to the consuming endpoint and, once * the consuming endpoint has created its local side Consumer, unpause the * server side Consumer using the resume() method. This is an optimization * to make it possible for the consuming endpoint to render the video as far * as possible. If the server side Consumer was created with paused: false, * mediasoup will immediately request a key frame to the remote Producer and * suych a key frame may reach the consuming endpoint even before it's ready * to consume it, generating “black” video until the device requests a keyframe * by itself. */ paused?: boolean; /** * The MID for the Consumer. If not specified, a sequentially growing * number will be assigned. */ mid?: string; /** * Preferred spatial and temporal layer for simulcast or SVC media sources. * If unset, the highest ones are selected. */ preferredLayers?: ConsumerLayers; /** * Whether this Consumer should enable RTP retransmissions, storing sent RTP * and processing the incoming RTCP NACK from the remote Consumer. If not set * it's true by default for video codecs and false for audio codecs. If set * to true, NACK will be enabled if both endpoints (mediasoup and the remote * Consumer) support NACK for this codec. When it comes to audio codecs, just * OPUS supports NACK. */ enableRtx?: boolean; /** * Whether this Consumer should ignore DTX packets (only valid for Opus codec). * If set, DTX packets are not forwarded to the remote Consumer. */ ignoreDtx?: boolean; /** * Whether this Consumer should consume all RTP streams generated by the * Producer. */ pipe?: boolean; /** * Custom application data. */ appData?: ConsumerAppData; }; /** * Consumer type. */ export type ConsumerType = 'simple' | 'simulcast' | 'svc' | 'pipe'; export type ConsumerScore = { /** * The score of the RTP stream of the consumer. */ score: number; /** * The score of the currently selected RTP stream of the producer. */ producerScore: number; /** * The scores of all RTP streams in the producer ordered by encoding (just * useful when the producer uses simulcast). */ producerScores: number[]; }; export type ConsumerLayers = { /** * The spatial layer index (from 0 to N). */ spatialLayer: number; /** * The temporal layer index (from 0 to N). */ temporalLayer?: number; }; export type ConsumerDump = | SimpleConsumerDump | SimulcastConsumerDump | SvcConsumerDump | PipeConsumerDump; export type SimpleConsumerDump = BaseConsumerDump & { type: string; rtpStream: RtpStreamDump; }; export type SimulcastConsumerDump = BaseConsumerDump & { type: string; rtpStream: RtpStreamDump; preferredSpatialLayer: number; targetSpatialLayer: number; currentSpatialLayer: number; preferredTemporalLayer: number; targetTemporalLayer: number; currentTemporalLayer: number; }; export type SvcConsumerDump = SimulcastConsumerDump; export type PipeConsumerDump = BaseConsumerDump & { type: string; rtpStreams: RtpStreamDump[]; }; export type BaseConsumerDump = { id: string; producerId: string; kind: MediaKind; rtpParameters: RtpParameters; consumableRtpEncodings?: RtpEncodingParameters[]; supportedCodecPayloadTypes: number[]; traceEventTypes: string[]; paused: boolean; producerPaused: boolean; priority: number; }; export type RtpStreamDump = { params: RtpStreamParametersDump; score: number; rtxStream?: RtxStreamDump; }; export type RtpStreamParametersDump = { encodingIdx: number; ssrc: number; payloadType: number; mimeType: string; clockRate: number; rid?: string; cname: string; rtxSsrc?: number; rtxPayloadType?: number; useNack: boolean; usePli: boolean; useFir: boolean; useInBandFec: boolean; useDtx: boolean; spatialLayers: number; temporalLayers: number; }; export type RtxStreamDump = { params: RtxStreamParameters; }; export type RtxStreamParameters = { ssrc: number; payloadType: number; mimeType: string; clockRate: number; rrid?: string; cname: string; }; export type ConsumerStat = RtpStreamSendStats; /** * Valid types for 'trace' event. */ export type ConsumerTraceEventType = | 'rtp' | 'keyframe' | 'nack' | 'pli' | 'fir'; /** * 'trace' event data. */ export type ConsumerTraceEventData = { /** * Trace type. */ type: ConsumerTraceEventType; /** * Event timestamp. */ timestamp: number; /** * Event direction. */ direction: 'in' | 'out'; /** * Per type information. */ info: Record; }; export type ConsumerEvents = { transportclose: []; producerclose: []; producerpause: []; producerresume: []; score: [ConsumerScore]; layerschange: [ConsumerLayers?]; trace: [ConsumerTraceEventData]; rtp: [Buffer]; // Private events. '@close': []; '@producerclose': []; }; export type ConsumerObserver = EnhancedEventEmitter; export type ConsumerObserverEvents = { close: []; pause: []; resume: []; score: [ConsumerScore]; layerschange: [ConsumerLayers?]; trace: [ConsumerTraceEventData]; }; export interface Consumer< ConsumerAppData extends AppData = AppData, > extends EnhancedEventEmitter { /** * Consumer id. */ get id(): string; /** * Associated Producer id. */ get producerId(): string; /** * Whether the Consumer is closed. */ get closed(): boolean; /** * Media kind. */ get kind(): MediaKind; /** * RTP parameters. */ get rtpParameters(): RtpParameters; /** * Consumer type. */ get type(): ConsumerType; /** * Whether the Consumer is paused. */ get paused(): boolean; /** * Whether the associate Producer is paused. */ get producerPaused(): boolean; /** * Current priority. */ get priority(): number; /** * Consumer score. */ get score(): ConsumerScore; /** * Preferred video layers. */ get preferredLayers(): ConsumerLayers | undefined; /** * Current video layers. */ get currentLayers(): ConsumerLayers | undefined; /** * App custom data. */ get appData(): ConsumerAppData; /** * App custom data setter. */ set appData(appData: ConsumerAppData); /** * Observer. */ get observer(): ConsumerObserver; /** * Close the Consumer. */ close(): void; /** * Transport was closed. * * @private */ transportClosed(): void; /** * Dump Consumer. */ dump(): Promise; /** * Get Consumer stats. */ getStats(): Promise<(ConsumerStat | ProducerStat)[]>; /** * Pause the Consumer. */ pause(): Promise; /** * Resume the Consumer. */ resume(): Promise; /** * Set preferred video layers. */ setPreferredLayers({ spatialLayer, temporalLayer, }: ConsumerLayers): Promise; /** * Set priority. */ setPriority(priority: number): Promise; /** * Unset priority. */ unsetPriority(): Promise; /** * Request a key frame to the Producer. */ requestKeyFrame(): Promise; /** * Enable 'trace' event. */ enableTraceEvent(types?: ConsumerTraceEventType[]): Promise; } ================================================ FILE: node/src/DataConsumer.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { DataConsumer, DataConsumerType, DataConsumerDump, DataConsumerStat, DataConsumerEvents, DataConsumerObserver, DataConsumerObserverEvents, } from './DataConsumerTypes'; import { Channel } from './Channel'; import type { TransportInternal } from './Transport'; import type { SctpStreamParameters } from './sctpParametersTypes'; import { parseSctpStreamParameters } from './sctpParametersFbsUtils'; import type { AppData } from './types'; import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; import * as FbsTransport from './fbs/transport'; import * as FbsRequest from './fbs/request'; import * as FbsDataConsumer from './fbs/data-consumer'; import * as FbsDataProducer from './fbs/data-producer'; type DataConsumerInternal = TransportInternal & { dataConsumerId: string; }; type DataConsumerData = { dataProducerId: string; type: DataConsumerType; sctpStreamParameters?: SctpStreamParameters; label: string; protocol: string; bufferedAmountLowThreshold: number; }; const logger = new Logger('DataConsumer'); export class DataConsumerImpl extends EnhancedEventEmitter implements DataConsumer { // Internal data. readonly #internal: DataConsumerInternal; // DataConsumer data. readonly #data: DataConsumerData; // Channel instance. readonly #channel: Channel; // Closed flag. #closed = false; // Paused flag. #paused = false; // Associated DataProducer paused flag. #dataProducerPaused = false; // Subchannels subscribed to. #subchannels: number[]; // Custom app data. #appData: DataConsumerAppData; // Observer instance. readonly #observer: DataConsumerObserver = new EnhancedEventEmitter(); constructor({ internal, data, channel, paused, dataProducerPaused, subchannels, appData, }: { internal: DataConsumerInternal; data: DataConsumerData; channel: Channel; paused: boolean; dataProducerPaused: boolean; subchannels: number[]; appData?: DataConsumerAppData; }) { super(); logger.debug('constructor()'); this.#internal = internal; this.#data = data; this.#channel = channel; this.#paused = paused; this.#dataProducerPaused = dataProducerPaused; this.#subchannels = subchannels; this.#appData = appData ?? ({} as DataConsumerAppData); this.handleWorkerNotifications(); this.handleListenerError(); } get id(): string { return this.#internal.dataConsumerId; } get dataProducerId(): string { return this.#data.dataProducerId; } get closed(): boolean { return this.#closed; } get type(): DataConsumerType { return this.#data.type; } get sctpStreamParameters(): SctpStreamParameters | undefined { return this.#data.sctpStreamParameters; } get label(): string { return this.#data.label; } get protocol(): string { return this.#data.protocol; } get paused(): boolean { return this.#paused; } get dataProducerPaused(): boolean { return this.#dataProducerPaused; } get subchannels(): number[] { return Array.from(this.#subchannels); } get appData(): DataConsumerAppData { return this.#appData; } set appData(appData: DataConsumerAppData) { this.#appData = appData; } get observer(): DataConsumerObserver { return this.#observer; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.dataConsumerId); /* Build Request. */ const requestOffset = new FbsTransport.CloseDataConsumerRequestT( this.#internal.dataConsumerId ).pack(this.#channel.bufferBuilder); this.#channel .request( FbsRequest.Method.TRANSPORT_CLOSE_DATACONSUMER, FbsRequest.Body.Transport_CloseDataConsumerRequest, requestOffset, this.#internal.transportId ) .catch(() => {}); this.emit('@close'); // Emit observer event. this.#observer.safeEmit('close'); } transportClosed(): void { if (this.#closed) { return; } logger.debug('transportClosed()'); this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.dataConsumerId); this.safeEmit('transportclose'); // Emit observer event. this.#observer.safeEmit('close'); } async dump(): Promise { logger.debug('dump()'); const response = await this.#channel.request( FbsRequest.Method.DATACONSUMER_DUMP, undefined, undefined, this.#internal.dataConsumerId ); /* Decode Response. */ const dumpResponse = new FbsDataConsumer.DumpResponse(); response.body(dumpResponse); return parseDataConsumerDumpResponse(dumpResponse); } async getStats(): Promise { logger.debug('getStats()'); const response = await this.#channel.request( FbsRequest.Method.DATACONSUMER_GET_STATS, undefined, undefined, this.#internal.dataConsumerId ); /* Decode Response. */ const data = new FbsDataConsumer.GetStatsResponse(); response.body(data); return [parseDataConsumerStats(data)]; } async pause(): Promise { logger.debug('pause()'); await this.#channel.request( FbsRequest.Method.DATACONSUMER_PAUSE, undefined, undefined, this.#internal.dataConsumerId ); const wasPaused = this.#paused; this.#paused = true; // Emit observer event. if (!wasPaused && !this.#dataProducerPaused) { this.#observer.safeEmit('pause'); } } async resume(): Promise { logger.debug('resume()'); await this.#channel.request( FbsRequest.Method.DATACONSUMER_RESUME, undefined, undefined, this.#internal.dataConsumerId ); const wasPaused = this.#paused; this.#paused = false; // Emit observer event. if (wasPaused && !this.#dataProducerPaused) { this.#observer.safeEmit('resume'); } } async setBufferedAmountLowThreshold(threshold: number): Promise { logger.debug(`setBufferedAmountLowThreshold() [threshold:${threshold}]`); /* Build Request. */ const requestOffset = FbsDataConsumer.SetBufferedAmountLowThresholdRequest.createSetBufferedAmountLowThresholdRequest( this.#channel.bufferBuilder, threshold ); await this.#channel.request( FbsRequest.Method.DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD, FbsRequest.Body.DataConsumer_SetBufferedAmountLowThresholdRequest, requestOffset, this.#internal.dataConsumerId ); } async getBufferedAmount(): Promise { logger.debug('getBufferedAmount()'); const response = await this.#channel.request( FbsRequest.Method.DATACONSUMER_GET_BUFFERED_AMOUNT, undefined, undefined, this.#internal.dataConsumerId ); const data = new FbsDataConsumer.GetBufferedAmountResponse(); response.body(data); return data.bufferedAmount(); } async send(message: string | Buffer, ppid?: number): Promise { if (typeof message !== 'string' && !Buffer.isBuffer(message)) { throw new TypeError('message must be a string or a Buffer'); } /* * +-------------------------------+----------+ * | Value | SCTP | * | | PPID | * +-------------------------------+----------+ * | WebRTC String | 51 | * | WebRTC Binary Partial | 52 | * | (Deprecated) | | * | WebRTC Binary | 53 | * | WebRTC String Partial | 54 | * | (Deprecated) | | * | WebRTC String Empty | 56 | * | WebRTC Binary Empty | 57 | * +-------------------------------+----------+ */ if (typeof ppid !== 'number') { ppid = typeof message === 'string' ? message.length > 0 ? 51 : 56 : message.length > 0 ? 53 : 57; } // Ensure we honor PPIDs. if (ppid === 56) { message = ' '; } else if (ppid === 57) { message = Buffer.alloc(1); } const builder = this.#channel.bufferBuilder; if (typeof message === 'string') { message = Buffer.from(message); } const dataOffset = FbsDataConsumer.SendRequest.createDataVector( builder, message ); const requestOffset = FbsDataConsumer.SendRequest.createSendRequest( builder, ppid, dataOffset ); await this.#channel.request( FbsRequest.Method.DATACONSUMER_SEND, FbsRequest.Body.DataConsumer_SendRequest, requestOffset, this.#internal.dataConsumerId ); } async setSubchannels(subchannels: number[]): Promise { logger.debug('setSubchannels()'); /* Build Request. */ const requestOffset = new FbsDataConsumer.SetSubchannelsRequestT( subchannels ).pack(this.#channel.bufferBuilder); const response = await this.#channel.request( FbsRequest.Method.DATACONSUMER_SET_SUBCHANNELS, FbsRequest.Body.DataConsumer_SetSubchannelsRequest, requestOffset, this.#internal.dataConsumerId ); /* Decode Response. */ const data = new FbsDataConsumer.SetSubchannelsResponse(); response.body(data); // Update subchannels. this.#subchannels = fbsUtils.parseVector(data, 'subchannels'); } async addSubchannel(subchannel: number): Promise { logger.debug('addSubchannel()'); /* Build Request. */ const requestOffset = FbsDataConsumer.AddSubchannelRequest.createAddSubchannelRequest( this.#channel.bufferBuilder, subchannel ); const response = await this.#channel.request( FbsRequest.Method.DATACONSUMER_ADD_SUBCHANNEL, FbsRequest.Body.DataConsumer_AddSubchannelRequest, requestOffset, this.#internal.dataConsumerId ); /* Decode Response. */ const data = new FbsDataConsumer.AddSubchannelResponse(); response.body(data); // Update subchannels. this.#subchannels = fbsUtils.parseVector(data, 'subchannels'); } async removeSubchannel(subchannel: number): Promise { logger.debug('removeSubchannel()'); /* Build Request. */ const requestOffset = FbsDataConsumer.RemoveSubchannelRequest.createRemoveSubchannelRequest( this.#channel.bufferBuilder, subchannel ); const response = await this.#channel.request( FbsRequest.Method.DATACONSUMER_REMOVE_SUBCHANNEL, FbsRequest.Body.DataConsumer_RemoveSubchannelRequest, requestOffset, this.#internal.dataConsumerId ); /* Decode Response. */ const data = new FbsDataConsumer.RemoveSubchannelResponse(); response.body(data); // Update subchannels. this.#subchannels = fbsUtils.parseVector(data, 'subchannels'); } private handleWorkerNotifications(): void { this.#channel.on( this.#internal.dataConsumerId, (event: Event, data?: Notification) => { switch (event) { case Event.DATACONSUMER_DATAPRODUCER_CLOSE: { if (this.#closed) { break; } this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.dataConsumerId); this.emit('@dataproducerclose'); this.safeEmit('dataproducerclose'); // Emit observer event. this.#observer.safeEmit('close'); break; } case Event.DATACONSUMER_DATAPRODUCER_PAUSE: { if (this.#dataProducerPaused) { break; } this.#dataProducerPaused = true; this.safeEmit('dataproducerpause'); // Emit observer event. if (!this.#paused) { this.#observer.safeEmit('pause'); } break; } case Event.DATACONSUMER_DATAPRODUCER_RESUME: { if (!this.#dataProducerPaused) { break; } this.#dataProducerPaused = false; this.safeEmit('dataproducerresume'); // Emit observer event. if (!this.#paused) { this.#observer.safeEmit('resume'); } break; } case Event.DATACONSUMER_SCTP_SENDBUFFER_FULL: { this.safeEmit('sctpsendbufferfull'); break; } case Event.DATACONSUMER_BUFFERED_AMOUNT_LOW: { const notification = new FbsDataConsumer.BufferedAmountLowNotification(); data!.body(notification); const bufferedAmount = notification.bufferedAmount(); this.safeEmit('bufferedamountlow', bufferedAmount); break; } case Event.DATACONSUMER_MESSAGE: { if (this.#closed) { break; } const notification = new FbsDataConsumer.MessageNotification(); data!.body(notification); this.safeEmit( 'message', Buffer.from(notification.dataArray()!), notification.ppid() ); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } } ); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } export function dataConsumerTypeToFbs( type: DataConsumerType ): FbsDataProducer.Type { switch (type) { case 'sctp': { return FbsDataProducer.Type.SCTP; } case 'direct': { return FbsDataProducer.Type.DIRECT; } default: { throw new TypeError('invalid DataConsumerType: ${type}'); } } } function dataConsumerTypeFromFbs(type: FbsDataProducer.Type): DataConsumerType { switch (type) { case FbsDataProducer.Type.SCTP: { return 'sctp'; } case FbsDataProducer.Type.DIRECT: { return 'direct'; } } } export function parseDataConsumerDumpResponse( data: FbsDataConsumer.DumpResponse ): DataConsumerDump { return { id: data.id()!, dataProducerId: data.dataProducerId()!, type: dataConsumerTypeFromFbs(data.type()), sctpStreamParameters: data.sctpStreamParameters() !== null ? parseSctpStreamParameters(data.sctpStreamParameters()!) : undefined, label: data.label()!, protocol: data.protocol()!, bufferedAmountLowThreshold: data.bufferedAmountLowThreshold(), paused: data.paused(), dataProducerPaused: data.dataProducerPaused(), subchannels: fbsUtils.parseVector(data, 'subchannels'), }; } function parseDataConsumerStats( binary: FbsDataConsumer.GetStatsResponse ): DataConsumerStat { return { type: 'data-consumer', timestamp: Number(binary.timestamp()), label: binary.label()!, protocol: binary.protocol()!, messagesSent: Number(binary.messagesSent()), bytesSent: Number(binary.bytesSent()), bufferedAmount: binary.bufferedAmount(), }; } ================================================ FILE: node/src/DataConsumerTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { SctpStreamParameters } from './sctpParametersTypes'; import type { AppData } from './types'; export type DataConsumerOptions = { /** * The id of the DataProducer to consume. */ dataProducerId: string; /** * Just if consuming over SCTP. * Whether data messages must be received in order. If true the messages will * be sent reliably. Defaults to the value in the DataProducer if it has type * 'sctp' or to true if it has type 'direct'. */ ordered?: boolean; /** * Just if consuming over SCTP. * When ordered is false indicates the time (in milliseconds) after which a * SCTP packet will stop being retransmitted. Defaults to the value in the * DataProducer if it has type 'sctp' or unset if it has type 'direct'. */ maxPacketLifeTime?: number; /** * Just if consuming over SCTP. * When ordered is false indicates the maximum number of times a packet will * be retransmitted. Defaults to the value in the DataProducer if it has type * 'sctp' or unset if it has type 'direct'. */ maxRetransmits?: number; /** * Whether the data consumer must start in paused mode. Default false. */ paused?: boolean; /** * Subchannels this data consumer initially subscribes to. * Only used in case this data consumer receives messages from a local data * producer that specifies subchannel(s) when calling send(). */ subchannels?: number[]; /** * Custom application data. */ appData?: DataConsumerAppData; }; /** * DataConsumer type. */ export type DataConsumerType = 'sctp' | 'direct'; export type DataConsumerDump = { id: string; paused: boolean; dataProducerPaused: boolean; subchannels: number[]; dataProducerId: string; type: DataConsumerType; sctpStreamParameters?: SctpStreamParameters; label: string; protocol: string; bufferedAmountLowThreshold: number; }; export type DataConsumerStat = { type: string; timestamp: number; label: string; protocol: string; messagesSent: number; bytesSent: number; bufferedAmount: number; }; export type DataConsumerEvents = { transportclose: []; dataproducerclose: []; dataproducerpause: []; dataproducerresume: []; message: [Buffer, number]; sctpsendbufferfull: []; bufferedamountlow: [number]; // Private events. '@close': []; '@dataproducerclose': []; }; export type DataConsumerObserver = EnhancedEventEmitter; export type DataConsumerObserverEvents = { close: []; pause: []; resume: []; }; export interface DataConsumer< DataConsumerAppData extends AppData = AppData, > extends EnhancedEventEmitter { /** * DataConsumer id. */ get id(): string; /** * Associated DataProducer id. */ get dataProducerId(): string; /** * Whether the DataConsumer is closed. */ get closed(): boolean; /** * DataConsumer type. */ get type(): DataConsumerType; /** * SCTP stream parameters. */ get sctpStreamParameters(): SctpStreamParameters | undefined; /** * DataChannel label. */ get label(): string; /** * DataChannel protocol. */ get protocol(): string; /** * Whether the DataConsumer is paused. */ get paused(): boolean; /** * Whether the associate DataProducer is paused. */ get dataProducerPaused(): boolean; /** * Get current subchannels this data consumer is subscribed to. */ get subchannels(): number[]; /** * App custom data. */ get appData(): DataConsumerAppData; /** * App custom data setter. */ set appData(appData: DataConsumerAppData); /** * Observer. */ get observer(): DataConsumerObserver; /** * Close the DataConsumer. */ close(): void; /** * Transport was closed. * * @private */ transportClosed(): void; /** * Dump DataConsumer. */ dump(): Promise; /** * Get DataConsumer stats. */ getStats(): Promise; /** * Pause the DataConsumer. */ pause(): Promise; /** * Resume the DataConsumer. */ resume(): Promise; /** * Set buffered amount low threshold. */ setBufferedAmountLowThreshold(threshold: number): Promise; /** * Get buffered amount size. */ getBufferedAmount(): Promise; /** * Send a message. */ send(message: string | Buffer, ppid?: number): Promise; /** * Set subchannels. */ setSubchannels(subchannels: number[]): Promise; /** * Add a subchannel. */ addSubchannel(subchannel: number): Promise; /** * Remove a subchannel. */ removeSubchannel(subchannel: number): Promise; } ================================================ FILE: node/src/DataProducer.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { DataProducer, DataProducerType, DataProducerDump, DataProducerStat, DataProducerEvents, DataProducerObserver, DataProducerObserverEvents, } from './DataProducerTypes'; import { Channel } from './Channel'; import type { TransportInternal } from './Transport'; import type { SctpStreamParameters } from './sctpParametersTypes'; import { parseSctpStreamParameters } from './sctpParametersFbsUtils'; import type { AppData } from './types'; import * as FbsTransport from './fbs/transport'; import * as FbsNotification from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsDataProducer from './fbs/data-producer'; type DataProducerInternal = TransportInternal & { dataProducerId: string; }; type DataProducerData = { type: DataProducerType; sctpStreamParameters?: SctpStreamParameters; label: string; protocol: string; }; const logger = new Logger('DataProducer'); export class DataProducerImpl extends EnhancedEventEmitter implements DataProducer { // Internal data. readonly #internal: DataProducerInternal; // DataProducer data. readonly #data: DataProducerData; // Channel instance. readonly #channel: Channel; // Closed flag. #closed = false; // Paused flag. #paused = false; // Custom app data. #appData: DataProducerAppData; // Observer instance. readonly #observer: DataProducerObserver = new EnhancedEventEmitter(); constructor({ internal, data, channel, paused, appData, }: { internal: DataProducerInternal; data: DataProducerData; channel: Channel; paused: boolean; appData?: DataProducerAppData; }) { super(); logger.debug('constructor()'); this.#internal = internal; this.#data = data; this.#channel = channel; this.#paused = paused; this.#appData = appData ?? ({} as DataProducerAppData); this.handleWorkerNotifications(); this.handleListenerError(); } get id(): string { return this.#internal.dataProducerId; } get closed(): boolean { return this.#closed; } get type(): DataProducerType { return this.#data.type; } get sctpStreamParameters(): SctpStreamParameters | undefined { return this.#data.sctpStreamParameters; } get label(): string { return this.#data.label; } get protocol(): string { return this.#data.protocol; } get paused(): boolean { return this.#paused; } get appData(): DataProducerAppData { return this.#appData; } set appData(appData: DataProducerAppData) { this.#appData = appData; } get observer(): DataProducerObserver { return this.#observer; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.dataProducerId); /* Build Request. */ const requestOffset = new FbsTransport.CloseDataProducerRequestT( this.#internal.dataProducerId ).pack(this.#channel.bufferBuilder); this.#channel .request( FbsRequest.Method.TRANSPORT_CLOSE_DATAPRODUCER, FbsRequest.Body.Transport_CloseDataProducerRequest, requestOffset, this.#internal.transportId ) .catch(() => {}); this.emit('@close'); // Emit observer event. this.#observer.safeEmit('close'); } transportClosed(): void { if (this.#closed) { return; } logger.debug('transportClosed()'); this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.dataProducerId); this.safeEmit('transportclose'); // Emit observer event. this.#observer.safeEmit('close'); } async dump(): Promise { logger.debug('dump()'); const response = await this.#channel.request( FbsRequest.Method.DATAPRODUCER_DUMP, undefined, undefined, this.#internal.dataProducerId ); /* Decode Response. */ const produceResponse = new FbsDataProducer.DumpResponse(); response.body(produceResponse); return parseDataProducerDumpResponse(produceResponse); } async getStats(): Promise { logger.debug('getStats()'); const response = await this.#channel.request( FbsRequest.Method.DATAPRODUCER_GET_STATS, undefined, undefined, this.#internal.dataProducerId ); /* Decode Response. */ const data = new FbsDataProducer.GetStatsResponse(); response.body(data); return [parseDataProducerStats(data)]; } async pause(): Promise { logger.debug('pause()'); await this.#channel.request( FbsRequest.Method.DATAPRODUCER_PAUSE, undefined, undefined, this.#internal.dataProducerId ); const wasPaused = this.#paused; this.#paused = true; // Emit observer event. if (!wasPaused) { this.#observer.safeEmit('pause'); } } async resume(): Promise { logger.debug('resume()'); await this.#channel.request( FbsRequest.Method.DATAPRODUCER_RESUME, undefined, undefined, this.#internal.dataProducerId ); const wasPaused = this.#paused; this.#paused = false; // Emit observer event. if (wasPaused) { this.#observer.safeEmit('resume'); } } send( message: string | Buffer, ppid?: number, subchannels?: number[], requiredSubchannel?: number ): void { if (typeof message !== 'string' && !Buffer.isBuffer(message)) { throw new TypeError('message must be a string or a Buffer'); } /* * +-------------------------------+----------+ * | Value | SCTP | * | | PPID | * +-------------------------------+----------+ * | WebRTC String | 51 | * | WebRTC Binary Partial | 52 | * | (Deprecated) | | * | WebRTC Binary | 53 | * | WebRTC String Partial | 54 | * | (Deprecated) | | * | WebRTC String Empty | 56 | * | WebRTC Binary Empty | 57 | * +-------------------------------+----------+ */ if (typeof ppid !== 'number') { ppid = typeof message === 'string' ? message.length > 0 ? 51 : 56 : message.length > 0 ? 53 : 57; } // Ensure we honor PPIDs. if (ppid === 56) { message = ' '; } else if (ppid === 57) { message = Buffer.alloc(1); } const builder = this.#channel.bufferBuilder; const subchannelsOffset = FbsDataProducer.SendNotification.createSubchannelsVector( builder, subchannels ?? [] ); if (typeof message === 'string') { message = Buffer.from(message); } const dataOffset = FbsDataProducer.SendNotification.createDataVector( builder, message ); const notificationOffset = FbsDataProducer.SendNotification.createSendNotification( builder, ppid, dataOffset, subchannelsOffset, requiredSubchannel ?? null ); this.#channel.notify( FbsNotification.Event.DATAPRODUCER_SEND, FbsNotification.Body.DataProducer_SendNotification, notificationOffset, this.#internal.dataProducerId ); } private handleWorkerNotifications(): void { // No need to subscribe to any event. } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } export function dataProducerTypeToFbs( type: DataProducerType ): FbsDataProducer.Type { switch (type) { case 'sctp': { return FbsDataProducer.Type.SCTP; } case 'direct': { return FbsDataProducer.Type.DIRECT; } default: { throw new TypeError('invalid DataConsumerType: ${type}'); } } } function dataProducerTypeFromFbs(type: FbsDataProducer.Type): DataProducerType { switch (type) { case FbsDataProducer.Type.SCTP: { return 'sctp'; } case FbsDataProducer.Type.DIRECT: { return 'direct'; } } } export function parseDataProducerDumpResponse( data: FbsDataProducer.DumpResponse ): DataProducerDump { return { id: data.id()!, type: dataProducerTypeFromFbs(data.type()), sctpStreamParameters: data.sctpStreamParameters() !== null ? parseSctpStreamParameters(data.sctpStreamParameters()!) : undefined, label: data.label()!, protocol: data.protocol()!, paused: data.paused(), }; } function parseDataProducerStats( binary: FbsDataProducer.GetStatsResponse ): DataProducerStat { return { type: 'data-producer', timestamp: Number(binary.timestamp()), label: binary.label()!, protocol: binary.protocol()!, messagesReceived: Number(binary.messagesReceived()), bytesReceived: Number(binary.bytesReceived()), }; } ================================================ FILE: node/src/DataProducerTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { SctpStreamParameters } from './sctpParametersTypes'; import type { AppData } from './types'; export type DataProducerOptions = { /** * DataProducer id (just for Router.pipeToRouter() method). */ id?: string; /** * SCTP parameters defining how the endpoint is sending the data. * Just if messages are sent over SCTP. */ sctpStreamParameters?: SctpStreamParameters; /** * A label which can be used to distinguish this DataChannel from others. */ label?: string; /** * Name of the sub-protocol used by this DataChannel. */ protocol?: string; /** * Whether the data producer must start in paused mode. Default false. */ paused?: boolean; /** * Custom application data. */ appData?: DataProducerAppData; }; /** * DataProducer type. */ export type DataProducerType = 'sctp' | 'direct'; export type DataProducerDump = { id: string; paused: boolean; type: DataProducerType; sctpStreamParameters?: SctpStreamParameters; label: string; protocol: string; }; export type DataProducerStat = { type: string; timestamp: number; label: string; protocol: string; messagesReceived: number; bytesReceived: number; }; export type DataProducerEvents = { transportclose: []; // Private events. '@close': []; }; export type DataProducerObserver = EnhancedEventEmitter; export type DataProducerObserverEvents = { close: []; pause: []; resume: []; }; export interface DataProducer< DataProducerAppData extends AppData = AppData, > extends EnhancedEventEmitter { /** * DataProducer id. */ get id(): string; /** * Whether the DataProducer is closed. */ get closed(): boolean; /** * DataProducer type. */ get type(): DataProducerType; /** * SCTP stream parameters. */ get sctpStreamParameters(): SctpStreamParameters | undefined; /** * DataChannel label. */ get label(): string; /** * DataChannel protocol. */ get protocol(): string; /** * Whether the DataProducer is paused. */ get paused(): boolean; /** * App custom data. */ get appData(): DataProducerAppData; /** * App custom data setter. */ set appData(appData: DataProducerAppData); /** * Observer. */ get observer(): DataProducerObserver; /** * Close the DataProducer. */ close(): void; /** * Transport was closed. * * @private */ transportClosed(): void; /** * Dump DataProducer. */ dump(): Promise; /** * Get DataProducer stats. */ getStats(): Promise; /** * Pause the DataProducer. */ pause(): Promise; /** * Resume the DataProducer. */ resume(): Promise; /** * Send data (just valid for DataProducers created on a DirectTransport). */ send( message: string | Buffer, ppid?: number, subchannels?: number[], requiredSubchannel?: number ): void; } ================================================ FILE: node/src/DirectTransport.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { DirectTransport, DirectTransportDump, DirectTransportStat, DirectTransportEvents, DirectTransportObserver, DirectTransportObserverEvents, } from './DirectTransportTypes'; import type { Transport, BaseTransportDump } from './TransportTypes'; import { TransportImpl, TransportConstructorOptions, parseBaseTransportDump, parseBaseTransportStats, parseTransportTraceEventData, } from './Transport'; import type { SctpParameters } from './sctpParametersTypes'; import type { AppData } from './types'; import { UnsupportedError } from './errors'; import { Event, Notification } from './fbs/notification'; import * as FbsDirectTransport from './fbs/direct-transport'; import * as FbsTransport from './fbs/transport'; import * as FbsNotification from './fbs/notification'; import * as FbsRequest from './fbs/request'; type DirectTransportConstructorOptions = TransportConstructorOptions & { data: DirectTransportData; }; export type DirectTransportData = { sctpParameters?: SctpParameters; }; const logger = new Logger('DirectTransport'); export class DirectTransportImpl< DirectTransportAppData extends AppData = AppData, > extends TransportImpl< DirectTransportAppData, DirectTransportEvents, DirectTransportObserver > implements Transport, DirectTransport { // DirectTransport data. // eslint-disable-next-line no-unused-private-class-members readonly #data: DirectTransportData; constructor( options: DirectTransportConstructorOptions ) { const observer: DirectTransportObserver = new EnhancedEventEmitter(); super(options, observer); logger.debug('constructor()'); this.#data = { // Nothing. }; this.handleWorkerNotifications(); this.handleListenerError(); } get type(): 'direct' { return 'direct'; } override get observer(): DirectTransportObserver { return super.observer; } override close(): void { if (this.closed) { return; } super.close(); } override routerClosed(): void { if (this.closed) { return; } super.routerClosed(); } async dump(): Promise { logger.debug('dump()'); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_DUMP, undefined, undefined, this.internal.transportId ); /* Decode Response. */ const data = new FbsDirectTransport.DumpResponse(); response.body(data); return parseDirectTransportDumpResponse(data); } async getStats(): Promise { logger.debug('getStats()'); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_GET_STATS, undefined, undefined, this.internal.transportId ); /* Decode Response. */ const data = new FbsDirectTransport.GetStatsResponse(); response.body(data); return [parseGetStatsResponse(data)]; } // eslint-disable-next-line @typescript-eslint/require-await async connect(): Promise { logger.debug('connect()'); } // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await override async setMaxIncomingBitrate(bitrate: number): Promise { throw new UnsupportedError( 'setMaxIncomingBitrate() not implemented in DirectTransport' ); } // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await override async setMaxOutgoingBitrate(bitrate: number): Promise { throw new UnsupportedError( 'setMaxOutgoingBitrate() not implemented in DirectTransport' ); } // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await override async setMinOutgoingBitrate(bitrate: number): Promise { throw new UnsupportedError( 'setMinOutgoingBitrate() not implemented in DirectTransport' ); } sendRtcp(rtcpPacket: Buffer): void { if (!Buffer.isBuffer(rtcpPacket)) { throw new TypeError('rtcpPacket must be a Buffer'); } const builder = this.channel.bufferBuilder; const dataOffset = FbsTransport.SendRtcpNotification.createDataVector( builder, rtcpPacket ); const notificationOffset = FbsTransport.SendRtcpNotification.createSendRtcpNotification( builder, dataOffset ); this.channel.notify( FbsNotification.Event.TRANSPORT_SEND_RTCP, FbsNotification.Body.Transport_SendRtcpNotification, notificationOffset, this.internal.transportId ); } private handleWorkerNotifications(): void { this.channel.on( this.internal.transportId, (event: Event, data?: Notification) => { switch (event) { case Event.TRANSPORT_TRACE: { const notification = new FbsTransport.TraceNotification(); data!.body(notification); const trace = parseTransportTraceEventData(notification); this.safeEmit('trace', trace); // Emit observer event. this.observer.safeEmit('trace', trace); break; } case Event.DIRECTTRANSPORT_RTCP: { if (this.closed) { break; } const notification = new FbsDirectTransport.RtcpNotification(); data!.body(notification); this.safeEmit('rtcp', Buffer.from(notification.dataArray()!)); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } } ); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } export function parseDirectTransportDumpResponse( binary: FbsDirectTransport.DumpResponse ): BaseTransportDump { return parseBaseTransportDump(binary.base()!); } function parseGetStatsResponse( binary: FbsDirectTransport.GetStatsResponse ): DirectTransportStat { const base = parseBaseTransportStats(binary.base()!); return { ...base, type: 'direct-transport', }; } ================================================ FILE: node/src/DirectTransportTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { Transport, BaseTransportDump, BaseTransportStats, TransportEvents, TransportObserverEvents, } from './TransportTypes'; import type { AppData } from './types'; export type DirectTransportOptions< DirectTransportAppData extends AppData = AppData, > = { /** * Maximum allowed size for direct messages sent from DataProducers. * Default 262144. */ maxMessageSize: number; /** * Custom application data. */ appData?: DirectTransportAppData; }; export type DirectTransportDump = BaseTransportDump; export type DirectTransportStat = BaseTransportStats & { type: string; }; export type DirectTransportEvents = TransportEvents & { rtcp: [Buffer]; }; export type DirectTransportObserver = EnhancedEventEmitter; export type DirectTransportObserverEvents = TransportObserverEvents & { rtcp: [Buffer]; }; export interface DirectTransport< DirectTransportAppData extends AppData = AppData, > extends Transport< DirectTransportAppData, DirectTransportEvents, DirectTransportObserver > { /** * Transport type. * * @override */ get type(): 'direct'; /** * Observer. * * @override */ get observer(): DirectTransportObserver; /** * Dump DirectTransport. * * @override */ dump(): Promise; /** * Get DirectTransport stats. * * @override */ getStats(): Promise; /** * NO-OP method in DirectTransport. * * @override */ connect(): Promise; /** * Send RTCP packet. */ sendRtcp(rtcpPacket: Buffer): void; } ================================================ FILE: node/src/Logger.ts ================================================ import { debug as consoleDebug, warn as consoleWarn, error as consoleError, } from 'node:console'; import debug from 'debug'; import type { EnhancedEventEmitter } from './enhancedEvents'; const APP_NAME = 'mediasoup'; type LoggerEmitterEvents = { debuglog: [string, string]; warnlog: [string, string]; errorlog: [string, string, Error?]; }; export type LoggerEmitter = EnhancedEventEmitter; export class Logger { private static debugLogEmitter?: LoggerEmitter; private static warnLogEmitter?: LoggerEmitter; private static errorLogEmitter?: LoggerEmitter; readonly #debug: debug.Debugger; readonly #warn: debug.Debugger; readonly #error: debug.Debugger; static setEmitters( debugLogEmitter?: LoggerEmitter, warnLogEmitter?: LoggerEmitter, errorLogEmitter?: LoggerEmitter ): void { Logger.debugLogEmitter = debugLogEmitter; Logger.warnLogEmitter = warnLogEmitter; Logger.errorLogEmitter = errorLogEmitter; } constructor(prefix?: string) { if (prefix) { this.#debug = debug(`${APP_NAME}:${prefix}`); this.#warn = debug(`${APP_NAME}:WARN:${prefix}`); this.#error = debug(`${APP_NAME}:ERROR:${prefix}`); } else { this.#debug = debug(APP_NAME); this.#warn = debug(`${APP_NAME}:WARN`); this.#error = debug(`${APP_NAME}:ERROR`); } this.#debug.log = consoleDebug; this.#warn.log = consoleWarn; this.#error.log = consoleError; } debug(log: string): void { this.#debug(log); Logger.debugLogEmitter?.safeEmit('debuglog', this.#debug.namespace, log); } warn(log: string): void { this.#warn(log); Logger.warnLogEmitter?.safeEmit('warnlog', this.#warn.namespace, log); } error(log: string, error?: Error): void { this.#error(log, error); Logger.errorLogEmitter?.safeEmit( 'errorlog', this.#error.namespace, log, error ); } } ================================================ FILE: node/src/PipeTransport.ts ================================================ import type * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; import type { PipeTransport, PipeConsumerOptions, PipeTransportDump, PipeTransportStat, PipeTransportEvents, PipeTransportObserver, PipeTransportObserverEvents, } from './PipeTransportTypes'; import type { Transport, TransportTuple, SctpState } from './TransportTypes'; import { TransportImpl, TransportConstructorOptions, parseBaseTransportDump, parseBaseTransportStats, parseSctpState, parseTuple, parseTransportTraceEventData, } from './Transport'; import type { Producer } from './ProducerTypes'; import type { Consumer, ConsumerType } from './ConsumerTypes'; import { ConsumerImpl } from './Consumer'; import type { RtpParameters } from './rtpParametersTypes'; import { serializeRtpEncodingParameters, serializeRtpParameters, } from './rtpParametersFbsUtils'; import type { SctpParameters } from './sctpParametersTypes'; import type { SrtpParameters } from './srtpParametersTypes'; import { parseSrtpParameters, serializeSrtpParameters, } from './srtpParametersFbsUtils'; import type { AppData } from './types'; import { generateUUIDv4 } from './utils'; import { MediaKind as FbsMediaKind } from './fbs/rtp-parameters/media-kind'; import * as FbsRtpParameters from './fbs/rtp-parameters'; import { Event, Notification } from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsTransport from './fbs/transport'; import * as FbsPipeTransport from './fbs/pipe-transport'; type PipeTransportConstructorOptions = TransportConstructorOptions & { data: PipeTransportData; }; export type PipeTransportData = { tuple: TransportTuple; sctpParameters?: SctpParameters; sctpState?: SctpState; rtx: boolean; srtpParameters?: SrtpParameters; }; const logger = new Logger('PipeTransport'); export class PipeTransportImpl extends TransportImpl< PipeTransportAppData, PipeTransportEvents, PipeTransportObserver > implements Transport, PipeTransport { // PipeTransport data. readonly #data: PipeTransportData; constructor(options: PipeTransportConstructorOptions) { const observer: PipeTransportObserver = new EnhancedEventEmitter(); super(options, observer); logger.debug('constructor()'); const { data } = options; this.#data = { tuple: data.tuple, sctpParameters: data.sctpParameters, sctpState: data.sctpState, rtx: data.rtx, srtpParameters: data.srtpParameters, }; this.handleWorkerNotifications(); this.handleListenerError(); } get type(): 'pipe' { return 'pipe'; } override get observer(): PipeTransportObserver { return super.observer; } get tuple(): TransportTuple { return this.#data.tuple; } get sctpParameters(): SctpParameters | undefined { return this.#data.sctpParameters; } get sctpState(): SctpState | undefined { return this.#data.sctpState; } get srtpParameters(): SrtpParameters | undefined { return this.#data.srtpParameters; } override close(): void { if (this.closed) { return; } if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.close(); } override routerClosed(): void { if (this.closed) { return; } if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.routerClosed(); } async dump(): Promise { logger.debug('dump()'); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_DUMP, undefined, undefined, this.internal.transportId ); /* Decode Response. */ const data = new FbsPipeTransport.DumpResponse(); response.body(data); return parsePipeTransportDumpResponse(data); } async getStats(): Promise { logger.debug('getStats()'); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_GET_STATS, undefined, undefined, this.internal.transportId ); /* Decode Response. */ const data = new FbsPipeTransport.GetStatsResponse(); response.body(data); return [parseGetStatsResponse(data)]; } async connect({ ip, port, srtpParameters, }: { ip: string; port: number; srtpParameters?: SrtpParameters; }): Promise { logger.debug('connect()'); const requestOffset = createConnectRequest({ builder: this.channel.bufferBuilder, ip, port, srtpParameters, }); // Wait for response. const response = await this.channel.request( FbsRequest.Method.PIPETRANSPORT_CONNECT, FbsRequest.Body.PipeTransport_ConnectRequest, requestOffset, this.internal.transportId ); /* Decode Response. */ const data = new FbsPipeTransport.ConnectResponse(); response.body(data); // Update data. if (data.tuple()) { this.#data.tuple = parseTuple(data.tuple()!); } } override async consume({ producerId, appData, }: PipeConsumerOptions): Promise> { logger.debug('consume()'); if (!producerId || typeof producerId !== 'string') { throw new TypeError('missing producerId'); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } const producer = this.getProducerById(producerId); if (!producer) { throw Error(`Producer with id "${producerId}" not found`); } // This may throw. const rtpParameters = ortc.getPipeConsumerRtpParameters({ consumableRtpParameters: producer.consumableRtpParameters, enableRtx: this.#data.rtx, }); const consumerId = generateUUIDv4(); const consumeRequestOffset = createConsumeRequest({ builder: this.channel.bufferBuilder, consumerId, producer, rtpParameters, }); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_CONSUME, FbsRequest.Body.Transport_ConsumeRequest, consumeRequestOffset, this.internal.transportId ); /* Decode Response. */ const consumeResponse = new FbsTransport.ConsumeResponse(); response.body(consumeResponse); const status = consumeResponse.unpack(); const data = { producerId, kind: producer.kind, rtpParameters, type: 'pipe' as ConsumerType, }; const consumer: Consumer = new ConsumerImpl({ internal: { ...this.internal, consumerId, }, data, channel: this.channel, appData, paused: status.paused, producerPaused: status.producerPaused, }); this.consumers.set(consumer.id, consumer); consumer.on('@close', () => this.consumers.delete(consumer.id)); consumer.on('@producerclose', () => this.consumers.delete(consumer.id)); // Emit observer event. this.observer.safeEmit('newconsumer', consumer); return consumer; } private handleWorkerNotifications(): void { this.channel.on( this.internal.transportId, (event: Event, data?: Notification) => { switch (event) { case Event.TRANSPORT_SCTP_STATE_CHANGE: { const notification = new FbsTransport.SctpStateChangeNotification(); data!.body(notification); const sctpState = parseSctpState(notification.sctpState()); this.#data.sctpState = sctpState; this.safeEmit('sctpstatechange', sctpState); // Emit observer event. this.observer.safeEmit('sctpstatechange', sctpState); break; } case Event.TRANSPORT_TRACE: { const notification = new FbsTransport.TraceNotification(); data!.body(notification); const trace = parseTransportTraceEventData(notification); this.safeEmit('trace', trace); // Emit observer event. this.observer.safeEmit('trace', trace); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } } ); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } /* * flatbuffers helpers. */ export function parsePipeTransportDumpResponse( binary: FbsPipeTransport.DumpResponse ): PipeTransportDump { // Retrieve BaseTransportDump. const baseTransportDump = parseBaseTransportDump(binary.base()!); // Retrieve RTP Tuple. const tuple = parseTuple(binary.tuple()!); // Retrieve SRTP Parameters. let srtpParameters: SrtpParameters | undefined; if (binary.srtpParameters()) { srtpParameters = parseSrtpParameters(binary.srtpParameters()!); } return { ...baseTransportDump, tuple: tuple, rtx: binary.rtx(), srtpParameters: srtpParameters, }; } function parseGetStatsResponse( binary: FbsPipeTransport.GetStatsResponse ): PipeTransportStat { const base = parseBaseTransportStats(binary.base()!); return { ...base, type: 'pipe-transport', tuple: parseTuple(binary.tuple()!), }; } function createConsumeRequest({ builder, consumerId, producer, rtpParameters, }: { builder: flatbuffers.Builder; consumerId: string; producer: Producer; rtpParameters: RtpParameters; }): number { // Build the request. const producerIdOffset = builder.createString(producer.id); const consumerIdOffset = builder.createString(consumerId); const rtpParametersOffset = serializeRtpParameters(builder, rtpParameters); let consumableRtpEncodingsOffset: number | undefined; if (producer.consumableRtpParameters.encodings) { consumableRtpEncodingsOffset = serializeRtpEncodingParameters( builder, producer.consumableRtpParameters.encodings ); } const ConsumeRequest = FbsTransport.ConsumeRequest; // Create Consume Request. ConsumeRequest.startConsumeRequest(builder); ConsumeRequest.addConsumerId(builder, consumerIdOffset); ConsumeRequest.addProducerId(builder, producerIdOffset); ConsumeRequest.addKind( builder, producer.kind === 'audio' ? FbsMediaKind.AUDIO : FbsMediaKind.VIDEO ); ConsumeRequest.addRtpParameters(builder, rtpParametersOffset); ConsumeRequest.addType(builder, FbsRtpParameters.Type.PIPE); if (consumableRtpEncodingsOffset) { ConsumeRequest.addConsumableRtpEncodings( builder, consumableRtpEncodingsOffset ); } return ConsumeRequest.endConsumeRequest(builder); } function createConnectRequest({ builder, ip, port, srtpParameters, }: { builder: flatbuffers.Builder; ip?: string; port?: number; srtpParameters?: SrtpParameters; }): number { let ipOffset = 0; let srtpParametersOffset = 0; if (ip) { ipOffset = builder.createString(ip); } // Serialize SrtpParameters. if (srtpParameters) { srtpParametersOffset = serializeSrtpParameters(builder, srtpParameters); } // Create PlainTransportConnectData. FbsPipeTransport.ConnectRequest.startConnectRequest(builder); FbsPipeTransport.ConnectRequest.addIp(builder, ipOffset); if (typeof port === 'number') { FbsPipeTransport.ConnectRequest.addPort(builder, port); } if (srtpParameters) { FbsPipeTransport.ConnectRequest.addSrtpParameters( builder, srtpParametersOffset ); } return FbsPipeTransport.ConnectRequest.endConnectRequest(builder); } ================================================ FILE: node/src/PipeTransportTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { Transport, TransportListenInfo, TransportListenIp, TransportTuple, SctpState, BaseTransportDump, BaseTransportStats, TransportEvents, TransportObserverEvents, } from './TransportTypes'; import type { Consumer } from './ConsumerTypes'; import type { SrtpParameters } from './srtpParametersTypes'; import type { SctpParameters, NumSctpStreams } from './sctpParametersTypes'; import type { Either, AppData } from './types'; export type PipeTransportOptions< PipeTransportAppData extends AppData = AppData, > = { /** * Create a SCTP association. Default false. */ enableSctp?: boolean; /** * SCTP streams number. */ numSctpStreams?: NumSctpStreams; /** * Maximum allowed size for SCTP messages sent by DataProducers. * Default 268435456. */ maxSctpMessageSize?: number; /** * Maximum SCTP send buffer used by DataConsumers. * Default 268435456. */ sctpSendBufferSize?: number; /** * Enable RTX and NACK for RTP retransmission. Useful if both Routers are * located in different hosts and there is packet lost in the link. For this * to work, both PipeTransports must enable this setting. Default false. */ enableRtx?: boolean; /** * Enable SRTP. Useful to protect the RTP and RTCP traffic if both Routers * are located in different hosts. For this to work, connect() must be called * with remote SRTP parameters. Default false. */ enableSrtp?: boolean; /** * Custom application data. */ appData?: PipeTransportAppData; } & PipeTransportListen; type PipeTransportListen = Either< PipeTransportListenInfo, PipeTransportListenIp >; type PipeTransportListenInfo = { /** * Listening info. */ listenInfo: TransportListenInfo; }; type PipeTransportListenIp = { /** * Listening IP address. */ listenIp: TransportListenIp | string; /** * Fixed port to listen on instead of selecting automatically from Worker's * port range. */ port?: number; }; export type PipeConsumerOptions = { /** * The id of the Producer to consume. */ producerId: string; /** * Custom application data. */ appData?: ConsumerAppData; }; export type PipeTransportDump = BaseTransportDump & { tuple: TransportTuple; rtx: boolean; srtpParameters?: SrtpParameters; }; export type PipeTransportStat = BaseTransportStats & { type: string; tuple: TransportTuple; }; export type PipeTransportEvents = TransportEvents & { sctpstatechange: [SctpState]; }; export type PipeTransportObserver = EnhancedEventEmitter; export type PipeTransportObserverEvents = TransportObserverEvents & { sctpstatechange: [SctpState]; }; export interface PipeTransport< PipeTransportAppData extends AppData = AppData, > extends Transport< PipeTransportAppData, PipeTransportEvents, PipeTransportObserver > { /** * Transport type. * * @override */ get type(): 'pipe'; /** * Observer. * * @override */ get observer(): PipeTransportObserver; /** * PipeTransport tuple. */ get tuple(): TransportTuple; /** * SCTP parameters. */ get sctpParameters(): SctpParameters | undefined; /** * SCTP state. */ get sctpState(): SctpState | undefined; /** * SRTP parameters. */ get srtpParameters(): SrtpParameters | undefined; /** * Dump PipeTransport. * * @override */ dump(): Promise; /** * Get PipeTransport stats. * * @override */ getStats(): Promise; /** * Provide the PipeTransport remote parameters. * * @override */ connect({ ip, port, srtpParameters, }: { ip: string; port: number; srtpParameters?: SrtpParameters; }): Promise; /** * Create a pipe Consumer. * * @override */ consume({ producerId, appData, }: PipeConsumerOptions): Promise>; } ================================================ FILE: node/src/PlainTransport.ts ================================================ import type * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { PlainTransport, PlainTransportDump, PlainTransportStat, PlainTransportEvents, PlainTransportObserver, PlainTransportObserverEvents, } from './PlainTransportTypes'; import type { Transport, TransportTuple, SctpState } from './TransportTypes'; import { TransportImpl, TransportConstructorOptions, parseSctpState, parseTuple, parseBaseTransportDump, parseBaseTransportStats, parseTransportTraceEventData, } from './Transport'; import type { SctpParameters } from './sctpParametersTypes'; import type { SrtpParameters } from './srtpParametersTypes'; import { parseSrtpParameters, serializeSrtpParameters, } from './srtpParametersFbsUtils'; import type { AppData } from './types'; import { Event, Notification } from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsTransport from './fbs/transport'; import * as FbsPlainTransport from './fbs/plain-transport'; type PlainTransportConstructorOptions = TransportConstructorOptions & { data: PlainTransportData; }; export type PlainTransportData = { rtcpMux?: boolean; comedia?: boolean; tuple: TransportTuple; rtcpTuple?: TransportTuple; sctpParameters?: SctpParameters; sctpState?: SctpState; srtpParameters?: SrtpParameters; }; const logger = new Logger('PlainTransport'); export class PlainTransportImpl extends TransportImpl< PlainTransportAppData, PlainTransportEvents, PlainTransportObserver > implements Transport, PlainTransport { // PlainTransport data. readonly #data: PlainTransportData; constructor( options: PlainTransportConstructorOptions ) { const observer: PlainTransportObserver = new EnhancedEventEmitter(); super(options, observer); logger.debug('constructor()'); const { data } = options; this.#data = { rtcpMux: data.rtcpMux, comedia: data.comedia, tuple: data.tuple, rtcpTuple: data.rtcpTuple, sctpParameters: data.sctpParameters, sctpState: data.sctpState, srtpParameters: data.srtpParameters, }; this.handleWorkerNotifications(); this.handleListenerError(); } get type(): 'plain' { return 'plain'; } override get observer(): PlainTransportObserver { return super.observer; } get tuple(): TransportTuple { return this.#data.tuple; } get rtcpTuple(): TransportTuple | undefined { return this.#data.rtcpTuple; } get sctpParameters(): SctpParameters | undefined { return this.#data.sctpParameters; } get sctpState(): SctpState | undefined { return this.#data.sctpState; } get srtpParameters(): SrtpParameters | undefined { return this.#data.srtpParameters; } override close(): void { if (this.closed) { return; } if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.close(); } override routerClosed(): void { if (this.closed) { return; } if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.routerClosed(); } async dump(): Promise { logger.debug('dump()'); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_DUMP, undefined, undefined, this.internal.transportId ); /* Decode Response. */ const data = new FbsPlainTransport.DumpResponse(); response.body(data); return parsePlainTransportDumpResponse(data); } async getStats(): Promise { logger.debug('getStats()'); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_GET_STATS, undefined, undefined, this.internal.transportId ); /* Decode Response. */ const data = new FbsPlainTransport.GetStatsResponse(); response.body(data); return [parseGetStatsResponse(data)]; } async connect({ ip, port, rtcpPort, srtpParameters, }: { ip?: string; port?: number; rtcpPort?: number; srtpParameters?: SrtpParameters; }): Promise { logger.debug('connect()'); const requestOffset = createConnectRequest({ builder: this.channel.bufferBuilder, ip, port, rtcpPort, srtpParameters, }); // Wait for response. const response = await this.channel.request( FbsRequest.Method.PLAINTRANSPORT_CONNECT, FbsRequest.Body.PlainTransport_ConnectRequest, requestOffset, this.internal.transportId ); /* Decode Response. */ const data = new FbsPlainTransport.ConnectResponse(); response.body(data); // Update data. if (data.tuple()) { this.#data.tuple = parseTuple(data.tuple()!); } if (data.rtcpTuple()) { this.#data.rtcpTuple = parseTuple(data.rtcpTuple()!); } if (data.srtpParameters()) { this.#data.srtpParameters = parseSrtpParameters(data.srtpParameters()!); } } private handleWorkerNotifications(): void { this.channel.on( this.internal.transportId, (event: Event, data?: Notification) => { switch (event) { case Event.PLAINTRANSPORT_TUPLE: { const notification = new FbsPlainTransport.TupleNotification(); data!.body(notification); const tuple = parseTuple(notification.tuple()!); this.#data.tuple = tuple; this.safeEmit('tuple', tuple); // Emit observer event. this.observer.safeEmit('tuple', tuple); break; } case Event.PLAINTRANSPORT_RTCP_TUPLE: { const notification = new FbsPlainTransport.RtcpTupleNotification(); data!.body(notification); const rtcpTuple = parseTuple(notification.tuple()!); this.#data.rtcpTuple = rtcpTuple; this.safeEmit('rtcptuple', rtcpTuple); // Emit observer event. this.observer.safeEmit('rtcptuple', rtcpTuple); break; } case Event.TRANSPORT_SCTP_STATE_CHANGE: { const notification = new FbsTransport.SctpStateChangeNotification(); data!.body(notification); const sctpState = parseSctpState(notification.sctpState()); this.#data.sctpState = sctpState; this.safeEmit('sctpstatechange', sctpState); // Emit observer event. this.observer.safeEmit('sctpstatechange', sctpState); break; } case Event.TRANSPORT_TRACE: { const notification = new FbsTransport.TraceNotification(); data!.body(notification); const trace = parseTransportTraceEventData(notification); this.safeEmit('trace', trace); // Emit observer event. this.observer.safeEmit('trace', trace); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } } ); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } export function parsePlainTransportDumpResponse( binary: FbsPlainTransport.DumpResponse ): PlainTransportDump { // Retrieve BaseTransportDump. const baseTransportDump = parseBaseTransportDump(binary.base()!); // Retrieve RTP Tuple. const tuple = parseTuple(binary.tuple()!); // Retrieve RTCP Tuple. let rtcpTuple: TransportTuple | undefined; if (binary.rtcpTuple()) { rtcpTuple = parseTuple(binary.rtcpTuple()!); } // Retrieve SRTP Parameters. let srtpParameters: SrtpParameters | undefined; if (binary.srtpParameters()) { srtpParameters = parseSrtpParameters(binary.srtpParameters()!); } return { ...baseTransportDump, rtcpMux: binary.rtcpMux(), comedia: binary.comedia(), tuple: tuple, rtcpTuple: rtcpTuple, srtpParameters: srtpParameters, }; } function parseGetStatsResponse( binary: FbsPlainTransport.GetStatsResponse ): PlainTransportStat { const base = parseBaseTransportStats(binary.base()!); return { ...base, type: 'plain-rtp-transport', rtcpMux: binary.rtcpMux(), comedia: binary.comedia(), tuple: parseTuple(binary.tuple()!), rtcpTuple: binary.rtcpTuple() ? parseTuple(binary.rtcpTuple()!) : undefined, }; } function createConnectRequest({ builder, ip, port, rtcpPort, srtpParameters, }: { builder: flatbuffers.Builder; ip?: string; port?: number; rtcpPort?: number; srtpParameters?: SrtpParameters; }): number { let ipOffset = 0; let srtpParametersOffset = 0; if (ip) { ipOffset = builder.createString(ip); } // Serialize SrtpParameters. if (srtpParameters) { srtpParametersOffset = serializeSrtpParameters(builder, srtpParameters); } // Create PlainTransportConnectData. FbsPlainTransport.ConnectRequest.startConnectRequest(builder); FbsPlainTransport.ConnectRequest.addIp(builder, ipOffset); if (typeof port === 'number') { FbsPlainTransport.ConnectRequest.addPort(builder, port); } if (typeof rtcpPort === 'number') { FbsPlainTransport.ConnectRequest.addRtcpPort(builder, rtcpPort); } if (srtpParameters) { FbsPlainTransport.ConnectRequest.addSrtpParameters( builder, srtpParametersOffset ); } return FbsPlainTransport.ConnectRequest.endConnectRequest(builder); } ================================================ FILE: node/src/PlainTransportTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { Transport, TransportListenInfo, TransportListenIp, TransportTuple, SctpState, BaseTransportDump, BaseTransportStats, TransportEvents, TransportObserverEvents, } from './TransportTypes'; import type { SrtpParameters, SrtpCryptoSuite } from './srtpParametersTypes'; import type { SctpParameters, NumSctpStreams } from './sctpParametersTypes'; import type { Either, AppData } from './types'; export type PlainTransportOptions< PlainTransportAppData extends AppData = AppData, > = { /** * Use RTCP-mux (RTP and RTCP in the same port). Default true. */ rtcpMux?: boolean; /** * Whether remote IP:port should be auto-detected based on first RTP/RTCP * packet received. If enabled, connect() method must not be called unless * SRTP is enabled. If so, it must be called with just remote SRTP parameters. * Default false. */ comedia?: boolean; /** * Create a SCTP association. Default false. */ enableSctp?: boolean; /** * SCTP streams number. */ numSctpStreams?: NumSctpStreams; /** * Maximum allowed size for SCTP messages sent by DataProducers. * Default 262144. */ maxSctpMessageSize?: number; /** * Maximum SCTP send buffer used by DataConsumers. * Default 262144. */ sctpSendBufferSize?: number; /** * Enable SRTP. For this to work, connect() must be called * with remote SRTP parameters. Default false. */ enableSrtp?: boolean; /** * The SRTP crypto suite to be used if enableSrtp is set. Default * 'AES_CM_128_HMAC_SHA1_80'. */ srtpCryptoSuite?: SrtpCryptoSuite; /** * Custom application data. */ appData?: PlainTransportAppData; } & PlainTransportListen; type PlainTransportListen = Either< PlainTransportListenInfo, PlainTransportListenIp >; type PlainTransportListenInfo = { /** * Listening info. */ listenInfo: TransportListenInfo; /** * Optional listening info for RTCP. */ rtcpListenInfo?: TransportListenInfo; }; type PlainTransportListenIp = { /** * Listening IP address. */ listenIp: TransportListenIp | string; /** * Fixed port to listen on instead of selecting automatically from Worker's * port range. */ port?: number; }; export type PlainTransportDump = BaseTransportDump & { rtcpMux: boolean; comedia: boolean; tuple: TransportTuple; rtcpTuple?: TransportTuple; srtpParameters?: SrtpParameters; }; export type PlainTransportStat = BaseTransportStats & { type: string; rtcpMux: boolean; comedia: boolean; tuple: TransportTuple; rtcpTuple?: TransportTuple; }; export type PlainTransportEvents = TransportEvents & { tuple: [TransportTuple]; rtcptuple: [TransportTuple]; sctpstatechange: [SctpState]; }; export type PlainTransportObserver = EnhancedEventEmitter; export type PlainTransportObserverEvents = TransportObserverEvents & { tuple: [TransportTuple]; rtcptuple: [TransportTuple]; sctpstatechange: [SctpState]; }; export interface PlainTransport< PlainTransportAppData extends AppData = AppData, > extends Transport< PlainTransportAppData, PlainTransportEvents, PlainTransportObserver > { /** * Transport type. * * @override */ get type(): 'plain'; /** * Observer. * * @override */ get observer(): PlainTransportObserver; /** * PlainTransport tuple. */ get tuple(): TransportTuple; /** * PlainTransport RTCP tuple. */ get rtcpTuple(): TransportTuple | undefined; /** * SCTP parameters. */ get sctpParameters(): SctpParameters | undefined; /** * SCTP state. */ get sctpState(): SctpState | undefined; /** * SRTP parameters. */ get srtpParameters(): SrtpParameters | undefined; /** * Dump PlainTransport. * * @override */ dump(): Promise; /** * Get PlainTransport stats. * * @override */ getStats(): Promise; /** * Provide the PlainTransport remote parameters. * * @override */ connect({ ip, port, rtcpPort, srtpParameters, }: { ip?: string; port?: number; rtcpPort?: number; srtpParameters?: SrtpParameters; }): Promise; } ================================================ FILE: node/src/Producer.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { Producer, ProducerType, ProducerScore, ProducerVideoOrientation, ProducerDump, ProducerStat, ProducerTraceEventType, ProducerTraceEventData, ProducerEvents, ProducerObserver, ProducerObserverEvents, } from './ProducerTypes'; import { Channel } from './Channel'; import type { TransportInternal } from './Transport'; import type { MediaKind, RtpParameters } from './rtpParametersTypes'; import { parseRtpParameters } from './rtpParametersFbsUtils'; import { parseRtpStreamRecvStats } from './rtpStreamStatsFbsUtils'; import type { AppData } from './types'; import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; import { TraceDirection as FbsTraceDirection } from './fbs/common'; import * as FbsNotification from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsTransport from './fbs/transport'; import * as FbsProducer from './fbs/producer'; import * as FbsProducerTraceInfo from './fbs/producer/trace-info'; import * as FbsRtpParameters from './fbs/rtp-parameters'; type ProducerInternal = TransportInternal & { producerId: string; }; const logger = new Logger('Producer'); type ProducerData = { kind: MediaKind; rtpParameters: RtpParameters; type: ProducerType; consumableRtpParameters: RtpParameters; }; export class ProducerImpl extends EnhancedEventEmitter implements Producer { // Internal data. readonly #internal: ProducerInternal; // Producer data. readonly #data: ProducerData; // Channel instance. readonly #channel: Channel; // Closed flag. #closed = false; // Paused flag. #paused = false; // Custom app data. #appData: ProducerAppData; // Current score. #score: ProducerScore[] = []; // Observer instance. readonly #observer: ProducerObserver = new EnhancedEventEmitter(); constructor({ internal, data, channel, appData, paused, }: { internal: ProducerInternal; data: ProducerData; channel: Channel; appData?: ProducerAppData; paused: boolean; }) { super(); logger.debug('constructor()'); this.#internal = internal; this.#data = data; this.#channel = channel; this.#paused = paused; this.#appData = appData ?? ({} as ProducerAppData); this.handleWorkerNotifications(); this.handleListenerError(); } get id(): string { return this.#internal.producerId; } get closed(): boolean { return this.#closed; } get kind(): MediaKind { return this.#data.kind; } get rtpParameters(): RtpParameters { return this.#data.rtpParameters; } get type(): ProducerType { return this.#data.type; } get consumableRtpParameters(): RtpParameters { return this.#data.consumableRtpParameters; } get paused(): boolean { return this.#paused; } get score(): ProducerScore[] { return this.#score; } get appData(): ProducerAppData { return this.#appData; } set appData(appData: ProducerAppData) { this.#appData = appData; } get observer(): ProducerObserver { return this.#observer; } /** * Just for testing purposes. * * @private */ get channelForTesting(): Channel { return this.#channel; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.producerId); /* Build Request. */ const requestOffset = new FbsTransport.CloseProducerRequestT( this.#internal.producerId ).pack(this.#channel.bufferBuilder); this.#channel .request( FbsRequest.Method.TRANSPORT_CLOSE_PRODUCER, FbsRequest.Body.Transport_CloseProducerRequest, requestOffset, this.#internal.transportId ) .catch(() => {}); this.emit('@close'); // Emit observer event. this.#observer.safeEmit('close'); } transportClosed(): void { if (this.#closed) { return; } logger.debug('transportClosed()'); this.#closed = true; // Remove notification subscriptions. this.#channel.removeAllListeners(this.#internal.producerId); this.safeEmit('transportclose'); // Emit observer event. this.#observer.safeEmit('close'); } async dump(): Promise { logger.debug('dump()'); const response = await this.#channel.request( FbsRequest.Method.PRODUCER_DUMP, undefined, undefined, this.#internal.producerId ); /* Decode Response. */ const dumpResponse = new FbsProducer.DumpResponse(); response.body(dumpResponse); return parseProducerDump(dumpResponse); } async getStats(): Promise { logger.debug('getStats()'); const response = await this.#channel.request( FbsRequest.Method.PRODUCER_GET_STATS, undefined, undefined, this.#internal.producerId ); /* Decode Response. */ const data = new FbsProducer.GetStatsResponse(); response.body(data); return parseProducerStats(data); } async pause(): Promise { logger.debug('pause()'); await this.#channel.request( FbsRequest.Method.PRODUCER_PAUSE, undefined, undefined, this.#internal.producerId ); const wasPaused = this.#paused; this.#paused = true; // Emit observer event. if (!wasPaused) { this.#observer.safeEmit('pause'); } } async resume(): Promise { logger.debug('resume()'); await this.#channel.request( FbsRequest.Method.PRODUCER_RESUME, undefined, undefined, this.#internal.producerId ); const wasPaused = this.#paused; this.#paused = false; // Emit observer event. if (wasPaused) { this.#observer.safeEmit('resume'); } } async enableTraceEvent(types: ProducerTraceEventType[] = []): Promise { logger.debug('enableTraceEvent()'); if (!Array.isArray(types)) { throw new TypeError('types must be an array'); } if (types.find(type => typeof type !== 'string')) { throw new TypeError('every type must be a string'); } // Convert event types. const fbsEventTypes: FbsProducer.TraceEventType[] = []; for (const eventType of types) { try { fbsEventTypes.push(producerTraceEventTypeToFbs(eventType)); } catch (error) { logger.warn('enableTraceEvent() | [error:${error}]'); } } /* Build Request. */ const requestOffset = new FbsProducer.EnableTraceEventRequestT( fbsEventTypes ).pack(this.#channel.bufferBuilder); await this.#channel.request( FbsRequest.Method.PRODUCER_ENABLE_TRACE_EVENT, FbsRequest.Body.Producer_EnableTraceEventRequest, requestOffset, this.#internal.producerId ); } send(rtpPacket: Buffer): void { if (!Buffer.isBuffer(rtpPacket)) { throw new TypeError('rtpPacket must be a Buffer'); } const builder = this.#channel.bufferBuilder; const dataOffset = FbsProducer.SendNotification.createDataVector( builder, rtpPacket ); const notificationOffset = FbsProducer.SendNotification.createSendNotification(builder, dataOffset); this.#channel.notify( FbsNotification.Event.PRODUCER_SEND, FbsNotification.Body.Producer_SendNotification, notificationOffset, this.#internal.producerId ); } private handleWorkerNotifications(): void { this.#channel.on( this.#internal.producerId, (event: Event, data?: Notification) => { switch (event) { case Event.PRODUCER_SCORE: { const notification = new FbsProducer.ScoreNotification(); data!.body(notification); const score: ProducerScore[] = fbsUtils.parseVector( notification, 'scores', parseProducerScore ); this.#score = score; this.safeEmit('score', score); // Emit observer event. this.#observer.safeEmit('score', score); break; } case Event.PRODUCER_VIDEO_ORIENTATION_CHANGE: { const notification = new FbsProducer.VideoOrientationChangeNotification(); data!.body(notification); const videoOrientation: ProducerVideoOrientation = notification.unpack(); this.safeEmit('videoorientationchange', videoOrientation); // Emit observer event. this.#observer.safeEmit('videoorientationchange', videoOrientation); break; } case Event.PRODUCER_TRACE: { const notification = new FbsProducer.TraceNotification(); data!.body(notification); const trace: ProducerTraceEventData = parseTraceEventData(notification); this.safeEmit('trace', trace); // Emit observer event. this.#observer.safeEmit('trace', trace); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } } ); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } export function producerTypeFromFbs(type: FbsRtpParameters.Type): ProducerType { switch (type) { case FbsRtpParameters.Type.SIMPLE: { return 'simple'; } case FbsRtpParameters.Type.SIMULCAST: { return 'simulcast'; } case FbsRtpParameters.Type.SVC: { return 'svc'; } default: { throw new TypeError(`invalid FbsRtpParameters.Type: ${type}`); } } } export function producerTypeToFbs(type: ProducerType): FbsRtpParameters.Type { switch (type) { case 'simple': { return FbsRtpParameters.Type.SIMPLE; } case 'simulcast': { return FbsRtpParameters.Type.SIMULCAST; } case 'svc': { return FbsRtpParameters.Type.SVC; } default: { throw new TypeError(`invalid ProducerType: ${type}`); } } } function producerTraceEventTypeToFbs( eventType: ProducerTraceEventType ): FbsProducer.TraceEventType { switch (eventType) { case 'keyframe': { return FbsProducer.TraceEventType.KEYFRAME; } case 'fir': { return FbsProducer.TraceEventType.FIR; } case 'nack': { return FbsProducer.TraceEventType.NACK; } case 'pli': { return FbsProducer.TraceEventType.PLI; } case 'rtp': { return FbsProducer.TraceEventType.RTP; } case 'sr': { return FbsProducer.TraceEventType.SR; } default: { throw new TypeError(`invalid ProducerTraceEventType: ${eventType}`); } } } function producerTraceEventTypeFromFbs( eventType: FbsProducer.TraceEventType ): ProducerTraceEventType { switch (eventType) { case FbsProducer.TraceEventType.KEYFRAME: { return 'keyframe'; } case FbsProducer.TraceEventType.FIR: { return 'fir'; } case FbsProducer.TraceEventType.NACK: { return 'nack'; } case FbsProducer.TraceEventType.PLI: { return 'pli'; } case FbsProducer.TraceEventType.RTP: { return 'rtp'; } case FbsProducer.TraceEventType.SR: { return 'sr'; } } } function parseProducerDump(data: FbsProducer.DumpResponse): ProducerDump { return { id: data.id()!, kind: data.kind() === FbsRtpParameters.MediaKind.AUDIO ? 'audio' : 'video', type: producerTypeFromFbs(data.type()), rtpParameters: parseRtpParameters(data.rtpParameters()!), // NOTE: optional values are represented with null instead of undefined. // TODO: Make flatbuffers TS return undefined instead of null. rtpMapping: data.rtpMapping()?.unpack(), // NOTE: optional values are represented with null instead of undefined. // TODO: Make flatbuffers TS return undefined instead of null. rtpStreams: data.rtpStreamsLength() > 0 ? fbsUtils.parseVector(data, 'rtpStreams', rtpStream => rtpStream.unpack() ) : undefined, traceEventTypes: fbsUtils.parseVector( data, 'traceEventTypes', producerTraceEventTypeFromFbs ), paused: data.paused(), }; } function parseProducerStats( binary: FbsProducer.GetStatsResponse ): ProducerStat[] { return fbsUtils.parseVector(binary, 'stats', parseRtpStreamRecvStats); } function parseProducerScore(binary: FbsProducer.Score): ProducerScore { return { encodingIdx: binary.encodingIdx(), ssrc: binary.ssrc(), rid: binary.rid() ?? undefined, score: binary.score(), }; } function parseTraceEventData( trace: FbsProducer.TraceNotification ): ProducerTraceEventData { // eslint-disable-next-line @typescript-eslint/no-explicit-any let info: any; if (trace.infoType() !== FbsProducer.TraceInfo.NONE) { const accessor = trace.info.bind(trace); info = FbsProducerTraceInfo.unionToTraceInfo(trace.infoType(), accessor); trace.info(info); } return { type: producerTraceEventTypeFromFbs(trace.type()), timestamp: Number(trace.timestamp()), direction: trace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out', info: info?.unpack(), }; } ================================================ FILE: node/src/ProducerTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { MediaKind, RtpParameters } from './rtpParametersTypes'; import type { RtpStreamRecvStats } from './rtpStreamStatsTypes'; import type { AppData } from './types'; export type ProducerOptions = { /** * Producer id (just for Router.pipeToRouter() method). */ id?: string; /** * Media kind ('audio' or 'video'). */ kind: MediaKind; /** * RTP parameters defining what the endpoint is sending. */ rtpParameters: RtpParameters; /** * Whether the producer must start in paused mode. Default false. */ paused?: boolean; /** * Just for video. Time (in ms) before asking the sender for a new key frame * after having asked a previous one. Default 0. */ keyFrameRequestDelay?: number; /** * Add mediasoup custom 'urn:mediasoup:params:rtp-hdrext:packet-id' header * extension to RTP packets received from the sender endpoint. */ enableMediasoupPacketIdHeaderExtension?: boolean; /** * Custom application data. */ appData?: ProducerAppData; }; /** * Producer type. */ export type ProducerType = 'simple' | 'simulcast' | 'svc'; export type ProducerScore = { /** * Index of the RTP stream in the rtpParameters.encodings array. */ encodingIdx: number; /** * SSRC of the RTP stream. */ ssrc: number; /** * RID of the RTP stream. */ rid?: string; /** * The score of the RTP stream. */ score: number; }; export type ProducerVideoOrientation = { /** * Whether the source is a video camera. */ camera: boolean; /** * Whether the video source is flipped. */ flip: boolean; /** * Rotation degrees (0, 90, 180 or 270). */ rotation: number; }; export type ProducerDump = { id: string; kind: string; type: ProducerType; rtpParameters: RtpParameters; rtpMapping: unknown; rtpStreams: unknown; traceEventTypes: string[]; paused: boolean; }; export type ProducerStat = RtpStreamRecvStats; /** * Valid types for 'trace' event. */ export type ProducerTraceEventType = | 'rtp' | 'keyframe' | 'nack' | 'pli' | 'fir' | 'sr'; /** * 'trace' event data. */ export type ProducerTraceEventData = { /** * Trace type. */ type: ProducerTraceEventType; /** * Event timestamp. */ timestamp: number; /** * Event direction. */ direction: 'in' | 'out'; /** * Per type information. */ info: Record; }; export type ProducerEvents = { transportclose: []; score: [ProducerScore[]]; videoorientationchange: [ProducerVideoOrientation]; trace: [ProducerTraceEventData]; // Private events. '@close': []; }; export type ProducerObserver = EnhancedEventEmitter; export type ProducerObserverEvents = { close: []; pause: []; resume: []; score: [ProducerScore[]]; videoorientationchange: [ProducerVideoOrientation]; trace: [ProducerTraceEventData]; }; export interface Producer< ProducerAppData extends AppData = AppData, > extends EnhancedEventEmitter { /** * Producer id. */ get id(): string; /** * Whether the Producer is closed. */ get closed(): boolean; /** * Media kind. */ get kind(): MediaKind; /** * RTP parameters. */ get rtpParameters(): RtpParameters; /** * Producer type. */ get type(): ProducerType; /** * Consumable RTP parameters. * * @private */ get consumableRtpParameters(): RtpParameters; /** * Whether the Producer is paused. */ get paused(): boolean; /** * Producer score list. */ get score(): ProducerScore[]; /** * App custom data. */ get appData(): ProducerAppData; /** * App custom data setter. */ set appData(appData: ProducerAppData); /** * Observer. */ get observer(): ProducerObserver; /** * Close the Producer. */ close(): void; /** * Transport was closed. * * @private */ transportClosed(): void; /** * Dump Producer. */ dump(): Promise; /** * Get Producer stats. */ getStats(): Promise; /** * Pause the Producer. */ pause(): Promise; /** * Resume the Producer. */ resume(): Promise; /** * Enable 'trace' event. */ enableTraceEvent(types?: ProducerTraceEventType[]): Promise; /** * Send RTP packet (just valid for Producers created on a DirectTransport). */ send(rtpPacket: Buffer): void; } ================================================ FILE: node/src/Router.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; import { InvalidStateError } from './errors'; import type { Channel } from './Channel'; import type { Router, PipeToRouterOptions, PipeToRouterResult, PipeTransportPair, RouterDump, RouterEvents, RouterObserver, RouterObserverEvents, } from './RouterTypes'; import type { Transport, TransportListenIp, TransportProtocol, } from './TransportTypes'; import { portRangeToFbs, socketFlagsToFbs } from './Transport'; import type { WebRtcTransport, WebRtcTransportOptions, } from './WebRtcTransportTypes'; import { WebRtcTransportImpl, parseWebRtcTransportDumpResponse, } from './WebRtcTransport'; import type { PlainTransport, PlainTransportOptions, } from './PlainTransportTypes'; import { PlainTransportImpl, parsePlainTransportDumpResponse, } from './PlainTransport'; import type { PipeTransport, PipeTransportOptions } from './PipeTransportTypes'; import { PipeTransportImpl, parsePipeTransportDumpResponse, } from './PipeTransport'; import type { DirectTransport, DirectTransportOptions, } from './DirectTransportTypes'; import { DirectTransportImpl, parseDirectTransportDumpResponse, } from './DirectTransport'; import type { Producer } from './ProducerTypes'; import type { Consumer } from './ConsumerTypes'; import type { DataProducer } from './DataProducerTypes'; import type { DataConsumer } from './DataConsumerTypes'; import type { RtpObserver } from './RtpObserverTypes'; import type { ActiveSpeakerObserver, ActiveSpeakerObserverOptions, } from './ActiveSpeakerObserverTypes'; import { ActiveSpeakerObserverImpl } from './ActiveSpeakerObserver'; import type { AudioLevelObserver, AudioLevelObserverOptions, } from './AudioLevelObserverTypes'; import { AudioLevelObserverImpl } from './AudioLevelObserver'; import type { RtpCapabilities, RouterRtpCodecCapability, } from './rtpParametersTypes'; import { cryptoSuiteToFbs } from './srtpParametersFbsUtils'; import type { AppData } from './types'; import * as utils from './utils'; import * as fbsUtils from './fbsUtils'; import * as FbsActiveSpeakerObserver from './fbs/active-speaker-observer'; import * as FbsAudioLevelObserver from './fbs/audio-level-observer'; import * as FbsRequest from './fbs/request'; import * as FbsWorker from './fbs/worker'; import * as FbsRouter from './fbs/router'; import * as FbsTransport from './fbs/transport'; import { Protocol as FbsTransportProtocol } from './fbs/transport/protocol'; import * as FbsWebRtcTransport from './fbs/web-rtc-transport'; import * as FbsPlainTransport from './fbs/plain-transport'; import * as FbsPipeTransport from './fbs/pipe-transport'; import * as FbsDirectTransport from './fbs/direct-transport'; import * as FbsSctpParameters from './fbs/sctp-parameters'; export type RouterInternal = { routerId: string; }; type RouterData = { rtpCapabilities: RtpCapabilities; }; const logger = new Logger('Router'); export class RouterImpl extends EnhancedEventEmitter implements Router { // Internal data. readonly #internal: RouterInternal; // Router data. readonly #data: RouterData; // Channel instance. readonly #channel: Channel; // Closed flag. #closed = false; // Custom app data. #appData: RouterAppData; // Transports map. readonly #transports: Map = new Map(); // Producers map. readonly #producers: Map = new Map(); // RtpObservers map. readonly #rtpObservers: Map = new Map(); // DataProducers map. readonly #dataProducers: Map = new Map(); // Map of PipeTransport pair Promises indexed by the id of the Router in // which pipeToRouter() was called. readonly #mapRouterPairPipeTransportPairPromise: Map< string, Promise > = new Map(); // Observer instance. readonly #observer: RouterObserver = new EnhancedEventEmitter(); constructor({ internal, data, channel, appData, }: { internal: RouterInternal; data: RouterData; channel: Channel; appData?: RouterAppData; }) { super(); logger.debug('constructor()'); this.#internal = internal; this.#data = data; this.#channel = channel; this.#appData = appData ?? ({} as RouterAppData); this.handleListenerError(); } get id(): string { return this.#internal.routerId; } get closed(): boolean { return this.#closed; } get rtpCapabilities(): RtpCapabilities { return this.#data.rtpCapabilities; } get appData(): RouterAppData { return this.#appData; } set appData(appData: RouterAppData) { this.#appData = appData; } get observer(): RouterObserver { return this.#observer; } /** * Just for testing purposes. * * @private */ get transportsForTesting(): Map { return this.#transports; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; const requestOffset = new FbsWorker.CloseRouterRequestT( this.#internal.routerId ).pack(this.#channel.bufferBuilder); this.#channel .request( FbsRequest.Method.WORKER_CLOSE_ROUTER, FbsRequest.Body.Worker_CloseRouterRequest, requestOffset ) .catch(() => {}); // Close every Transport. for (const transport of this.#transports.values()) { transport.routerClosed(); } this.#transports.clear(); // Clear the Producers map. this.#producers.clear(); // Close every RtpObserver. for (const rtpObserver of this.#rtpObservers.values()) { rtpObserver.routerClosed(); } this.#rtpObservers.clear(); // Clear the DataProducers map. this.#dataProducers.clear(); this.emit('@close'); // Emit observer event. this.#observer.safeEmit('close'); } workerClosed(): void { if (this.#closed) { return; } logger.debug('workerClosed()'); this.#closed = true; // Close every Transport. for (const transport of this.#transports.values()) { transport.routerClosed(); } this.#transports.clear(); // Clear the Producers map. this.#producers.clear(); // Close every RtpObserver. for (const rtpObserver of this.#rtpObservers.values()) { rtpObserver.routerClosed(); } this.#rtpObservers.clear(); // Clear the DataProducers map. this.#dataProducers.clear(); this.safeEmit('workerclose'); // Emit observer event. this.#observer.safeEmit('close'); } async dump(): Promise { logger.debug('dump()'); // Send the request and wait for the response. const response = await this.#channel.request( FbsRequest.Method.ROUTER_DUMP, undefined, undefined, this.#internal.routerId ); /* Decode Response. */ const dump = new FbsRouter.DumpResponse(); response.body(dump); return parseRouterDumpResponse(dump); } async createWebRtcTransport< WebRtcTransportAppData extends AppData = AppData, >({ webRtcServer, listenInfos, listenIps, port, enableUdp, enableTcp, preferUdp = false, preferTcp = false, initialAvailableOutgoingBitrate = 600000, enableSctp = false, numSctpStreams = { OS: 1024, MIS: 1024 }, maxSctpMessageSize = 262144, sctpSendBufferSize = 262144, iceConsentTimeout = 30, appData, }: WebRtcTransportOptions): Promise< WebRtcTransport > { logger.debug('createWebRtcTransport()'); if ( !webRtcServer && !Array.isArray(listenInfos) && !Array.isArray(listenIps) ) { throw new TypeError( 'missing webRtcServer, listenInfos and listenIps (one of them is mandatory)' ); } else if (webRtcServer && listenInfos && listenIps) { throw new TypeError( 'only one of webRtcServer, listenInfos and listenIps must be given' ); } else if ( numSctpStreams && (typeof numSctpStreams.OS !== 'number' || typeof numSctpStreams.MIS !== 'number') ) { throw new TypeError('if given, numSctpStreams must contain OS and MIS'); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } // If webRtcServer is given, then do not force default values for enableUdp // and enableTcp. Otherwise set them if unset. if (webRtcServer) { enableUdp ??= true; enableTcp ??= true; } else { enableUdp ??= true; enableTcp ??= false; } // Convert deprecated TransportListenIps to TransportListenInfos. if (listenIps) { // Normalize IP strings to TransportListenIp objects. listenIps = listenIps.map(listenIp => { if (typeof listenIp === 'string') { return { ip: listenIp }; } else { return listenIp; } }); listenInfos = []; const orderedProtocols: TransportProtocol[] = []; if (enableUdp && (preferUdp || !enableTcp || !preferTcp)) { orderedProtocols.push('udp'); if (enableTcp) { orderedProtocols.push('tcp'); } } else if (enableTcp && ((preferTcp && !preferUdp) || !enableUdp)) { orderedProtocols.push('tcp'); if (enableUdp) { orderedProtocols.push('udp'); } } for (const listenIp of listenIps as TransportListenIp[]) { for (const protocol of orderedProtocols) { listenInfos.push({ protocol: protocol, ip: listenIp.ip, announcedAddress: listenIp.announcedIp, port: port, }); } } } const transportId = utils.generateUUIDv4(); /* Build Request. */ let webRtcTransportListenServer: | FbsWebRtcTransport.ListenServerT | undefined; let webRtcTransportListenIndividual: | FbsWebRtcTransport.ListenIndividualT | undefined; if (webRtcServer) { webRtcTransportListenServer = new FbsWebRtcTransport.ListenServerT( webRtcServer.id ); } else { const fbsListenInfos: FbsTransport.ListenInfoT[] = []; for (const listenInfo of listenInfos!) { fbsListenInfos.push( new FbsTransport.ListenInfoT( listenInfo.protocol === 'udp' ? FbsTransportProtocol.UDP : FbsTransportProtocol.TCP, listenInfo.ip, listenInfo.announcedAddress ?? listenInfo.announcedIp, Boolean(listenInfo.exposeInternalIp), listenInfo.port, portRangeToFbs(listenInfo.portRange), socketFlagsToFbs(listenInfo.flags), listenInfo.sendBufferSize, listenInfo.recvBufferSize ) ); } webRtcTransportListenIndividual = new FbsWebRtcTransport.ListenIndividualT(fbsListenInfos); } const baseTransportOptions = new FbsTransport.OptionsT( undefined /* direct */, undefined /* maxMessageSize */, initialAvailableOutgoingBitrate, enableSctp, new FbsSctpParameters.NumSctpStreamsT( numSctpStreams.OS, numSctpStreams.MIS ), maxSctpMessageSize, sctpSendBufferSize, true /* isDataChannel */ ); const webRtcTransportOptions = new FbsWebRtcTransport.WebRtcTransportOptionsT( baseTransportOptions, webRtcServer ? FbsWebRtcTransport.Listen.ListenServer : FbsWebRtcTransport.Listen.ListenIndividual, webRtcServer ? webRtcTransportListenServer : webRtcTransportListenIndividual, enableUdp, enableTcp, preferUdp, preferTcp, iceConsentTimeout ); const requestOffset = new FbsRouter.CreateWebRtcTransportRequestT( transportId, webRtcTransportOptions ).pack(this.#channel.bufferBuilder); const response = await this.#channel.request( webRtcServer ? FbsRequest.Method.ROUTER_CREATE_WEBRTCTRANSPORT_WITH_SERVER : FbsRequest.Method.ROUTER_CREATE_WEBRTCTRANSPORT, FbsRequest.Body.Router_CreateWebRtcTransportRequest, requestOffset, this.#internal.routerId ); /* Decode Response. */ const data = new FbsWebRtcTransport.DumpResponse(); response.body(data); const webRtcTransportData = parseWebRtcTransportDumpResponse(data); const transport: WebRtcTransport = new WebRtcTransportImpl({ internal: { ...this.#internal, transportId: transportId, }, data: webRtcTransportData, channel: this.#channel, appData, getRouterRtpCapabilities: (): RtpCapabilities => this.#data.rtpCapabilities, getProducerById: (producerId: string): Producer | undefined => this.#producers.get(producerId), getDataProducerById: ( dataProducerId: string ): DataProducer | undefined => this.#dataProducers.get(dataProducerId), }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); transport.on('@listenserverclose', () => this.#transports.delete(transport.id) ); transport.on('@newproducer', (producer: Producer) => this.#producers.set(producer.id, producer) ); transport.on('@producerclose', (producer: Producer) => this.#producers.delete(producer.id) ); transport.on('@newdataproducer', (dataProducer: DataProducer) => this.#dataProducers.set(dataProducer.id, dataProducer) ); transport.on('@dataproducerclose', (dataProducer: DataProducer) => this.#dataProducers.delete(dataProducer.id) ); // Emit observer event. this.#observer.safeEmit('newtransport', transport); if (webRtcServer) { webRtcServer.handleWebRtcTransport(transport); } return transport; } async createPlainTransport({ listenInfo, rtcpListenInfo, listenIp, port, rtcpMux = true, comedia = false, enableSctp = false, numSctpStreams = { OS: 1024, MIS: 1024 }, maxSctpMessageSize = 262144, sctpSendBufferSize = 262144, enableSrtp = false, srtpCryptoSuite = 'AES_CM_128_HMAC_SHA1_80', appData, }: PlainTransportOptions): Promise< PlainTransport > { logger.debug('createPlainTransport()'); if (!listenInfo && !listenIp) { throw new TypeError( 'missing listenInfo and listenIp (one of them is mandatory)' ); } else if (listenInfo && listenIp) { throw new TypeError('only one of listenInfo and listenIp must be given'); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } // If rtcpMux is enabled, ignore rtcpListenInfo. if (rtcpMux && rtcpListenInfo) { logger.warn( 'createPlainTransport() | ignoring rtcpMux since rtcpListenInfo is given' ); rtcpMux = false; } // Convert deprecated TransportListenIps to TransportListenInfos. if (listenIp) { // Normalize IP string to TransportListenIp object. if (typeof listenIp === 'string') { listenIp = { ip: listenIp }; } listenInfo = { protocol: 'udp', ip: listenIp.ip, announcedAddress: listenIp.announcedIp, port: port, }; } const transportId = utils.generateUUIDv4(); /* Build Request. */ const baseTransportOptions = new FbsTransport.OptionsT( undefined /* direct */, undefined /* maxMessageSize */, undefined /* initialAvailableOutgoingBitrate */, enableSctp, new FbsSctpParameters.NumSctpStreamsT( numSctpStreams.OS, numSctpStreams.MIS ), maxSctpMessageSize, sctpSendBufferSize, false /* isDataChannel */ ); const plainTransportOptions = new FbsPlainTransport.PlainTransportOptionsT( baseTransportOptions, new FbsTransport.ListenInfoT( listenInfo!.protocol === 'udp' ? FbsTransportProtocol.UDP : FbsTransportProtocol.TCP, listenInfo!.ip, listenInfo!.announcedAddress ?? listenInfo!.announcedIp, Boolean(listenInfo!.exposeInternalIp), listenInfo!.port, portRangeToFbs(listenInfo!.portRange), socketFlagsToFbs(listenInfo!.flags), listenInfo!.sendBufferSize, listenInfo!.recvBufferSize ), rtcpListenInfo ? new FbsTransport.ListenInfoT( rtcpListenInfo.protocol === 'udp' ? FbsTransportProtocol.UDP : FbsTransportProtocol.TCP, rtcpListenInfo.ip, rtcpListenInfo.announcedAddress ?? rtcpListenInfo.announcedIp, Boolean(rtcpListenInfo.exposeInternalIp), rtcpListenInfo.port, portRangeToFbs(rtcpListenInfo.portRange), socketFlagsToFbs(rtcpListenInfo.flags), rtcpListenInfo.sendBufferSize, rtcpListenInfo.recvBufferSize ) : undefined, rtcpMux, comedia, enableSrtp, cryptoSuiteToFbs(srtpCryptoSuite) ); const requestOffset = new FbsRouter.CreatePlainTransportRequestT( transportId, plainTransportOptions ).pack(this.#channel.bufferBuilder); const response = await this.#channel.request( FbsRequest.Method.ROUTER_CREATE_PLAINTRANSPORT, FbsRequest.Body.Router_CreatePlainTransportRequest, requestOffset, this.#internal.routerId ); /* Decode Response. */ const data = new FbsPlainTransport.DumpResponse(); response.body(data); const plainTransportData = parsePlainTransportDumpResponse(data); const transport: PlainTransport = new PlainTransportImpl({ internal: { ...this.#internal, transportId: transportId, }, data: plainTransportData, channel: this.#channel, appData, getRouterRtpCapabilities: (): RtpCapabilities => this.#data.rtpCapabilities, getProducerById: (producerId: string): Producer | undefined => this.#producers.get(producerId), getDataProducerById: ( dataProducerId: string ): DataProducer | undefined => this.#dataProducers.get(dataProducerId), }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); transport.on('@listenserverclose', () => this.#transports.delete(transport.id) ); transport.on('@newproducer', (producer: Producer) => this.#producers.set(producer.id, producer) ); transport.on('@producerclose', (producer: Producer) => this.#producers.delete(producer.id) ); transport.on('@newdataproducer', (dataProducer: DataProducer) => this.#dataProducers.set(dataProducer.id, dataProducer) ); transport.on('@dataproducerclose', (dataProducer: DataProducer) => this.#dataProducers.delete(dataProducer.id) ); // Emit observer event. this.#observer.safeEmit('newtransport', transport); return transport; } async createPipeTransport({ listenInfo, listenIp, port, enableSctp = false, numSctpStreams = { OS: 1024, MIS: 1024 }, maxSctpMessageSize = 268435456, sctpSendBufferSize = 268435456, enableRtx = false, enableSrtp = false, appData, }: PipeTransportOptions): Promise< PipeTransport > { logger.debug('createPipeTransport()'); if (!listenInfo && !listenIp) { throw new TypeError( 'missing listenInfo and listenIp (one of them is mandatory)' ); } else if (listenInfo && listenIp) { throw new TypeError('only one of listenInfo and listenIp must be given'); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } // Convert deprecated TransportListenIps to TransportListenInfos. if (listenIp) { // Normalize IP string to TransportListenIp object. if (typeof listenIp === 'string') { listenIp = { ip: listenIp }; } listenInfo = { protocol: 'udp', ip: listenIp.ip, announcedAddress: listenIp.announcedIp, port: port, }; } const transportId = utils.generateUUIDv4(); /* Build Request. */ const baseTransportOptions = new FbsTransport.OptionsT( undefined /* direct */, undefined /* maxMessageSize */, undefined /* initialAvailableOutgoingBitrate */, enableSctp, new FbsSctpParameters.NumSctpStreamsT( numSctpStreams.OS, numSctpStreams.MIS ), maxSctpMessageSize, sctpSendBufferSize, false /* isDataChannel */ ); const pipeTransportOptions = new FbsPipeTransport.PipeTransportOptionsT( baseTransportOptions, new FbsTransport.ListenInfoT( listenInfo!.protocol === 'udp' ? FbsTransportProtocol.UDP : FbsTransportProtocol.TCP, listenInfo!.ip, listenInfo!.announcedAddress ?? listenInfo!.announcedIp, Boolean(listenInfo!.exposeInternalIp), listenInfo!.port, portRangeToFbs(listenInfo!.portRange), socketFlagsToFbs(listenInfo!.flags), listenInfo!.sendBufferSize, listenInfo!.recvBufferSize ), enableRtx, enableSrtp ); const requestOffset = new FbsRouter.CreatePipeTransportRequestT( transportId, pipeTransportOptions ).pack(this.#channel.bufferBuilder); const response = await this.#channel.request( FbsRequest.Method.ROUTER_CREATE_PIPETRANSPORT, FbsRequest.Body.Router_CreatePipeTransportRequest, requestOffset, this.#internal.routerId ); /* Decode Response. */ const data = new FbsPipeTransport.DumpResponse(); response.body(data); const pipeTransportData = parsePipeTransportDumpResponse(data); const transport: PipeTransport = new PipeTransportImpl({ internal: { ...this.#internal, transportId, }, data: pipeTransportData, channel: this.#channel, appData, getRouterRtpCapabilities: (): RtpCapabilities => this.#data.rtpCapabilities, getProducerById: (producerId: string): Producer | undefined => this.#producers.get(producerId), getDataProducerById: ( dataProducerId: string ): DataProducer | undefined => this.#dataProducers.get(dataProducerId), }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); transport.on('@listenserverclose', () => this.#transports.delete(transport.id) ); transport.on('@newproducer', (producer: Producer) => this.#producers.set(producer.id, producer) ); transport.on('@producerclose', (producer: Producer) => this.#producers.delete(producer.id) ); transport.on('@newdataproducer', (dataProducer: DataProducer) => this.#dataProducers.set(dataProducer.id, dataProducer) ); transport.on('@dataproducerclose', (dataProducer: DataProducer) => this.#dataProducers.delete(dataProducer.id) ); // Emit observer event. this.#observer.safeEmit('newtransport', transport); return transport; } async createDirectTransport( { maxMessageSize = 262144, appData, }: DirectTransportOptions = { maxMessageSize: 262144, } ): Promise> { logger.debug('createDirectTransport()'); if (typeof maxMessageSize !== 'number' || maxMessageSize < 0) { throw new TypeError('if given, maxMessageSize must be a positive number'); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } const transportId = utils.generateUUIDv4(); /* Build Request. */ const baseTransportOptions = new FbsTransport.OptionsT( true /* direct */, maxMessageSize, undefined /* initialAvailableOutgoingBitrate */, undefined /* enableSctp */, undefined /* numSctpStreams */, undefined /* maxSctpMessageSize */, undefined /* sctpSendBufferSize */, undefined /* isDataChannel */ ); const directTransportOptions = new FbsDirectTransport.DirectTransportOptionsT(baseTransportOptions); const requestOffset = new FbsRouter.CreateDirectTransportRequestT( transportId, directTransportOptions ).pack(this.#channel.bufferBuilder); const response = await this.#channel.request( FbsRequest.Method.ROUTER_CREATE_DIRECTTRANSPORT, FbsRequest.Body.Router_CreateDirectTransportRequest, requestOffset, this.#internal.routerId ); /* Decode Response. */ const data = new FbsDirectTransport.DumpResponse(); response.body(data); const directTransportData = parseDirectTransportDumpResponse(data); const transport: DirectTransport = new DirectTransportImpl({ internal: { ...this.#internal, transportId: transportId, }, data: directTransportData, channel: this.#channel, appData, getRouterRtpCapabilities: (): RtpCapabilities => this.#data.rtpCapabilities, getProducerById: (producerId: string): Producer | undefined => this.#producers.get(producerId), getDataProducerById: ( dataProducerId: string ): DataProducer | undefined => this.#dataProducers.get(dataProducerId), }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); transport.on('@listenserverclose', () => this.#transports.delete(transport.id) ); transport.on('@newproducer', (producer: Producer) => this.#producers.set(producer.id, producer) ); transport.on('@producerclose', (producer: Producer) => this.#producers.delete(producer.id) ); transport.on('@newdataproducer', (dataProducer: DataProducer) => this.#dataProducers.set(dataProducer.id, dataProducer) ); transport.on('@dataproducerclose', (dataProducer: DataProducer) => this.#dataProducers.delete(dataProducer.id) ); // Emit observer event. this.#observer.safeEmit('newtransport', transport); return transport; } async pipeToRouter({ producerId, dataProducerId, router, keepId = true, listenInfo, listenIp, enableSctp = true, numSctpStreams = { OS: 1024, MIS: 1024 }, enableRtx = false, enableSrtp = false, }: PipeToRouterOptions): Promise { logger.debug('pipeToRouter()'); if (!listenInfo && !listenIp) { listenInfo = { protocol: 'udp', ip: '127.0.0.1', }; } if (listenInfo && listenIp) { throw new TypeError('only one of listenInfo and listenIp must be given'); } else if (!producerId && !dataProducerId) { throw new TypeError('missing producerId or dataProducerId'); } else if (producerId && dataProducerId) { throw new TypeError('just producerId or dataProducerId can be given'); } else if (!router) { throw new TypeError('Router not found'); } else if (router === this) { throw new TypeError('cannot use this Router as destination'); } // Convert deprecated TransportListenIps to TransportListenInfos. if (listenIp) { // Normalize IP string to TransportListenIp object. if (typeof listenIp === 'string') { listenIp = { ip: listenIp }; } listenInfo = { protocol: 'udp', ip: listenIp.ip, announcedAddress: listenIp.announcedIp, }; } let producer: Producer | undefined; let dataProducer: DataProducer | undefined; if (producerId) { producer = this.#producers.get(producerId); if (!producer) { throw new TypeError('Producer not found'); } } else if (dataProducerId) { dataProducer = this.#dataProducers.get(dataProducerId); if (!dataProducer) { throw new TypeError('DataProducer not found'); } } const pipeTransportPairKey = router.id; let pipeTransportPairPromise = this.#mapRouterPairPipeTransportPairPromise.get(pipeTransportPairKey); let pipeTransportPair: PipeTransportPair; let localPipeTransport: PipeTransport; let remotePipeTransport: PipeTransport; if (pipeTransportPairPromise) { pipeTransportPair = await pipeTransportPairPromise; localPipeTransport = pipeTransportPair[this.id]!; remotePipeTransport = pipeTransportPair[router.id]!; } else { pipeTransportPairPromise = new Promise((resolve, reject) => { Promise.all([ this.createPipeTransport({ listenInfo: listenInfo!, enableSctp, numSctpStreams, enableRtx, enableSrtp, }), router.createPipeTransport({ listenInfo: listenInfo!, enableSctp, numSctpStreams, enableRtx, enableSrtp, }), ]) .then(pipeTransports => { localPipeTransport = pipeTransports[0]; remotePipeTransport = pipeTransports[1]; }) .then(() => { return Promise.all([ localPipeTransport.connect({ ip: remotePipeTransport.tuple.localAddress, port: remotePipeTransport.tuple.localPort, srtpParameters: remotePipeTransport.srtpParameters, }), remotePipeTransport.connect({ ip: localPipeTransport.tuple.localAddress, port: localPipeTransport.tuple.localPort, srtpParameters: localPipeTransport.srtpParameters, }), ]); }) .then(() => { localPipeTransport.observer.on('close', () => { remotePipeTransport.close(); this.#mapRouterPairPipeTransportPairPromise.delete( pipeTransportPairKey ); }); remotePipeTransport.observer.on('close', () => { localPipeTransport.close(); this.#mapRouterPairPipeTransportPairPromise.delete( pipeTransportPairKey ); }); resolve({ [this.id]: localPipeTransport, [router.id]: remotePipeTransport, }); }) .catch(error => { logger.error( 'pipeToRouter() | error creating PipeTransport pair:', error ); if (localPipeTransport) { localPipeTransport.close(); } if (remotePipeTransport) { remotePipeTransport.close(); } reject(error instanceof Error ? error : new Error(String(error))); }); }); this.#mapRouterPairPipeTransportPairPromise.set( pipeTransportPairKey, pipeTransportPairPromise ); router.addPipeTransportPair(this.id, pipeTransportPairPromise); await pipeTransportPairPromise; } if (producer) { let pipeConsumer: Consumer | undefined; let pipeProducer: Producer | undefined; try { pipeConsumer = await localPipeTransport!.consume({ producerId: producerId!, }); pipeProducer = await remotePipeTransport!.produce({ // If requested, generate a new id for the pipeProducer. id: keepId ? producer.id : utils.generateUUIDv4(), kind: pipeConsumer.kind, rtpParameters: pipeConsumer.rtpParameters, paused: pipeConsumer.producerPaused, appData: producer.appData, }); // Ensure that the producer has not been closed in the meanwhile. if (producer.closed) { throw new InvalidStateError('original Producer closed'); } // Ensure that producer.paused has not changed in the meanwhile and, if // so, sync the pipeProducer. if (pipeProducer.paused !== producer.paused) { if (producer.paused) { await pipeProducer.pause(); } else { await pipeProducer.resume(); } } // Pipe events from the pipe Consumer to the pipe Producer. pipeConsumer.observer.on('close', () => pipeProducer!.close()); pipeConsumer.observer.on('pause', () => void pipeProducer!.pause()); pipeConsumer.observer.on('resume', () => void pipeProducer!.resume()); // Pipe events from the pipe Producer to the pipe Consumer. pipeProducer.observer.on('close', () => pipeConsumer!.close()); return { pipeConsumer, pipeProducer }; } catch (error) { logger.error( 'pipeToRouter() | error creating pipe Consumer/Producer pair:', error as Error ); if (pipeConsumer) { pipeConsumer.close(); } if (pipeProducer) { pipeProducer.close(); } throw error; } } else if (dataProducer) { let pipeDataConsumer: DataConsumer | undefined; let pipeDataProducer: DataProducer | undefined; try { pipeDataConsumer = await localPipeTransport!.consumeData({ dataProducerId: dataProducerId!, }); pipeDataProducer = await remotePipeTransport!.produceData({ // If requested, generate a new id for the pipeDataProducer. id: keepId ? dataProducer.id : utils.generateUUIDv4(), sctpStreamParameters: pipeDataConsumer.sctpStreamParameters, label: pipeDataConsumer.label, protocol: pipeDataConsumer.protocol, appData: dataProducer.appData, }); // Ensure that the dataProducer has not been closed in the meanwhile. if (dataProducer.closed) { throw new InvalidStateError('original DataProducer closed'); } // Pipe events from the pipe DataConsumer to the pipe DataProducer. pipeDataConsumer.observer.on('close', () => pipeDataProducer!.close()); // Pipe events from the pipe DataProducer to the pipe DataConsumer. pipeDataProducer.observer.on('close', () => pipeDataConsumer!.close()); return { pipeDataConsumer, pipeDataProducer }; } catch (error) { logger.error( 'pipeToRouter() | error creating pipe DataConsumer/DataProducer pair:', error as Error ); pipeDataConsumer?.close(); pipeDataProducer?.close(); throw error; } } else { // NOTE: This cannot happen since it's guaranteed that producer or // dataProducer exists, but TypeScript is not that smart. throw new Error('internal error'); } } addPipeTransportPair( pipeTransportPairKey: string, pipeTransportPairPromise: Promise ): void { if (this.#mapRouterPairPipeTransportPairPromise.has(pipeTransportPairKey)) { throw new Error( 'given pipeTransportPairKey already exists in this Router' ); } this.#mapRouterPairPipeTransportPairPromise.set( pipeTransportPairKey, pipeTransportPairPromise ); pipeTransportPairPromise .then(pipeTransportPair => { const localPipeTransport = pipeTransportPair[this.id]!; // NOTE: No need to do any other cleanup here since that is done by the // Router calling this method on us. localPipeTransport.observer.on('close', () => { this.#mapRouterPairPipeTransportPairPromise.delete( pipeTransportPairKey ); }); }) .catch(() => { this.#mapRouterPairPipeTransportPairPromise.delete( pipeTransportPairKey ); }); } async createActiveSpeakerObserver< ActiveSpeakerObserverAppData extends AppData = AppData, >({ interval = 300, appData, }: ActiveSpeakerObserverOptions = {}): Promise< ActiveSpeakerObserver > { logger.debug('createActiveSpeakerObserver()'); if (typeof interval !== 'number') { throw new TypeError('if given, interval must be an number'); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } const rtpObserverId = utils.generateUUIDv4(); /* Build Request. */ const activeRtpObserverOptions = new FbsActiveSpeakerObserver.ActiveSpeakerObserverOptionsT(interval); const requestOffset = new FbsRouter.CreateActiveSpeakerObserverRequestT( rtpObserverId, activeRtpObserverOptions ).pack(this.#channel.bufferBuilder); await this.#channel.request( FbsRequest.Method.ROUTER_CREATE_ACTIVESPEAKEROBSERVER, FbsRequest.Body.Router_CreateActiveSpeakerObserverRequest, requestOffset, this.#internal.routerId ); const activeSpeakerObserver: ActiveSpeakerObserver = new ActiveSpeakerObserverImpl({ internal: { ...this.#internal, rtpObserverId: rtpObserverId, }, channel: this.#channel, appData, getProducerById: (producerId: string): Producer | undefined => this.#producers.get(producerId), }); this.#rtpObservers.set(activeSpeakerObserver.id, activeSpeakerObserver); activeSpeakerObserver.on('@close', () => { this.#rtpObservers.delete(activeSpeakerObserver.id); }); // Emit observer event. this.#observer.safeEmit('newrtpobserver', activeSpeakerObserver); return activeSpeakerObserver; } async createAudioLevelObserver< AudioLevelObserverAppData extends AppData = AppData, >({ maxEntries = 1, threshold = -80, interval = 1000, appData, }: AudioLevelObserverOptions = {}): Promise< AudioLevelObserver > { logger.debug('createAudioLevelObserver()'); if (typeof maxEntries !== 'number' || maxEntries <= 0) { throw new TypeError('if given, maxEntries must be a positive number'); } else if ( typeof threshold !== 'number' || threshold < -127 || threshold > 0 ) { throw new TypeError( 'if given, threshole must be a negative number greater than -127' ); } else if (typeof interval !== 'number') { throw new TypeError('if given, interval must be an number'); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } const rtpObserverId = utils.generateUUIDv4(); /* Build Request. */ const audioLevelObserverOptions = new FbsAudioLevelObserver.AudioLevelObserverOptionsT( maxEntries, threshold, interval ); const requestOffset = new FbsRouter.CreateAudioLevelObserverRequestT( rtpObserverId, audioLevelObserverOptions ).pack(this.#channel.bufferBuilder); await this.#channel.request( FbsRequest.Method.ROUTER_CREATE_AUDIOLEVELOBSERVER, FbsRequest.Body.Router_CreateAudioLevelObserverRequest, requestOffset, this.#internal.routerId ); const audioLevelObserver: AudioLevelObserver = new AudioLevelObserverImpl({ internal: { ...this.#internal, rtpObserverId: rtpObserverId, }, channel: this.#channel, appData, getProducerById: (producerId: string): Producer | undefined => this.#producers.get(producerId), }); this.#rtpObservers.set(audioLevelObserver.id, audioLevelObserver); audioLevelObserver.on('@close', () => { this.#rtpObservers.delete(audioLevelObserver.id); }); // Emit observer event. this.#observer.safeEmit('newrtpobserver', audioLevelObserver); return audioLevelObserver; } canConsume({ producerId, rtpCapabilities, }: { producerId: string; rtpCapabilities: RtpCapabilities; }): boolean { const producer = this.#producers.get(producerId); if (!producer) { logger.error(`canConsume() | Producer with id "${producerId}" not found`); return false; } // Clone given RTP capabilities to not modify input data. const clonedRtpCapabilities = utils.clone(rtpCapabilities); try { return ortc.canConsume( producer.consumableRtpParameters, clonedRtpCapabilities ); } catch (error) { logger.error(`canConsume() | unexpected error: ${error}`); return false; } } updateMediaCodecs(mediaCodecs: RouterRtpCodecCapability[]): void { logger.debug('updateMediaCodecs()'); // Clone given media codecs to not modify input data. const clonedMediaCodecs = utils.clone< RouterRtpCodecCapability[] | undefined >(mediaCodecs); // This may throw. const rtpCapabilities = ortc.generateRouterRtpCapabilities(clonedMediaCodecs); this.#data.rtpCapabilities = rtpCapabilities; } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } function parseRouterDumpResponse(binary: FbsRouter.DumpResponse): RouterDump { return { id: binary.id()!, transportIds: fbsUtils.parseVector(binary, 'transportIds'), rtpObserverIds: fbsUtils.parseVector(binary, 'rtpObserverIds'), mapProducerIdConsumerIds: fbsUtils.parseStringStringArrayVector( binary, 'mapProducerIdConsumerIds' ), mapConsumerIdProducerId: fbsUtils.parseStringStringVector( binary, 'mapConsumerIdProducerId' ), mapProducerIdObserverIds: fbsUtils.parseStringStringArrayVector( binary, 'mapProducerIdObserverIds' ), mapDataProducerIdDataConsumerIds: fbsUtils.parseStringStringArrayVector( binary, 'mapDataProducerIdDataConsumerIds' ), mapDataConsumerIdDataProducerId: fbsUtils.parseStringStringVector( binary, 'mapDataConsumerIdDataProducerId' ), }; } ================================================ FILE: node/src/RouterTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { Transport, TransportListenInfo, TransportListenIp, } from './TransportTypes'; import type { WebRtcTransport, WebRtcTransportOptions, } from './WebRtcTransportTypes'; import type { PlainTransport, PlainTransportOptions, } from './PlainTransportTypes'; import type { PipeTransport, PipeTransportOptions } from './PipeTransportTypes'; import type { DirectTransport, DirectTransportOptions, } from './DirectTransportTypes'; import type { Producer } from './ProducerTypes'; import type { Consumer } from './ConsumerTypes'; import type { DataProducer } from './DataProducerTypes'; import type { DataConsumer } from './DataConsumerTypes'; import type { RtpObserver } from './RtpObserverTypes'; import type { ActiveSpeakerObserver, ActiveSpeakerObserverOptions, } from './ActiveSpeakerObserverTypes'; import type { AudioLevelObserver, AudioLevelObserverOptions, } from './AudioLevelObserverTypes'; import type { RtpCapabilities, RouterRtpCodecCapability, } from './rtpParametersTypes'; import type { NumSctpStreams } from './sctpParametersTypes'; import type { Either, AppData } from './types'; export type RouterOptions = { /** * Router media codecs. */ mediaCodecs?: RouterRtpCodecCapability[]; /** * Custom application data. */ appData?: RouterAppData; }; export type PipeToRouterOptions = { /** * The id of the Producer to consume. */ producerId?: string; /** * The id of the DataProducer to consume. */ dataProducerId?: string; /** * Target Router instance. */ router: Router; /** * Whether the `id` of the returned Producer or DataProducer should be the * same than the `id` of the original Producer or DataProducer. Default true. * * @remarks * - If set to true, then the origin router and target router cannot be in the * same worker (if so, `pipeToRouter()` with throw). */ keepId?: boolean; /** * Create a SCTP association. Default true. */ enableSctp?: boolean; /** * SCTP streams number. */ numSctpStreams?: NumSctpStreams; /** * Enable RTX and NACK for RTP retransmission. */ enableRtx?: boolean; /** * Enable SRTP. */ enableSrtp?: boolean; } & PipeToRouterListen; type PipeToRouterListen = Either; type PipeToRouterListenInfo = { listenInfo: TransportListenInfo; }; type PipeToRouterListenIp = { /** * IP used in the PipeTransport pair. Default '127.0.0.1'. */ listenIp?: TransportListenIp | string; }; export type PipeToRouterResult = { /** * The Consumer created in the current Router. */ pipeConsumer?: Consumer; /** * The Producer created in the target Router. */ pipeProducer?: Producer; /** * The DataConsumer created in the current Router. */ pipeDataConsumer?: DataConsumer; /** * The DataProducer created in the target Router. */ pipeDataProducer?: DataProducer; }; export type PipeTransportPair = { [key: string]: PipeTransport; }; export type RouterDump = { /** * The Router id. */ id: string; /** * Id of Transports. */ transportIds: string[]; /** * Id of RtpObservers. */ rtpObserverIds: string[]; /** * Array of Producer id and its respective Consumer ids. */ mapProducerIdConsumerIds: { key: string; values: string[] }[]; /** * Array of Consumer id and its Producer id. */ mapConsumerIdProducerId: { key: string; value: string }[]; /** * Array of Producer id and its respective Observer ids. */ mapProducerIdObserverIds: { key: string; values: string[] }[]; /** * Array of Producer id and its respective DataConsumer ids. */ mapDataProducerIdDataConsumerIds: { key: string; values: string[] }[]; /** * Array of DataConsumer id and its DataProducer id. */ mapDataConsumerIdDataProducerId: { key: string; value: string }[]; }; export type RouterEvents = { workerclose: []; // Private events. '@close': []; }; export type RouterObserver = EnhancedEventEmitter; export type RouterObserverEvents = { close: []; newtransport: [Transport]; newrtpobserver: [RtpObserver]; }; export interface Router< RouterAppData extends AppData = AppData, > extends EnhancedEventEmitter { /** * Router id. */ get id(): string; /** * Whether the Router is closed. */ get closed(): boolean; /** * RTP capabilities of the Router. */ get rtpCapabilities(): RtpCapabilities; /** * App custom data. */ get appData(): RouterAppData; /** * App custom data setter. */ set appData(appData: RouterAppData); /** * Observer. */ get observer(): RouterObserver; /** * Close the Router. */ close(): void; /** * Worker was closed. * * @private */ workerClosed(): void; /** * Dump Router. */ dump(): Promise; /** * Create a WebRtcTransport. */ createWebRtcTransport( options: WebRtcTransportOptions ): Promise>; /** * Create a PlainTransport. */ createPlainTransport( options: PlainTransportOptions ): Promise>; /** * Create a PipeTransport. */ createPipeTransport( options: PipeTransportOptions ): Promise>; /** * Create a DirectTransport. */ createDirectTransport( options?: DirectTransportOptions ): Promise>; /** * Pipes the given Producer or DataProducer into another Router in same host. */ pipeToRouter(options: PipeToRouterOptions): Promise; /** * @private */ addPipeTransportPair( pipeTransportPairKey: string, pipeTransportPairPromise: Promise ): void; /** * Create an ActiveSpeakerObserver */ createActiveSpeakerObserver< ActiveSpeakerObserverAppData extends AppData = AppData, >( options?: ActiveSpeakerObserverOptions ): Promise>; /** * Create an AudioLevelObserver. */ createAudioLevelObserver( options?: AudioLevelObserverOptions ): Promise>; /** * Check whether the given RTP capabilities can consume the given Producer. */ canConsume({ producerId, rtpCapabilities, }: { producerId: string; rtpCapabilities: RtpCapabilities; }): boolean; /** * Update the Router media codecs. Once called, the return value of the * router.rtpCapabilities getter changes. */ updateMediaCodecs(mediaCodecs: RouterRtpCodecCapability[]): void; } ================================================ FILE: node/src/RtpObserver.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { RtpObserverEvents, RtpObserverObserver, } from './RtpObserverTypes'; import type { Channel } from './Channel'; import type { RouterInternal } from './Router'; import type { Producer } from './ProducerTypes'; import type { AppData } from './types'; import * as FbsRequest from './fbs/request'; import * as FbsRouter from './fbs/router'; import * as FbsRtpObserver from './fbs/rtp-observer'; export type RtpObserverConstructorOptions = { internal: RtpObserverObserverInternal; channel: Channel; appData?: RtpObserverAppData; getProducerById: (producerId: string) => Producer | undefined; }; type RtpObserverObserverInternal = RouterInternal & { rtpObserverId: string; }; const logger = new Logger('RtpObserver'); export abstract class RtpObserverImpl< RtpObserverAppData extends AppData = AppData, Events extends RtpObserverEvents = RtpObserverEvents, Observer extends RtpObserverObserver = RtpObserverObserver, > extends EnhancedEventEmitter { // Internal data. protected readonly internal: RtpObserverObserverInternal; // Channel instance. protected readonly channel: Channel; // Closed flag. #closed = false; // Paused flag. #paused = false; // Custom app data. #appData: RtpObserverAppData; // Method to retrieve a Producer. protected readonly getProducerById: ( producerId: string ) => Producer | undefined; // Observer instance. readonly #observer: Observer; protected constructor( { internal, channel, appData, getProducerById, }: RtpObserverConstructorOptions, observer: Observer ) { super(); logger.debug('constructor()'); this.internal = internal; this.channel = channel; this.#appData = appData ?? ({} as RtpObserverAppData); this.getProducerById = getProducerById; this.#observer = observer; } get id(): string { return this.internal.rtpObserverId; } get closed(): boolean { return this.#closed; } get paused(): boolean { return this.#paused; } get appData(): RtpObserverAppData { return this.#appData; } set appData(appData: RtpObserverAppData) { this.#appData = appData; } get observer(): Observer { return this.#observer; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; // Remove notification subscriptions. this.channel.removeAllListeners(this.internal.rtpObserverId); /* Build Request. */ const requestOffset = new FbsRouter.CloseRtpObserverRequestT( this.internal.rtpObserverId ).pack(this.channel.bufferBuilder); this.channel .request( FbsRequest.Method.ROUTER_CLOSE_RTPOBSERVER, FbsRequest.Body.Router_CloseRtpObserverRequest, requestOffset, this.internal.routerId ) .catch(() => {}); this.emit('@close'); // Emit observer event. this.#observer.safeEmit('close'); } routerClosed(): void { if (this.#closed) { return; } logger.debug('routerClosed()'); this.#closed = true; // Remove notification subscriptions. this.channel.removeAllListeners(this.internal.rtpObserverId); this.safeEmit('routerclose'); // Emit observer event. this.#observer.safeEmit('close'); } async pause(): Promise { logger.debug('pause()'); const wasPaused = this.#paused; await this.channel.request( FbsRequest.Method.RTPOBSERVER_PAUSE, undefined, undefined, this.internal.rtpObserverId ); this.#paused = true; // Emit observer event. if (!wasPaused) { this.#observer.safeEmit('pause'); } } async resume(): Promise { logger.debug('resume()'); const wasPaused = this.#paused; await this.channel.request( FbsRequest.Method.RTPOBSERVER_RESUME, undefined, undefined, this.internal.rtpObserverId ); this.#paused = false; // Emit observer event. if (wasPaused) { this.#observer.safeEmit('resume'); } } async addProducer({ producerId }: { producerId: string }): Promise { logger.debug('addProducer()'); const producer = this.getProducerById(producerId); if (!producer) { throw Error(`Producer with id "${producerId}" not found`); } const requestOffset = new FbsRtpObserver.AddProducerRequestT( producerId ).pack(this.channel.bufferBuilder); await this.channel.request( FbsRequest.Method.RTPOBSERVER_ADD_PRODUCER, FbsRequest.Body.RtpObserver_AddProducerRequest, requestOffset, this.internal.rtpObserverId ); // Emit observer event. this.#observer.safeEmit('addproducer', producer); } async removeProducer({ producerId }: { producerId: string }): Promise { logger.debug('removeProducer()'); const producer = this.getProducerById(producerId); if (!producer) { throw Error(`Producer with id "${producerId}" not found`); } const requestOffset = new FbsRtpObserver.RemoveProducerRequestT( producerId ).pack(this.channel.bufferBuilder); await this.channel.request( FbsRequest.Method.RTPOBSERVER_REMOVE_PRODUCER, FbsRequest.Body.RtpObserver_RemoveProducerRequest, requestOffset, this.internal.rtpObserverId ); // Emit observer event. this.#observer.safeEmit('removeproducer', producer); } } ================================================ FILE: node/src/RtpObserverTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { Producer } from './ProducerTypes'; import type { AppData } from './types'; /** * RtpObserver type. */ export type RtpObserverType = 'audiolevel' | 'activespeaker'; export type RtpObserverEvents = { routerclose: []; // Private events. '@close': []; }; export type RtpObserverObserver = EnhancedEventEmitter; export type RtpObserverObserverEvents = { close: []; pause: []; resume: []; addproducer: [Producer]; removeproducer: [Producer]; }; export interface RtpObserver< RtpObserverAppData extends AppData = AppData, Events extends RtpObserverEvents = RtpObserverEvents, Observer extends RtpObserverObserver = RtpObserverObserver, > extends EnhancedEventEmitter { /** * RtpObserver id. */ get id(): string; /** * Whether the RtpObserver is closed. */ get closed(): boolean; /** * RtpObserver type. * * @virtual * @privateRemarks * - It's marked as virtual getter since each RtpObserver class overrides it. */ get type(): RtpObserverType; /** * Whether the RtpObserver is paused. */ get paused(): boolean; /** * App custom data. */ get appData(): RtpObserverAppData; /** * App custom data setter. */ set appData(appData: RtpObserverAppData); /** * Observer. * * @virtual */ get observer(): Observer; /** * Close the RtpObserver. */ close(): void; /** * Router was closed. * * @private */ routerClosed(): void; /** * Pause the RtpObserver. */ pause(): Promise; /** * Resume the RtpObserver. */ resume(): Promise; /** * Add a Producer to the RtpObserver. */ addProducer({ producerId }: { producerId: string }): Promise; /** * Remove a Producer from the RtpObserver. */ removeProducer({ producerId }: { producerId: string }): Promise; } ================================================ FILE: node/src/Transport.ts ================================================ import type * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; import type { Transport, TransportType, TransportProtocol, TransportPortRange, TransportSocketFlags, TransportTuple, SctpState, RtpListenerDump, SctpListenerDump, RecvRtpHeaderExtensions, BaseTransportDump, BaseTransportStats, TransportTraceEventType, TransportTraceEventData, TransportEvents, TransportObserver, } from './TransportTypes'; import type { Channel } from './Channel'; import type { RouterInternal } from './Router'; import type { WebRtcTransportData } from './WebRtcTransport'; import type { PlainTransportData } from './PlainTransport'; import type { PipeTransportData } from './PipeTransport'; import type { DirectTransportData } from './DirectTransport'; import type { Producer, ProducerOptions } from './ProducerTypes'; import { ProducerImpl, producerTypeFromFbs, producerTypeToFbs, } from './Producer'; import type { Consumer, ConsumerOptions, ConsumerType, ConsumerLayers, } from './ConsumerTypes'; import { ConsumerImpl } from './Consumer'; import type { DataProducer, DataProducerOptions, DataProducerType, } from './DataProducerTypes'; import { DataProducerImpl, dataProducerTypeToFbs, parseDataProducerDumpResponse, } from './DataProducer'; import type { DataConsumer, DataConsumerOptions, DataConsumerType, } from './DataConsumerTypes'; import { DataConsumerImpl, dataConsumerTypeToFbs, parseDataConsumerDumpResponse, } from './DataConsumer'; import type { MediaKind, RtpCapabilities, RtpParameters, } from './rtpParametersTypes'; import { serializeRtpEncodingParameters, serializeRtpParameters, } from './rtpParametersFbsUtils'; import type { SctpParameters, SctpStreamParameters, } from './sctpParametersTypes'; import { parseSctpParametersDump, serializeSctpStreamParameters, } from './sctpParametersFbsUtils'; import type { AppData } from './types'; import * as utils from './utils'; import * as fbsUtils from './fbsUtils'; import { TraceDirection as FbsTraceDirection } from './fbs/common'; import * as FbsRequest from './fbs/request'; import { MediaKind as FbsMediaKind } from './fbs/rtp-parameters/media-kind'; import * as FbsConsumer from './fbs/consumer'; import * as FbsDataConsumer from './fbs/data-consumer'; import * as FbsDataProducer from './fbs/data-producer'; import * as FbsTransport from './fbs/transport'; import * as FbsRouter from './fbs/router'; import * as FbsRtpParameters from './fbs/rtp-parameters'; import { SctpState as FbsSctpState } from './fbs/sctp-association/sctp-state'; export type TransportConstructorOptions = { internal: TransportInternal; data: TransportData; channel: Channel; appData?: TransportAppData; getRouterRtpCapabilities: () => RtpCapabilities; getProducerById: (producerId: string) => Producer | undefined; getDataProducerById: (dataProducerId: string) => DataProducer | undefined; }; export type TransportInternal = RouterInternal & { transportId: string; }; type TransportData = | WebRtcTransportData | PlainTransportData | PipeTransportData | DirectTransportData; const logger = new Logger('Transport'); export abstract class TransportImpl< TransportAppData extends AppData = AppData, Events extends TransportEvents = TransportEvents, Observer extends TransportObserver = TransportObserver, > extends EnhancedEventEmitter implements Transport { // Internal data. protected readonly internal: TransportInternal; // Transport data. This is set by the subclass. readonly #data: TransportData; // Channel instance. protected readonly channel: Channel; // Close flag. #closed = false; // Custom app data. #appData: TransportAppData; // Method to retrieve Router RTP capabilities. readonly #getRouterRtpCapabilities: () => RtpCapabilities; // Method to retrieve a Producer. protected readonly getProducerById: ( producerId: string ) => Producer | undefined; // Method to retrieve a DataProducer. protected readonly getDataProducerById: ( dataProducerId: string ) => DataProducer | undefined; // Producers map. readonly #producers: Map = new Map(); // Consumers map. protected readonly consumers: Map = new Map(); // DataProducers map. protected readonly dataProducers: Map = new Map(); // DataConsumers map. protected readonly dataConsumers: Map = new Map(); // RTCP CNAME for Producers. #cnameForProducers?: string; // Next MID for Consumers. It's converted into string when used. #nextMidForConsumers = 0; // Buffer with available SCTP stream ids. #sctpStreamIds?: Buffer; // Next SCTP stream id. #nextSctpStreamId = 0; // Observer instance. readonly #observer: Observer; protected constructor( { internal, data, channel, appData, getRouterRtpCapabilities, getProducerById, getDataProducerById, }: TransportConstructorOptions, observer: Observer ) { super(); logger.debug('constructor()'); this.internal = internal; this.#data = data; this.channel = channel; this.#appData = appData ?? ({} as TransportAppData); this.#getRouterRtpCapabilities = getRouterRtpCapabilities; this.getProducerById = getProducerById; this.getDataProducerById = getDataProducerById; this.#observer = observer; } get id(): string { return this.internal.transportId; } get closed(): boolean { return this.#closed; } abstract get type(): TransportType; get appData(): TransportAppData { return this.#appData; } set appData(appData: TransportAppData) { this.#appData = appData; } get observer(): Observer { return this.#observer; } /** * Just for testing purposes. */ get channelForTesting(): Channel { return this.channel; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; // Remove notification subscriptions. this.channel.removeAllListeners(this.internal.transportId); /* Build Request. */ const requestOffset = new FbsRouter.CloseTransportRequestT( this.internal.transportId ).pack(this.channel.bufferBuilder); this.channel .request( FbsRequest.Method.ROUTER_CLOSE_TRANSPORT, FbsRequest.Body.Router_CloseTransportRequest, requestOffset, this.internal.routerId ) .catch(() => {}); // Close every Producer. for (const producer of this.#producers.values()) { producer.transportClosed(); // Must tell the Router. this.emit('@producerclose', producer); } this.#producers.clear(); // Close every Consumer. for (const consumer of this.consumers.values()) { consumer.transportClosed(); } this.consumers.clear(); // Close every DataProducer. for (const dataProducer of this.dataProducers.values()) { dataProducer.transportClosed(); // Must tell the Router. this.emit('@dataproducerclose', dataProducer); } this.dataProducers.clear(); // Close every DataConsumer. for (const dataConsumer of this.dataConsumers.values()) { dataConsumer.transportClosed(); } this.dataConsumers.clear(); this.emit('@close'); // Emit observer event. this.#observer.safeEmit('close'); } routerClosed(): void { if (this.#closed) { return; } logger.debug('routerClosed()'); this.#closed = true; // Remove notification subscriptions. this.channel.removeAllListeners(this.internal.transportId); // Close every Producer. for (const producer of this.#producers.values()) { producer.transportClosed(); // NOTE: No need to tell the Router since it already knows (it has // been closed in fact). } this.#producers.clear(); // Close every Consumer. for (const consumer of this.consumers.values()) { consumer.transportClosed(); } this.consumers.clear(); // Close every DataProducer. for (const dataProducer of this.dataProducers.values()) { dataProducer.transportClosed(); // NOTE: No need to tell the Router since it already knows (it has // been closed in fact). } this.dataProducers.clear(); // Close every DataConsumer. for (const dataConsumer of this.dataConsumers.values()) { dataConsumer.transportClosed(); } this.dataConsumers.clear(); this.safeEmit('routerclose'); // Emit observer event. this.#observer.safeEmit('close'); } listenServerClosed(): void { if (this.#closed) { return; } logger.debug('listenServerClosed()'); this.#closed = true; // Remove notification subscriptions. this.channel.removeAllListeners(this.internal.transportId); // Close every Producer. for (const producer of this.#producers.values()) { producer.transportClosed(); // NOTE: No need to tell the Router since it already knows (it has // been closed in fact). } this.#producers.clear(); // Close every Consumer. for (const consumer of this.consumers.values()) { consumer.transportClosed(); } this.consumers.clear(); // Close every DataProducer. for (const dataProducer of this.dataProducers.values()) { dataProducer.transportClosed(); // NOTE: No need to tell the Router since it already knows (it has // been closed in fact). } this.dataProducers.clear(); // Close every DataConsumer. for (const dataConsumer of this.dataConsumers.values()) { dataConsumer.transportClosed(); } this.dataConsumers.clear(); // Need to emit this event to let the parent Router know since // transport.listenServerClosed() is called by the listen server. // NOTE: Currently there is just WebRtcServer for WebRtcTransports. this.emit('@listenserverclose'); this.safeEmit('listenserverclose'); // Emit observer event. this.#observer.safeEmit('close'); } abstract dump(): Promise; abstract getStats(): Promise; abstract connect(params: unknown): Promise; async setMaxIncomingBitrate(bitrate: number): Promise { logger.debug(`setMaxIncomingBitrate() [bitrate:${bitrate}]`); /* Build Request. */ const requestOffset = FbsTransport.SetMaxIncomingBitrateRequest.createSetMaxIncomingBitrateRequest( this.channel.bufferBuilder, bitrate ); await this.channel.request( FbsRequest.Method.TRANSPORT_SET_MAX_INCOMING_BITRATE, FbsRequest.Body.Transport_SetMaxIncomingBitrateRequest, requestOffset, this.internal.transportId ); } async setMaxOutgoingBitrate(bitrate: number): Promise { logger.debug(`setMaxOutgoingBitrate() [bitrate:${bitrate}]`); /* Build Request. */ const requestOffset = new FbsTransport.SetMaxOutgoingBitrateRequestT( bitrate ).pack(this.channel.bufferBuilder); await this.channel.request( FbsRequest.Method.TRANSPORT_SET_MAX_OUTGOING_BITRATE, FbsRequest.Body.Transport_SetMaxOutgoingBitrateRequest, requestOffset, this.internal.transportId ); } async setMinOutgoingBitrate(bitrate: number): Promise { logger.debug(`setMinOutgoingBitrate() [bitrate:${bitrate}]`); /* Build Request. */ const requestOffset = new FbsTransport.SetMinOutgoingBitrateRequestT( bitrate ).pack(this.channel.bufferBuilder); await this.channel.request( FbsRequest.Method.TRANSPORT_SET_MIN_OUTGOING_BITRATE, FbsRequest.Body.Transport_SetMinOutgoingBitrateRequest, requestOffset, this.internal.transportId ); } async produce({ id = undefined, kind, rtpParameters, paused = false, keyFrameRequestDelay, enableMediasoupPacketIdHeaderExtension, appData, }: ProducerOptions): Promise> { logger.debug('produce()'); if (id && this.#producers.has(id)) { throw new TypeError(`a Producer with same id "${id}" already exists`); } else if (!['audio', 'video'].includes(kind)) { throw new TypeError(`invalid kind "${kind}"`); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } // Clone given RTP parameters to not modify input data. const clonedRtpParameters = utils.clone(rtpParameters); // This may throw. ortc.validateAndNormalizeRtpParameters(clonedRtpParameters); // If missing or empty encodings, add one. if ( !clonedRtpParameters.encodings || !Array.isArray(clonedRtpParameters.encodings) || clonedRtpParameters.encodings.length === 0 ) { clonedRtpParameters.encodings = [{}]; } // Don't do this in PipeTransports since there we must keep CNAME value in // each Producer. if (this.type !== 'pipe') { // If CNAME is given and we don't have yet a CNAME for Producers in this // Transport, take it. if (!this.#cnameForProducers && clonedRtpParameters.rtcp?.cname) { this.#cnameForProducers = clonedRtpParameters.rtcp.cname; } // Otherwise if we don't have yet a CNAME for Producers and the RTP // parameters do not include CNAME, create a random one. else if (!this.#cnameForProducers) { this.#cnameForProducers = utils.generateUUIDv4().substr(0, 8); } // Override Producer's CNAME. clonedRtpParameters.rtcp = clonedRtpParameters.rtcp ?? {}; clonedRtpParameters.rtcp.cname = this.#cnameForProducers; } const routerRtpCapabilities = this.#getRouterRtpCapabilities(); // This may throw. const rtpMapping = ortc.getProducerRtpParametersMapping( clonedRtpParameters, routerRtpCapabilities ); // This may throw. const consumableRtpParameters = ortc.getConsumableRtpParameters( kind, clonedRtpParameters, routerRtpCapabilities, rtpMapping ); const producerId = id ?? utils.generateUUIDv4(); const requestOffset = createProduceRequest({ builder: this.channel.bufferBuilder, producerId, kind, rtpParameters: clonedRtpParameters, rtpMapping, paused, keyFrameRequestDelay, enableMediasoupPacketIdHeaderExtension, }); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_PRODUCE, FbsRequest.Body.Transport_ProduceRequest, requestOffset, this.internal.transportId ); /* Decode Response. */ const produceResponse = new FbsTransport.ProduceResponse(); response.body(produceResponse); const status = produceResponse.unpack(); const data = { kind, rtpParameters: clonedRtpParameters, type: producerTypeFromFbs(status.type), consumableRtpParameters, }; const producer: Producer = new ProducerImpl({ internal: { ...this.internal, producerId, }, data, channel: this.channel, appData, paused, }); this.#producers.set(producer.id, producer); producer.on('@close', () => { this.#producers.delete(producer.id); this.emit('@producerclose', producer); }); this.emit('@newproducer', producer); // Emit observer event. this.#observer.safeEmit('newproducer', producer); return producer; } async consume({ producerId, rtpCapabilities, paused = false, mid, preferredLayers, ignoreDtx = false, enableRtx, pipe = false, appData, }: ConsumerOptions): Promise> { logger.debug('consume()'); if (!producerId || typeof producerId !== 'string') { throw new TypeError('missing producerId'); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } else if (mid && (typeof mid !== 'string' || mid.length === 0)) { throw new TypeError('if given, mid must be non empty string'); } // Clone given RTP capabilities to not modify input data. const clonedRtpCapabilities = utils.clone(rtpCapabilities); // This may throw. ortc.validateAndNormalizeRtpCapabilities(clonedRtpCapabilities); const producer = this.getProducerById(producerId); if (!producer) { throw Error(`Producer with id "${producerId}" not found`); } // If enableRtx is not given, set it to true if video and false if audio. if (enableRtx === undefined) { enableRtx = producer.kind === 'video'; } // This may throw. const rtpParameters = ortc.getConsumerRtpParameters({ consumableRtpParameters: producer.consumableRtpParameters, remoteRtpCapabilities: clonedRtpCapabilities, pipe, enableRtx, }); // Set MID. if (!pipe) { if (mid) { rtpParameters.mid = mid; } else { rtpParameters.mid = `${this.#nextMidForConsumers++}`; // We use up to 8 bytes for MID (string). if (this.#nextMidForConsumers === 100000000) { logger.error( `consume() | reaching max MID value "${this.#nextMidForConsumers}"` ); this.#nextMidForConsumers = 0; } } } const consumerId = utils.generateUUIDv4(); const requestOffset = createConsumeRequest({ builder: this.channel.bufferBuilder, producer, consumerId, rtpParameters, paused, preferredLayers, ignoreDtx, pipe, }); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_CONSUME, FbsRequest.Body.Transport_ConsumeRequest, requestOffset, this.internal.transportId ); /* Decode Response. */ const consumeResponse = new FbsTransport.ConsumeResponse(); response.body(consumeResponse); const status = consumeResponse.unpack(); const data = { producerId, kind: producer.kind, rtpParameters, type: pipe ? 'pipe' : (producer.type as ConsumerType), }; const consumer: Consumer = new ConsumerImpl({ internal: { ...this.internal, consumerId, }, data, channel: this.channel, appData, paused: status.paused, producerPaused: status.producerPaused, score: status.score ?? undefined, preferredLayers: status.preferredLayers ? { spatialLayer: status.preferredLayers.spatialLayer, temporalLayer: status.preferredLayers.temporalLayer ?? undefined, } : undefined, }); this.consumers.set(consumer.id, consumer); consumer.on('@close', () => this.consumers.delete(consumer.id)); consumer.on('@producerclose', () => this.consumers.delete(consumer.id)); // Emit observer event. this.#observer.safeEmit('newconsumer', consumer); return consumer; } async produceData({ id = undefined, sctpStreamParameters, label = '', protocol = '', paused = false, appData, }: DataProducerOptions = {}): Promise< DataProducer > { logger.debug('produceData()'); if (id && this.dataProducers.has(id)) { throw new TypeError(`a DataProducer with same id "${id}" already exists`); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } let type: DataProducerType; // Clone given SCTP stream parameters to not modify input data. let clonedSctpStreamParameters = utils.clone< SctpStreamParameters | undefined >(sctpStreamParameters); // If this is not a DirectTransport, sctpStreamParameters are required. if (this.type !== 'direct') { type = 'sctp'; // This may throw. ortc.validateAndNormalizeSctpStreamParameters( clonedSctpStreamParameters! ); } // If this is a DirectTransport, sctpStreamParameters must not be given. else { type = 'direct'; if (sctpStreamParameters) { logger.warn( 'produceData() | sctpStreamParameters are ignored when producing data on a DirectTransport' ); clonedSctpStreamParameters = undefined; } } const dataProducerId = id ?? utils.generateUUIDv4(); const requestOffset = createProduceDataRequest({ builder: this.channel.bufferBuilder, dataProducerId, type, sctpStreamParameters: clonedSctpStreamParameters, label, protocol, paused, }); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_PRODUCE_DATA, FbsRequest.Body.Transport_ProduceDataRequest, requestOffset, this.internal.transportId ); /* Decode Response. */ const produceDataResponse = new FbsDataProducer.DumpResponse(); response.body(produceDataResponse); const dump = parseDataProducerDumpResponse(produceDataResponse); const dataProducer: DataProducer = new DataProducerImpl({ internal: { ...this.internal, dataProducerId, }, data: { type: dump.type, sctpStreamParameters: dump.sctpStreamParameters, label: dump.label, protocol: dump.protocol, }, channel: this.channel, paused, appData, }); this.dataProducers.set(dataProducer.id, dataProducer); dataProducer.on('@close', () => { this.dataProducers.delete(dataProducer.id); this.emit('@dataproducerclose', dataProducer); }); this.emit('@newdataproducer', dataProducer); // Emit observer event. this.#observer.safeEmit('newdataproducer', dataProducer); return dataProducer; } async consumeData({ dataProducerId, ordered, maxPacketLifeTime, maxRetransmits, paused = false, subchannels, appData, }: DataConsumerOptions): Promise< DataConsumer > { logger.debug('consumeData()'); if (!dataProducerId || typeof dataProducerId !== 'string') { throw new TypeError('missing dataProducerId'); } else if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } const dataProducer = this.getDataProducerById(dataProducerId); if (!dataProducer) { throw Error(`DataProducer with id "${dataProducerId}" not found`); } let type: DataConsumerType; let sctpStreamParameters: SctpStreamParameters | undefined; let sctpStreamId: number; // If this is a DirectTransport, sctpStreamParameters must not be used. if (this.type === 'direct') { type = 'direct'; if ( ordered !== undefined || maxPacketLifeTime !== undefined || maxRetransmits !== undefined ) { logger.warn( 'consumeData() | ordered, maxPacketLifeTime and maxRetransmits are ignored when consuming data on a DirectTransport' ); } } // If this is not a DirectTransport, use sctpStreamParameters from the // DataProducer (if type 'sctp') unless they are given in method parameters. // If the DataProducer is type 'sctp' and no sctpStreamParameters are given, // generate proper ones. else { type = 'sctp'; // This may throw. sctpStreamId = this.getNextSctpStreamId(); sctpStreamParameters = dataProducer.sctpStreamParameters ? utils.clone(dataProducer.sctpStreamParameters) : { streamId: sctpStreamId, ordered: true, }; this.#sctpStreamIds![sctpStreamId] = 1; sctpStreamParameters.streamId = sctpStreamId; if (ordered !== undefined) { sctpStreamParameters.ordered = ordered; if (ordered) { sctpStreamParameters.maxPacketLifeTime = undefined; sctpStreamParameters.maxRetransmits = undefined; } } if (!ordered) { if (maxPacketLifeTime !== undefined) { sctpStreamParameters.ordered = false; sctpStreamParameters.maxPacketLifeTime = maxPacketLifeTime; } if (maxRetransmits !== undefined) { sctpStreamParameters.ordered = false; sctpStreamParameters.maxRetransmits = maxRetransmits; } } } const { label, protocol } = dataProducer; const dataConsumerId = utils.generateUUIDv4(); const requestOffset = createConsumeDataRequest({ builder: this.channel.bufferBuilder, dataConsumerId, dataProducerId, type, sctpStreamParameters, label, protocol, paused, subchannels, }); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_CONSUME_DATA, FbsRequest.Body.Transport_ConsumeDataRequest, requestOffset, this.internal.transportId ); /* Decode Response. */ const consumeDataResponse = new FbsDataConsumer.DumpResponse(); response.body(consumeDataResponse); const dump = parseDataConsumerDumpResponse(consumeDataResponse); const dataConsumer: DataConsumer = new DataConsumerImpl({ internal: { ...this.internal, dataConsumerId, }, data: { dataProducerId: dump.dataProducerId, type: dump.type, sctpStreamParameters: dump.sctpStreamParameters, label: dump.label, protocol: dump.protocol, bufferedAmountLowThreshold: dump.bufferedAmountLowThreshold, }, channel: this.channel, paused: dump.paused, subchannels: dump.subchannels, dataProducerPaused: dump.dataProducerPaused, appData, }); this.dataConsumers.set(dataConsumer.id, dataConsumer); dataConsumer.on('@close', () => { this.dataConsumers.delete(dataConsumer.id); if (this.#sctpStreamIds) { this.#sctpStreamIds[sctpStreamId] = 0; } }); dataConsumer.on('@dataproducerclose', () => { this.dataConsumers.delete(dataConsumer.id); if (this.#sctpStreamIds) { this.#sctpStreamIds[sctpStreamId] = 0; } }); // Emit observer event. this.#observer.safeEmit('newdataconsumer', dataConsumer); return dataConsumer; } async enableTraceEvent(types: TransportTraceEventType[] = []): Promise { logger.debug('enableTraceEvent()'); if (!Array.isArray(types)) { throw new TypeError('types must be an array'); } else if (types.find(type => typeof type !== 'string')) { throw new TypeError('every type must be a string'); } // Convert event types. const fbsEventTypes: FbsTransport.TraceEventType[] = []; for (const eventType of types) { try { fbsEventTypes.push(transportTraceEventTypeToFbs(eventType)); } catch (error) { // Ignore invalid event types. } } /* Build Request. */ const requestOffset = new FbsTransport.EnableTraceEventRequestT( fbsEventTypes ).pack(this.channel.bufferBuilder); await this.channel.request( FbsRequest.Method.TRANSPORT_ENABLE_TRACE_EVENT, FbsRequest.Body.Transport_EnableTraceEventRequest, requestOffset, this.internal.transportId ); } private getNextSctpStreamId(): number { if ( !this.#data.sctpParameters || typeof this.#data.sctpParameters.MIS !== 'number' ) { throw new TypeError('missing sctpParameters.MIS'); } const numStreams = this.#data.sctpParameters.MIS; if (!this.#sctpStreamIds) { this.#sctpStreamIds = Buffer.alloc(numStreams, 0); } let sctpStreamId; for (let idx = 0; idx < this.#sctpStreamIds.length; ++idx) { sctpStreamId = (this.#nextSctpStreamId + idx) % this.#sctpStreamIds.length; if (!this.#sctpStreamIds[sctpStreamId]) { this.#nextSctpStreamId = sctpStreamId + 1; return sctpStreamId; } } throw new Error('no sctpStreamId available'); } } export function portRangeToFbs( portRange: TransportPortRange = { min: 0, max: 0 } ): FbsTransport.PortRangeT { return new FbsTransport.PortRangeT(portRange.min, portRange.max); } export function socketFlagsToFbs( flags: TransportSocketFlags = {} ): FbsTransport.SocketFlagsT { return new FbsTransport.SocketFlagsT( Boolean(flags.ipv6Only), Boolean(flags.udpReusePort) ); } export function parseSctpState(fbsSctpState: FbsSctpState): SctpState { switch (fbsSctpState) { case FbsSctpState.NEW: { return 'new'; } case FbsSctpState.CONNECTING: { return 'connecting'; } case FbsSctpState.CONNECTED: { return 'connected'; } case FbsSctpState.FAILED: { return 'failed'; } case FbsSctpState.CLOSED: { return 'closed'; } } } export function parseProtocol( protocol: FbsTransport.Protocol ): TransportProtocol { switch (protocol) { case FbsTransport.Protocol.UDP: { return 'udp'; } case FbsTransport.Protocol.TCP: { return 'tcp'; } } } export function serializeProtocol( protocol: TransportProtocol ): FbsTransport.Protocol { switch (protocol) { case 'udp': { return FbsTransport.Protocol.UDP; } case 'tcp': { return FbsTransport.Protocol.TCP; } } } export function parseTuple(binary: FbsTransport.Tuple): TransportTuple { return { // @deprecated Use localAddress instead. localIp: binary.localAddress()!, localAddress: binary.localAddress()!, localPort: binary.localPort(), remoteIp: binary.remoteIp() ?? undefined, remotePort: binary.remotePort(), protocol: parseProtocol(binary.protocol()), }; } export function parseBaseTransportDump( binary: FbsTransport.Dump ): BaseTransportDump { // Retrieve producerIds. const producerIds = fbsUtils.parseVector(binary, 'producerIds'); // Retrieve consumerIds. const consumerIds = fbsUtils.parseVector(binary, 'consumerIds'); // Retrieve map SSRC consumerId. const mapSsrcConsumerId = fbsUtils.parseUint32StringVector( binary, 'mapSsrcConsumerId' ); // Retrieve map RTX SSRC consumerId. const mapRtxSsrcConsumerId = fbsUtils.parseUint32StringVector( binary, 'mapRtxSsrcConsumerId' ); // Retrieve dataProducerIds. const dataProducerIds = fbsUtils.parseVector( binary, 'dataProducerIds' ); // Retrieve dataConsumerIds. const dataConsumerIds = fbsUtils.parseVector( binary, 'dataConsumerIds' ); // Retrieve recvRtpHeaderExtesions. const recvRtpHeaderExtensions = parseRecvRtpHeaderExtensions( binary.recvRtpHeaderExtensions()! ); // Retrieve RtpListener. const rtpListener = parseRtpListenerDump(binary.rtpListener()!); // Retrieve SctpParameters. const fbsSctpParameters = binary.sctpParameters(); let sctpParameters: SctpParameters | undefined; if (fbsSctpParameters) { sctpParameters = parseSctpParametersDump(fbsSctpParameters); } // Retrieve sctpState. const sctpState = binary.sctpState() === null ? undefined : parseSctpState(binary.sctpState()!); // Retrive sctpListener. const sctpListener = binary.sctpListener() ? parseSctpListenerDump(binary.sctpListener()!) : undefined; // Retrieve traceEventTypes. const traceEventTypes = fbsUtils.parseVector( binary, 'traceEventTypes', transportTraceEventTypeFromFbs ); return { id: binary.id()!, producerIds: producerIds, consumerIds: consumerIds, mapSsrcConsumerId: mapSsrcConsumerId, mapRtxSsrcConsumerId: mapRtxSsrcConsumerId, dataProducerIds: dataProducerIds, dataConsumerIds: dataConsumerIds, recvRtpHeaderExtensions: recvRtpHeaderExtensions, rtpListener: rtpListener, maxMessageSize: binary.maxMessageSize(), sctpParameters: sctpParameters, sctpState: sctpState, sctpListener: sctpListener, traceEventTypes: traceEventTypes, }; } export function parseBaseTransportStats( binary: FbsTransport.Stats ): BaseTransportStats { const sctpState = binary.sctpState() === null ? undefined : parseSctpState(binary.sctpState()!); return { transportId: binary.transportId()!, timestamp: Number(binary.timestamp()), sctpState, bytesReceived: Number(binary.bytesReceived()), recvBitrate: Number(binary.recvBitrate()), bytesSent: Number(binary.bytesSent()), sendBitrate: Number(binary.sendBitrate()), rtpBytesReceived: Number(binary.rtpBytesReceived()), rtpRecvBitrate: Number(binary.rtpRecvBitrate()), rtpBytesSent: Number(binary.rtpBytesSent()), rtpSendBitrate: Number(binary.rtpSendBitrate()), rtxBytesReceived: Number(binary.rtxBytesReceived()), rtxRecvBitrate: Number(binary.rtxRecvBitrate()), rtxBytesSent: Number(binary.rtxBytesSent()), rtxSendBitrate: Number(binary.rtxSendBitrate()), probationBytesSent: Number(binary.probationBytesSent()), probationSendBitrate: Number(binary.probationSendBitrate()), availableOutgoingBitrate: typeof binary.availableOutgoingBitrate() === 'number' ? Number(binary.availableOutgoingBitrate()) : undefined, availableIncomingBitrate: typeof binary.availableIncomingBitrate() === 'number' ? Number(binary.availableIncomingBitrate()) : undefined, maxIncomingBitrate: typeof binary.maxIncomingBitrate() === 'number' ? Number(binary.maxIncomingBitrate()) : undefined, maxOutgoingBitrate: typeof binary.maxOutgoingBitrate() === 'number' ? Number(binary.maxOutgoingBitrate()) : undefined, minOutgoingBitrate: typeof binary.minOutgoingBitrate() === 'number' ? Number(binary.minOutgoingBitrate()) : undefined, rtpPacketLossReceived: typeof binary.rtpPacketLossReceived() === 'number' ? Number(binary.rtpPacketLossReceived()) : undefined, rtpPacketLossSent: typeof binary.rtpPacketLossSent() === 'number' ? Number(binary.rtpPacketLossSent()) : undefined, }; } export function parseTransportTraceEventData( trace: FbsTransport.TraceNotification ): TransportTraceEventData { switch (trace.type()) { case FbsTransport.TraceEventType.BWE: { const info = new FbsTransport.BweTraceInfo(); trace.info(info); return { type: 'bwe', timestamp: Number(trace.timestamp()), direction: trace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out', info: parseBweTraceInfo(info), }; } case FbsTransport.TraceEventType.PROBATION: { return { type: 'probation', timestamp: Number(trace.timestamp()), direction: trace.direction() === FbsTraceDirection.DIRECTION_IN ? 'in' : 'out', info: {}, }; } } } function parseRecvRtpHeaderExtensions( binary: FbsTransport.RecvRtpHeaderExtensions ): RecvRtpHeaderExtensions { return { mid: binary.mid() !== null ? binary.mid()! : undefined, rid: binary.rid() !== null ? binary.rid()! : undefined, rrid: binary.rrid() !== null ? binary.rrid()! : undefined, absSendTime: binary.absSendTime() !== null ? binary.absSendTime()! : undefined, transportWideCc01: binary.transportWideCc01() !== null ? binary.transportWideCc01()! : undefined, }; } function transportTraceEventTypeToFbs( eventType: TransportTraceEventType ): FbsTransport.TraceEventType { switch (eventType) { case 'probation': { return FbsTransport.TraceEventType.PROBATION; } case 'bwe': { return FbsTransport.TraceEventType.BWE; } } } function transportTraceEventTypeFromFbs( eventType: FbsTransport.TraceEventType ): TransportTraceEventType { switch (eventType) { case FbsTransport.TraceEventType.PROBATION: { return 'probation'; } case FbsTransport.TraceEventType.BWE: { return 'bwe'; } } } function parseBweTraceInfo(binary: FbsTransport.BweTraceInfo): { desiredBitrate: number; effectiveDesiredBitrate: number; minBitrate: number; maxBitrate: number; startBitrate: number; maxPaddingBitrate: number; availableBitrate: number; bweType: 'transport-cc' | 'remb'; } { return { desiredBitrate: binary.desiredBitrate(), effectiveDesiredBitrate: binary.effectiveDesiredBitrate(), minBitrate: binary.minBitrate(), maxBitrate: binary.maxBitrate(), startBitrate: binary.startBitrate(), maxPaddingBitrate: binary.maxPaddingBitrate(), availableBitrate: binary.availableBitrate(), bweType: binary.bweType() === FbsTransport.BweType.TRANSPORT_CC ? 'transport-cc' : 'remb', }; } function createConsumeRequest({ builder, producer, consumerId, rtpParameters, paused, preferredLayers, ignoreDtx, pipe, }: { builder: flatbuffers.Builder; producer: Producer; consumerId: string; rtpParameters: RtpParameters; paused: boolean; preferredLayers?: ConsumerLayers; ignoreDtx?: boolean; pipe: boolean; }): number { const rtpParametersOffset = serializeRtpParameters(builder, rtpParameters); const consumerIdOffset = builder.createString(consumerId); const producerIdOffset = builder.createString(producer.id); let consumableRtpEncodingsOffset: number | undefined; let preferredLayersOffset: number | undefined; if (producer.consumableRtpParameters.encodings) { consumableRtpEncodingsOffset = serializeRtpEncodingParameters( builder, producer.consumableRtpParameters.encodings ); } if (preferredLayers) { FbsConsumer.ConsumerLayers.startConsumerLayers(builder); FbsConsumer.ConsumerLayers.addSpatialLayer( builder, preferredLayers.spatialLayer ); if (preferredLayers.temporalLayer !== undefined) { FbsConsumer.ConsumerLayers.addTemporalLayer( builder, preferredLayers.temporalLayer ); } preferredLayersOffset = FbsConsumer.ConsumerLayers.endConsumerLayers(builder); } const ConsumeRequest = FbsTransport.ConsumeRequest; // Create Consume Request. ConsumeRequest.startConsumeRequest(builder); ConsumeRequest.addConsumerId(builder, consumerIdOffset); ConsumeRequest.addProducerId(builder, producerIdOffset); ConsumeRequest.addKind( builder, producer.kind === 'audio' ? FbsMediaKind.AUDIO : FbsMediaKind.VIDEO ); ConsumeRequest.addRtpParameters(builder, rtpParametersOffset); ConsumeRequest.addType( builder, pipe ? FbsRtpParameters.Type.PIPE : producerTypeToFbs(producer.type) ); if (consumableRtpEncodingsOffset) { ConsumeRequest.addConsumableRtpEncodings( builder, consumableRtpEncodingsOffset ); } ConsumeRequest.addPaused(builder, paused); if (preferredLayersOffset) { ConsumeRequest.addPreferredLayers(builder, preferredLayersOffset); } ConsumeRequest.addIgnoreDtx(builder, Boolean(ignoreDtx)); return ConsumeRequest.endConsumeRequest(builder); } function createProduceRequest({ builder, producerId, kind, rtpParameters, rtpMapping, paused, keyFrameRequestDelay, enableMediasoupPacketIdHeaderExtension, }: { builder: flatbuffers.Builder; producerId: string; kind: MediaKind; rtpParameters: RtpParameters; rtpMapping: ortc.RtpCodecsEncodingsMapping; paused: boolean; keyFrameRequestDelay?: number; enableMediasoupPacketIdHeaderExtension?: boolean; }): number { const producerIdOffset = builder.createString(producerId); const rtpParametersOffset = serializeRtpParameters(builder, rtpParameters); const rtpMappingOffset = ortc.serializeRtpMapping(builder, rtpMapping); FbsTransport.ProduceRequest.startProduceRequest(builder); FbsTransport.ProduceRequest.addProducerId(builder, producerIdOffset); FbsTransport.ProduceRequest.addKind( builder, kind === 'audio' ? FbsMediaKind.AUDIO : FbsMediaKind.VIDEO ); FbsTransport.ProduceRequest.addRtpParameters(builder, rtpParametersOffset); FbsTransport.ProduceRequest.addRtpMapping(builder, rtpMappingOffset); FbsTransport.ProduceRequest.addPaused(builder, paused); FbsTransport.ProduceRequest.addKeyFrameRequestDelay( builder, keyFrameRequestDelay ?? 0 ); FbsTransport.ProduceRequest.addEnableMediasoupPacketIdHeaderExtension( builder, enableMediasoupPacketIdHeaderExtension ?? false ); return FbsTransport.ProduceRequest.endProduceRequest(builder); } function createProduceDataRequest({ builder, dataProducerId, type, sctpStreamParameters, label, protocol, paused, }: { builder: flatbuffers.Builder; dataProducerId: string; type: DataProducerType; sctpStreamParameters?: SctpStreamParameters; label: string; protocol: string; paused: boolean; }): number { const dataProducerIdOffset = builder.createString(dataProducerId); const labelOffset = builder.createString(label); const protocolOffset = builder.createString(protocol); let sctpStreamParametersOffset = 0; if (sctpStreamParameters) { sctpStreamParametersOffset = serializeSctpStreamParameters( builder, sctpStreamParameters ); } FbsTransport.ProduceDataRequest.startProduceDataRequest(builder); FbsTransport.ProduceDataRequest.addDataProducerId( builder, dataProducerIdOffset ); FbsTransport.ProduceDataRequest.addType(builder, dataProducerTypeToFbs(type)); if (sctpStreamParametersOffset) { FbsTransport.ProduceDataRequest.addSctpStreamParameters( builder, sctpStreamParametersOffset ); } FbsTransport.ProduceDataRequest.addLabel(builder, labelOffset); FbsTransport.ProduceDataRequest.addProtocol(builder, protocolOffset); FbsTransport.ProduceDataRequest.addPaused(builder, paused); return FbsTransport.ProduceDataRequest.endProduceDataRequest(builder); } function createConsumeDataRequest({ builder, dataConsumerId, dataProducerId, type, sctpStreamParameters, label, protocol, paused, subchannels = [], }: { builder: flatbuffers.Builder; dataConsumerId: string; dataProducerId: string; type: DataConsumerType; sctpStreamParameters?: SctpStreamParameters; label: string; protocol: string; paused: boolean; subchannels?: number[]; }): number { const dataConsumerIdOffset = builder.createString(dataConsumerId); const dataProducerIdOffset = builder.createString(dataProducerId); const labelOffset = builder.createString(label); const protocolOffset = builder.createString(protocol); let sctpStreamParametersOffset = 0; if (sctpStreamParameters) { sctpStreamParametersOffset = serializeSctpStreamParameters( builder, sctpStreamParameters ); } const subchannelsOffset = FbsTransport.ConsumeDataRequest.createSubchannelsVector( builder, subchannels ); FbsTransport.ConsumeDataRequest.startConsumeDataRequest(builder); FbsTransport.ConsumeDataRequest.addDataConsumerId( builder, dataConsumerIdOffset ); FbsTransport.ConsumeDataRequest.addDataProducerId( builder, dataProducerIdOffset ); FbsTransport.ConsumeDataRequest.addType(builder, dataConsumerTypeToFbs(type)); if (sctpStreamParametersOffset) { FbsTransport.ConsumeDataRequest.addSctpStreamParameters( builder, sctpStreamParametersOffset ); } FbsTransport.ConsumeDataRequest.addLabel(builder, labelOffset); FbsTransport.ConsumeDataRequest.addProtocol(builder, protocolOffset); FbsTransport.ConsumeDataRequest.addPaused(builder, paused); FbsTransport.ConsumeDataRequest.addSubchannels(builder, subchannelsOffset); return FbsTransport.ConsumeDataRequest.endConsumeDataRequest(builder); } function parseRtpListenerDump( binary: FbsTransport.RtpListener ): RtpListenerDump { // Retrieve ssrcTable. const ssrcTable = fbsUtils.parseUint32StringVector(binary, 'ssrcTable'); // Retrieve midTable. const midTable = fbsUtils.parseUint32StringVector(binary, 'midTable'); // Retrieve ridTable. const ridTable = fbsUtils.parseUint32StringVector(binary, 'ridTable'); return { ssrcTable, midTable, ridTable, }; } function parseSctpListenerDump( binary: FbsTransport.SctpListener ): SctpListenerDump { // Retrieve streamIdTable. const streamIdTable = fbsUtils.parseUint32StringVector( binary, 'streamIdTable' ); return { streamIdTable }; } ================================================ FILE: node/src/TransportTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { Producer, ProducerOptions } from './ProducerTypes'; import type { Consumer, ConsumerOptions } from './ConsumerTypes'; import type { DataProducer, DataProducerOptions } from './DataProducerTypes'; import type { DataConsumer, DataConsumerOptions } from './DataConsumerTypes'; import type { SctpParameters } from './sctpParametersTypes'; import type { AppData } from './types'; /** * Transport type. */ export type TransportType = 'webrtc' | 'plain' | 'pipe' | 'direct'; export type TransportListenInfo = { /** * Network protocol. */ protocol: TransportProtocol; /** * Listening IPv4 or IPv6. */ ip: string; /** * @deprecated Use |announcedAddress| instead. * * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT * with private IP). */ announcedIp?: string; /** * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT * with private IP). */ announcedAddress?: string; /** * In transports with ICE candidates, this field determines whether to also * expose an ICE candidate with the IP of the |ip| field when |announcedAddress| * is given. */ exposeInternalIp?: boolean; /** * Listening port. */ port?: number; /** * Listening port range. If given then |port| will be ignored. */ portRange?: TransportPortRange; /** * Socket flags. */ flags?: TransportSocketFlags; /** * Send buffer size (bytes). */ sendBufferSize?: number; /** * Recv buffer size (bytes). */ recvBufferSize?: number; }; /** * Use TransportListenInfo instead. * @deprecated */ export type TransportListenIp = { /** * Listening IPv4 or IPv6. */ ip: string; /** * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT * with private IP). */ announcedIp?: string; }; /** * Transport protocol. */ export type TransportProtocol = 'udp' | 'tcp'; /** * Port range.. */ export type TransportPortRange = { /** * Lowest port in the range. */ min: number; /** * Highest port in the range. */ max: number; }; /** * UDP/TCP socket flags. */ export type TransportSocketFlags = { /** * Disable dual-stack support so only IPv6 is used (only if |ip| is IPv6). */ ipv6Only?: boolean; /** * Make different transports bind to the same IP and port (only for UDP). * Useful for multicast scenarios with plain transport. Use with caution. */ udpReusePort?: boolean; }; export type TransportTuple = { // @deprecated Use localAddress instead. localIp: string; localAddress: string; localPort: number; remoteIp?: string; remotePort?: number; protocol: TransportProtocol; }; export type SctpState = | 'new' | 'connecting' | 'connected' | 'failed' | 'closed'; export type RtpListenerDump = { ssrcTable: { key: number; value: string }[]; midTable: { key: number; value: string }[]; ridTable: { key: number; value: string }[]; }; export type SctpListenerDump = { streamIdTable: { key: number; value: string }[]; }; export type RecvRtpHeaderExtensions = { mid?: number; rid?: number; rrid?: number; absSendTime?: number; transportWideCc01?: number; }; export type BaseTransportDump = { id: string; producerIds: string[]; consumerIds: string[]; mapSsrcConsumerId: { key: number; value: string }[]; mapRtxSsrcConsumerId: { key: number; value: string }[]; recvRtpHeaderExtensions: RecvRtpHeaderExtensions; rtpListener: RtpListenerDump; maxMessageSize: number; dataProducerIds: string[]; dataConsumerIds: string[]; sctpParameters?: SctpParameters; sctpState?: SctpState; sctpListener?: SctpListenerDump; traceEventTypes?: string[]; }; export type BaseTransportStats = { transportId: string; timestamp: number; sctpState?: SctpState; bytesReceived: number; recvBitrate: number; bytesSent: number; sendBitrate: number; rtpBytesReceived: number; rtpRecvBitrate: number; rtpBytesSent: number; rtpSendBitrate: number; rtxBytesReceived: number; rtxRecvBitrate: number; rtxBytesSent: number; rtxSendBitrate: number; probationBytesSent: number; probationSendBitrate: number; availableOutgoingBitrate?: number; availableIncomingBitrate?: number; maxIncomingBitrate?: number; maxOutgoingBitrate?: number; minOutgoingBitrate?: number; rtpPacketLossReceived?: number; rtpPacketLossSent?: number; }; /** * Valid types for 'trace' event. */ export type TransportTraceEventType = 'probation' | 'bwe'; /** * 'trace' event data. */ export type TransportTraceEventData = { /** * Trace type. */ type: TransportTraceEventType; /** * Event timestamp. */ timestamp: number; /** * Event direction. */ direction: 'in' | 'out'; /** * Per type information. */ info: unknown; }; export type TransportEvents = { routerclose: []; listenserverclose: []; trace: [TransportTraceEventData]; // Private events. '@close': []; '@newproducer': [Producer]; '@producerclose': [Producer]; '@newdataproducer': [DataProducer]; '@dataproducerclose': [DataProducer]; '@listenserverclose': []; }; export type TransportObserver = EnhancedEventEmitter; export type TransportObserverEvents = { close: []; newproducer: [Producer]; newconsumer: [Consumer]; newdataproducer: [DataProducer]; newdataconsumer: [DataConsumer]; trace: [TransportTraceEventData]; }; export interface Transport< TransportAppData extends AppData = AppData, Events extends TransportEvents = TransportEvents, Observer extends TransportObserver = TransportObserver, > extends EnhancedEventEmitter { /** * Transport id. */ get id(): string; /** * Whether the Transport is closed. */ get closed(): boolean; /** * Transport type. * * @virtual * @privateRemarks * - It's marked as virtual getter since each Transport class overrides it. */ get type(): TransportType; /** * App custom data. */ get appData(): TransportAppData; /** * App custom data setter. */ set appData(appData: TransportAppData); /** * Observer. * * @virtual */ get observer(): Observer; /** * Close the Transport. * * @virtual */ close(): void; /** * Router was closed. * * @private * @virtual */ routerClosed(): void; /** * Listen server was closed (this just happens in WebRtcTransports when their * associated WebRtcServer is closed). * * @private * @virtual */ listenServerClosed(): void; /** * Dump Transport. * * @abstract */ dump(): Promise; /** * Get Transport stats. * * @abstract */ getStats(): Promise; /** * Provide the Transport remote parameters. * * @abstract */ connect(params: unknown): Promise; /** * Set maximum incoming bitrate for receiving media. * * @virtual * @privateRemarks * - It's marked as virtual method because DirectTransport overrides it. */ setMaxIncomingBitrate(bitrate: number): Promise; /** * Set maximum outgoing bitrate for sending media. * * @virtual * @privateRemarks * - It's marked as virtual method because DirectTransport overrides it. */ setMaxOutgoingBitrate(bitrate: number): Promise; /** * Set minimum outgoing bitrate for sending media. * * @virtual * @privateRemarks * - It's marked as virtual method because DirectTransport overrides it. */ setMinOutgoingBitrate(bitrate: number): Promise; /** * Create a Producer. */ produce( options: ProducerOptions ): Promise>; /** * Create a Consumer. * * @virtual * @privateRemarks * - It's marked as virtual method because PipeTransport overrides it. */ consume( options: ConsumerOptions ): Promise>; /** * Create a DataProducer. */ produceData( options?: DataProducerOptions ): Promise>; /** * Create a DataConsumer. */ consumeData( options: DataConsumerOptions ): Promise>; /** * Enable 'trace' event. */ enableTraceEvent(types?: TransportTraceEventType[]): Promise; } ================================================ FILE: node/src/WebRtcServer.ts ================================================ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { Channel } from './Channel'; import type { WebRtcServer, IpPort, IceUserNameFragment, TupleHash, WebRtcServerDump, WebRtcServerEvents, WebRtcServerObserver, WebRtcServerObserverEvents, } from './WebRtcServerTypes'; import type { WebRtcTransport } from './WebRtcTransportTypes'; import type { AppData } from './types'; import * as fbsUtils from './fbsUtils'; import { Body as RequestBody, Method } from './fbs/request'; import * as FbsWorker from './fbs/worker'; import * as FbsWebRtcServer from './fbs/web-rtc-server'; type WebRtcServerInternal = { webRtcServerId: string; }; const logger = new Logger('WebRtcServer'); export class WebRtcServerImpl extends EnhancedEventEmitter implements WebRtcServer { // Internal data. readonly #internal: WebRtcServerInternal; // Channel instance. readonly #channel: Channel; // Closed flag. #closed = false; // Custom app data. #appData: WebRtcServerAppData; // Transports map. readonly #webRtcTransports: Map = new Map(); // Observer instance. readonly #observer: WebRtcServerObserver = new EnhancedEventEmitter(); constructor({ internal, channel, appData, }: { internal: WebRtcServerInternal; channel: Channel; appData?: WebRtcServerAppData; }) { super(); logger.debug('constructor()'); this.#internal = internal; this.#channel = channel; this.#appData = appData ?? ({} as WebRtcServerAppData); this.handleListenerError(); } get id(): string { return this.#internal.webRtcServerId; } get closed(): boolean { return this.#closed; } get appData(): WebRtcServerAppData { return this.#appData; } set appData(appData: WebRtcServerAppData) { this.#appData = appData; } get observer(): WebRtcServerObserver { return this.#observer; } /** * Just for testing purposes. */ get webRtcTransportsForTesting(): Map { return this.#webRtcTransports; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; // Build the request. const requestOffset = new FbsWorker.CloseWebRtcServerRequestT( this.#internal.webRtcServerId ).pack(this.#channel.bufferBuilder); this.#channel .request( Method.WORKER_WEBRTCSERVER_CLOSE, RequestBody.Worker_CloseWebRtcServerRequest, requestOffset ) .catch(() => {}); // Close every WebRtcTransport. for (const webRtcTransport of this.#webRtcTransports.values()) { webRtcTransport.listenServerClosed(); // Emit observer event. this.#observer.safeEmit('webrtctransportunhandled', webRtcTransport); } this.#webRtcTransports.clear(); this.emit('@close'); // Emit observer event. this.#observer.safeEmit('close'); } workerClosed(): void { if (this.#closed) { return; } logger.debug('workerClosed()'); this.#closed = true; // NOTE: No need to close WebRtcTransports since they are closed by their // respective Router parents. this.#webRtcTransports.clear(); this.safeEmit('workerclose'); // Emit observer event. this.#observer.safeEmit('close'); } async dump(): Promise { logger.debug('dump()'); const response = await this.#channel.request( Method.WEBRTCSERVER_DUMP, undefined, undefined, this.#internal.webRtcServerId ); /* Decode Response. */ const dump = new FbsWebRtcServer.DumpResponse(); response.body(dump); return parseWebRtcServerDump(dump); } handleWebRtcTransport(webRtcTransport: WebRtcTransport): void { this.#webRtcTransports.set(webRtcTransport.id, webRtcTransport); // Emit observer event. this.#observer.safeEmit('webrtctransporthandled', webRtcTransport); webRtcTransport.on('@close', () => { this.#webRtcTransports.delete(webRtcTransport.id); // Emit observer event. this.#observer.safeEmit('webrtctransportunhandled', webRtcTransport); }); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } function parseIpPort(binary: FbsWebRtcServer.IpPort): IpPort { return { ip: binary.ip()!, port: binary.port(), }; } function parseIceUserNameFragment( binary: FbsWebRtcServer.IceUserNameFragment ): IceUserNameFragment { return { localIceUsernameFragment: binary.localIceUsernameFragment()!, webRtcTransportId: binary.webRtcTransportId()!, }; } function parseTupleHash(binary: FbsWebRtcServer.TupleHash): TupleHash { return { tupleHash: Number(binary.tupleHash()), webRtcTransportId: binary.webRtcTransportId()!, }; } function parseWebRtcServerDump( data: FbsWebRtcServer.DumpResponse ): WebRtcServerDump { return { id: data.id()!, udpSockets: fbsUtils.parseVector(data, 'udpSockets', parseIpPort), tcpServers: fbsUtils.parseVector(data, 'tcpServers', parseIpPort), webRtcTransportIds: fbsUtils.parseVector(data, 'webRtcTransportIds'), localIceUsernameFragments: fbsUtils.parseVector( data, 'localIceUsernameFragments', parseIceUserNameFragment ), tupleHashes: fbsUtils.parseVector(data, 'tupleHashes', parseTupleHash), }; } ================================================ FILE: node/src/WebRtcServerTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { TransportListenInfo } from './TransportTypes'; import type { WebRtcTransport } from './WebRtcTransportTypes'; import type { AppData } from './types'; export type WebRtcServerOptions = { /** * Listen infos. */ listenInfos: TransportListenInfo[]; /** * Custom application data. */ appData?: WebRtcServerAppData; }; /** * @deprecated Use TransportListenInfo instead. */ export type WebRtcServerListenInfo = TransportListenInfo; export type IpPort = { ip: string; port: number; }; export type IceUserNameFragment = { localIceUsernameFragment: string; webRtcTransportId: string; }; export type TupleHash = { tupleHash: number; webRtcTransportId: string; }; export type WebRtcServerDump = { id: string; udpSockets: IpPort[]; tcpServers: IpPort[]; webRtcTransportIds: string[]; localIceUsernameFragments: IceUserNameFragment[]; tupleHashes: TupleHash[]; }; export type WebRtcServerEvents = { workerclose: []; // Private events. '@close': []; }; export type WebRtcServerObserver = EnhancedEventEmitter; export type WebRtcServerObserverEvents = { close: []; webrtctransporthandled: [WebRtcTransport]; webrtctransportunhandled: [WebRtcTransport]; }; export interface WebRtcServer< WebRtcServerAppData extends AppData = AppData, > extends EnhancedEventEmitter { /** * WebRtcServer id. */ get id(): string; /** * Whether the WebRtcServer is closed. */ get closed(): boolean; /** * App custom data. */ get appData(): WebRtcServerAppData; /** * App custom data setter. */ set appData(appData: WebRtcServerAppData); /** * Observer. */ get observer(): WebRtcServerObserver; /** * Close the WebRtcServer. */ close(): void; /** * Worker was closed. * * @private */ workerClosed(): void; /** * Dump WebRtcServer. */ dump(): Promise; /** * @private */ handleWebRtcTransport(webRtcTransport: WebRtcTransport): void; } ================================================ FILE: node/src/WebRtcTransport.ts ================================================ import type * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { WebRtcTransport, IceParameters, IceCandidate, DtlsParameters, FingerprintAlgorithm, DtlsFingerprint, IceRole, IceState, IceCandidateType, IceCandidateTcpType, DtlsRole, DtlsState, WebRtcTransportDump, WebRtcTransportStat, WebRtcTransportEvents, WebRtcTransportObserver, WebRtcTransportObserverEvents, } from './WebRtcTransportTypes'; import type { Transport, TransportTuple, SctpState } from './TransportTypes'; import { TransportImpl, TransportConstructorOptions, parseSctpState, parseBaseTransportDump, parseBaseTransportStats, parseProtocol, parseTransportTraceEventData, parseTuple, } from './Transport'; import type { SctpParameters } from './sctpParametersTypes'; import type { AppData } from './types'; import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsTransport from './fbs/transport'; import * as FbsWebRtcTransport from './fbs/web-rtc-transport'; import { DtlsState as FbsDtlsState } from './fbs/web-rtc-transport/dtls-state'; import { DtlsRole as FbsDtlsRole } from './fbs/web-rtc-transport/dtls-role'; import { FingerprintAlgorithm as FbsFingerprintAlgorithm } from './fbs/web-rtc-transport/fingerprint-algorithm'; import { IceState as FbsIceState } from './fbs/web-rtc-transport/ice-state'; import { IceRole as FbsIceRole } from './fbs/web-rtc-transport/ice-role'; import { IceCandidateType as FbsIceCandidateType } from './fbs/web-rtc-transport/ice-candidate-type'; import { IceCandidateTcpType as FbsIceCandidateTcpType } from './fbs/web-rtc-transport/ice-candidate-tcp-type'; type WebRtcTransportConstructorOptions = TransportConstructorOptions & { data: WebRtcTransportData; }; export type WebRtcTransportData = { iceRole: 'controlled'; iceParameters: IceParameters; iceCandidates: IceCandidate[]; iceState: IceState; iceSelectedTuple?: TransportTuple; dtlsParameters: DtlsParameters; dtlsState: DtlsState; dtlsRemoteCert?: string; sctpParameters?: SctpParameters; sctpState?: SctpState; }; const logger = new Logger('WebRtcTransport'); export class WebRtcTransportImpl< WebRtcTransportAppData extends AppData = AppData, > extends TransportImpl< WebRtcTransportAppData, WebRtcTransportEvents, WebRtcTransportObserver > implements Transport, WebRtcTransport { // WebRtcTransport data. readonly #data: WebRtcTransportData; constructor( options: WebRtcTransportConstructorOptions ) { const observer: WebRtcTransportObserver = new EnhancedEventEmitter(); super(options, observer); logger.debug('constructor()'); const { data } = options; this.#data = { iceRole: data.iceRole, iceParameters: data.iceParameters, iceCandidates: data.iceCandidates, iceState: data.iceState, iceSelectedTuple: data.iceSelectedTuple, dtlsParameters: data.dtlsParameters, dtlsState: data.dtlsState, dtlsRemoteCert: data.dtlsRemoteCert, sctpParameters: data.sctpParameters, sctpState: data.sctpState, }; this.handleWorkerNotifications(); this.handleListenerError(); } get type(): 'webrtc' { return 'webrtc'; } override get observer(): WebRtcTransportObserver { return super.observer; } get iceRole(): 'controlled' { return this.#data.iceRole; } get iceParameters(): IceParameters { return this.#data.iceParameters; } get iceCandidates(): IceCandidate[] { return this.#data.iceCandidates; } get iceState(): IceState { return this.#data.iceState; } get iceSelectedTuple(): TransportTuple | undefined { return this.#data.iceSelectedTuple; } get dtlsParameters(): DtlsParameters { return this.#data.dtlsParameters; } get dtlsState(): DtlsState { return this.#data.dtlsState; } get dtlsRemoteCert(): string | undefined { return this.#data.dtlsRemoteCert; } get sctpParameters(): SctpParameters | undefined { return this.#data.sctpParameters; } get sctpState(): SctpState | undefined { return this.#data.sctpState; } override close(): void { if (this.closed) { return; } this.#data.iceState = 'closed'; this.#data.iceSelectedTuple = undefined; this.#data.dtlsState = 'closed'; if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.close(); } override routerClosed(): void { if (this.closed) { return; } this.#data.iceState = 'closed'; this.#data.iceSelectedTuple = undefined; this.#data.dtlsState = 'closed'; if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.routerClosed(); } override listenServerClosed(): void { if (this.closed) { return; } this.#data.iceState = 'closed'; this.#data.iceSelectedTuple = undefined; this.#data.dtlsState = 'closed'; if (this.#data.sctpState) { this.#data.sctpState = 'closed'; } super.listenServerClosed(); } async dump(): Promise { logger.debug('dump()'); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_DUMP, undefined, undefined, this.internal.transportId ); /* Decode Response. */ const data = new FbsWebRtcTransport.DumpResponse(); response.body(data); return parseWebRtcTransportDumpResponse(data); } async getStats(): Promise { logger.debug('getStats()'); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_GET_STATS, undefined, undefined, this.internal.transportId ); /* Decode Response. */ const data = new FbsWebRtcTransport.GetStatsResponse(); response.body(data); return [parseGetStatsResponse(data)]; } async connect({ dtlsParameters, }: { dtlsParameters: DtlsParameters; }): Promise { logger.debug('connect()'); const requestOffset = createConnectRequest({ builder: this.channel.bufferBuilder, dtlsParameters, }); // Wait for response. const response = await this.channel.request( FbsRequest.Method.WEBRTCTRANSPORT_CONNECT, FbsRequest.Body.WebRtcTransport_ConnectRequest, requestOffset, this.internal.transportId ); /* Decode Response. */ const data = new FbsWebRtcTransport.ConnectResponse(); response.body(data); // Update data. this.#data.dtlsParameters.role = dtlsRoleFromFbs(data.dtlsLocalRole()); } async restartIce(): Promise { logger.debug('restartIce()'); const response = await this.channel.request( FbsRequest.Method.TRANSPORT_RESTART_ICE, undefined, undefined, this.internal.transportId ); /* Decode Response. */ const restartIceResponse = new FbsTransport.RestartIceResponse(); response.body(restartIceResponse); const iceParameters = { usernameFragment: restartIceResponse.usernameFragment()!, password: restartIceResponse.password()!, iceLite: restartIceResponse.iceLite(), }; this.#data.iceParameters = iceParameters; return iceParameters; } private handleWorkerNotifications(): void { this.channel.on( this.internal.transportId, (event: Event, data?: Notification) => { switch (event) { case Event.WEBRTCTRANSPORT_ICE_STATE_CHANGE: { const notification = new FbsWebRtcTransport.IceStateChangeNotification(); data!.body(notification); const iceState = iceStateFromFbs(notification.iceState()); this.#data.iceState = iceState; this.safeEmit('icestatechange', iceState); // Emit observer event. this.observer.safeEmit('icestatechange', iceState); break; } case Event.WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE: { const notification = new FbsWebRtcTransport.IceSelectedTupleChangeNotification(); data!.body(notification); const iceSelectedTuple = parseTuple(notification.tuple()!); this.#data.iceSelectedTuple = iceSelectedTuple; this.safeEmit('iceselectedtuplechange', iceSelectedTuple); // Emit observer event. this.observer.safeEmit('iceselectedtuplechange', iceSelectedTuple); break; } case Event.WEBRTCTRANSPORT_DTLS_STATE_CHANGE: { const notification = new FbsWebRtcTransport.DtlsStateChangeNotification(); data!.body(notification); const dtlsState = dtlsStateFromFbs(notification.dtlsState()); this.#data.dtlsState = dtlsState; if (dtlsState === 'connected') { this.#data.dtlsRemoteCert = notification.remoteCert()!; } this.safeEmit('dtlsstatechange', dtlsState); // Emit observer event. this.observer.safeEmit('dtlsstatechange', dtlsState); break; } case Event.TRANSPORT_SCTP_STATE_CHANGE: { const notification = new FbsTransport.SctpStateChangeNotification(); data!.body(notification); const sctpState = parseSctpState(notification.sctpState()); this.#data.sctpState = sctpState; this.safeEmit('sctpstatechange', sctpState); // Emit observer event. this.observer.safeEmit('sctpstatechange', sctpState); break; } case Event.TRANSPORT_TRACE: { const notification = new FbsTransport.TraceNotification(); data!.body(notification); const trace = parseTransportTraceEventData(notification); this.safeEmit('trace', trace); // Emit observer event. this.observer.safeEmit('trace', trace); break; } default: { logger.error(`ignoring unknown event "${event}"`); } } } ); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } } function iceStateFromFbs(fbsIceState: FbsIceState): IceState { switch (fbsIceState) { case FbsIceState.NEW: { return 'new'; } case FbsIceState.CONNECTED: { return 'connected'; } case FbsIceState.COMPLETED: { return 'completed'; } case FbsIceState.DISCONNECTED: { return 'disconnected'; } } } function iceRoleFromFbs(role: FbsIceRole): IceRole { switch (role) { case FbsIceRole.CONTROLLED: { return 'controlled'; } case FbsIceRole.CONTROLLING: { return 'controlling'; } } } function iceCandidateTypeFromFbs(type: FbsIceCandidateType): IceCandidateType { switch (type) { case FbsIceCandidateType.HOST: { return 'host'; } } } function iceCandidateTcpTypeFromFbs( type: FbsIceCandidateTcpType ): IceCandidateTcpType { switch (type) { case FbsIceCandidateTcpType.PASSIVE: { return 'passive'; } } } function dtlsStateFromFbs(fbsDtlsState: FbsDtlsState): DtlsState { switch (fbsDtlsState) { case FbsDtlsState.NEW: { return 'new'; } case FbsDtlsState.CONNECTING: { return 'connecting'; } case FbsDtlsState.CONNECTED: { return 'connected'; } case FbsDtlsState.FAILED: { return 'failed'; } case FbsDtlsState.CLOSED: { return 'closed'; } } } function dtlsRoleFromFbs(role: FbsDtlsRole): DtlsRole { switch (role) { case FbsDtlsRole.AUTO: { return 'auto'; } case FbsDtlsRole.CLIENT: { return 'client'; } case FbsDtlsRole.SERVER: { return 'server'; } } } function fingerprintAlgorithmsFromFbs( algorithm: FbsFingerprintAlgorithm ): FingerprintAlgorithm { switch (algorithm) { case FbsFingerprintAlgorithm.SHA1: { return 'sha-1'; } case FbsFingerprintAlgorithm.SHA224: { return 'sha-224'; } case FbsFingerprintAlgorithm.SHA256: { return 'sha-256'; } case FbsFingerprintAlgorithm.SHA384: { return 'sha-384'; } case FbsFingerprintAlgorithm.SHA512: { return 'sha-512'; } } } function fingerprintAlgorithmToFbs( algorithm: FingerprintAlgorithm ): FbsFingerprintAlgorithm { switch (algorithm) { case 'sha-1': { return FbsFingerprintAlgorithm.SHA1; } case 'sha-224': { return FbsFingerprintAlgorithm.SHA224; } case 'sha-256': { return FbsFingerprintAlgorithm.SHA256; } case 'sha-384': { return FbsFingerprintAlgorithm.SHA384; } case 'sha-512': { return FbsFingerprintAlgorithm.SHA512; } default: { throw new TypeError(`invalid FingerprintAlgorithm: ${algorithm}`); } } } function dtlsRoleToFbs(role: DtlsRole): FbsDtlsRole { switch (role) { case 'auto': { return FbsDtlsRole.AUTO; } case 'client': { return FbsDtlsRole.CLIENT; } case 'server': { return FbsDtlsRole.SERVER; } default: { throw new TypeError(`invalid DtlsRole: ${role}`); } } } export function parseWebRtcTransportDumpResponse( binary: FbsWebRtcTransport.DumpResponse ): WebRtcTransportDump { // Retrieve BaseTransportDump. const baseTransportDump = parseBaseTransportDump(binary.base()!); // Retrieve ICE candidates. const iceCandidates = fbsUtils.parseVector( binary, 'iceCandidates', parseIceCandidate ); // Retrieve ICE parameters. const iceParameters = parseIceParameters(binary.iceParameters()!); // Retrieve DTLS parameters. const dtlsParameters = parseDtlsParameters(binary.dtlsParameters()!); return { ...baseTransportDump, sctpParameters: baseTransportDump.sctpParameters, sctpState: baseTransportDump.sctpState, iceRole: 'controlled', iceParameters: iceParameters, iceCandidates: iceCandidates, iceState: iceStateFromFbs(binary.iceState()), dtlsParameters: dtlsParameters, dtlsState: dtlsStateFromFbs(binary.dtlsState()), }; } function createConnectRequest({ builder, dtlsParameters, }: { builder: flatbuffers.Builder; dtlsParameters: DtlsParameters; }): number { // Serialize DtlsParameters. This can throw. const dtlsParametersOffset = serializeDtlsParameters(builder, dtlsParameters); return FbsWebRtcTransport.ConnectRequest.createConnectRequest( builder, dtlsParametersOffset ); } function parseGetStatsResponse( binary: FbsWebRtcTransport.GetStatsResponse ): WebRtcTransportStat { const base = parseBaseTransportStats(binary.base()!); return { ...base, type: 'webrtc-transport', iceRole: iceRoleFromFbs(binary.iceRole()), iceState: iceStateFromFbs(binary.iceState()), iceSelectedTuple: binary.iceSelectedTuple() ? parseTuple(binary.iceSelectedTuple()!) : undefined, dtlsState: dtlsStateFromFbs(binary.dtlsState()), }; } function parseIceCandidate( binary: FbsWebRtcTransport.IceCandidate ): IceCandidate { return { foundation: binary.foundation()!, priority: binary.priority(), ip: binary.address()!, address: binary.address()!, protocol: parseProtocol(binary.protocol()), port: binary.port(), type: iceCandidateTypeFromFbs(binary.type()), tcpType: binary.tcpType() === null ? undefined : iceCandidateTcpTypeFromFbs(binary.tcpType()!), }; } function parseIceParameters( binary: FbsWebRtcTransport.IceParameters ): IceParameters { return { usernameFragment: binary.usernameFragment()!, password: binary.password()!, iceLite: binary.iceLite(), }; } function parseDtlsParameters( binary: FbsWebRtcTransport.DtlsParameters ): DtlsParameters { const fingerprints: DtlsFingerprint[] = []; for (let i = 0; i < binary.fingerprintsLength(); ++i) { const fbsFingerprint = binary.fingerprints(i)!; const fingerPrint: DtlsFingerprint = { algorithm: fingerprintAlgorithmsFromFbs(fbsFingerprint.algorithm()), value: fbsFingerprint.value()!, }; fingerprints.push(fingerPrint); } return { fingerprints: fingerprints, role: binary.role() === null ? undefined : dtlsRoleFromFbs(binary.role()), }; } function serializeDtlsParameters( builder: flatbuffers.Builder, dtlsParameters: DtlsParameters ): number { const fingerprints: number[] = []; for (const fingerprint of dtlsParameters.fingerprints) { const algorithm = fingerprintAlgorithmToFbs(fingerprint.algorithm); const valueOffset = builder.createString(fingerprint.value); const fingerprintOffset = FbsWebRtcTransport.Fingerprint.createFingerprint( builder, algorithm, valueOffset ); fingerprints.push(fingerprintOffset); } const fingerprintsOffset = FbsWebRtcTransport.DtlsParameters.createFingerprintsVector( builder, fingerprints ); const role = dtlsParameters.role !== undefined ? dtlsRoleToFbs(dtlsParameters.role) : FbsWebRtcTransport.DtlsRole.AUTO; return FbsWebRtcTransport.DtlsParameters.createDtlsParameters( builder, fingerprintsOffset, role ); } ================================================ FILE: node/src/WebRtcTransportTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { Transport, TransportListenInfo, TransportListenIp, TransportProtocol, TransportTuple, SctpState, BaseTransportDump, BaseTransportStats, TransportEvents, TransportObserverEvents, } from './TransportTypes'; import type { WebRtcServer } from './WebRtcServerTypes'; import type { SctpParameters, NumSctpStreams } from './sctpParametersTypes'; import type { Either, AppData } from './types'; export type WebRtcTransportOptions< WebRtcTransportAppData extends AppData = AppData, > = WebRtcTransportOptionsBase & WebRtcTransportListen; type WebRtcTransportOptionsBase = { /** * Listen in UDP. Default true. */ enableUdp?: boolean; /** * Listen in TCP. Default true if webrtcServer is given, false otherwise. */ enableTcp?: boolean; /** * Prefer UDP. Default false. */ preferUdp?: boolean; /** * Prefer TCP. Default false. */ preferTcp?: boolean; /** * ICE consent timeout (in seconds). If 0 it is disabled. Default 30. */ iceConsentTimeout?: number; /** * Initial available outgoing bitrate (in bps). Default 600000. */ initialAvailableOutgoingBitrate?: number; /** * Create a SCTP association. Default false. */ enableSctp?: boolean; /** * SCTP streams number. */ numSctpStreams?: NumSctpStreams; /** * Maximum allowed size for SCTP messages sent by DataProducers. * Default 262144. */ maxSctpMessageSize?: number; /** * Maximum SCTP send buffer used by DataConsumers. * Default 262144. */ sctpSendBufferSize?: number; /** * Custom application data. */ appData?: WebRtcTransportAppData; }; type WebRtcTransportListen = Either< Either< WebRtcTransportListenIndividualListenInfo, WebRtcTransportListenIndividualListenIp >, WebRtcTransportListenServer >; type WebRtcTransportListenIndividualListenInfo = { /** * Listening info. */ listenInfos: TransportListenInfo[]; }; type WebRtcTransportListenIndividualListenIp = { /** * Listening IP address or addresses in order of preference (first one is the * preferred one). */ listenIps: (TransportListenIp | string)[]; /** * Fixed port to listen on instead of selecting automatically from Worker's port * range. */ port?: number; }; type WebRtcTransportListenServer = { /** * Instance of WebRtcServer. */ webRtcServer: WebRtcServer; }; export type IceParameters = { usernameFragment: string; password: string; iceLite?: boolean; }; export type IceCandidate = { foundation: string; priority: number; // @deprecated Use |address| instead. ip: string; address: string; protocol: TransportProtocol; port: number; type: IceCandidateType; tcpType?: IceCandidateTcpType; }; export type DtlsParameters = { role?: DtlsRole; fingerprints: DtlsFingerprint[]; }; /** * The hash function algorithm (as defined in the "Hash function Textual Names" * registry initially specified in RFC 4572 Section 8). */ export type FingerprintAlgorithm = | 'sha-1' | 'sha-224' | 'sha-256' | 'sha-384' | 'sha-512'; /** * The hash function algorithm and its corresponding certificate fingerprint * value (in lowercase hex string as expressed utilizing the syntax of * "fingerprint" in RFC 4572 Section 5). */ export type DtlsFingerprint = { algorithm: FingerprintAlgorithm; value: string; }; export type IceRole = 'controlled' | 'controlling'; export type IceState = | 'new' | 'connected' | 'completed' | 'disconnected' | 'closed'; export type IceCandidateType = 'host'; export type IceCandidateTcpType = 'passive'; export type DtlsRole = 'auto' | 'client' | 'server'; export type DtlsState = | 'new' | 'connecting' | 'connected' | 'failed' | 'closed'; export type WebRtcTransportDump = BaseTransportDump & { iceRole: 'controlled'; iceParameters: IceParameters; iceCandidates: IceCandidate[]; iceState: IceState; iceSelectedTuple?: TransportTuple; dtlsParameters: DtlsParameters; dtlsState: DtlsState; dtlsRemoteCert?: string; }; export type WebRtcTransportStat = BaseTransportStats & { type: string; iceRole: string; iceState: IceState; iceSelectedTuple?: TransportTuple; dtlsState: DtlsState; }; export type WebRtcTransportEvents = TransportEvents & { icestatechange: [IceState]; iceselectedtuplechange: [TransportTuple]; dtlsstatechange: [DtlsState]; sctpstatechange: [SctpState]; }; export type WebRtcTransportObserver = EnhancedEventEmitter; export type WebRtcTransportObserverEvents = TransportObserverEvents & { icestatechange: [IceState]; iceselectedtuplechange: [TransportTuple]; dtlsstatechange: [DtlsState]; sctpstatechange: [SctpState]; }; export interface WebRtcTransport< WebRtcTransportAppData extends AppData = AppData, > extends Transport< WebRtcTransportAppData, WebRtcTransportEvents, WebRtcTransportObserver > { /** * Transport type. * * @override */ get type(): 'webrtc'; /** * Observer. * * @override */ get observer(): WebRtcTransportObserver; /** * ICE role. */ get iceRole(): 'controlled'; /** * ICE parameters. */ get iceParameters(): IceParameters; /** * ICE candidates. */ get iceCandidates(): IceCandidate[]; /** * ICE state. */ get iceState(): IceState; /** * ICE selected tuple. */ get iceSelectedTuple(): TransportTuple | undefined; /** * DTLS parameters. */ get dtlsParameters(): DtlsParameters; /** * DTLS state. */ get dtlsState(): DtlsState; /** * Remote certificate in PEM format. */ get dtlsRemoteCert(): string | undefined; /** * SCTP parameters. */ get sctpParameters(): SctpParameters | undefined; /** * SCTP state. */ get sctpState(): SctpState | undefined; /** * Dump WebRtcTransport. * * @override */ dump(): Promise; /** * Get WebRtcTransport stats. * * @override */ getStats(): Promise; /** * Provide the WebRtcTransport remote parameters. * * @override */ connect({ dtlsParameters, }: { dtlsParameters: DtlsParameters; }): Promise; /** * Restart ICE. */ restartIce(): Promise; } ================================================ FILE: node/src/Worker.ts ================================================ // NOTE: Must import `process` using default import otherwise we get the // `process` namespace and it doesn't have `removeListener()`. import process from 'node:process'; import * as path from 'node:path'; import type { Duplex } from 'node:stream'; import { spawn, ChildProcess } from 'node:child_process'; import { version } from './'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; import type { Worker, WorkerSettings, WorkerUpdateableSettings, WorkerResourceUsage, WorkerDump, WorkerEvents, WorkerObserver, WorkerObserverEvents, } from './WorkerTypes'; import { Channel } from './Channel'; import type { WebRtcServer, WebRtcServerOptions } from './WebRtcServerTypes'; import { WebRtcServerImpl } from './WebRtcServer'; import type { Router, RouterOptions } from './RouterTypes'; import { RouterImpl } from './Router'; import { portRangeToFbs, socketFlagsToFbs } from './Transport'; import type { RouterRtpCodecCapability } from './rtpParametersTypes'; import * as utils from './utils'; import * as fbsUtils from './fbsUtils'; import type { AppData } from './types'; import { Event } from './fbs/notification'; import * as FbsNotification from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsWorker from './fbs/worker'; import * as FbsTransport from './fbs/transport'; import { Protocol as FbsTransportProtocol } from './fbs/transport/protocol'; const logger = new Logger('Worker'); const workerLogger = new Logger('Worker'); export const defaultWorkerBin: string = getDefaultWorkerBin(); export class WorkerImpl extends EnhancedEventEmitter implements Worker { // mediasoup-worker child process. #child: ChildProcess; // Worker process PID. readonly #pid: number; // Channel instance. readonly #channel: Channel; // Closed flag. #closed = false; // Died dlag. #died = false; // Worker process closed flag. #subprocessClosed = false; // Custom app data. #appData: WorkerAppData; // WebRtcServers set. readonly #webRtcServers: Set = new Set(); // Routers set. readonly #routers: Set = new Set(); // Observer instance. readonly #observer: WorkerObserver = new EnhancedEventEmitter(); constructor({ logLevel, logTags, rtcMinPort, rtcMaxPort, dtlsCertificateFile, dtlsPrivateKeyFile, workerBin, libwebrtcFieldTrials, disableLiburing, useBuiltInSctpStack, appData, }: WorkerSettings) { super(); logger.debug('constructor()'); workerBin = workerBin ?? defaultWorkerBin; let spawnBin = workerBin; let spawnArgs: string[] = []; if (process.env['MEDIASOUP_USE_VALGRIND'] === 'true') { spawnBin = process.env['MEDIASOUP_VALGRIND_BIN'] ?? 'valgrind'; if (process.env['MEDIASOUP_VALGRIND_OPTIONS']) { spawnArgs = spawnArgs.concat( process.env['MEDIASOUP_VALGRIND_OPTIONS'].split(/\s+/) ); } spawnArgs.push(workerBin); } if (typeof logLevel === 'string' && logLevel) { spawnArgs.push(`--logLevel=${logLevel}`); } for (const logTag of Array.isArray(logTags) ? logTags : []) { if (typeof logTag === 'string' && logTag) { spawnArgs.push(`--logTag=${logTag}`); } } if (typeof rtcMinPort === 'number' && !Number.isNaN(rtcMinPort)) { spawnArgs.push(`--rtcMinPort=${rtcMinPort}`); } if (typeof rtcMaxPort === 'number' && !Number.isNaN(rtcMaxPort)) { spawnArgs.push(`--rtcMaxPort=${rtcMaxPort}`); } if (typeof dtlsCertificateFile === 'string' && dtlsCertificateFile) { spawnArgs.push(`--dtlsCertificateFile=${dtlsCertificateFile}`); } if (typeof dtlsPrivateKeyFile === 'string' && dtlsPrivateKeyFile) { spawnArgs.push(`--dtlsPrivateKeyFile=${dtlsPrivateKeyFile}`); } if (typeof libwebrtcFieldTrials === 'string' && libwebrtcFieldTrials) { spawnArgs.push(`--libwebrtcFieldTrials=${libwebrtcFieldTrials}`); } if (disableLiburing) { spawnArgs.push('--disableLiburing=true'); } if (useBuiltInSctpStack) { spawnArgs.push('--useBuiltInSctpStack=true'); } else { spawnArgs.push('--useBuiltInSctpStack=false'); } logger.debug(`spawning worker process: ${spawnBin} ${spawnArgs.join(' ')}`); this.#child = spawn( // command spawnBin, // args spawnArgs, // options { env: { MEDIASOUP_VERSION: version, // Let the worker process inherit all environment variables, useful // if a custom and not in the path GCC is used so the user can set // LD_LIBRARY_PATH environment variable for runtime. ...process.env, }, detached: false, // fd 0 (stdin) : Just ignore it. // fd 1 (stdout) : Pipe it for 3rd libraries that log their own stuff. // fd 2 (stderr) : Same as stdout. // fd 3 (channel) : Producer Channel fd. // fd 4 (channel) : Consumer Channel fd. stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'], windowsHide: true, } ); this.#pid = this.#child.pid!; this.#channel = new Channel({ producerSocket: this.#child.stdio[3] as Duplex, consumerSocket: this.#child.stdio[4] as Duplex, pid: this.#pid, }); this.#appData = appData ?? ({} as WorkerAppData); let spawnDone = false; // Listen for 'running' notification. this.#channel.once(String(this.#pid), (event: Event) => { if (!spawnDone && event === Event.WORKER_RUNNING) { spawnDone = true; logger.debug(`worker process running [pid:${this.#pid}]`); this.emit('@success'); } }); this.#child.on('exit', (code, signal) => { // If closed by ourselves, do nothing. if (this.#closed) { return; } if (!spawnDone) { spawnDone = true; if (code === 42) { logger.error( `worker process failed due to wrong settings [pid:${this.#pid}]` ); this.close(); this.emit('@failure', new TypeError('wrong settings')); } else { logger.error( `worker process failed unexpectedly [pid:${this.#pid}, code:${code}, signal:${signal}]` ); this.close(); this.emit( '@failure', new Error(`[pid:${this.#pid}, code:${code}, signal:${signal}]`) ); } } else { logger.error( `worker process died unexpectedly [pid:${this.#pid}, code:${code}, signal:${signal}]` ); this.workerDied( new Error(`[pid:${this.#pid}, code:${code}, signal:${signal}]`) ); } }); this.#child.on('error', error => { // If closed by ourselves, do nothing. if (this.#closed) { return; } if (!spawnDone) { spawnDone = true; logger.error( `worker process failed [pid:${this.#pid}]: ${error.message}` ); this.close(); this.emit('@failure', new Error(error.message)); } else { logger.error( `worker process error [pid:${this.#pid}]: ${error.message}` ); this.workerDied(error); } }); this.#child.on('close', (code, signal) => { logger.debug( `worker process closed [pid:${this.#pid}, code:${code}, signal:${signal}]` ); if (!this.#subprocessClosed) { this.#subprocessClosed = true; logger.debug(`emitting 'subprocessclose' event`); this.safeEmit('subprocessclose'); } }); // Be ready for 3rd party worker libraries logging to stdout. this.#child.stdout!.on('data', buffer => { for (const line of buffer.toString('utf8').split('\n')) { if (line) { workerLogger.debug(`(stdout) ${line}`); } } }); // In case of a worker bug, mediasoup will log to stderr. this.#child.stderr!.on('data', buffer => { for (const line of buffer.toString('utf8').split('\n')) { if (line) { workerLogger.error(`(stderr) ${line}`); } } }); // NOTE: Avoid "Possible EventEmitter memory leak detected" Node warning. const processListenerCount = process.getMaxListeners(); if (processListenerCount >= 10) { process.setMaxListeners(processListenerCount + 2); } process.once('SIGINT', this.onSignal); process.once('SIGTERM', this.onSignal); this.handleListenerError(); } get pid(): number { return this.#pid; } get closed(): boolean { return this.#closed; } get died(): boolean { return this.#died; } get subprocessClosed(): boolean { return this.#subprocessClosed; } get appData(): WorkerAppData { return this.#appData; } set appData(appData: WorkerAppData) { this.#appData = appData; } get observer(): WorkerObserver { return this.#observer; } /** * Just for testing purposes. */ get webRtcServersForTesting(): Set { return this.#webRtcServers; } /** * Just for testing purposes. */ get routersForTesting(): Set { return this.#routers; } close(): void { if (this.#closed) { return; } logger.debug('close()'); this.#closed = true; process.removeListener('SIGINT', this.onSignal); process.removeListener('SIGTERM', this.onSignal); // Close every Router. for (const router of this.#routers) { router.workerClosed(); } this.#routers.clear(); // Close every WebRtcServer. for (const webRtcServer of this.#webRtcServers) { webRtcServer.workerClosed(); } this.#webRtcServers.clear(); // Send notification to worker process. this.#channel.notify(FbsNotification.Event.WORKER_CLOSE); // Close the Channel instance now. this.#channel.close(); // Emit observer event. this.#observer.safeEmit('close'); } async dump(): Promise { logger.debug('dump()'); // Send the request and wait for the response. const response = await this.#channel.request(FbsRequest.Method.WORKER_DUMP); /* Decode Response. */ const dump = new FbsWorker.DumpResponse(); response.body(dump); return parseWorkerDumpResponse(dump); } async getResourceUsage(): Promise { logger.debug('getResourceUsage()'); const response = await this.#channel.request( FbsRequest.Method.WORKER_GET_RESOURCE_USAGE ); /* Decode Response. */ const resourceUsage = new FbsWorker.ResourceUsageResponse(); response.body(resourceUsage); const ru = resourceUsage.unpack(); return { ru_utime: Number(ru.ruUtime), ru_stime: Number(ru.ruStime), ru_maxrss: Number(ru.ruMaxrss), ru_ixrss: Number(ru.ruIxrss), ru_idrss: Number(ru.ruIdrss), ru_isrss: Number(ru.ruIsrss), ru_minflt: Number(ru.ruMinflt), ru_majflt: Number(ru.ruMajflt), ru_nswap: Number(ru.ruNswap), ru_inblock: Number(ru.ruInblock), ru_oublock: Number(ru.ruOublock), ru_msgsnd: Number(ru.ruMsgsnd), ru_msgrcv: Number(ru.ruMsgrcv), ru_nsignals: Number(ru.ruNsignals), ru_nvcsw: Number(ru.ruNvcsw), ru_nivcsw: Number(ru.ruNivcsw), }; } async updateSettings({ logLevel, logTags, }: WorkerUpdateableSettings = {}): Promise { logger.debug('updateSettings()'); // Build the request. const requestOffset = new FbsWorker.UpdateSettingsRequestT( logLevel, logTags ).pack(this.#channel.bufferBuilder); await this.#channel.request( FbsRequest.Method.WORKER_UPDATE_SETTINGS, FbsRequest.Body.Worker_UpdateSettingsRequest, requestOffset ); } async createWebRtcServer({ listenInfos, appData, }: WebRtcServerOptions): Promise< WebRtcServer > { logger.debug('createWebRtcServer()'); if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } // Build the request. const fbsListenInfos: FbsTransport.ListenInfoT[] = []; for (const listenInfo of listenInfos) { fbsListenInfos.push( new FbsTransport.ListenInfoT( listenInfo.protocol === 'udp' ? FbsTransportProtocol.UDP : FbsTransportProtocol.TCP, listenInfo.ip, listenInfo.announcedAddress ?? listenInfo.announcedIp, Boolean(listenInfo.exposeInternalIp), listenInfo.port, portRangeToFbs(listenInfo.portRange), socketFlagsToFbs(listenInfo.flags), listenInfo.sendBufferSize, listenInfo.recvBufferSize ) ); } const webRtcServerId = utils.generateUUIDv4(); const createWebRtcServerRequestOffset = new FbsWorker.CreateWebRtcServerRequestT( webRtcServerId, fbsListenInfos ).pack(this.#channel.bufferBuilder); await this.#channel.request( FbsRequest.Method.WORKER_CREATE_WEBRTCSERVER, FbsRequest.Body.Worker_CreateWebRtcServerRequest, createWebRtcServerRequestOffset ); const webRtcServer: WebRtcServer = new WebRtcServerImpl({ internal: { webRtcServerId }, channel: this.#channel, appData, }); this.#webRtcServers.add(webRtcServer); webRtcServer.on('@close', () => this.#webRtcServers.delete(webRtcServer)); // Emit observer event. this.#observer.safeEmit('newwebrtcserver', webRtcServer); return webRtcServer; } async createRouter({ mediaCodecs, appData, }: RouterOptions = {}): Promise> { logger.debug('createRouter()'); if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } // Clone given media codecs to not modify input data. const clonedMediaCodecs = utils.clone< RouterRtpCodecCapability[] | undefined >(mediaCodecs); // This may throw. const rtpCapabilities = ortc.generateRouterRtpCapabilities(clonedMediaCodecs); const routerId = utils.generateUUIDv4(); // Get flatbuffer builder. const createRouterRequestOffset = new FbsWorker.CreateRouterRequestT( routerId ).pack(this.#channel.bufferBuilder); await this.#channel.request( FbsRequest.Method.WORKER_CREATE_ROUTER, FbsRequest.Body.Worker_CreateRouterRequest, createRouterRequestOffset ); const data = { rtpCapabilities }; const router: Router = new RouterImpl({ internal: { routerId, }, data, channel: this.#channel, appData, }); this.#routers.add(router); router.on('@close', () => this.#routers.delete(router)); // Emit observer event. this.#observer.safeEmit('newrouter', router); return router; } private workerDied(error: Error): void { if (this.#closed) { return; } logger.debug(`workerDied() [error:${error.toString()}]`); this.#closed = true; this.#subprocessClosed = true; this.#died = true; // Close the Channel instance. this.#channel.close(); // Close every Router. for (const router of this.#routers) { router.workerClosed(); } this.#routers.clear(); // Close every WebRtcServer. for (const webRtcServer of this.#webRtcServers) { webRtcServer.workerClosed(); } this.#webRtcServers.clear(); logger.debug(`workerDied() | emitting 'died' and 'subprocessclose' events`); this.safeEmit('died', error); this.safeEmit('subprocessclose'); // Emit observer event. this.#observer.safeEmit('close'); } private handleListenerError(): void { this.on('listenererror', (eventName, error) => { logger.error( `event listener threw an error [eventName:${eventName}]:`, error ); }); } // NOTE: Arrow method on purpose. private onSignal = (signal: 'SIGINT' | 'SIGTERM'): void => { logger.debug( `signal received, closing the worker process [pid:${this.#pid}, signal:${signal}]` ); this.close(); }; } function parseWorkerDumpResponse(binary: FbsWorker.DumpResponse): WorkerDump { const dump: WorkerDump = { pid: binary.pid(), webRtcServerIds: fbsUtils.parseVector(binary, 'webRtcServerIds'), routerIds: fbsUtils.parseVector(binary, 'routerIds'), channelMessageHandlers: { channelRequestHandlers: fbsUtils.parseVector( binary.channelMessageHandlers()!, 'channelRequestHandlers' ), channelNotificationHandlers: fbsUtils.parseVector( binary.channelMessageHandlers()!, 'channelNotificationHandlers' ), }, }; if (binary.liburing()) { dump.liburing = { sqeProcessCount: Number(binary.liburing()!.sqeProcessCount()), sqeMissCount: Number(binary.liburing()!.sqeMissCount()), userDataMissCount: Number(binary.liburing()!.userDataMissCount()), }; } return dump; } function getDefaultWorkerBin(): string { // If MEDIASOUP_WORKER_BIN env is given, use it as worker binary. if (process.env['MEDIASOUP_WORKER_BIN']) { logger.debug( `getDefaultWorkerBin() | using MEDIASOUP_WORKER_BIN environment variable: ${process.env['MEDIASOUP_WORKER_BIN']}` ); return process.env['MEDIASOUP_WORKER_BIN']; } // Obtain the path of the mediasoup module. let mediasoupModulePath: string | undefined; try { // NOTE: This will throw `MODULE_NOT_FOUND` if mediasoup is installed // globally. mediasoupModulePath = require.resolve('mediasoup'); // NOTE: Returned path will include 'node/lib/index.js' since that's the // main entry point in package.json, so remove it. mediasoupModulePath = path.join( path.dirname(mediasoupModulePath), '..', '..' ); } catch (error) { logger.warn( `getDefaultWorkerBin() | require.resolve('mediasoup') failed, using __dirname: ${error}` ); // mediasoup module path is two folders above this file. mediasoupModulePath = path.join(__dirname, '..', '..'); } // If env MEDIASOUP_BUILDTYPE is 'Debug' use the Debug binary. Otherwise use // the Release binary. const buildType: 'Release' | 'Debug' = process.env['MEDIASOUP_BUILDTYPE'] === 'Debug' ? 'Debug' : 'Release'; const defaultWorkerBinPath = path.join( mediasoupModulePath, 'worker', 'out', buildType, 'mediasoup-worker' ); logger.debug( `getDefaultWorkerBin() | detected worker binary path: ${defaultWorkerBinPath}` ); return defaultWorkerBinPath; } ================================================ FILE: node/src/WorkerTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { WebRtcServer, WebRtcServerOptions } from './WebRtcServerTypes'; import type { Router, RouterOptions } from './RouterTypes'; import type { AppData } from './types'; export type WorkerLogLevel = 'debug' | 'warn' | 'error' | 'none'; export type WorkerLogTag = | 'info' | 'ice' | 'dtls' | 'rtp' | 'srtp' | 'rtcp' | 'rtx' | 'bwe' | 'score' | 'simulcast' | 'svc' | 'sctp' | 'message'; export type WorkerSettings = { /** * Logging level for logs generated by the media worker subprocesses (check * the Debugging documentation). Valid values are 'debug', 'warn', 'error' and * 'none'. Default 'error'. */ logLevel?: WorkerLogLevel; /** * Log tags for debugging. Check the meaning of each available tag in the * Debugging documentation. */ logTags?: WorkerLogTag[]; /** * Minimun RTC port for ICE, DTLS, RTP, etc. Default 10000. * * @deprecated Use |portRange| in TransportListenInfo object instead. */ rtcMinPort?: number; /** * Maximum RTC port for ICE, DTLS, RTP, etc. Default 59999. * * @deprecated Use |portRange| in TransportListenInfo object instead. */ rtcMaxPort?: number; /** * Absolute path to the DTLS public certificate file in PEM format. If unset, * a certificate is dynamically created. */ dtlsCertificateFile?: string; /** * Absolute path to the DTLS certificate private key file in PEM format. If * unset, a certificate is dynamically created. */ dtlsPrivateKeyFile?: string; /** * Absolute path to the mediasoup-worker binary. If given it overrides the * default location of the binary and the MEDIASOUP_WORKER_BIN environment * variable. */ workerBin?: string; /** * Field trials for libwebrtc. * * @remarks For advanced users only. An invalid value will make the worker * crash. Default value is "WebRTC-Bwe-AlrLimitedBackoff/Enabled/". */ libwebrtcFieldTrials?: string; /** * Disable liburing (io_uring) despite it's supported in current host. */ disableLiburing?: boolean; /** * Use the mediasoup built-in SCTP stack instead usrsctp. */ useBuiltInSctpStack?: boolean; /** * Custom application data. */ appData?: WorkerAppData; }; export type WorkerUpdateableSettings = Pick< WorkerSettings, 'logLevel' | 'logTags' >; /** * An object with the fields of the uv_rusage_t struct. * * - http://docs.libuv.org/en/v1.x/misc.html#c.uv_rusage_t * - https://linux.die.net/man/2/getrusage */ export type WorkerResourceUsage = { /** * User CPU time used (in ms). */ ru_utime: number; /** * System CPU time used (in ms). */ ru_stime: number; /** * Maximum resident set size. */ ru_maxrss: number; /** * Integral shared memory size. */ ru_ixrss: number; /** * Integral unshared data size. */ ru_idrss: number; /** * Integral unshared stack size. */ ru_isrss: number; /** * Page reclaims (soft page faults). */ ru_minflt: number; /** * Page faults (hard page faults). */ ru_majflt: number; /** * Swaps. */ ru_nswap: number; /** * Block input operations. */ ru_inblock: number; /** * Block output operations. */ ru_oublock: number; /** * IPC messages sent. */ ru_msgsnd: number; /** * IPC messages received. */ ru_msgrcv: number; /** * Signals received. */ ru_nsignals: number; /** * Voluntary context switches. */ ru_nvcsw: number; /** * Involuntary context switches. */ ru_nivcsw: number; }; export type WorkerDump = { pid: number; webRtcServerIds: string[]; routerIds: string[]; channelMessageHandlers: { channelRequestHandlers: string[]; channelNotificationHandlers: string[]; }; liburing?: { sqeProcessCount: number; sqeMissCount: number; userDataMissCount: number; }; }; export type WorkerEvents = { died: [Error]; subprocessclose: []; // Private events. '@success': []; '@failure': [Error]; }; export type WorkerObserver = EnhancedEventEmitter; export type WorkerObserverEvents = { close: []; newwebrtcserver: [WebRtcServer]; newrouter: [Router]; }; export interface Worker< WorkerAppData extends AppData = AppData, > extends EnhancedEventEmitter { /** * Worker process identifier (PID). */ get pid(): number; /** * Whether the Worker is closed. */ get closed(): boolean; /** * Whether the Worker died. */ get died(): boolean; /** * Whether the Worker subprocess is closed. */ get subprocessClosed(): boolean; /** * App custom data. */ get appData(): WorkerAppData; /** * App custom data setter. */ set appData(appData: WorkerAppData); /** * Observer. */ get observer(): WorkerObserver; /** * Close the Worker. */ close(): void; /** * Dump Worker. */ dump(): Promise; /** * Get mediasoup-worker process resource usage. */ getResourceUsage(): Promise; /** * Update settings. */ updateSettings( options?: WorkerUpdateableSettings ): Promise; /** * Create a WebRtcServer. */ createWebRtcServer( options: WebRtcServerOptions ): Promise>; /** * Create a Router. */ createRouter( options?: RouterOptions ): Promise>; } ================================================ FILE: node/src/enhancedEvents.ts ================================================ /* eslint-disable @typescript-eslint/no-explicit-any */ import { EventEmitter, once } from 'node:events'; type Events = Record; export class EnhancedEventEmitter< E extends Events = Events, BuiltInEvents extends Events = { listenererror: [keyof E, Error]; }, E2 extends Events = E & BuiltInEvents, > extends EventEmitter { constructor() { super(); this.setMaxListeners(Infinity); } override emit( eventName: K, ...args: E2[K] ): boolean { return super.emit(eventName, ...args); } /** * Special addition to the EventEmitter API. */ safeEmit(eventName: K, ...args: E2[K]): boolean { try { return super.emit(eventName, ...args); } catch (error) { try { super.emit('listenererror', eventName, error); } catch (error2) { // Ignore it. } return Boolean(super.listenerCount(eventName)); } } override on( eventName: K, listener: (...args: E2[K]) => void ): this { super.on(eventName, listener as (...args: any[]) => void); return this; } override off( eventName: K, listener: (...args: E2[K]) => void ): this { super.off(eventName, listener as (...args: any[]) => void); return this; } override addListener( eventName: K, listener: (...args: E2[K]) => void ): this { super.on(eventName, listener as (...args: any[]) => void); return this; } override prependListener( eventName: K, listener: (...args: E2[K]) => void ): this { super.prependListener(eventName, listener as (...args: any[]) => void); return this; } override once( eventName: K, listener: (...args: E2[K]) => void ): this { super.once(eventName, listener as (...args: any[]) => void); return this; } override prependOnceListener( eventName: K, listener: (...args: E2[K]) => void ): this { super.prependOnceListener(eventName, listener as (...args: any[]) => void); return this; } override removeListener( eventName: K, listener: (...args: E2[K]) => void ): this { super.off(eventName, listener as (...args: any[]) => void); return this; } override removeAllListeners( eventName?: K ): this { super.removeAllListeners(eventName); return this; } override listenerCount(eventName: K): number { return super.listenerCount(eventName); } override listeners( eventName: K ): ((...args: any[]) => void)[] { return super.listeners(eventName); } override rawListeners( eventName: K ): ((...args: any[]) => void)[] { return super.rawListeners(eventName); } } /** * TypeScript version of events.once(): * https://nodejs.org/api/events.html#eventsonceemitter-name-options * * Usage example: * ```ts * await enhancedOnce(videoConsumer, 'producerpause'); * ``` */ export async function enhancedOnce( emmiter: EnhancedEventEmitter, eventName: keyof E & string, options?: any ): Promise { return once(emmiter, eventName, options); } ================================================ FILE: node/src/errors.ts ================================================ /** * Error indicating not support for something. */ export class UnsupportedError extends Error { constructor(message: string) { super(message); this.name = 'UnsupportedError'; if (Error.hasOwnProperty('captureStackTrace')) { // Just in V8. Error.captureStackTrace(this, UnsupportedError); } else { this.stack = new Error(message).stack; } } } /** * Error produced when calling a method in an invalid state. */ export class InvalidStateError extends Error { constructor(message: string) { super(message); this.name = 'InvalidStateError'; if (Error.hasOwnProperty('captureStackTrace')) { // Just in V8. Error.captureStackTrace(this, InvalidStateError); } else { this.stack = new Error(message).stack; } } } ================================================ FILE: node/src/extras.ts ================================================ export { EnhancedEventEmitter, enhancedOnce } from './enhancedEvents'; ================================================ FILE: node/src/fbsUtils.ts ================================================ /** * Parse flatbuffers vector into an array of the given T. */ export function parseVector( // eslint-disable-next-line @typescript-eslint/no-explicit-any binary: any, methodName: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any parseFn?: (binary2: any) => T ): T[] { const array: T[] = []; for (let i = 0; i < binary[`${methodName}Length`](); ++i) { if (parseFn) { array.push(parseFn(binary[methodName](i))); } else { array.push(binary[methodName](i) as T); } } return array; } /** * Parse flatbuffers vector of StringString into the corresponding array. */ export function parseStringStringVector( // eslint-disable-next-line @typescript-eslint/no-explicit-any binary: any, methodName: string ): { key: string; value: string }[] { const array: { key: string; value: string }[] = []; for (let i = 0; i < binary[`${methodName}Length`](); ++i) { const kv = binary[methodName](i)!; array.push({ key: kv.key(), value: kv.value() }); } return array; } /** * Parse flatbuffers vector of StringUint8 into the corresponding array. */ export function parseStringUint8Vector( // eslint-disable-next-line @typescript-eslint/no-explicit-any binary: any, methodName: string ): { key: string; value: number }[] { const array: { key: string; value: number }[] = []; for (let i = 0; i < binary[`${methodName}Length`](); ++i) { const kv = binary[methodName](i)!; array.push({ key: kv.key(), value: kv.value() }); } return array; } /** * Parse flatbuffers vector of Uint16String into the corresponding array. */ export function parseUint16StringVector( // eslint-disable-next-line @typescript-eslint/no-explicit-any binary: any, methodName: string ): { key: number; value: string }[] { const array: { key: number; value: string }[] = []; for (let i = 0; i < binary[`${methodName}Length`](); ++i) { const kv = binary[methodName](i)!; array.push({ key: kv.key(), value: kv.value() }); } return array; } /** * Parse flatbuffers vector of Uint32String into the corresponding array. */ export function parseUint32StringVector( // eslint-disable-next-line @typescript-eslint/no-explicit-any binary: any, methodName: string ): { key: number; value: string }[] { const array: { key: number; value: string }[] = []; for (let i = 0; i < binary[`${methodName}Length`](); ++i) { const kv = binary[methodName](i)!; array.push({ key: kv.key(), value: kv.value() }); } return array; } /** * Parse flatbuffers vector of StringStringArray into the corresponding array. */ export function parseStringStringArrayVector( // eslint-disable-next-line @typescript-eslint/no-explicit-any binary: any, methodName: string ): { key: string; values: string[] }[] { const array: { key: string; values: string[] }[] = []; for (let i = 0; i < binary[`${methodName}Length`](); ++i) { const kv = binary[methodName](i)!; const values: string[] = []; for (let i2 = 0; i2 < kv.valuesLength(); ++i2) { values.push(kv.values(i2)! as string); } array.push({ key: kv.key(), values }); } return array; } ================================================ FILE: node/src/index.ts ================================================ import { Logger, LoggerEmitter } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import type { Observer, ObserverEvents, LogEventListeners, Index, } from './indexTypes'; import type { Worker, WorkerSettings } from './WorkerTypes'; import { WorkerImpl, defaultWorkerBin as workerBin } from './Worker'; import { supportedRtpCapabilities } from './supportedRtpCapabilities'; import type { RouterRtpCapabilities } from './rtpParametersTypes'; import { parseScalabilityMode } from './scalabilityModesUtils'; import type { AppData } from './types'; import * as utils from './utils'; /** * Expose all types. */ export type * as types from './types'; /** * Expose mediasoup version. */ // eslint-disable-next-line @typescript-eslint/no-require-imports export const version: string = require('../../package.json').version; const observer: Observer = new EnhancedEventEmitter(); /** * Observer. */ export { observer }; /** * Absolute path of the mediasoup-worker binary. */ export { defaultWorkerBin as workerBin } from './Worker'; const logger = new Logger(); /** * Set event listeners for mediasoup generated logs. If called with no arguments * then no events will be emitted. * * @example * ```ts * mediasoup.setLogEventListeners({ * ondebug: undefined, * onwarn: (namespace: string, log: string) => { * MyEnterpriseLogger.warn(`${namespace} ${log}`); * }, * onerror: (namespace: string, log: string, error?: Error) => { * if (error) { * MyEnterpriseLogger.error(`${namespace} ${log}: ${error}`); * } else { * MyEnterpriseLogger.error(`${namespace} ${log}`); * } * } * }); * ``` */ export function setLogEventListeners(listeners?: LogEventListeners): void { logger.debug('setLogEventListeners()'); let debugLogEmitter: LoggerEmitter | undefined; let warnLogEmitter: LoggerEmitter | undefined; let errorLogEmitter: LoggerEmitter | undefined; if (listeners?.ondebug) { debugLogEmitter = new EnhancedEventEmitter(); debugLogEmitter.on('debuglog', listeners.ondebug); } if (listeners?.onwarn) { warnLogEmitter = new EnhancedEventEmitter(); warnLogEmitter.on('warnlog', listeners.onwarn); } if (listeners?.onerror) { errorLogEmitter = new EnhancedEventEmitter(); errorLogEmitter.on('errorlog', listeners.onerror); } Logger.setEmitters(debugLogEmitter, warnLogEmitter, errorLogEmitter); } /** * Create a Worker. */ export async function createWorker({ logLevel = 'error', logTags, rtcMinPort = 10000, rtcMaxPort = 59999, dtlsCertificateFile, dtlsPrivateKeyFile, // eslint-disable-next-line no-shadow workerBin, libwebrtcFieldTrials, disableLiburing = false, useBuiltInSctpStack = false, appData, }: WorkerSettings = {}): Promise> { logger.debug('createWorker()'); if (appData && typeof appData !== 'object') { throw new TypeError('if given, appData must be an object'); } const worker: Worker = new WorkerImpl({ logLevel, logTags, rtcMinPort, rtcMaxPort, dtlsCertificateFile, dtlsPrivateKeyFile, workerBin, libwebrtcFieldTrials, disableLiburing, useBuiltInSctpStack, appData, }); return new Promise((resolve, reject) => { worker.on('@success', () => { // Emit observer event. observer.safeEmit('newworker', worker); resolve(worker); }); worker.on('@failure', reject); }); } /** * Get a cloned copy of the mediasoup supported RTP capabilities. */ export function getSupportedRtpCapabilities(): RouterRtpCapabilities { return utils.clone(supportedRtpCapabilities); } /** * Expose parseScalabilityMode() function. */ export { parseScalabilityMode } from './scalabilityModesUtils'; /** * Expose all ORTC functions. */ export * as ortc from './ortc'; /** * Expose extras module. */ export * as extras from './extras'; // NOTE: This constant of type Index is created just to check at TypeScript // level that everything exported here (all but TS types) matches the Index // interface exposed by indexTypes.ts. // // eslint-disable-next-line @typescript-eslint/no-unused-vars const indexImpl: Index = { version, observer, workerBin, setLogEventListeners, createWorker, getSupportedRtpCapabilities, parseScalabilityMode, }; ================================================ FILE: node/src/indexTypes.ts ================================================ import type { EnhancedEventEmitter } from './enhancedEvents'; import type { Worker, WorkerSettings } from './WorkerTypes'; import type { RouterRtpCapabilities } from './rtpParametersTypes'; import type { parseScalabilityMode } from './scalabilityModesUtils'; import type { AppData } from './types'; export type ObserverEvents = { newworker: [Worker]; }; export type Observer = EnhancedEventEmitter; /** * Event listeners for mediasoup generated logs. */ export type LogEventListeners = { ondebug?: (namespace: string, log: string) => void; onwarn?: (namespace: string, log: string) => void; onerror?: (namespace: string, log: string, error?: Error) => void; }; export interface Index { version: string; observer: EnhancedEventEmitter; workerBin: string; setLogEventListeners: (listeners?: LogEventListeners) => void; createWorker: ( options?: WorkerSettings ) => Promise>; getSupportedRtpCapabilities: () => RouterRtpCapabilities; parseScalabilityMode: typeof parseScalabilityMode; } ================================================ FILE: node/src/ortc.ts ================================================ import * as h264 from 'h264-profile-level-id'; import type * as flatbuffers from 'flatbuffers'; import { supportedRtpCapabilities } from './supportedRtpCapabilities'; import { parseScalabilityMode } from './scalabilityModesUtils'; import type { RtpCapabilities, RouterRtpCapabilities, MediaKind, RtpCodecCapability, RouterRtpCodecCapability, RtpHeaderExtension, RtpParameters, RtpCodecParameters, RtcpFeedback, RtpEncodingParameters, RtpHeaderExtensionParameters, RtcpParameters, } from './rtpParametersTypes'; import type { SctpStreamParameters } from './sctpParametersTypes'; import * as utils from './utils'; import { UnsupportedError } from './errors'; import * as FbsRtpParameters from './fbs/rtp-parameters'; export type RtpCodecsEncodingsMapping = { codecs: { payloadType: number; mappedPayloadType: number; }[]; encodings: { ssrc?: number; rid?: string; scalabilityMode?: string; mappedSsrc: number; }[]; }; const DynamicPayloadTypes = [ 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 96, 97, 98, 99, ]; // TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header // extension. // // This is an object where we store some objects we may later need. type Cache = { dependencyDescriptorHeaderExtensionParametersForPipeConsumer?: RtpHeaderExtensionParameters; }; const cache: Cache = { dependencyDescriptorHeaderExtensionParametersForPipeConsumer: undefined, }; /** * Validates RtpCapabilities. It may modify given data by adding missing * fields with default values. * It throws if invalid. */ export function validateAndNormalizeRtpCapabilities( caps: RtpCapabilities | RouterRtpCapabilities ): void { if (typeof caps !== 'object') { throw new TypeError('caps is not an object'); } // codecs is optional. If unset, fill with an empty array. if (caps.codecs && !Array.isArray(caps.codecs)) { throw new TypeError('caps.codecs is not an array'); } else if (!caps.codecs) { caps.codecs = []; } for (const codec of caps.codecs) { validateAndNormalizeRtpCodecCapability(codec); } // headerExtensions is optional. If unset, fill with an empty array. if (caps.headerExtensions && !Array.isArray(caps.headerExtensions)) { throw new TypeError('caps.headerExtensions is not an array'); } else if (!caps.headerExtensions) { caps.headerExtensions = []; } for (const ext of caps.headerExtensions) { validateAndNormalizeRtpHeaderExtension(ext); } } /** * Validates RtpParameters. It may modify given data by adding missing * fields with default values. * It throws if invalid. */ export function validateAndNormalizeRtpParameters(params: RtpParameters): void { if (typeof params !== 'object') { throw new TypeError('params is not an object'); } // mid is optional. if (params.mid && typeof params.mid !== 'string') { throw new TypeError('params.mid is not a string'); } // codecs is mandatory. if (!Array.isArray(params.codecs)) { throw new TypeError('missing params.codecs'); } for (const codec of params.codecs) { validateAndNormalizeRtpCodecParameters(codec); } // headerExtensions is optional. If unset, fill with an empty array. if (params.headerExtensions && !Array.isArray(params.headerExtensions)) { throw new TypeError('params.headerExtensions is not an array'); } else if (!params.headerExtensions) { params.headerExtensions = []; } for (const ext of params.headerExtensions) { validateAndNormalizeRtpHeaderExtensionParameters(ext); } // encodings is optional. If unset, fill with an empty array. if (params.encodings && !Array.isArray(params.encodings)) { throw new TypeError('params.encodings is not an array'); } else if (!params.encodings) { params.encodings = []; } for (const encoding of params.encodings) { validateAndNormalizeRtpEncodingParameters(encoding); } // rtcp is optional. If unset, fill with an empty object. if (params.rtcp && typeof params.rtcp !== 'object') { throw new TypeError('params.rtcp is not an object'); } else if (!params.rtcp) { params.rtcp = {}; } // msid is optional. if (params.msid && typeof params.msid !== 'string') { throw new TypeError('params.msid is not a string'); } validateAndNormalizeRtcpParameters(params.rtcp); } /** * Validates SctpStreamParameters. It may modify given data by adding missing * fields with default values. * It throws if invalid. */ export function validateAndNormalizeSctpStreamParameters( params: SctpStreamParameters ): void { if (typeof params !== 'object') { throw new TypeError('params is not an object'); } // streamId is mandatory. if (typeof params.streamId !== 'number') { throw new TypeError('missing params.streamId'); } // ordered is optional. let orderedGiven = false; if (typeof params.ordered === 'boolean') { orderedGiven = true; } else { params.ordered = true; } // maxPacketLifeTime is optional. if ( params.maxPacketLifeTime && typeof params.maxPacketLifeTime !== 'number' ) { throw new TypeError('invalid params.maxPacketLifeTime'); } // maxRetransmits is optional. if (params.maxRetransmits && typeof params.maxRetransmits !== 'number') { throw new TypeError('invalid params.maxRetransmits'); } if (params.maxPacketLifeTime && params.maxRetransmits) { throw new TypeError( 'cannot provide both maxPacketLifeTime and maxRetransmits' ); } if ( orderedGiven && params.ordered && (params.maxPacketLifeTime || params.maxRetransmits) ) { throw new TypeError( 'cannot be ordered with maxPacketLifeTime or maxRetransmits' ); } else if ( !orderedGiven && (params.maxPacketLifeTime || params.maxRetransmits) ) { params.ordered = false; } } /** * Generate RTP capabilities for the Router based on the given media codecs and * mediasoup supported RTP capabilities. */ export function generateRouterRtpCapabilities( mediaCodecs: RouterRtpCodecCapability[] = [] ): RtpCapabilities { // Normalize supported RTP capabilities. validateAndNormalizeRtpCapabilities(supportedRtpCapabilities); if (!Array.isArray(mediaCodecs)) { throw new TypeError('mediaCodecs must be an Array'); } const clonedSupportedRtpCapabilities = utils.clone( supportedRtpCapabilities ); const dynamicPayloadTypes = utils.clone(DynamicPayloadTypes); const caps: RtpCapabilities = { codecs: [], headerExtensions: clonedSupportedRtpCapabilities.headerExtensions, }; for (const mediaCodec of mediaCodecs) { // This may throw. validateAndNormalizeRtpCodecCapability(mediaCodec); const matchedSupportedCodec = clonedSupportedRtpCapabilities.codecs!.find( supportedCodec => matchCodecs(mediaCodec, supportedCodec, { strict: false }) ); if (!matchedSupportedCodec) { throw new UnsupportedError( `media codec not supported [mimeType:${mediaCodec.mimeType}]` ); } // Clone the supported codec. const codec = utils.clone(matchedSupportedCodec); // If the given media codec has preferredPayloadType, keep it. if (typeof mediaCodec.preferredPayloadType === 'number') { codec.preferredPayloadType = mediaCodec.preferredPayloadType; // Also remove the pt from the list of available dynamic values. const idx = dynamicPayloadTypes.indexOf(codec.preferredPayloadType); if (idx > -1) { dynamicPayloadTypes.splice(idx, 1); } } // Otherwise if the supported codec has preferredPayloadType, use it. else if (typeof codec.preferredPayloadType === 'number') { // No need to remove it from the list since it's not a dynamic value. } // Otherwise choose a dynamic one. else { // Take the first available pt and remove it from the list. const pt = dynamicPayloadTypes.shift(); if (!pt) { throw new Error('cannot allocate more dynamic codec payload types'); } codec.preferredPayloadType = pt; } // Ensure there is not duplicated preferredPayloadType values. if ( caps.codecs!.some( c => c.preferredPayloadType === codec.preferredPayloadType ) ) { throw new TypeError('duplicated codec.preferredPayloadType'); } // Merge the media codec parameters. codec.parameters = { ...codec.parameters, ...mediaCodec.parameters }; // Append to the codec list. caps.codecs!.push(codec as RtpCodecCapability); // Add a RTX video codec if video. if (codec.kind === 'video') { // Take the first available pt and remove it from the list. const pt = dynamicPayloadTypes.shift(); if (!pt) { throw new Error('cannot allocate more dynamic codec payload types'); } const rtxCodec: RtpCodecCapability = { kind: codec.kind, mimeType: `${codec.kind}/rtx`, preferredPayloadType: pt, clockRate: codec.clockRate, parameters: { apt: codec.preferredPayloadType, }, rtcpFeedback: [], }; // Append to the codec list. caps.codecs!.push(rtxCodec); } } // TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header // extension. // // We need to create and store this Dependency-Descriptor header extension to // leter be used by `getPipeConsumerRtpParameters()` function. const dependencyDescriptorHeaderExtensionForPipeConsumer: | RtpHeaderExtension | undefined = supportedRtpCapabilities.headerExtensions!.find( headerExtension => headerExtension.uri === 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension' && headerExtension.direction !== 'sendrecv' ); if (dependencyDescriptorHeaderExtensionForPipeConsumer) { cache.dependencyDescriptorHeaderExtensionParametersForPipeConsumer = { uri: dependencyDescriptorHeaderExtensionForPipeConsumer.uri, id: dependencyDescriptorHeaderExtensionForPipeConsumer.preferredId, encrypt: dependencyDescriptorHeaderExtensionForPipeConsumer.preferredEncrypt, parameters: {}, }; } return caps; } /** * Get a mapping of codec payloads and encodings of the given Producer RTP * parameters as values expected by the Router. * * It may throw if invalid or non supported RTP parameters are given. */ export function getProducerRtpParametersMapping( params: RtpParameters, caps: RtpCapabilities ): RtpCodecsEncodingsMapping { const rtpMapping: RtpCodecsEncodingsMapping = { codecs: [], encodings: [], }; // Match parameters media codecs to capabilities media codecs. const codecToCapCodec: Map = new Map(); for (const codec of params.codecs) { if (isRtxCodec(codec)) { continue; } // Search for the same media codec in capabilities. const matchedCapCodec = caps.codecs!.find(capCodec => matchCodecs(codec, capCodec, { strict: true, modify: true }) ); if (!matchedCapCodec) { throw new UnsupportedError( `unsupported codec [mimeType:${codec.mimeType}, payloadType:${codec.payloadType}]` ); } codecToCapCodec.set(codec, matchedCapCodec); } // Match parameters RTX codecs to capabilities RTX codecs. for (const codec of params.codecs) { if (!isRtxCodec(codec)) { continue; } // Search for the associated media codec. const associatedMediaCodec = params.codecs.find( mediaCodec => mediaCodec.payloadType === codec.parameters!['apt'] ); if (!associatedMediaCodec) { throw new TypeError( `missing media codec found for RTX PT ${codec.payloadType}` ); } const capMediaCodec = codecToCapCodec.get(associatedMediaCodec); // Ensure that the capabilities media codec has a RTX codec. const associatedCapRtxCodec = caps.codecs!.find( capCodec => isRtxCodec(capCodec) && capCodec.parameters!['apt'] === capMediaCodec!.preferredPayloadType ); if (!associatedCapRtxCodec) { throw new UnsupportedError( `no RTX codec for capability codec PT ${ capMediaCodec!.preferredPayloadType }` ); } codecToCapCodec.set(codec, associatedCapRtxCodec); } // Generate codecs mapping. for (const [codec, capCodec] of codecToCapCodec) { rtpMapping.codecs.push({ payloadType: codec.payloadType, mappedPayloadType: capCodec.preferredPayloadType, }); } // Generate encodings mapping. let mappedSsrc = utils.generateRandomNumber(); for (const encoding of params.encodings!) { const mappedEncoding = { ssrc: encoding.ssrc, rid: encoding.rid, scalabilityMode: encoding.scalabilityMode, mappedSsrc: mappedSsrc++, }; rtpMapping.encodings.push(mappedEncoding); } return rtpMapping; } /** * Generate RTP parameters to be internally used by Consumers given the RTP * parameters of a Producer and the RTP capabilities of the Router. */ export function getConsumableRtpParameters( kind: string, params: RtpParameters, caps: RtpCapabilities, rtpMapping: RtpCodecsEncodingsMapping ): RtpParameters { const consumableParams: RtpParameters = { codecs: [], headerExtensions: [], encodings: [], rtcp: {}, msid: undefined, }; for (const codec of params.codecs) { if (isRtxCodec(codec)) { continue; } const consumableCodecPt = rtpMapping.codecs.find( entry => entry.payloadType === codec.payloadType )!.mappedPayloadType; const matchedCapCodec = caps.codecs!.find( capCodec => capCodec.preferredPayloadType === consumableCodecPt )!; const consumableCodec: RtpCodecParameters = { mimeType: matchedCapCodec.mimeType, payloadType: matchedCapCodec.preferredPayloadType, clockRate: matchedCapCodec.clockRate, channels: matchedCapCodec.channels, parameters: codec.parameters, // Keep the Producer codec parameters. rtcpFeedback: matchedCapCodec.rtcpFeedback, }; consumableParams.codecs.push(consumableCodec); const consumableCapRtxCodec = caps.codecs!.find( capRtxCodec => isRtxCodec(capRtxCodec) && capRtxCodec.parameters!['apt'] === consumableCodec.payloadType ); if (consumableCapRtxCodec) { const consumableRtxCodec: RtpCodecParameters = { mimeType: consumableCapRtxCodec.mimeType, payloadType: consumableCapRtxCodec.preferredPayloadType, clockRate: consumableCapRtxCodec.clockRate, parameters: consumableCapRtxCodec.parameters, rtcpFeedback: consumableCapRtxCodec.rtcpFeedback, }; consumableParams.codecs.push(consumableRtxCodec); } } for (const capExt of caps.headerExtensions!) { // Just take RTP header extension that can be used in Consumers. if ( capExt.kind !== kind || (capExt.direction !== 'sendrecv' && capExt.direction !== 'sendonly') ) { continue; } const consumableExt = { uri: capExt.uri, id: capExt.preferredId, encrypt: capExt.preferredEncrypt, parameters: {}, }; consumableParams.headerExtensions!.push(consumableExt); } // Clone Producer encodings since we'll mangle them. const consumableEncodings = utils.clone(params.encodings) ?? []; for (let i = 0; i < consumableEncodings.length; ++i) { const consumableEncoding = consumableEncodings[i]!; const { mappedSsrc } = rtpMapping.encodings[i]!; // Remove useless fields. delete consumableEncoding.rid; delete consumableEncoding.rtx; delete consumableEncoding.codecPayloadType; // Set the mapped ssrc. consumableEncoding.ssrc = mappedSsrc; consumableParams.encodings!.push(consumableEncoding); } consumableParams.rtcp = { cname: params.rtcp!.cname, reducedSize: true, }; consumableParams.msid = params.msid; return consumableParams; } /** * Check whether the given RTP capabilities can consume the given Producer. */ export function canConsume( consumableParams: RtpParameters, caps: RtpCapabilities ): boolean { // This may throw. validateAndNormalizeRtpCapabilities(caps); const matchingCodecs: RtpCodecParameters[] = []; for (const codec of consumableParams.codecs) { const matchedCapCodec = caps.codecs!.find(capCodec => matchCodecs(capCodec, codec, { strict: true }) ); if (!matchedCapCodec) { continue; } matchingCodecs.push(codec); } // Ensure there is at least one media codec. if (matchingCodecs.length === 0 || isRtxCodec(matchingCodecs[0]!)) { return false; } return true; } /** * Generate RTP parameters for a specific Consumer. * * It reduces encodings to just one and takes into account given RTP * capabilities to reduce codecs, codecs' RTCP feedback and header extensions, * and also enables or disables RTX. */ export function getConsumerRtpParameters({ consumableRtpParameters, remoteRtpCapabilities, pipe, enableRtx, }: { consumableRtpParameters: RtpParameters; remoteRtpCapabilities: RtpCapabilities; pipe: boolean; enableRtx: boolean; }): RtpParameters { const consumerParams: RtpParameters = { codecs: [], headerExtensions: [], encodings: [], rtcp: consumableRtpParameters.rtcp, msid: consumableRtpParameters.msid, }; for (const capCodec of remoteRtpCapabilities.codecs!) { validateAndNormalizeRtpCodecCapability(capCodec); } const consumableCodecs = utils.clone( consumableRtpParameters.codecs ) ?? []; let rtxSupported = false; for (const codec of consumableCodecs) { if (!enableRtx && isRtxCodec(codec)) { continue; } const matchedCapCodec = remoteRtpCapabilities.codecs!.find(capCodec => matchCodecs(capCodec, codec, { strict: true }) ); if (!matchedCapCodec) { continue; } codec.rtcpFeedback = matchedCapCodec.rtcpFeedback!.filter( fb => enableRtx || fb.type !== 'nack' || fb.parameter ); consumerParams.codecs.push(codec); } // Must sanitize the list of matched codecs by removing useless RTX codecs. for (let idx = consumerParams.codecs.length - 1; idx >= 0; --idx) { const codec = consumerParams.codecs[idx]!; if (isRtxCodec(codec)) { // Search for the associated media codec. const associatedMediaCodec = consumerParams.codecs.find( mediaCodec => mediaCodec.payloadType === codec.parameters!['apt'] ); if (associatedMediaCodec) { rtxSupported = true; } else { consumerParams.codecs.splice(idx, 1); } } } // Ensure there is at least one media codec. if ( consumerParams.codecs.length === 0 || isRtxCodec(consumerParams.codecs[0]!) ) { throw new UnsupportedError('no compatible media codecs'); } consumerParams.headerExtensions = consumableRtpParameters.headerExtensions!.filter(ext => remoteRtpCapabilities.headerExtensions!.some( capExt => capExt.preferredId === ext.id && capExt.uri === ext.uri ) ); // Reduce codecs' RTCP feedback. Use Transport-CC if available, REMB otherwise. if ( consumerParams.headerExtensions.some( ext => ext.uri === 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01' ) ) { for (const codec of consumerParams.codecs) { codec.rtcpFeedback = codec.rtcpFeedback!.filter( fb => fb.type !== 'goog-remb' ); } } else if ( consumerParams.headerExtensions.some( ext => ext.uri === 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' ) ) { for (const codec of consumerParams.codecs) { codec.rtcpFeedback = codec.rtcpFeedback!.filter( fb => fb.type !== 'transport-cc' ); } } else { for (const codec of consumerParams.codecs) { codec.rtcpFeedback = codec.rtcpFeedback!.filter( fb => fb.type !== 'transport-cc' && fb.type !== 'goog-remb' ); } } if (!pipe) { const consumerEncoding: RtpEncodingParameters = { ssrc: utils.generateRandomNumber(), }; if (rtxSupported) { consumerEncoding.rtx = { ssrc: consumerEncoding.ssrc! + 1 }; } // If any of the consumableRtpParameters.encodings has scalabilityMode, // process it (assume all encodings have the same value). const encodingWithScalabilityMode = consumableRtpParameters.encodings!.find( encoding => encoding.scalabilityMode ); let scalabilityMode = encodingWithScalabilityMode ? encodingWithScalabilityMode.scalabilityMode : undefined; // If there is simulast, mangle spatial layers in scalabilityMode. if (consumableRtpParameters.encodings!.length > 1) { const { temporalLayers } = parseScalabilityMode(scalabilityMode); scalabilityMode = `L${ consumableRtpParameters.encodings!.length }T${temporalLayers}`; } if (scalabilityMode) { consumerEncoding.scalabilityMode = scalabilityMode; } // Use the maximum maxBitrate in any encoding and honor it in the Consumer's // encoding. const maxEncodingMaxBitrate = consumableRtpParameters.encodings!.reduce( (maxBitrate, encoding) => encoding.maxBitrate && encoding.maxBitrate > maxBitrate ? encoding.maxBitrate : maxBitrate, 0 ); if (maxEncodingMaxBitrate) { consumerEncoding.maxBitrate = maxEncodingMaxBitrate; } // Set a single encoding for the Consumer. consumerParams.encodings!.push(consumerEncoding); } else { const consumableEncodings = utils.clone( consumableRtpParameters.encodings ) ?? []; const baseSsrc = utils.generateRandomNumber(); const baseRtxSsrc = utils.generateRandomNumber(); for (let i = 0; i < consumableEncodings.length; ++i) { const encoding = consumableEncodings[i]!; encoding.ssrc = baseSsrc + i; if (rtxSupported) { encoding.rtx = { ssrc: baseRtxSsrc + i }; } else { delete encoding.rtx; } consumerParams.encodings!.push(encoding); } } return consumerParams; } /** * Generate RTP parameters for a pipe Consumer. * * It keeps all original consumable encodings and removes support for BWE. If * enableRtx is false, it also removes RTX and NACK support. */ export function getPipeConsumerRtpParameters({ consumableRtpParameters, enableRtx, }: { consumableRtpParameters: RtpParameters; enableRtx: boolean; }): RtpParameters { const consumerParams: RtpParameters = { codecs: [], headerExtensions: [], encodings: [], rtcp: consumableRtpParameters.rtcp, msid: consumableRtpParameters.msid, }; const consumableCodecs = utils.clone( consumableRtpParameters.codecs ) ?? []; for (const codec of consumableCodecs) { if (!enableRtx && isRtxCodec(codec)) { continue; } codec.rtcpFeedback = codec.rtcpFeedback!.filter( fb => (fb.type === 'nack' && fb.parameter === 'pli') || (fb.type === 'ccm' && fb.parameter === 'fir') || (enableRtx && fb.type === 'nack' && !fb.parameter) ); consumerParams.codecs.push(codec); } // Reduce RTP extensions by disabling transport MID and BWE related ones. consumerParams.headerExtensions = consumableRtpParameters.headerExtensions!.filter( ext => ext.uri !== 'urn:ietf:params:rtp-hdrext:sdes:mid' && ext.uri !== 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' && ext.uri !== 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01' ); // TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header // extension. // // We need to add Dependency-Descriptor header extension manually since it's // 'recvonly' so it's not present in received `consumableRtpParameters`. if (cache.dependencyDescriptorHeaderExtensionParametersForPipeConsumer) { consumerParams.headerExtensions.push( cache.dependencyDescriptorHeaderExtensionParametersForPipeConsumer ); // Sort header extensions by ID. consumerParams.headerExtensions.sort((a, b) => a.id - b.id); } const consumableEncodings = utils.clone( consumableRtpParameters.encodings ) ?? []; const baseSsrc = utils.generateRandomNumber(); const baseRtxSsrc = utils.generateRandomNumber(); for (let i = 0; i < consumableEncodings.length; ++i) { const encoding = consumableEncodings[i]!; encoding.ssrc = baseSsrc + i; if (enableRtx) { encoding.rtx = { ssrc: baseRtxSsrc + i }; } else { delete encoding.rtx; } consumerParams.encodings!.push(encoding); } return consumerParams; } export function serializeRtpMapping( builder: flatbuffers.Builder, rtpMapping: RtpCodecsEncodingsMapping ): number { const codecs: number[] = []; for (const codec of rtpMapping.codecs) { codecs.push( FbsRtpParameters.CodecMapping.createCodecMapping( builder, codec.payloadType, codec.mappedPayloadType ) ); } const codecsOffset = FbsRtpParameters.RtpMapping.createCodecsVector( builder, codecs ); const encodings: number[] = []; for (const encoding of rtpMapping.encodings) { encodings.push( FbsRtpParameters.EncodingMapping.createEncodingMapping( builder, builder.createString(encoding.rid), encoding.ssrc ?? null, builder.createString(encoding.scalabilityMode), encoding.mappedSsrc ) ); } const encodingsOffset = FbsRtpParameters.RtpMapping.createEncodingsVector( builder, encodings ); return FbsRtpParameters.RtpMapping.createRtpMapping( builder, codecsOffset, encodingsOffset ); } function isRtxCodec(codec: RtpCodecCapability | RtpCodecParameters): boolean { return /.+\/rtx$/i.test(codec.mimeType); } function matchCodecs( aCodec: RtpCodecCapability | RouterRtpCodecCapability | RtpCodecParameters, bCodec: RtpCodecCapability | RouterRtpCodecCapability | RtpCodecParameters, { strict = false, modify = false } = {} ): boolean { const aMimeType = aCodec.mimeType.toLowerCase(); const bMimeType = bCodec.mimeType.toLowerCase(); if (aMimeType !== bMimeType) { return false; } if (aCodec.clockRate !== bCodec.clockRate) { return false; } if (aCodec.channels !== bCodec.channels) { return false; } // Per codec special checks. switch (aMimeType) { case 'audio/multiopus': { const aNumStreams = aCodec.parameters!['num_streams']; const bNumStreams = bCodec.parameters!['num_streams']; if (aNumStreams !== bNumStreams) { return false; } const aCoupledStreams = aCodec.parameters!['coupled_streams']; const bCoupledStreams = bCodec.parameters!['coupled_streams']; if (aCoupledStreams !== bCoupledStreams) { return false; } break; } case 'video/h264': { if (strict) { const aPacketizationMode = aCodec.parameters!['packetization-mode'] || 0; const bPacketizationMode = bCodec.parameters!['packetization-mode'] || 0; if (aPacketizationMode !== bPacketizationMode) { return false; } if (!h264.isSameProfile(aCodec.parameters, bCodec.parameters)) { return false; } let selectedProfileLevelId; try { selectedProfileLevelId = h264.generateProfileLevelIdStringForAnswer( aCodec.parameters, bCodec.parameters ); } catch (error) { return false; } if (modify) { if (selectedProfileLevelId) { aCodec.parameters!['profile-level-id'] = selectedProfileLevelId; } else { delete aCodec.parameters!['profile-level-id']; } } } break; } case 'video/vp9': { if (strict) { const aProfileId = aCodec.parameters!['profile-id'] || 0; const bProfileId = bCodec.parameters!['profile-id'] || 0; if (aProfileId !== bProfileId) { return false; } } break; } } return true; } /** * Validates RtpCodecCapability. It may modify given data by adding missing * fields with default values. * It throws if invalid. */ function validateAndNormalizeRtpCodecCapability( codec: RtpCodecCapability | RouterRtpCodecCapability ): void { const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i'); if (typeof codec !== 'object') { throw new TypeError('codec is not an object'); } // mimeType is mandatory. if (!codec.mimeType || typeof codec.mimeType !== 'string') { throw new TypeError('missing codec.mimeType'); } const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType); if (!mimeTypeMatch) { throw new TypeError('invalid codec.mimeType'); } // Just override kind with media component of mimeType. codec.kind = mimeTypeMatch[1]!.toLowerCase() as MediaKind; // preferredPayloadType is optional in RouterRtpCodecCapability. if ( codec.preferredPayloadType && typeof codec.preferredPayloadType !== 'number' ) { throw new TypeError('invalid codec.preferredPayloadType'); } // clockRate is mandatory. if (typeof codec.clockRate !== 'number') { throw new TypeError('missing codec.clockRate'); } // channels is optional. If unset, set it to 1 (just if audio). if (codec.kind === 'audio') { if (typeof codec.channels !== 'number') { codec.channels = 1; } } else { delete codec.channels; } // parameters is optional. If unset, set it to an empty object. if (!codec.parameters || typeof codec.parameters !== 'object') { codec.parameters = {}; } for (const key of Object.keys(codec.parameters)) { let value = codec.parameters[key]; if (value === undefined) { codec.parameters[key] = ''; value = ''; } if (typeof value !== 'string' && typeof value !== 'number') { throw new TypeError( `invalid codec parameter [key:${key}s, value:${value}]` ); } // Specific parameters validation. if (key === 'apt') { if (typeof value !== 'number') { throw new TypeError('invalid codec apt parameter'); } } } // rtcpFeedback is optional. If unset, set it to an empty array. if (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) { codec.rtcpFeedback = []; } for (const fb of codec.rtcpFeedback) { validateAndNormalizeRtcpFeedback(fb); } } /** * Validates RtcpFeedback. It may modify given data by adding missing * fields with default values. * It throws if invalid. */ function validateAndNormalizeRtcpFeedback(fb: RtcpFeedback): void { if (typeof fb !== 'object') { throw new TypeError('fb is not an object'); } // type is mandatory. if (!fb.type || typeof fb.type !== 'string') { throw new TypeError('missing fb.type'); } // parameter is optional. If unset set it to an empty string. if (!fb.parameter || typeof fb.parameter !== 'string') { fb.parameter = ''; } } /** * Validates RtpHeaderExtension. It may modify given data by adding missing * fields with default values. * It throws if invalid. */ function validateAndNormalizeRtpHeaderExtension(ext: RtpHeaderExtension): void { if (typeof ext !== 'object') { throw new TypeError('ext is not an object'); } if (ext.kind !== 'audio' && ext.kind !== 'video') { throw new TypeError('invalid ext.kind'); } // uri is mandatory. if (!ext.uri || typeof ext.uri !== 'string') { throw new TypeError('missing ext.uri'); } // preferredId is mandatory. if (typeof ext.preferredId !== 'number') { throw new TypeError('missing ext.preferredId'); } // preferredEncrypt is optional. If unset set it to false. if (ext.preferredEncrypt && typeof ext.preferredEncrypt !== 'boolean') { throw new TypeError('invalid ext.preferredEncrypt'); } else if (!ext.preferredEncrypt) { ext.preferredEncrypt = false; } // direction is optional. If unset set it to sendrecv. if (ext.direction && typeof ext.direction !== 'string') { throw new TypeError('invalid ext.direction'); } else if (!ext.direction) { ext.direction = 'sendrecv'; } } /** * Validates RtpCodecParameters. It may modify given data by adding missing * fields with default values. * It throws if invalid. */ function validateAndNormalizeRtpCodecParameters( codec: RtpCodecParameters ): void { const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i'); if (typeof codec !== 'object') { throw new TypeError('codec is not an object'); } // mimeType is mandatory. if (!codec.mimeType || typeof codec.mimeType !== 'string') { throw new TypeError('missing codec.mimeType'); } const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType); if (!mimeTypeMatch) { throw new TypeError('invalid codec.mimeType'); } // payloadType is mandatory. if (typeof codec.payloadType !== 'number') { throw new TypeError('missing codec.payloadType'); } // clockRate is mandatory. if (typeof codec.clockRate !== 'number') { throw new TypeError('missing codec.clockRate'); } const kind = mimeTypeMatch[1]!.toLowerCase() as MediaKind; // channels is optional. If unset, set it to 1 (just if audio). if (kind === 'audio') { if (typeof codec.channels !== 'number') { codec.channels = 1; } } else { delete codec.channels; } // parameters is optional. If unset, set it to an empty object. if (!codec.parameters || typeof codec.parameters !== 'object') { codec.parameters = {}; } for (const key of Object.keys(codec.parameters)) { let value = codec.parameters[key]; if (value === undefined) { codec.parameters[key] = ''; value = ''; } if (typeof value !== 'string' && typeof value !== 'number') { throw new TypeError( `invalid codec parameter [key:${key}s, value:${value}]` ); } // Specific parameters validation. if (key === 'apt') { if (typeof value !== 'number') { throw new TypeError('invalid codec apt parameter'); } } } // rtcpFeedback is optional. If unset, set it to an empty array. if (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) { codec.rtcpFeedback = []; } for (const fb of codec.rtcpFeedback) { validateAndNormalizeRtcpFeedback(fb); } } /** * Validates RtpHeaderExtensionParameteters. It may modify given data by adding * missing fields with default values. It throws if invalid. */ function validateAndNormalizeRtpHeaderExtensionParameters( ext: RtpHeaderExtensionParameters ): void { if (typeof ext !== 'object') { throw new TypeError('ext is not an object'); } // uri is mandatory. if (!ext.uri || typeof ext.uri !== 'string') { throw new TypeError('missing ext.uri'); } // id is mandatory. if (typeof ext.id !== 'number') { throw new TypeError('missing ext.id'); } // encrypt is optional. If unset set it to false. if (ext.encrypt && typeof ext.encrypt !== 'boolean') { throw new TypeError('invalid ext.encrypt'); } else if (!ext.encrypt) { ext.encrypt = false; } // parameters is optional. If unset, set it to an empty object. if (!ext.parameters || typeof ext.parameters !== 'object') { ext.parameters = {}; } for (const key of Object.keys(ext.parameters)) { let value = ext.parameters[key]; if (value === undefined) { ext.parameters[key] = ''; value = ''; } if (typeof value !== 'string' && typeof value !== 'number') { throw new TypeError('invalid header extension parameter'); } } } /** * Validates RtpEncodingParameters. It may modify given data by adding missing * fields with default values. * It throws if invalid. */ function validateAndNormalizeRtpEncodingParameters( encoding: RtpEncodingParameters ): void { if (typeof encoding !== 'object') { throw new TypeError('encoding is not an object'); } // ssrc is optional. if (encoding.ssrc && typeof encoding.ssrc !== 'number') { throw new TypeError('invalid encoding.ssrc'); } // rid is optional. if (encoding.rid && typeof encoding.rid !== 'string') { throw new TypeError('invalid encoding.rid'); } // rtx is optional. if (encoding.rtx && typeof encoding.rtx !== 'object') { throw new TypeError('invalid encoding.rtx'); } else if (encoding.rtx) { // RTX ssrc is mandatory if rtx is present. if (typeof encoding.rtx.ssrc !== 'number') { throw new TypeError('missing encoding.rtx.ssrc'); } } // dtx is optional. If unset set it to false. if (!encoding.dtx || typeof encoding.dtx !== 'boolean') { encoding.dtx = false; } // scalabilityMode is optional. if ( encoding.scalabilityMode && typeof encoding.scalabilityMode !== 'string' ) { throw new TypeError('invalid encoding.scalabilityMode'); } } /** * Validates RtcpParameters. It may modify given data by adding missing * fields with default values. * It throws if invalid. */ function validateAndNormalizeRtcpParameters(rtcp: RtcpParameters): void { if (typeof rtcp !== 'object') { throw new TypeError('rtcp is not an object'); } // cname is optional. if (rtcp.cname && typeof rtcp.cname !== 'string') { throw new TypeError('invalid rtcp.cname'); } // reducedSize is optional. If unset set it to true. if (!rtcp.reducedSize || typeof rtcp.reducedSize !== 'boolean') { rtcp.reducedSize = true; } } ================================================ FILE: node/src/rtpParametersFbsUtils.ts ================================================ import type * as flatbuffers from 'flatbuffers'; import type { RtpParameters, RtpCodecParameters, RtcpFeedback, RtpEncodingParameters, RtpHeaderExtensionUri, RtpHeaderExtensionParameters, RtcpParameters, } from './rtpParametersTypes'; import * as fbsUtils from './fbsUtils'; import { Boolean as FbsBoolean, Double as FbsDouble, Integer32 as FbsInteger32, Integer32Array as FbsInteger32Array, String as FbsString, Parameter as FbsParameter, RtcpFeedback as FbsRtcpFeedback, RtcpParameters as FbsRtcpParameters, RtpCodecParameters as FbsRtpCodecParameters, RtpEncodingParameters as FbsRtpEncodingParameters, RtpHeaderExtensionParameters as FbsRtpHeaderExtensionParameters, RtpHeaderExtensionUri as FbsRtpHeaderExtensionUri, RtpParameters as FbsRtpParameters, Rtx as FbsRtx, Value as FbsValue, } from './fbs/rtp-parameters'; export function serializeRtpParameters( builder: flatbuffers.Builder, rtpParameters: RtpParameters ): number { const codecs: number[] = []; const headerExtensions: number[] = []; for (const codec of rtpParameters.codecs) { const mimeTypeOffset = builder.createString(codec.mimeType); const parameters = serializeParameters(builder, codec.parameters!); const parametersOffset = FbsRtpCodecParameters.createParametersVector( builder, parameters ); const rtcpFeedback: number[] = []; for (const rtcp of codec.rtcpFeedback ?? []) { const typeOffset = builder.createString(rtcp.type); const rtcpParametersOffset = builder.createString(rtcp.parameter); rtcpFeedback.push( FbsRtcpFeedback.createRtcpFeedback( builder, typeOffset, rtcpParametersOffset ) ); } const rtcpFeedbackOffset = FbsRtpCodecParameters.createRtcpFeedbackVector( builder, rtcpFeedback ); codecs.push( FbsRtpCodecParameters.createRtpCodecParameters( builder, mimeTypeOffset, codec.payloadType, codec.clockRate, Number(codec.channels), parametersOffset, rtcpFeedbackOffset ) ); } const codecsOffset = FbsRtpParameters.createCodecsVector(builder, codecs); // RtpHeaderExtensionParameters. for (const headerExtension of rtpParameters.headerExtensions ?? []) { const uri = rtpHeaderExtensionUriToFbs(headerExtension.uri); const parameters = serializeParameters( builder, headerExtension.parameters! ); const parametersOffset = FbsRtpCodecParameters.createParametersVector( builder, parameters ); headerExtensions.push( FbsRtpHeaderExtensionParameters.createRtpHeaderExtensionParameters( builder, uri, headerExtension.id, Boolean(headerExtension.encrypt), parametersOffset ) ); } const headerExtensionsOffset = FbsRtpParameters.createHeaderExtensionsVector( builder, headerExtensions ); // RtpEncodingParameters. const encodingsOffset = serializeRtpEncodingParameters( builder, rtpParameters.encodings ?? [] ); // RtcpParameters. const { cname, reducedSize } = rtpParameters.rtcp ?? { reducedSize: true }; const cnameOffset = builder.createString(cname); const rtcpOffset = FbsRtcpParameters.createRtcpParameters( builder, cnameOffset, Boolean(reducedSize) ); const midOffset = builder.createString(rtpParameters.mid); const msidOffset = builder.createString(rtpParameters.msid); FbsRtpParameters.startRtpParameters(builder); FbsRtpParameters.addMid(builder, midOffset); FbsRtpParameters.addCodecs(builder, codecsOffset); FbsRtpParameters.addHeaderExtensions(builder, headerExtensionsOffset); FbsRtpParameters.addEncodings(builder, encodingsOffset); FbsRtpParameters.addRtcp(builder, rtcpOffset); FbsRtpParameters.addMsid(builder, msidOffset); return FbsRtpParameters.endRtpParameters(builder); } export function serializeRtpEncodingParameters( builder: flatbuffers.Builder, rtpEncodingParameters: RtpEncodingParameters[] = [] ): number { const encodings: number[] = []; for (const encoding of rtpEncodingParameters) { // Prepare Rid. const ridOffset = builder.createString(encoding.rid); // Prepare Rtx. let rtxOffset: number | undefined; if (encoding.rtx) { rtxOffset = FbsRtx.createRtx(builder, encoding.rtx.ssrc); } // Prepare scalability mode. let scalabilityModeOffset: number | undefined; if (encoding.scalabilityMode) { scalabilityModeOffset = builder.createString(encoding.scalabilityMode); } // Start serialization. FbsRtpEncodingParameters.startRtpEncodingParameters(builder); // Add SSRC. if (encoding.ssrc) { FbsRtpEncodingParameters.addSsrc(builder, encoding.ssrc); } // Add Rid. FbsRtpEncodingParameters.addRid(builder, ridOffset); // Add payload type. if (encoding.codecPayloadType) { FbsRtpEncodingParameters.addCodecPayloadType( builder, encoding.codecPayloadType ); } // Add RTX. if (rtxOffset) { FbsRtpEncodingParameters.addRtx(builder, rtxOffset); } // Add DTX. if (encoding.dtx !== undefined) { FbsRtpEncodingParameters.addDtx(builder, encoding.dtx); } // Add scalability ode. if (scalabilityModeOffset) { FbsRtpEncodingParameters.addScalabilityMode( builder, scalabilityModeOffset ); } // Add max bitrate. if (encoding.maxBitrate !== undefined) { FbsRtpEncodingParameters.addMaxBitrate(builder, encoding.maxBitrate); } // End serialization. encodings.push(FbsRtpEncodingParameters.endRtpEncodingParameters(builder)); } return FbsRtpParameters.createEncodingsVector(builder, encodings); } export function serializeParameters( builder: flatbuffers.Builder, parameters: Record ): number[] { const fbsParameters: number[] = []; for (const key of Object.keys(parameters)) { const value = parameters[key]; const keyOffset = builder.createString(key); let parameterOffset: number; if (typeof value === 'boolean') { parameterOffset = FbsParameter.createParameter( builder, keyOffset, FbsValue.Boolean, value === true ? 1 : 0 ); } else if (typeof value === 'number') { // Integer. if (value % 1 === 0) { const valueOffset = FbsInteger32.createInteger32(builder, value); parameterOffset = FbsParameter.createParameter( builder, keyOffset, FbsValue.Integer32, valueOffset ); } // Float. else { const valueOffset = FbsDouble.createDouble(builder, value); parameterOffset = FbsParameter.createParameter( builder, keyOffset, FbsValue.Double, valueOffset ); } } else if (typeof value === 'string') { const valueOffset = FbsString.createString( builder, builder.createString(value) ); parameterOffset = FbsParameter.createParameter( builder, keyOffset, FbsValue.String, valueOffset ); } else if (Array.isArray(value)) { const valueOffset = FbsInteger32Array.createValueVector(builder, value); parameterOffset = FbsParameter.createParameter( builder, keyOffset, FbsValue.Integer32Array, valueOffset ); } else { throw new Error(`invalid parameter type [key:'${key}', value:${value}]`); } fbsParameters.push(parameterOffset); } return fbsParameters; } export function parseRtcpFeedback(data: FbsRtcpFeedback): RtcpFeedback { return { type: data.type()!, parameter: data.parameter() ?? undefined, }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any export function parseParameters(data: any): Record { const parameters: Record = {}; for (let i = 0; i < data.parametersLength(); i++) { const fbsParameter = data.parameters(i)!; switch (fbsParameter.valueType()) { case FbsValue.Boolean: { const value = new FbsBoolean(); fbsParameter.value(value); parameters[String(fbsParameter.name())] = value.value(); break; } case FbsValue.Integer32: { const value = new FbsInteger32(); fbsParameter.value(value); parameters[String(fbsParameter.name())] = value.value(); break; } case FbsValue.Double: { const value = new FbsDouble(); fbsParameter.value(value); parameters[String(fbsParameter.name())] = value.value(); break; } case FbsValue.String: { const value = new FbsString(); fbsParameter.value(value); parameters[String(fbsParameter.name())] = value.value(); break; } case FbsValue.Integer32Array: { const value = new FbsInteger32Array(); fbsParameter.value(value); parameters[String(fbsParameter.name())] = value.valueArray(); break; } } } return parameters; } export function parseRtpCodecParameters( data: FbsRtpCodecParameters ): RtpCodecParameters { const parameters = parseParameters(data); let rtcpFeedback: RtcpFeedback[] = []; if (data.rtcpFeedbackLength() > 0) { rtcpFeedback = fbsUtils.parseVector( data, 'rtcpFeedback', parseRtcpFeedback ); } return { mimeType: data.mimeType()!, payloadType: data.payloadType(), clockRate: data.clockRate(), channels: data.channels() ?? undefined, parameters, rtcpFeedback, }; } export function rtpHeaderExtensionUriFromFbs( uri: FbsRtpHeaderExtensionUri ): RtpHeaderExtensionUri { switch (uri) { case FbsRtpHeaderExtensionUri.Mid: { return 'urn:ietf:params:rtp-hdrext:sdes:mid'; } case FbsRtpHeaderExtensionUri.RtpStreamId: { return 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id'; } case FbsRtpHeaderExtensionUri.RepairRtpStreamId: { return 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id'; } case FbsRtpHeaderExtensionUri.AbsSendTime: { return 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time'; } case FbsRtpHeaderExtensionUri.TransportWideCcDraft01: { return 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'; } case FbsRtpHeaderExtensionUri.SsrcAudioLevel: { return 'urn:ietf:params:rtp-hdrext:ssrc-audio-level'; } case FbsRtpHeaderExtensionUri.DependencyDescriptor: { return 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension'; } case FbsRtpHeaderExtensionUri.VideoOrientation: { return 'urn:3gpp:video-orientation'; } case FbsRtpHeaderExtensionUri.TimeOffset: { return 'urn:ietf:params:rtp-hdrext:toffset'; } case FbsRtpHeaderExtensionUri.AbsCaptureTime: { return 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time'; } case FbsRtpHeaderExtensionUri.PlayoutDelay: { return 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay'; } case FbsRtpHeaderExtensionUri.MediasoupPacketId: { return 'urn:mediasoup:params:rtp-hdrext:packet-id'; } } } export function rtpHeaderExtensionUriToFbs( uri: RtpHeaderExtensionUri ): FbsRtpHeaderExtensionUri { switch (uri) { case 'urn:ietf:params:rtp-hdrext:sdes:mid': { return FbsRtpHeaderExtensionUri.Mid; } case 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id': { return FbsRtpHeaderExtensionUri.RtpStreamId; } case 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id': { return FbsRtpHeaderExtensionUri.RepairRtpStreamId; } case 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time': { return FbsRtpHeaderExtensionUri.AbsSendTime; } case 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01': { return FbsRtpHeaderExtensionUri.TransportWideCcDraft01; } case 'urn:ietf:params:rtp-hdrext:ssrc-audio-level': { return FbsRtpHeaderExtensionUri.SsrcAudioLevel; } case 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension': { return FbsRtpHeaderExtensionUri.DependencyDescriptor; } case 'urn:3gpp:video-orientation': { return FbsRtpHeaderExtensionUri.VideoOrientation; } case 'urn:ietf:params:rtp-hdrext:toffset': { return FbsRtpHeaderExtensionUri.TimeOffset; } case 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time': { return FbsRtpHeaderExtensionUri.AbsCaptureTime; } case 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay': { return FbsRtpHeaderExtensionUri.PlayoutDelay; } case 'urn:mediasoup:params:rtp-hdrext:packet-id': { return FbsRtpHeaderExtensionUri.MediasoupPacketId; } default: { throw new TypeError(`invalid RtpHeaderExtensionUri: ${uri}`); } } } export function parseRtpHeaderExtensionParameters( data: FbsRtpHeaderExtensionParameters ): RtpHeaderExtensionParameters { return { uri: rtpHeaderExtensionUriFromFbs(data.uri()), id: data.id(), encrypt: data.encrypt(), parameters: parseParameters(data), }; } export function parseRtpEncodingParameters( data: FbsRtpEncodingParameters ): RtpEncodingParameters { return { ssrc: data.ssrc() ?? undefined, rid: data.rid() || undefined, codecPayloadType: data.codecPayloadType() !== null ? data.codecPayloadType()! : undefined, rtx: data.rtx() ? { ssrc: data.rtx()!.ssrc() } : undefined, dtx: data.dtx(), scalabilityMode: data.scalabilityMode() || undefined, maxBitrate: data.maxBitrate() !== null ? data.maxBitrate()! : undefined, }; } export function parseRtpParameters(data: FbsRtpParameters): RtpParameters { const codecs = fbsUtils.parseVector(data, 'codecs', parseRtpCodecParameters); let headerExtensions: RtpHeaderExtensionParameters[] = []; if (data.headerExtensionsLength() > 0) { headerExtensions = fbsUtils.parseVector( data, 'headerExtensions', parseRtpHeaderExtensionParameters ); } let encodings: RtpEncodingParameters[] = []; if (data.encodingsLength() > 0) { encodings = fbsUtils.parseVector( data, 'encodings', parseRtpEncodingParameters ); } let rtcp: RtcpParameters | undefined; if (data.rtcp()) { const fbsRtcp = data.rtcp()!; rtcp = { cname: fbsRtcp.cname() || undefined, reducedSize: fbsRtcp.reducedSize(), }; } return { mid: data.mid() || undefined, codecs, headerExtensions, encodings, rtcp, msid: data.msid() || undefined, }; } ================================================ FILE: node/src/rtpParametersTypes.ts ================================================ /** * Media kind ('audio' or 'video'). */ export type MediaKind = 'audio' | 'video'; /** * The RTP capabilities define what mediasoup or an endpoint can receive at * media level. */ export type RtpCapabilities = { /** * Supported media and RTX codecs. */ codecs?: RtpCodecCapability[]; /** * Supported RTP header extensions. */ headerExtensions?: RtpHeaderExtension[]; }; /** * Special RtpCapabilities for `supportedRtpCapabilities` in which `codecs` * is an array of RouterRtpCodecCapability. */ export type RouterRtpCapabilities = Omit & { codecs?: RouterRtpCodecCapability[]; }; /** * Provides information on the capabilities of a codec within the RTP * capabilities. The list of media codecs supported by mediasoup and their * settings is defined in the supportedRtpCapabilities.ts file. * * Exactly one RtpCodecCapability will be present for each supported combination * of parameters that requires a distinct value of preferredPayloadType. For * example: * * - Multiple H264 codecs, each with their own distinct 'packetization-mode' and * 'profile-level-id' values. * - Multiple VP9 codecs, each with their own distinct 'profile-id' value. * * RtpCodecCapability entries in the mediaCodecs array of RouterOptions do not * require preferredPayloadType field (if unset, mediasoup will choose a random * one). If given, make sure it's in the 96-127 range. */ export type RtpCodecCapability = { /** * Media kind. */ kind: MediaKind; /** * The codec MIME media type/subtype (e.g. 'audio/opus', 'video/VP8'). */ mimeType: string; /** * The preferred RTP payload type. * * NOTE: Despite it's a mandatory field, it's optional in `mediaCodecs` of * RouterOptions. */ preferredPayloadType: number; /** * Codec clock rate expressed in Hertz. */ clockRate: number; /** * The number of channels supported (e.g. two for stereo). Just for audio. * Default 1. */ channels?: number; /** * Codec specific parameters. Some parameters (such as 'packetization-mode' * and 'profile-level-id' in H264 or 'profile-id' in VP9) are critical for * codec matching. */ parameters?: Record; /** * Transport layer and codec-specific feedback messages for this codec. */ rtcpFeedback?: RtcpFeedback[]; }; /** * Special RtpCodecCapability for RouterOptions in which `preferredPayloadType` * is optional. */ export type RouterRtpCodecCapability = Omit< RtpCodecCapability, 'preferredPayloadType' > & { preferredPayloadType?: number; }; /** * Direction of RTP header extension. */ export type RtpHeaderExtensionDirection = | 'sendrecv' | 'sendonly' | 'recvonly' | 'inactive'; /** * Provides information relating to supported header extensions. The list of * RTP header extensions supported by mediasoup is defined in the * supportedRtpCapabilities.ts file. * * mediasoup does not currently support encrypted RTP header extensions. The * direction field is just present in mediasoup RTP capabilities (retrieved via * router.rtpCapabilities or mediasoup.getSupportedRtpCapabilities()). It's * ignored if present in endpoints' RTP capabilities. */ export type RtpHeaderExtension = { /** * Media kind. */ kind: MediaKind; /* * The URI of the RTP header extension, as defined in RFC 5285. */ uri: RtpHeaderExtensionUri; /** * The preferred numeric identifier that goes in the RTP packet. Must be * unique. */ preferredId: number; /** * If true, it is preferred that the value in the header be encrypted as per * RFC 6904. Default false. */ preferredEncrypt?: boolean; /** * If 'sendrecv', mediasoup supports sending and receiving this RTP extension. * 'sendonly' means that mediasoup can send (but not receive) it. 'recvonly' * means that mediasoup can receive (but not send) it. */ direction?: RtpHeaderExtensionDirection; }; /** * The RTP send parameters describe a media stream received by mediasoup from * an endpoint through its corresponding mediasoup Producer. These parameters * may include a mid value that the mediasoup transport will use to match * received RTP packets based on their MID RTP extension value. * * mediasoup allows RTP send parameters with a single encoding and with multiple * encodings (simulcast). In the latter case, each entry in the encodings array * must include a ssrc field or a rid field (the RID RTP extension value). Check * the Simulcast and SVC sections for more information. * * The RTP receive parameters describe a media stream as sent by mediasoup to * an endpoint through its corresponding mediasoup Consumer. The mid value is * unset (mediasoup does not include the MID RTP extension into RTP packets * being sent to endpoints). * * There is a single entry in the encodings array (even if the corresponding * producer uses simulcast). The consumer sends a single and continuous RTP * stream to the endpoint and spatial/temporal layer selection is possible via * consumer.setPreferredLayers(). * * As an exception, previous bullet is not true when consuming a stream over a * PipeTransport, in which all RTP streams from the associated producer are * forwarded verbatim through the consumer. * * The RTP receive parameters will always have their ssrc values randomly * generated for all of its encodings (and optional rtx: { ssrc: XXXX } if the * endpoint supports RTX), regardless of the original RTP send parameters in * the associated producer. This applies even if the producer's encodings have * rid set. */ export type RtpParameters = { /** * The MID RTP extension value as defined in the BUNDLE specification. */ mid?: string; /** * Media and RTX codecs in use. */ codecs: RtpCodecParameters[]; /** * RTP header extensions in use. */ headerExtensions?: RtpHeaderExtensionParameters[]; /** * Transmitted RTP streams and their settings. */ encodings?: RtpEncodingParameters[]; /** * Parameters used for RTCP. */ rtcp?: RtcpParameters; /** * MSID (WebRTC MediaStream Identification). * * @see https://datatracker.ietf.org/doc/html/rfc8830 */ msid?: string; }; /** * Provides information on codec settings within the RTP parameters. The list * of media codecs supported by mediasoup and their settings is defined in the * supportedRtpCapabilities.ts file. */ export type RtpCodecParameters = { /** * The codec MIME media type/subtype (e.g. 'audio/opus', 'video/VP8'). */ mimeType: string; /** * The value that goes in the RTP Payload Type Field. Must be unique. */ payloadType: number; /** * Codec clock rate expressed in Hertz. */ clockRate: number; /** * The number of channels supported (e.g. two for stereo). Just for audio. * Default 1. */ channels?: number; /** * Codec-specific parameters available for signaling. Some parameters (such * as 'packetization-mode' and 'profile-level-id' in H264 or 'profile-id' in * VP9) are critical for codec matching. */ parameters?: Record; /** * Transport layer and codec-specific feedback messages for this codec. */ rtcpFeedback?: RtcpFeedback[]; }; /** * Provides information on RTCP feedback messages for a specific codec. Those * messages can be transport layer feedback messages or codec-specific feedback * messages. The list of RTCP feedbacks supported by mediasoup is defined in the * supportedRtpCapabilities.ts file. */ export type RtcpFeedback = { /** * RTCP feedback type. */ type: string; /** * RTCP feedback parameter. */ parameter?: string; }; /** * Provides information relating to an encoding, which represents a media RTP * stream and its associated RTX stream (if any). */ export type RtpEncodingParameters = { /** * The media SSRC. */ ssrc?: number; /** * The RID RTP extension value. Must be unique. */ rid?: string; /** * Codec payload type this encoding affects. If unset, first media codec is * chosen. */ codecPayloadType?: number; /** * RTX stream information. It must contain a numeric ssrc field indicating * the RTX SSRC. */ rtx?: { ssrc: number }; /** * It indicates whether discontinuous RTP transmission will be used. Useful * for audio (if the codec supports it) and for video screen sharing (when * static content is being transmitted, this option disables the RTP * inactivity checks in mediasoup). Default false. */ dtx?: boolean; /** * Number of spatial and temporal layers in the RTP stream (e.g. 'L1T3'). * See webrtc-svc. */ scalabilityMode?: string; /** * Maximum bitrate (bps) announced for this stream. */ maxBitrate?: number; }; export type RtpHeaderExtensionUri = | 'urn:ietf:params:rtp-hdrext:sdes:mid' | 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id' | 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id' | 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' | 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01' | 'urn:ietf:params:rtp-hdrext:ssrc-audio-level' | 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension' | 'urn:3gpp:video-orientation' | 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time' | 'urn:ietf:params:rtp-hdrext:toffset' | 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay' | 'urn:mediasoup:params:rtp-hdrext:packet-id'; /** * Defines a RTP header extension within the RTP parameters. The list of RTP * header extensions supported by mediasoup is defined in the * supportedRtpCapabilities.ts file. * * mediasoup does not currently support encrypted RTP header extensions and no * parameters are currently considered. */ export type RtpHeaderExtensionParameters = { /** * The URI of the RTP header extension, as defined in RFC 5285. */ uri: RtpHeaderExtensionUri; /** * The numeric identifier that goes in the RTP packet. Must be unique. */ id: number; /** * If true, the value in the header is encrypted as per RFC 6904. Default false. */ encrypt?: boolean; /** * Configuration parameters for the header extension. */ parameters?: Record; }; /** * Provides information on RTCP settings within the RTP parameters. * * If no cname is given in a producer's RTP parameters, the mediasoup transport * will choose a random one that will be used into RTCP SDES messages sent to * all its associated consumers. * * mediasoup assumes reducedSize to always be true. */ export type RtcpParameters = { /** * The Canonical Name (CNAME) used by RTCP (e.g. in SDES messages). */ cname?: string; /** * Whether reduced size RTCP RFC 5506 is configured (if true) or compound RTCP * as specified in RFC 3550 (if false). Default true. */ reducedSize?: boolean; }; ================================================ FILE: node/src/rtpStreamStatsFbsUtils.ts ================================================ import { RtpStreamRecvStats, RtpStreamSendStats, BaseRtpStreamStats, BitrateByLayer, } from './rtpStreamStatsTypes'; import * as FbsRtpStream from './fbs/rtp-stream'; import * as FbsRtpParameters from './fbs/rtp-parameters'; export function parseRtpStreamStats( binary: FbsRtpStream.Stats ): RtpStreamRecvStats | RtpStreamSendStats { if (binary.dataType() === FbsRtpStream.StatsData.RecvStats) { return parseRtpStreamRecvStats(binary); } else { return parseSendStreamStats(binary); } } export function parseRtpStreamRecvStats( binary: FbsRtpStream.Stats ): RtpStreamRecvStats { const recvStats = new FbsRtpStream.RecvStats(); const baseStats = new FbsRtpStream.BaseStats(); binary.data(recvStats); recvStats.base()!.data(baseStats); const base = parseBaseStreamStats(baseStats); return { ...base, type: 'inbound-rtp', byteCount: Number(recvStats.byteCount()), packetCount: Number(recvStats.packetCount()), bitrate: Number(recvStats.bitrate()), bitrateByLayer: parseBitrateByLayer(recvStats), }; } export function parseSendStreamStats( binary: FbsRtpStream.Stats ): RtpStreamSendStats { const sendStats = new FbsRtpStream.SendStats(); const baseStats = new FbsRtpStream.BaseStats(); binary.data(sendStats); sendStats.base()!.data(baseStats); const base = parseBaseStreamStats(baseStats); return { ...base, type: 'outbound-rtp', byteCount: Number(sendStats.byteCount()), packetCount: Number(sendStats.packetCount()), bitrate: Number(sendStats.bitrate()), }; } function parseBaseStreamStats( binary: FbsRtpStream.BaseStats ): BaseRtpStreamStats { return { timestamp: Number(binary.timestamp()), ssrc: binary.ssrc(), rtxSsrc: binary.rtxSsrc() ?? undefined, rid: binary.rid() || undefined, kind: binary.kind() === FbsRtpParameters.MediaKind.AUDIO ? 'audio' : 'video', mimeType: binary.mimeType()!, packetsLost: Number(binary.packetsLost()), fractionLost: Number(binary.fractionLost()), jitter: Number(binary.jitter()), packetsDiscarded: Number(binary.packetsDiscarded()), packetsRetransmitted: Number(binary.packetsRetransmitted()), packetsRepaired: Number(binary.packetsRepaired()), nackCount: Number(binary.nackCount()), nackPacketCount: Number(binary.nackPacketCount()), pliCount: Number(binary.pliCount()), firCount: Number(binary.firCount()), roundTripTime: binary.roundTripTime(), rtxPacketsDiscarded: binary.rtxPacketsDiscarded() ? Number(binary.rtxPacketsDiscarded()) : undefined, score: binary.score(), }; } function parseBitrateByLayer(binary: FbsRtpStream.RecvStats): BitrateByLayer { if (binary.bitrateByLayerLength() === 0) { return {}; } const bitRateByLayer: { [key: string]: number } = {}; for (let i = 0; i < binary.bitrateByLayerLength(); ++i) { const layer: string = binary.bitrateByLayer(i)!.layer()!; const bitrate = binary.bitrateByLayer(i)!.bitrate(); bitRateByLayer[layer] = Number(bitrate); } return bitRateByLayer; } ================================================ FILE: node/src/rtpStreamStatsTypes.ts ================================================ export type RtpStreamRecvStats = BaseRtpStreamStats & { type: string; packetCount: number; byteCount: number; bitrate: number; bitrateByLayer: BitrateByLayer; }; export type RtpStreamSendStats = BaseRtpStreamStats & { type: string; packetCount: number; byteCount: number; bitrate: number; }; export type BaseRtpStreamStats = { timestamp: number; ssrc: number; rtxSsrc?: number; rid?: string; kind: string; mimeType: string; packetsLost: number; fractionLost: number; jitter: number; packetsDiscarded: number; packetsRetransmitted: number; packetsRepaired: number; nackCount: number; nackPacketCount: number; pliCount: number; firCount: number; roundTripTime?: number; rtxPacketsDiscarded?: number; score: number; }; export type BitrateByLayer = { [key: string]: number }; ================================================ FILE: node/src/scalabilityModesTypes.ts ================================================ export type ScalabilityMode = { spatialLayers: number; temporalLayers: number; ksvc: boolean; }; ================================================ FILE: node/src/scalabilityModesUtils.ts ================================================ import { ScalabilityMode } from './scalabilityModesTypes'; const ScalabilityModeRegex = new RegExp( '^[LS]([1-9]\\d{0,1})T([1-9]\\d{0,1})(_KEY)?' ); export function parseScalabilityMode( scalabilityMode?: string ): ScalabilityMode { const match = ScalabilityModeRegex.exec(scalabilityMode ?? ''); if (match) { return { spatialLayers: Number(match[1]), temporalLayers: Number(match[2]), ksvc: Boolean(match[3]), }; } else { return { spatialLayers: 1, temporalLayers: 1, ksvc: false, }; } } ================================================ FILE: node/src/sctpParametersFbsUtils.ts ================================================ import type * as flatbuffers from 'flatbuffers'; import type { SctpStreamParameters, SctpParametersDump, } from './sctpParametersTypes'; import * as FbsSctpParameters from './fbs/sctp-parameters'; export function parseSctpParametersDump( binary: FbsSctpParameters.SctpParameters ): SctpParametersDump { return { port: binary.port(), OS: binary.os(), MIS: binary.mis(), maxMessageSize: binary.maxMessageSize(), sendBufferSize: binary.sendBufferSize(), sctpBufferedAmount: binary.sctpBufferedAmount(), isDataChannel: binary.isDataChannel(), }; } export function serializeSctpStreamParameters( builder: flatbuffers.Builder, parameters: SctpStreamParameters ): number { return FbsSctpParameters.SctpStreamParameters.createSctpStreamParameters( builder, parameters.streamId, parameters.ordered!, typeof parameters.maxPacketLifeTime === 'number' ? parameters.maxPacketLifeTime : null, typeof parameters.maxRetransmits === 'number' ? parameters.maxRetransmits : null ); } export function parseSctpStreamParameters( parameters: FbsSctpParameters.SctpStreamParameters ): SctpStreamParameters { return { streamId: parameters.streamId(), ordered: parameters.ordered()!, maxPacketLifeTime: parameters.maxPacketLifeTime() !== null ? parameters.maxPacketLifeTime()! : undefined, maxRetransmits: parameters.maxRetransmits() !== null ? parameters.maxRetransmits()! : undefined, }; } ================================================ FILE: node/src/sctpParametersTypes.ts ================================================ export type SctpCapabilities = { numStreams: NumSctpStreams; }; /** * Both OS and MIS are part of the SCTP INIT+ACK handshake. OS refers to the * initial number of outgoing SCTP streams that the server side transport creates * (to be used by DataConsumers), while MIS refers to the maximum number of * incoming SCTP streams that the server side transport can receive (to be used * by DataProducers). So, if the server side transport will just be used to * create data producers (but no data consumers), OS can be low (~1). * * mediasoup-client provides specific per browser/version OS and MIS values via * the device.sctpCapabilities getter. However those values must be reversed * when provided to the mediasoup server transport. */ export type NumSctpStreams = { /** * Initially requested number of outgoing SCTP streams. */ OS: number; /** * Maximum number of incoming SCTP streams. */ MIS: number; }; export type SctpParameters = { /** * Must always equal 5000. */ port: number; /** * Initially requested number of outgoing SCTP streams. */ OS: number; /** * Maximum number of incoming SCTP streams. */ MIS: number; /** * Maximum allowed size for SCTP messages. */ maxMessageSize: number; }; /** * SCTP stream parameters describe the reliability of a certain SCTP stream. * If ordered is true then maxPacketLifeTime and maxRetransmits must be * false. * If ordered if false, only one of maxPacketLifeTime or maxRetransmits * can be true. */ export type SctpStreamParameters = { /** * SCTP stream id. */ streamId: number; /** * Whether data messages must be received in order. If true the messages will * be sent reliably. Default true. */ ordered?: boolean; /** * When ordered is false indicates the time (in milliseconds) after which a * SCTP packet will stop being retransmitted. */ maxPacketLifeTime?: number; /** * When ordered is false indicates the maximum number of times a packet will * be retransmitted. */ maxRetransmits?: number; }; export type SctpParametersDump = { port: number; OS: number; MIS: number; maxMessageSize: number; sendBufferSize: number; sctpBufferedAmount: number; isDataChannel: boolean; }; ================================================ FILE: node/src/srtpParametersFbsUtils.ts ================================================ import type * as flatbuffers from 'flatbuffers'; import type { SrtpParameters, SrtpCryptoSuite } from './srtpParametersTypes'; import * as FbsSrtpParameters from './fbs/srtp-parameters'; export function cryptoSuiteFromFbs( binary: FbsSrtpParameters.SrtpCryptoSuite ): SrtpCryptoSuite { switch (binary) { case FbsSrtpParameters.SrtpCryptoSuite.AEAD_AES_256_GCM: { return 'AEAD_AES_256_GCM'; } case FbsSrtpParameters.SrtpCryptoSuite.AEAD_AES_128_GCM: { return 'AEAD_AES_128_GCM'; } case FbsSrtpParameters.SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_80: { return 'AES_CM_128_HMAC_SHA1_80'; } case FbsSrtpParameters.SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_32: { return 'AES_CM_128_HMAC_SHA1_32'; } } } export function cryptoSuiteToFbs( cryptoSuite: SrtpCryptoSuite ): FbsSrtpParameters.SrtpCryptoSuite { switch (cryptoSuite) { case 'AEAD_AES_256_GCM': { return FbsSrtpParameters.SrtpCryptoSuite.AEAD_AES_256_GCM; } case 'AEAD_AES_128_GCM': { return FbsSrtpParameters.SrtpCryptoSuite.AEAD_AES_128_GCM; } case 'AES_CM_128_HMAC_SHA1_80': { return FbsSrtpParameters.SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_80; } case 'AES_CM_128_HMAC_SHA1_32': { return FbsSrtpParameters.SrtpCryptoSuite.AES_CM_128_HMAC_SHA1_32; } default: { throw new TypeError(`invalid SrtpCryptoSuite: ${cryptoSuite}`); } } } export function parseSrtpParameters( binary: FbsSrtpParameters.SrtpParameters ): SrtpParameters { return { cryptoSuite: cryptoSuiteFromFbs(binary.cryptoSuite()), keyBase64: binary.keyBase64()!, }; } export function serializeSrtpParameters( builder: flatbuffers.Builder, srtpParameters: SrtpParameters ): number { const keyBase64Offset = builder.createString(srtpParameters.keyBase64); return FbsSrtpParameters.SrtpParameters.createSrtpParameters( builder, cryptoSuiteToFbs(srtpParameters.cryptoSuite), keyBase64Offset ); } ================================================ FILE: node/src/srtpParametersTypes.ts ================================================ /** * SRTP parameters. */ export type SrtpParameters = { /** * Encryption and authentication transforms to be used. */ cryptoSuite: SrtpCryptoSuite; /** * SRTP keying material (master key and salt) in Base64. */ keyBase64: string; }; /** * SRTP crypto suite. */ export type SrtpCryptoSuite = | 'AEAD_AES_256_GCM' | 'AEAD_AES_128_GCM' | 'AES_CM_128_HMAC_SHA1_80' | 'AES_CM_128_HMAC_SHA1_32'; ================================================ FILE: node/src/supportedRtpCapabilities.ts ================================================ import type { RouterRtpCapabilities } from './rtpParametersTypes'; const supportedRtpCapabilities: RouterRtpCapabilities = { codecs: [ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, rtcpFeedback: [{ type: 'nack' }, { type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/multiopus', clockRate: 48000, channels: 4, // Quad channel. parameters: { channel_mapping: '0,1,2,3', num_streams: 2, coupled_streams: 2, }, rtcpFeedback: [{ type: 'nack' }, { type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/multiopus', clockRate: 48000, channels: 6, // 5.1. parameters: { channel_mapping: '0,4,1,2,3,5', num_streams: 4, coupled_streams: 2, }, rtcpFeedback: [{ type: 'nack' }, { type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/multiopus', clockRate: 48000, channels: 8, // 7.1. parameters: { channel_mapping: '0,6,1,2,3,4,5,7', num_streams: 5, coupled_streams: 3, }, rtcpFeedback: [{ type: 'nack' }, { type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/PCMU', preferredPayloadType: 0, clockRate: 8000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/PCMA', preferredPayloadType: 8, clockRate: 8000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/ISAC', clockRate: 32000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/ISAC', clockRate: 16000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/G722', preferredPayloadType: 9, clockRate: 8000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/iLBC', clockRate: 8000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/SILK', clockRate: 24000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/SILK', clockRate: 16000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/SILK', clockRate: 12000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/SILK', clockRate: 8000, rtcpFeedback: [{ type: 'transport-cc' }], }, { kind: 'audio', mimeType: 'audio/CN', preferredPayloadType: 13, clockRate: 32000, }, { kind: 'audio', mimeType: 'audio/CN', preferredPayloadType: 13, clockRate: 16000, }, { kind: 'audio', mimeType: 'audio/CN', preferredPayloadType: 13, clockRate: 8000, }, { kind: 'audio', mimeType: 'audio/telephone-event', clockRate: 48000, }, { kind: 'audio', mimeType: 'audio/telephone-event', clockRate: 32000, }, { kind: 'audio', mimeType: 'audio/telephone-event', clockRate: 16000, }, { kind: 'audio', mimeType: 'audio/telephone-event', clockRate: 8000, }, { kind: 'video', mimeType: 'video/VP8', clockRate: 90000, rtcpFeedback: [ { type: 'nack' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb' }, { type: 'transport-cc' }, ], }, { kind: 'video', mimeType: 'video/VP9', clockRate: 90000, rtcpFeedback: [ { type: 'nack' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb' }, { type: 'transport-cc' }, ], }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'level-asymmetry-allowed': 1, }, rtcpFeedback: [ { type: 'nack' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb' }, { type: 'transport-cc' }, ], }, { kind: 'video', mimeType: 'video/AV1', clockRate: 90000, parameters: {}, rtcpFeedback: [ { type: 'nack' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb' }, { type: 'transport-cc' }, ], }, ], headerExtensions: [ { kind: 'audio', uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', preferredId: 1, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', preferredId: 1, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id', preferredId: 2, preferredEncrypt: false, direction: 'recvonly', }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id', preferredId: 3, preferredEncrypt: false, direction: 'recvonly', }, { kind: 'audio', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', preferredId: 4, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', preferredId: 4, preferredEncrypt: false, direction: 'sendrecv', }, // NOTE: For audio we just enable transport-wide-cc-01 when receiving media. { kind: 'audio', uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01', preferredId: 5, preferredEncrypt: false, direction: 'recvonly', }, { kind: 'video', uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01', preferredId: 5, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'audio', uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', preferredId: 6, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension', preferredId: 7, preferredEncrypt: false, direction: 'recvonly', }, { kind: 'video', uri: 'urn:3gpp:video-orientation', preferredId: 8, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:toffset', preferredId: 9, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'audio', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time', preferredId: 10, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time', preferredId: 10, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'audio', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay', preferredId: 11, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay', preferredId: 11, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'audio', uri: 'urn:mediasoup:params:rtp-hdrext:packet-id', preferredId: 12, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'urn:mediasoup:params:rtp-hdrext:packet-id', preferredId: 12, preferredEncrypt: false, direction: 'sendrecv', }, ], }; export { supportedRtpCapabilities }; ================================================ FILE: node/src/test/data/dtls-cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIUXy3udbf5+Rvhx3MaNGn7vj+zi+UwDQYJKoZIhvcNAQEL BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA2MjQyMTQwNTZaFw0yMzA2 MjQyMTQwNTZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB AQUAA4ICDwAwggIKAoICAQDLXJlS6702GKmSsfOFWzMP2+NvlgNySLiqnAf6bBuX Vx4UN7feOIHz4mFh4OUtxNzrB44XMzRMZKf21qaY7VTUbUag6i+Ghc4I15ZRzONP gv/2BlQ7PR8LqMTDJ6xMmIZfJPsmvpm/8VQa+h1spKyBHs96pFQQzpVNAnGSifk7 hPPWEy60fLQ4hxDPxXUKj/z/j5c8W9mqt8KlJgm1fIbxdtVn60T7hz54/OuikHBD jgBNjGwLeyybU0dGuXHBiriOLlVmtPNMpyjptUGA3BUISf66SS31rP+ol2frlOY9 QZGG7Z5jzhfNvgIAQKTJmVOhpEmT6NbE7uQKMO76LaFcRnkbO0GxDnKw/T+1LGqb JOdHW0m7xYkoLLpsjnF8ROFP7EbP6lLtaFtOzDxueJT9fXBAW5etrp7KvWmDY6/5 Hn3gX/1nvLzqFF2itze+11o4BeE+Oj9XUSXDtZzACRaZ9hjCe8WmzPXVEEOvVIP9 h5pVp7qW6rIxwNPTE25GPzbdquJmfjauHMKuf/ax+0gpeu5VpHNkvOIUyM/QTiBi D8Z62iWpooHXr3P3qbp4/qa3g0bQZ6uNveTT112E3RJJueYTc3IzUPefiHFmCfFZ eUmOhqf9v2eYroVq0MKSnw2+1MQxdYDWV7QpKOLIJik9tPm8hScggwLvq0YIwnvN VQIDAQABo1MwUTAdBgNVHQ4EFgQUe/9tLjutYDz+myVgALKjHDuXm2QwHwYDVR0j BBgwFoAUe/9tLjutYDz+myVgALKjHDuXm2QwDwYDVR0TAQH/BAUwAwEB/zANBgkq hkiG9w0BAQsFAAOCAgEAXi18lNqjeOXV6P/snVs4b34OiWpkRlDxKKMG95rdRKeF kEmPpO7T293nXCGvFmGfME6KBhio4w0MVMlbtC5TVdTFJk6mSgkCEtncA5yEv8Ga w2HCoEWfkOpea3S1XL9i5EWVPKvJG/rJ3YMZuh1BvppfC73dHVFSdik5SXNCGqEy 9OuhJHbpbdlMuwFTLKwQCKDwh5Yvzd0eASyYlJ6Ytpf7TLCc3nvUS9haSOKEfnHQ v3TLt2WA+xHK7XIj9qoYuWIsnkXAWIsSJy/utJrDhtym7BcxB+ss7doHkS0LCTHd DiAhJwvTMKPYSRA55oF9iejynrcBOidH+tQxYMXHaDisNtH+6ZOtxkcLleneKhNB 9EAMw9qeyiVl+MHDQi+sA5ksMfVoXzvxqObNGSz9g5z1AfnNuiaX1z9ajXsiHJ5e 7EQFVCU4Id519RcSEUY2qcKTNMBPyXNbTQfd/oV1C6pzF01tVaNpl12dsXdx+JTh 9vbK9tI2U+jzIb0Y8ersKrAjRQO/1lfIbjdJ+e0rFlxX1+SmlKlcArOfd8PSuifx wbzCJeRjFdOdf2D6mVHO0uMghjnVTHxB3KX6QonKOFzkupwWBeVqginXm4pEaUhK nssuSpkKrVshQ3RSEq3H9yRdQQ0qqwdOd3dISEqucVvaEsxhUwHBX/R5p8W0OCo= -----END CERTIFICATE----- ================================================ FILE: node/src/test/data/dtls-key.pem ================================================ -----BEGIN PRIVATE KEY----- MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDLXJlS6702GKmS sfOFWzMP2+NvlgNySLiqnAf6bBuXVx4UN7feOIHz4mFh4OUtxNzrB44XMzRMZKf2 1qaY7VTUbUag6i+Ghc4I15ZRzONPgv/2BlQ7PR8LqMTDJ6xMmIZfJPsmvpm/8VQa +h1spKyBHs96pFQQzpVNAnGSifk7hPPWEy60fLQ4hxDPxXUKj/z/j5c8W9mqt8Kl Jgm1fIbxdtVn60T7hz54/OuikHBDjgBNjGwLeyybU0dGuXHBiriOLlVmtPNMpyjp tUGA3BUISf66SS31rP+ol2frlOY9QZGG7Z5jzhfNvgIAQKTJmVOhpEmT6NbE7uQK MO76LaFcRnkbO0GxDnKw/T+1LGqbJOdHW0m7xYkoLLpsjnF8ROFP7EbP6lLtaFtO zDxueJT9fXBAW5etrp7KvWmDY6/5Hn3gX/1nvLzqFF2itze+11o4BeE+Oj9XUSXD tZzACRaZ9hjCe8WmzPXVEEOvVIP9h5pVp7qW6rIxwNPTE25GPzbdquJmfjauHMKu f/ax+0gpeu5VpHNkvOIUyM/QTiBiD8Z62iWpooHXr3P3qbp4/qa3g0bQZ6uNveTT 112E3RJJueYTc3IzUPefiHFmCfFZeUmOhqf9v2eYroVq0MKSnw2+1MQxdYDWV7Qp KOLIJik9tPm8hScggwLvq0YIwnvNVQIDAQABAoICAAd5PTdDa5431M+L06fEfMFp 8tdQe4rxKjw25MIqw+7RaE0U6SB1Ei2M1chblACVGgtXKT0oCBWAo32aUOAQ5Muz wmM6iAmZFEPV7HPQJFBxP4finqjYq7Hpf2WuqRHdjx6jBMndOlhH/a/KHle2S5Kp N7XJoT9G4EzGuLbKdErgLXfiF5bReGwVZqREHPOI7CLWO5gfxgWUoEYiejvdujXY iKo7hrr5su2OWfiM91s8Nj2jWfsoCTEiI93xBcG3n+W1xTSzlLdt8z53h1M9g1Zd Jcvh0ZsUQwcGnW6Wd8mrhbYgOHBxjAVnJLqupX+1L1Ii8ANMDMyK/P104+t0zte9 BpuBzerm9h69UQq7d728YKW2KfY4M5+oLG2l4PKH6YDhoymIso38TYMixutkXpIs xZXATFA82sZlcpbSjLJypeJnKxBCbKxu8eYTq5K2SDdq8r82DwZZT/N8FfQMGZoO YYtP2KfUSxc2azpQtEHmB00fiPXgCdJNU2I8+J84Ve/ltBDlJcNkJN09h5ob9q/g Q8qJQRrzpLedBRhyqharYyDS9oGDUlR9OfmR7vFzUNP5npgNts0TDGy/JpVdY9aC YmqWsC8c/Kmms7X+4KaXMsBEpaVNSx9FsoQiPi1CMdy/KpWGcshusdVwgdklrOnz iKBvvMNvP+gu7bV+uoR3AoIBAQDqBkUaMbVUVup94WqOIFjEl13CQwVwLgmLQaw0 9BjPkv8BaPiMUfg7ANqEi8V+iJX5m27fcsvWIMUKBe0FvLdo/kH/NahNxD0bub3S OikGeJEo0QDsIKhVXTLjYJ/N+qk2P3WYWy/7yGV3kVDkEam0oebmdYNQsfpXK+wu Gt0hpjU2RDMB2NCmtLHQXCI5DUxX0CtfJD5JTop9tLTRJKnputsIsQZPSmNSTuTl FVO0JdyJNv97Xhc8YUzwTfwl73EW7srcJNKO2ZwkzOZ8EghaEa1c5BZ6oV2MWe5f HIrRbW9LCYnBavoTC7VGWYkKiklAm5pRfvj9fQylv4tiSRUfAoIBAQDedTl3fAFf i/8ak0WfJKn/H0Z2W3AcgeChyBAWdrOrTDZUdhV0olcIT7m1v2qbWQju8boPdJiY BqG2DD3CZHS+qETt4lP5r+9j2gvy5g3wdQ4bYpjyr2hRXkwMK2yYQbXCmgmDETb+ yNTj2avhAA32BmTH9QyxXSGN7ympVwEP1kDcXHM8vDGL7FvGGdRPFg0h4LKHrCHa xCQEWPjBv8oPo8Gb2ateSZjeWCraRhTXd7yVWC5s+3uEfW/h+g/QJVrRh2p8ipGv zlWDR1BaF+t4racWHFeftrSkc3ZpN/eAVyLXBSTo7plOx8tFujrakJedmZssrHDc Rc0JeDOrjnsLAoIBAQDGXoo0qe4Kj6I1Ed5Amyqjear//8+cR2nPoNtYB5EAYpnF mDUWvGStnwubTt8ZYq295wMUZTpjR2O+G0fOlSji1qMasWD4il9CIS/GA4bC9XAW KROfFA+cTGPWWREciFzmnuQPQTxrMHLR51up907izlnq/7FPtY1+VrzcV+kZnMl+ NlEGP8KdjI0tEOvxcFRGGy6odxBVEz5RT9v1bB6bAMiplWTD0UpfeoCLrohFK9LE fNoSuK75f4C4MWKKxWwXBFLwSEYy0EKK7yRwBtkNf+5zzuM/D4k8bv6foJIK87hi 4rLiQMu5WTNPbpW7WXy+RyeH7Rkhxd3yoWqE5W4BAoIBADjNOdU2hqs89fB1NkvC ct2/wKAsDN5ak1771I/H02yj0yOR2zyizxJCOSsdKz1raIqKknWr0eLPnq77RTHD sMOV97O+HK8eq0OVw4NMFrcVTHrVnDQrcbmFGGnrFJlz/dMovdEHrkE0Spe7VtXm y6nMTCN6gLkxDIZPURX6Lz05+enKeWpCq2wM+AoHQlzHRqcl1rAp1aMkfgXWKf5e 2FtR9veyhr1WkYAEhzygtGWoHzELCR+uvwU/ejf7P9poD15880XFpBl91/vjU7MN dISl4ooUxpLzdgCfstZ/AeV1WmII4DnR4rdo8JBnUuvIC86kEClCBrdX41jNpnPh t60CggEAQnioYh4vwJctZENvO6nKlexCui2sQmHi4hWwzzA0s8tG2iG++nWXUEV+ T+SBuCnIZMhnXlFIbqx+gjke3xLxJVbsgeRHDwDMZbkcaE35L1ZdAjIhuWgoNa20 RSXApmp376VNEoU3lJH6jK0Wa67N9IaD3P5Mb+jekU16beqVgMYWf/YXycvD6bEp L9CODZuTPS/Ue1waBP7bKWAH8PRKAdXQACkn+aRBqYm9a5o7Z5jD/Qzo9ia5VaK4 j+EQ0Gk4BaVBnvIu4LQ/HKuZm4v1NWNy3UhFuoTAzxNoCrQamYII/EaCMqiLATGH jBJvmV3y/KoVcZGzNGFVB29Ns8VK1g== -----END PRIVATE KEY----- ================================================ FILE: node/src/test/test-ActiveSpeakerObserver.ts ================================================ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents, ActiveSpeakerObserverEvents } from '../types'; import * as utils from '../utils'; type TestContext = { mediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; }; const ctx: TestContext = { mediaCodecs: utils.deepFreeze([ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, foo: 'bar', }, }, ]), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); ctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs }); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('router.createActiveSpeakerObserver() succeeds', async () => { const onObserverNewRtpObserver = jest.fn(); ctx.router!.observer.once('newrtpobserver', onObserverNewRtpObserver); const activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver(); expect(onObserverNewRtpObserver).toHaveBeenCalledTimes(1); expect(onObserverNewRtpObserver).toHaveBeenCalledWith(activeSpeakerObserver); expect(typeof activeSpeakerObserver.id).toBe('string'); expect(activeSpeakerObserver.closed).toBe(false); expect(activeSpeakerObserver.type).toBe('activespeaker'); expect(activeSpeakerObserver.paused).toBe(false); expect(activeSpeakerObserver.appData).toEqual({}); await expect(ctx.router!.dump()).resolves.toMatchObject({ rtpObserverIds: [activeSpeakerObserver.id], }); }, 2000); test('router.createActiveSpeakerObserver() with wrong arguments rejects with TypeError', async () => { await expect( ctx.router!.createActiveSpeakerObserver( // @ts-expect-error --- Testing purposes. { interval: false } ) ).rejects.toThrow(TypeError); await expect( ctx.router!.createActiveSpeakerObserver( // @ts-expect-error --- Testing purposes. { appData: 'NOT-AN-OBJECT' } ) ).rejects.toThrow(TypeError); }, 2000); test('activeSpeakerObserver.pause() and resume() succeed', async () => { const activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver(); await activeSpeakerObserver.pause(); expect(activeSpeakerObserver.paused).toBe(true); await activeSpeakerObserver.resume(); expect(activeSpeakerObserver.paused).toBe(false); }, 2000); test('activeSpeakerObserver.close() succeeds', async () => { const activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver({ interval: 500, }); let dump = await ctx.router!.dump(); expect(dump.rtpObserverIds.length).toBe(1); activeSpeakerObserver.close(); expect(activeSpeakerObserver.closed).toBe(true); dump = await ctx.router!.dump(); expect(dump.rtpObserverIds.length).toBe(0); }, 2000); test('ActiveSpeakerObserver emits "routerclose" if Router is closed', async () => { const activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver(); const promise = enhancedOnce( activeSpeakerObserver, 'routerclose' ); ctx.router!.close(); await promise; expect(activeSpeakerObserver.closed).toBe(true); }, 2000); test('ActiveSpeakerObserver emits "routerclose" if Worker is closed', async () => { const activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver(); const promise = enhancedOnce( activeSpeakerObserver, 'routerclose' ); ctx.worker!.close(); await promise; expect(activeSpeakerObserver.closed).toBe(true); }, 2000); ================================================ FILE: node/src/test/test-AudioLevelObserver.ts ================================================ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents, AudioLevelObserverEvents } from '../types'; import * as utils from '../utils'; type TestContext = { mediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; }; const ctx: TestContext = { mediaCodecs: utils.deepFreeze([ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, foo: 'bar', }, }, ]), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); ctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs }); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('router.createAudioLevelObserver() succeeds', async () => { const onObserverNewRtpObserver = jest.fn(); ctx.router!.observer.once('newrtpobserver', onObserverNewRtpObserver); const audioLevelObserver = await ctx.router!.createAudioLevelObserver(); expect(onObserverNewRtpObserver).toHaveBeenCalledTimes(1); expect(onObserverNewRtpObserver).toHaveBeenCalledWith(audioLevelObserver); expect(typeof audioLevelObserver.id).toBe('string'); expect(audioLevelObserver.closed).toBe(false); expect(audioLevelObserver.type).toBe('audiolevel'); expect(audioLevelObserver.paused).toBe(false); expect(audioLevelObserver.appData).toEqual({}); await expect(ctx.router!.dump()).resolves.toMatchObject({ rtpObserverIds: [audioLevelObserver.id], }); }, 2000); test('router.createAudioLevelObserver() with wrong arguments rejects with TypeError', async () => { await expect( ctx.router!.createAudioLevelObserver({ maxEntries: 0 }) ).rejects.toThrow(TypeError); await expect( ctx.router!.createAudioLevelObserver({ maxEntries: -10 }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.router!.createAudioLevelObserver({ threshold: 'foo' }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.router!.createAudioLevelObserver({ interval: false }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.router!.createAudioLevelObserver({ appData: 'NOT-AN-OBJECT' }) ).rejects.toThrow(TypeError); }, 2000); test('audioLevelObserver.pause() and resume() succeed', async () => { const audioLevelObserver = await ctx.router!.createAudioLevelObserver(); await audioLevelObserver.pause(); expect(audioLevelObserver.paused).toBe(true); await audioLevelObserver.resume(); expect(audioLevelObserver.paused).toBe(false); }, 2000); test('audioLevelObserver.close() succeeds', async () => { const audioLevelObserver = await ctx.router!.createAudioLevelObserver({ maxEntries: 8, }); let dump = await ctx.router!.dump(); expect(dump.rtpObserverIds.length).toBe(1); audioLevelObserver.close(); expect(audioLevelObserver.closed).toBe(true); dump = await ctx.router!.dump(); expect(dump.rtpObserverIds.length).toBe(0); }, 2000); test('AudioLevelObserver emits "routerclose" if Router is closed', async () => { const audioLevelObserver = await ctx.router!.createAudioLevelObserver(); const promise = enhancedOnce( audioLevelObserver, 'routerclose' ); ctx.router!.close(); await promise; expect(audioLevelObserver.closed).toBe(true); }, 2000); test('AudioLevelObserver emits "routerclose" if Worker is closed', async () => { const audioLevelObserver = await ctx.router!.createAudioLevelObserver(); const promise = enhancedOnce( audioLevelObserver, 'routerclose' ); ctx.worker!.close(); await promise; expect(audioLevelObserver.closed).toBe(true); }, 2000); ================================================ FILE: node/src/test/test-Consumer.ts ================================================ import * as flatbuffers from 'flatbuffers'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents, ConsumerEvents } from '../types'; import type { ConsumerImpl } from '../Consumer'; import { UnsupportedError } from '../errors'; import * as utils from '../utils'; import { Notification, Body as NotificationBody, Event, } from '../fbs/notification'; import * as FbsConsumer from '../fbs/consumer'; type TestContext = { mediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; audioProducerOptions: mediasoup.types.ProducerOptions; videoProducerOptions: mediasoup.types.ProducerOptions; consumerDeviceCapabilities: mediasoup.types.RtpCapabilities; worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; webRtcTransport1?: mediasoup.types.WebRtcTransport; webRtcTransport2?: mediasoup.types.WebRtcTransport; audioProducer?: mediasoup.types.Producer; videoProducer?: mediasoup.types.Producer; }; const ctx: TestContext = { mediaCodecs: utils.deepFreeze([ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, parameters: { foo: 'bar', }, }, { kind: 'video', mimeType: 'video/VP8', clockRate: 90000, }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'level-asymmetry-allowed': 1, 'packetization-mode': 1, 'profile-level-id': '4d0032', foo: 'bar', }, }, ]), audioProducerOptions: utils.deepFreeze({ kind: 'audio', rtpParameters: { mid: 'AUDIO', codecs: [ { mimeType: 'audio/opus', payloadType: 111, clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, usedtx: 1, foo: 222.222, bar: '333', }, }, ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, }, { uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', id: 12, }, ], encodings: [{ ssrc: 11111111 }], rtcp: { cname: 'FOOBAR', }, msid: '1111-1111-1111-1111 2222-2222-2222-2222', }, appData: { foo: 1, bar: '2' }, }), videoProducerOptions: utils.deepFreeze({ kind: 'video', rtpParameters: { mid: 'VIDEO', codecs: [ { mimeType: 'video/h264', payloadType: 112, clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '4d0032', }, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'goog-remb', parameter: '' }, ], }, { mimeType: 'video/rtx', payloadType: 113, clockRate: 90000, parameters: { apt: 112 }, }, ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, }, { uri: 'urn:3gpp:video-orientation', id: 13, }, ], encodings: [ { ssrc: 22222222, scalabilityMode: 'L1T5', rtx: { ssrc: 22222223 } }, { ssrc: 22222224, scalabilityMode: 'L1T5', rtx: { ssrc: 22222225 } }, { ssrc: 22222226, scalabilityMode: 'L1T5', rtx: { ssrc: 22222227 } }, { ssrc: 22222228, scalabilityMode: 'L1T5', rtx: { ssrc: 22222229 } }, ], rtcp: { cname: 'FOOBAR', }, }, appData: { foo: 1, bar: '2' }, }), consumerDeviceCapabilities: utils.deepFreeze( { codecs: [ { mimeType: 'audio/opus', kind: 'audio', preferredPayloadType: 100, clockRate: 48000, channels: 2, rtcpFeedback: [{ type: 'nack', parameter: '' }], }, { mimeType: 'video/H264', kind: 'video', preferredPayloadType: 101, clockRate: 90000, parameters: { 'level-asymmetry-allowed': 1, 'packetization-mode': 1, 'profile-level-id': '4d0032', }, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb', parameter: '' }, ], }, { mimeType: 'video/rtx', kind: 'video', preferredPayloadType: 102, clockRate: 90000, parameters: { apt: 101, }, rtcpFeedback: [], }, ], headerExtensions: [ { kind: 'audio', uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', preferredId: 1, preferredEncrypt: false, }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', preferredId: 1, preferredEncrypt: false, }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id', preferredId: 2, preferredEncrypt: false, }, { kind: 'audio', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', preferredId: 4, preferredEncrypt: false, }, { kind: 'video', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', preferredId: 4, preferredEncrypt: false, }, { kind: 'audio', uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', preferredId: 6, preferredEncrypt: false, }, { kind: 'video', uri: 'urn:3gpp:video-orientation', preferredId: 8, preferredEncrypt: false, }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:toffset', preferredId: 9, preferredEncrypt: false, }, ], } ), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); ctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs }); ctx.webRtcTransport1 = await ctx.router.createWebRtcTransport({ listenIps: ['127.0.0.1'], }); ctx.webRtcTransport2 = await ctx.router.createWebRtcTransport({ listenIps: ['127.0.0.1'], }); ctx.audioProducer = await ctx.webRtcTransport1.produce( ctx.audioProducerOptions ); ctx.videoProducer = await ctx.webRtcTransport1.produce( ctx.videoProducerOptions ); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('transport.consume() succeeds', async () => { const onObserverNewConsumer1 = jest.fn(); ctx.webRtcTransport2!.observer.once('newconsumer', onObserverNewConsumer1); expect( ctx.router!.canConsume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }) ).toBe(true); const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, appData: { baz: 'LOL' }, }); expect(onObserverNewConsumer1).toHaveBeenCalledTimes(1); expect(onObserverNewConsumer1).toHaveBeenCalledWith(audioConsumer); expect(typeof audioConsumer.id).toBe('string'); expect(audioConsumer.producerId).toBe(ctx.audioProducer!.id); expect(audioConsumer.closed).toBe(false); expect(audioConsumer.kind).toBe('audio'); expect(typeof audioConsumer.rtpParameters).toBe('object'); expect(audioConsumer.rtpParameters.mid).toBe('0'); expect(audioConsumer.rtpParameters.codecs.length).toBe(1); expect(audioConsumer.rtpParameters.codecs[0]).toEqual({ mimeType: 'audio/opus', payloadType: 100, clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, usedtx: 1, foo: 222.222, bar: '333', }, rtcpFeedback: [], }); expect(audioConsumer.rtpParameters.msid).toBe( '1111-1111-1111-1111 2222-2222-2222-2222' ); expect(audioConsumer.type).toBe('simple'); expect(audioConsumer.paused).toBe(false); expect(audioConsumer.producerPaused).toBe(false); expect(audioConsumer.priority).toBe(1); expect(audioConsumer.score).toEqual({ score: 10, producerScore: 0, producerScores: [0], }); expect(audioConsumer.preferredLayers).toBeUndefined(); expect(audioConsumer.currentLayers).toBeUndefined(); expect(audioConsumer.appData).toEqual({ baz: 'LOL' }); const dump1 = await ctx.router!.dump(); expect(dump1.mapProducerIdConsumerIds).toEqual( expect.arrayContaining([ { key: ctx.audioProducer!.id, values: [audioConsumer.id] }, ]) ); expect(dump1.mapConsumerIdProducerId).toEqual( expect.arrayContaining([ { key: audioConsumer.id, value: ctx.audioProducer!.id }, ]) ); await expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport2!.id, producerIds: [], consumerIds: [audioConsumer.id], }); const onObserverNewConsumer2 = jest.fn(); ctx.webRtcTransport2!.observer.once('newconsumer', onObserverNewConsumer2); expect( ctx.router!.canConsume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }) ).toBe(true); // Pause videoProducer. await ctx.videoProducer!.pause(); const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, paused: true, preferredLayers: { spatialLayer: 12, temporalLayer: 0 }, appData: { baz: 'LOL' }, }); expect(onObserverNewConsumer2).toHaveBeenCalledTimes(1); expect(onObserverNewConsumer2).toHaveBeenCalledWith(videoConsumer); expect(typeof videoConsumer.id).toBe('string'); expect(videoConsumer.producerId).toBe(ctx.videoProducer!.id); expect(videoConsumer.closed).toBe(false); expect(videoConsumer.kind).toBe('video'); expect(typeof videoConsumer.rtpParameters).toBe('object'); expect(videoConsumer.rtpParameters.mid).toBe('1'); expect(videoConsumer.rtpParameters.codecs.length).toBe(2); expect(videoConsumer.rtpParameters.codecs[0]).toEqual({ mimeType: 'video/H264', payloadType: 103, clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '4d0032', }, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb', parameter: '' }, ], }); expect(videoConsumer.rtpParameters.codecs[1]).toEqual({ mimeType: 'video/rtx', payloadType: 104, clockRate: 90000, parameters: { apt: 103 }, rtcpFeedback: [], }); expect(videoConsumer.rtpParameters.msid).toBe(undefined); expect(videoConsumer.type).toBe('simulcast'); expect(videoConsumer.paused).toBe(true); expect(videoConsumer.producerPaused).toBe(true); expect(videoConsumer.priority).toBe(1); expect(videoConsumer.score).toEqual({ score: 10, producerScore: 0, producerScores: [0, 0, 0, 0], }); expect(videoConsumer.preferredLayers).toEqual({ spatialLayer: 3, temporalLayer: 0, }); expect(videoConsumer.currentLayers).toBeUndefined(); expect(videoConsumer.appData).toEqual({ baz: 'LOL' }); const onObserverNewConsumer3 = jest.fn(); ctx.webRtcTransport2!.observer.once('newconsumer', onObserverNewConsumer3); expect( ctx.router!.canConsume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }) ).toBe(true); const videoPipeConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, pipe: true, }); expect(onObserverNewConsumer3).toHaveBeenCalledTimes(1); expect(onObserverNewConsumer3).toHaveBeenCalledWith(videoPipeConsumer); expect(typeof videoPipeConsumer.id).toBe('string'); expect(videoPipeConsumer.producerId).toBe(ctx.videoProducer!.id); expect(videoPipeConsumer.closed).toBe(false); expect(videoPipeConsumer.kind).toBe('video'); expect(typeof videoPipeConsumer.rtpParameters).toBe('object'); expect(videoPipeConsumer.rtpParameters.mid).toBeUndefined(); expect(videoPipeConsumer.rtpParameters.codecs.length).toBe(2); expect(videoPipeConsumer.rtpParameters.codecs[0]).toEqual({ mimeType: 'video/H264', payloadType: 103, clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '4d0032', }, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb', parameter: '' }, ], }); expect(videoPipeConsumer.rtpParameters.codecs[1]).toEqual({ mimeType: 'video/rtx', payloadType: 104, clockRate: 90000, parameters: { apt: 103 }, rtcpFeedback: [], }); expect(videoPipeConsumer.type).toBe('pipe'); expect(videoPipeConsumer.paused).toBe(false); expect(videoPipeConsumer.producerPaused).toBe(true); expect(videoPipeConsumer.priority).toBe(1); expect(videoPipeConsumer.score).toEqual({ score: 10, producerScore: 10, producerScores: [0, 0, 0, 0], }); expect(videoPipeConsumer.preferredLayers).toBeUndefined(); expect(videoPipeConsumer.currentLayers).toBeUndefined(); expect(videoPipeConsumer.appData).toEqual({}); const dump2 = await ctx.router!.dump(); expect(Array.isArray(dump2.mapProducerIdConsumerIds)).toBe(true); expect(dump2.mapProducerIdConsumerIds).toEqual( expect.arrayContaining([ { key: ctx.audioProducer!.id, values: [audioConsumer.id], }, { key: ctx.videoProducer!.id, values: expect.arrayContaining([ videoConsumer.id, videoPipeConsumer.id, ]), }, ]) ); expect(dump2.mapConsumerIdProducerId).toEqual( expect.arrayContaining([ { key: audioConsumer.id, value: ctx.audioProducer!.id }, { key: videoConsumer.id, value: ctx.videoProducer!.id }, { key: videoPipeConsumer.id, value: ctx.videoProducer!.id }, ]) ); await expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport2!.id, producerIds: [], consumerIds: expect.arrayContaining([ audioConsumer.id, videoConsumer.id, videoPipeConsumer.id, ]), }); }, 2000); test('transport.consume() with enableRtx succeeds', async () => { const audioConsumer2 = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, enableRtx: true, }); expect(audioConsumer2.kind).toBe('audio'); expect(audioConsumer2.rtpParameters.codecs.length).toBe(1); expect(audioConsumer2.rtpParameters.codecs[0]).toEqual({ mimeType: 'audio/opus', payloadType: 100, clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, usedtx: 1, foo: 222.222, bar: '333', }, rtcpFeedback: [{ type: 'nack', parameter: '' }], }); }, 2000); test('transport.consume() can be created with user provided mid', async () => { const audioConsumer1 = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); expect(audioConsumer1.rtpParameters.mid).toEqual( expect.stringMatching(/^[0-9]+/) ); const audioConsumer2 = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, mid: 'custom-mid', rtpCapabilities: ctx.consumerDeviceCapabilities, }); expect(audioConsumer2.rtpParameters.mid).toBe('custom-mid'); const audioConsumer3 = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); expect(audioConsumer3.rtpParameters.mid).toEqual( expect.stringMatching(/^[0-9]+/) ); expect(Number(audioConsumer1.rtpParameters.mid) + 1).toBe( Number(audioConsumer3.rtpParameters.mid) ); }, 2000); test('transport.consume() with incompatible rtpCapabilities rejects with UnsupportedError', async () => { let invalidDeviceCapabilities: mediasoup.types.RtpCapabilities; invalidDeviceCapabilities = { codecs: [ { kind: 'audio', mimeType: 'audio/ISAC', preferredPayloadType: 100, clockRate: 32000, channels: 1, }, ], headerExtensions: [], }; expect( ctx.router!.canConsume({ producerId: ctx.audioProducer!.id, rtpCapabilities: invalidDeviceCapabilities, }) ).toBe(false); await expect( ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: invalidDeviceCapabilities, }) ).rejects.toThrow(UnsupportedError); invalidDeviceCapabilities = { codecs: [], headerExtensions: [], }; expect( ctx.router!.canConsume({ producerId: ctx.audioProducer!.id, rtpCapabilities: invalidDeviceCapabilities, }) ).toBe(false); await expect( ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: invalidDeviceCapabilities, }) ).rejects.toThrow(UnsupportedError); }, 2000); test('consumer.dump() succeeds', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); const dump1 = await audioConsumer.dump(); expect(dump1.id).toBe(audioConsumer.id); expect(dump1.producerId).toBe(audioConsumer.producerId); expect(dump1.kind).toBe(audioConsumer.kind); expect(typeof dump1.rtpParameters).toBe('object'); expect(Array.isArray(dump1.rtpParameters.codecs)).toBe(true); expect(dump1.rtpParameters.codecs.length).toBe(1); expect(dump1.rtpParameters.codecs[0]!.mimeType).toBe('audio/opus'); expect(dump1.rtpParameters.codecs[0]!.payloadType).toBe(100); expect(dump1.rtpParameters.codecs[0]!.clockRate).toBe(48000); expect(dump1.rtpParameters.codecs[0]!.channels).toBe(2); expect(dump1.rtpParameters.codecs[0]!.parameters).toEqual({ useinbandfec: 1, usedtx: 1, foo: 222.222, bar: '333', }); expect(dump1.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([]); expect(Array.isArray(dump1.rtpParameters.headerExtensions)).toBe(true); expect(dump1.rtpParameters.headerExtensions!.length).toBe(3); expect(dump1.rtpParameters.headerExtensions).toEqual([ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 1, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', id: 4, parameters: {}, encrypt: false, }, { uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', id: 6, parameters: {}, encrypt: false, }, ]); expect(Array.isArray(dump1.rtpParameters.encodings)).toBe(true); expect(dump1.rtpParameters.encodings!.length).toBe(1); expect(dump1.rtpParameters.encodings).toEqual([ expect.objectContaining({ codecPayloadType: 100, ssrc: audioConsumer.rtpParameters.encodings![0]!.ssrc, }), ]); expect(dump1.rtpParameters.msid).toBe( '1111-1111-1111-1111 2222-2222-2222-2222' ); expect(dump1.type).toBe('simple'); expect(Array.isArray(dump1.consumableRtpEncodings)).toBe(true); expect(dump1.consumableRtpEncodings!.length).toBe(1); expect(dump1.consumableRtpEncodings).toEqual([ expect.objectContaining({ ssrc: ctx.audioProducer!.consumableRtpParameters.encodings![0]!.ssrc, }), ]); expect(dump1.supportedCodecPayloadTypes).toEqual([100]); expect(dump1.paused).toBe(false); expect(dump1.producerPaused).toBe(false); expect(dump1.priority).toBe(1); const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, paused: true, }); const dump2 = await videoConsumer.dump(); expect(dump2.id).toBe(videoConsumer.id); expect(dump2.producerId).toBe(videoConsumer.producerId); expect(dump2.kind).toBe(videoConsumer.kind); expect(typeof dump2.rtpParameters).toBe('object'); expect(Array.isArray(dump2.rtpParameters.codecs)).toBe(true); expect(dump2.rtpParameters.codecs.length).toBe(2); expect(dump2.rtpParameters.codecs[0]!.mimeType).toBe('video/H264'); expect(dump2.rtpParameters.codecs[0]!.payloadType).toBe(103); expect(dump2.rtpParameters.codecs[0]!.clockRate).toBe(90000); expect(dump2.rtpParameters.codecs[0]!.channels).toBeUndefined(); expect(dump2.rtpParameters.codecs[0]!.parameters).toEqual({ 'packetization-mode': 1, 'profile-level-id': '4d0032', }); expect(dump2.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([ { type: 'nack' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb' }, ]); expect(Array.isArray(dump2.rtpParameters.headerExtensions)).toBe(true); expect(dump2.rtpParameters.headerExtensions!.length).toBe(4); expect(dump2.rtpParameters.headerExtensions).toEqual([ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 1, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', id: 4, parameters: {}, encrypt: false, }, { uri: 'urn:3gpp:video-orientation', id: 8, parameters: {}, encrypt: false, }, { uri: 'urn:ietf:params:rtp-hdrext:toffset', id: 9, parameters: {}, encrypt: false, }, ]); expect(Array.isArray(dump2.rtpParameters.encodings)).toBe(true); expect(dump2.rtpParameters.encodings!.length).toBe(1); expect(dump2.rtpParameters.encodings).toMatchObject([ { codecPayloadType: 103, ssrc: videoConsumer.rtpParameters.encodings![0]!.ssrc, rtx: { ssrc: videoConsumer.rtpParameters.encodings![0]!.rtx?.ssrc, }, scalabilityMode: 'L4T5', }, ]); expect(dump2.rtpParameters.msid).toBe(undefined); expect(Array.isArray(dump2.consumableRtpEncodings)).toBe(true); expect(dump2.consumableRtpEncodings!.length).toBe(4); expect(dump2.consumableRtpEncodings![0]).toEqual( expect.objectContaining({ ssrc: ctx.videoProducer!.consumableRtpParameters.encodings![0]!.ssrc, scalabilityMode: 'L1T5', }) ); expect(dump2.consumableRtpEncodings![1]).toEqual( expect.objectContaining({ ssrc: ctx.videoProducer!.consumableRtpParameters.encodings![1]!.ssrc, scalabilityMode: 'L1T5', }) ); expect(dump2.consumableRtpEncodings![2]).toEqual( expect.objectContaining({ ssrc: ctx.videoProducer!.consumableRtpParameters.encodings![2]!.ssrc, scalabilityMode: 'L1T5', }) ); expect(dump2.consumableRtpEncodings![3]).toEqual( expect.objectContaining({ ssrc: ctx.videoProducer!.consumableRtpParameters.encodings![3]!.ssrc, scalabilityMode: 'L1T5', }) ); expect(dump2.supportedCodecPayloadTypes).toEqual([103]); expect(dump2.paused).toBe(true); expect(dump2.producerPaused).toBe(false); expect(dump2.priority).toBe(1); }, 2000); test('consumer.getStats() succeeds', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); await expect(audioConsumer.getStats()).resolves.toEqual([ expect.objectContaining({ type: 'outbound-rtp', kind: 'audio', mimeType: 'audio/opus', ssrc: audioConsumer.rtpParameters.encodings![0]!.ssrc, }), ]); const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); await expect(videoConsumer.getStats()).resolves.toEqual([ expect.objectContaining({ type: 'outbound-rtp', kind: 'video', mimeType: 'video/H264', ssrc: videoConsumer.rtpParameters.encodings![0]!.ssrc, }), ]); }, 2000); test('consumer.pause() and resume() succeed', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); const onObserverPause = jest.fn(); const onObserverResume = jest.fn(); audioConsumer.observer.on('pause', onObserverPause); audioConsumer.observer.on('resume', onObserverResume); await audioConsumer.pause(); expect(audioConsumer.paused).toBe(true); await expect(audioConsumer.dump()).resolves.toMatchObject({ paused: true }); await audioConsumer.resume(); expect(audioConsumer.paused).toBe(false); await expect(audioConsumer.dump()).resolves.toMatchObject({ paused: false }); // Even if we don't await for pause()/resume() completion, the observer must // fire 'pause' and 'resume' events if state was the opposite. void audioConsumer.pause(); void audioConsumer.resume(); void audioConsumer.pause(); void audioConsumer.pause(); void audioConsumer.pause(); await audioConsumer.resume(); expect(onObserverPause).toHaveBeenCalledTimes(3); expect(onObserverResume).toHaveBeenCalledTimes(3); }, 2000); test('producer.pause() and resume() emit events', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); const promises = []; const events: string[] = []; audioConsumer.observer.once('resume', () => { events.push('resume'); }); audioConsumer.observer.once('pause', () => { events.push('pause'); }); promises.push(ctx.audioProducer!.pause()); promises.push(ctx.audioProducer!.resume()); await Promise.all(promises); // Must also wait a bit for the corresponding events in the consumer. await new Promise(resolve => setTimeout(resolve, 100)); expect(events).toEqual(['pause', 'resume']); expect(audioConsumer.paused).toBe(false); }, 2000); test('consumer.setPreferredLayers() succeed', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); await audioConsumer.setPreferredLayers({ spatialLayer: 1, temporalLayer: 1 }); expect(audioConsumer.preferredLayers).toBeUndefined(); await videoConsumer.setPreferredLayers({ spatialLayer: 2, temporalLayer: 3 }); expect(videoConsumer.preferredLayers).toEqual({ spatialLayer: 2, temporalLayer: 3, }); await videoConsumer.setPreferredLayers({ spatialLayer: 3 }); expect(videoConsumer.preferredLayers).toEqual({ spatialLayer: 3, temporalLayer: 4, }); await videoConsumer.setPreferredLayers({ spatialLayer: 3, temporalLayer: 0 }); expect(videoConsumer.preferredLayers).toEqual({ spatialLayer: 3, temporalLayer: 0, }); await videoConsumer.setPreferredLayers({ spatialLayer: 66, temporalLayer: 66, }); expect(videoConsumer.preferredLayers).toEqual({ spatialLayer: 3, temporalLayer: 4, }); }, 2000); test('consumer.setPreferredLayers() with wrong arguments rejects with TypeError', async () => { const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); // @ts-expect-error --- Testing purposes. await expect(videoConsumer.setPreferredLayers({})).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. videoConsumer.setPreferredLayers({ foo: '123' }) ).rejects.toThrow(TypeError); // @ts-expect-error --- Testing purposes. await expect(videoConsumer.setPreferredLayers('foo')).rejects.toThrow( TypeError ); // Missing spatialLayer. await expect( // @ts-expect-error --- Testing purposes. videoConsumer.setPreferredLayers({ temporalLayer: 2 }) ).rejects.toThrow(TypeError); }, 2000); test('consumer.setPriority() succeed', async () => { const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); await videoConsumer.setPriority(2); expect(videoConsumer.priority).toBe(2); }, 2000); test('consumer.setPriority() with wrong arguments rejects with TypeError', async () => { const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); // @ts-expect-error --- Testing purposes. await expect(videoConsumer.setPriority()).rejects.toThrow(TypeError); await expect(videoConsumer.setPriority(0)).rejects.toThrow(TypeError); // @ts-expect-error --- Testing purposes. await expect(videoConsumer.setPriority('foo')).rejects.toThrow(TypeError); }, 2000); test('consumer.unsetPriority() succeed', async () => { const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); await videoConsumer.unsetPriority(); expect(videoConsumer.priority).toBe(1); }, 2000); test('consumer.enableTraceEvent() succeed', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); await audioConsumer.enableTraceEvent(['rtp', 'pli']); const dump1 = await audioConsumer.dump(); expect(dump1.traceEventTypes).toEqual(expect.arrayContaining(['rtp', 'pli'])); await audioConsumer.enableTraceEvent(); const dump2 = await audioConsumer.dump(); expect(dump2.traceEventTypes).toEqual(expect.arrayContaining([])); // @ts-expect-error --- Testing purposes. await audioConsumer.enableTraceEvent(['nack', 'FOO', 'fir']); const dump3 = await audioConsumer.dump(); expect(dump3.traceEventTypes).toEqual( expect.arrayContaining(['nack', 'fir']) ); await audioConsumer.enableTraceEvent(); const dump4 = await audioConsumer.dump(); expect(dump4.traceEventTypes).toEqual(expect.arrayContaining([])); }, 2000); test('consumer.enableTraceEvent() with wrong arguments rejects with TypeError', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); // @ts-expect-error --- Testing purposes. await expect(audioConsumer.enableTraceEvent(123)).rejects.toThrow(TypeError); // @ts-expect-error --- Testing purposes. await expect(audioConsumer.enableTraceEvent('rtp')).rejects.toThrow( TypeError ); await expect( // @ts-expect-error --- Testing purposes. audioConsumer.enableTraceEvent(['fir', 123.123]) ).rejects.toThrow(TypeError); }, 2000); test('Consumer emits "producerpause" and "producerresume"', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); await Promise.all([ enhancedOnce(audioConsumer, 'producerpause'), // Let's await for pause() to resolve to avoid aborted channel requests // due to worker closure. ctx.audioProducer!.pause(), ]); expect(audioConsumer.paused).toBe(false); expect(audioConsumer.producerPaused).toBe(true); await Promise.all([ enhancedOnce(audioConsumer, 'producerresume'), // Let's await for resume() to resolve to avoid aborted channel requests // due to worker closure. ctx.audioProducer!.resume(), ]); expect(audioConsumer.paused).toBe(false); expect(audioConsumer.producerPaused).toBe(false); }, 2000); test('Consumer emits "score"', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); // API not exposed in the interface. const channel = (audioConsumer as ConsumerImpl).channelForTesting; const onScore = jest.fn(); audioConsumer.on('score', onScore); // Simulate a 'score' notification coming through the channel. const builder = new flatbuffers.Builder(); const consumerScore = new FbsConsumer.ConsumerScoreT(9, 10, [8]); const consumerScoreNotification = new FbsConsumer.ScoreNotificationT( consumerScore ); const notificationOffset = Notification.createNotification( builder, builder.createString(audioConsumer.id), Event.CONSUMER_SCORE, NotificationBody.Consumer_ScoreNotification, consumerScoreNotification.pack(builder) ); builder.finish(notificationOffset); const notification = Notification.getRootAsNotification( new flatbuffers.ByteBuffer(builder.asUint8Array()) ); channel.emit(audioConsumer.id, Event.CONSUMER_SCORE, notification); channel.emit(audioConsumer.id, Event.CONSUMER_SCORE, notification); channel.emit(audioConsumer.id, Event.CONSUMER_SCORE, notification); expect(onScore).toHaveBeenCalledTimes(3); expect(audioConsumer.score).toEqual({ score: 9, producerScore: 10, producerScores: [8], }); }, 2000); test('consumer.close() succeeds', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); const onObserverClose = jest.fn(); audioConsumer.observer.once('close', onObserverClose); audioConsumer.close(); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(audioConsumer.closed).toBe(true); const routerDump = await ctx.router!.dump(); expect(routerDump.mapProducerIdConsumerIds).toEqual( expect.arrayContaining([ { key: ctx.audioProducer!.id, values: [] }, { key: ctx.videoProducer!.id, values: [videoConsumer.id] }, ]) ); expect(routerDump.mapConsumerIdProducerId).toEqual([ { key: videoConsumer!.id, value: ctx.videoProducer!.id }, ]); const transportDump = await ctx.webRtcTransport2!.dump(); expect(transportDump).toMatchObject({ id: ctx.webRtcTransport2!.id, producerIds: [], consumerIds: [videoConsumer.id], }); }, 2000); test('Consumer methods reject if closed', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); audioConsumer.close(); await expect(audioConsumer.dump()).rejects.toThrow(Error); await expect(audioConsumer.getStats()).rejects.toThrow(Error); await expect(audioConsumer.pause()).rejects.toThrow(Error); await expect(audioConsumer.resume()).rejects.toThrow(Error); // @ts-expect-error --- Testing purposes. await expect(audioConsumer.setPreferredLayers({})).rejects.toThrow(Error); await expect(audioConsumer.setPriority(2)).rejects.toThrow(Error); await expect(audioConsumer.requestKeyFrame()).rejects.toThrow(Error); }, 2000); test('Consumer emits "producerclose" if Producer is closed', async () => { const audioConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.audioProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); const onObserverClose = jest.fn(); audioConsumer.observer.once('close', onObserverClose); const promise = enhancedOnce(audioConsumer, 'producerclose'); ctx.audioProducer!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(audioConsumer.closed).toBe(true); }, 2000); test('Consumer emits "transportclose" if Transport is closed', async () => { const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: ctx.videoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); const onObserverClose = jest.fn(); videoConsumer.observer.once('close', onObserverClose); const promise = enhancedOnce(videoConsumer, 'transportclose'); ctx.webRtcTransport2!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(videoConsumer.closed).toBe(true); await expect(ctx.router!.dump()).resolves.toMatchObject({ mapProducerIdConsumerIds: expect.arrayContaining([ { key: ctx.audioProducer!.id, values: [] }, { key: ctx.videoProducer!.id, values: [] }, ]), mapConsumerIdProducerId: [], }); }, 2000); ================================================ FILE: node/src/test/test-DataConsumer.ts ================================================ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents, DataConsumerEvents } from '../types'; import * as utils from '../utils'; type TestContext = { sctpDataProducerOptions: mediasoup.types.DataProducerOptions; worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; webRtcTransport1?: mediasoup.types.WebRtcTransport; webRtcTransport2?: mediasoup.types.WebRtcTransport; directTransport?: mediasoup.types.DirectTransport; sctpDataProducer?: mediasoup.types.DataProducer; directDataProducer?: mediasoup.types.DataProducer; }; const ctx: TestContext = { sctpDataProducerOptions: utils.deepFreeze({ sctpStreamParameters: { streamId: 12345, ordered: false, maxPacketLifeTime: 5000, }, label: 'foo', protocol: 'bar', }), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); ctx.router = await ctx.worker.createRouter(); ctx.webRtcTransport1 = await ctx.router.createWebRtcTransport({ listenIps: ['127.0.0.1'], enableSctp: true, }); ctx.webRtcTransport2 = await ctx.router.createWebRtcTransport({ listenIps: ['127.0.0.1'], enableSctp: true, }); ctx.directTransport = await ctx.router.createDirectTransport(); ctx.sctpDataProducer = await ctx.webRtcTransport1.produceData( ctx.sctpDataProducerOptions ); ctx.directDataProducer = await ctx.directTransport.produceData(); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('transport.consumeData() succeeds', async () => { const onObserverNewDataConsumer = jest.fn(); ctx.webRtcTransport2!.observer.once( 'newdataconsumer', onObserverNewDataConsumer ); const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, maxPacketLifeTime: 4000, // Valid values are 0...65535 so others and duplicated ones will be // discarded. subchannels: [0, 1, 1, 1, 2, 65535, 65536, 65537, 100], appData: { baz: 'LOL' }, }); expect(onObserverNewDataConsumer).toHaveBeenCalledTimes(1); expect(onObserverNewDataConsumer).toHaveBeenCalledWith(dataConsumer); expect(typeof dataConsumer.id).toBe('string'); expect(dataConsumer.dataProducerId).toBe(ctx.sctpDataProducer!.id); expect(dataConsumer.closed).toBe(false); expect(dataConsumer.type).toBe('sctp'); expect(typeof dataConsumer.sctpStreamParameters).toBe('object'); expect(typeof dataConsumer.sctpStreamParameters!.streamId).toBe('number'); expect(dataConsumer.sctpStreamParameters!.ordered).toBe(false); expect(dataConsumer.sctpStreamParameters!.maxPacketLifeTime).toBe(4000); expect(dataConsumer.sctpStreamParameters!.maxRetransmits).toBeUndefined(); expect(dataConsumer.label).toBe('foo'); expect(dataConsumer.protocol).toBe('bar'); expect(dataConsumer.paused).toBe(false); expect(dataConsumer.subchannels).toEqual( expect.arrayContaining([0, 1, 2, 100, 65535]) ); expect(dataConsumer.appData).toEqual({ baz: 'LOL' }); const dump = await ctx.router!.dump(); expect(dump.mapDataProducerIdDataConsumerIds).toEqual( expect.arrayContaining([ { key: ctx.sctpDataProducer!.id, values: [dataConsumer.id] }, ]) ); expect(dump.mapDataConsumerIdDataProducerId).toEqual( expect.arrayContaining([ { key: dataConsumer.id, value: ctx.sctpDataProducer!.id }, ]) ); await expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport2!.id, dataProducerIds: [], dataConsumerIds: [dataConsumer.id], }); }, 2000); test('dataConsumer.dump() succeeds', async () => { const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, ordered: true, // Valid values are 0...65535 so others and duplicated ones will be // discarded. subchannels: [0, 1, 1, 1, 2, 65535, 65536, 65537, 100], appData: { baz: 'LOL' }, }); const dump = await dataConsumer.dump(); expect(dump.id).toBe(dataConsumer.id); expect(dump.dataProducerId).toBe(dataConsumer.dataProducerId); expect(dump.type).toBe('sctp'); expect(typeof dump.sctpStreamParameters).toBe('object'); expect(dump.sctpStreamParameters!.streamId).toBe( dataConsumer.sctpStreamParameters!.streamId ); expect(dump.sctpStreamParameters!.ordered).toBe(true); expect(dump.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined(); expect(dump.sctpStreamParameters!.maxRetransmits).toBeUndefined(); expect(dump.label).toBe('foo'); expect(dump.protocol).toBe('bar'); expect(dump.paused).toBe(false); expect(dump.dataProducerPaused).toBe(false); expect(dump.subchannels).toEqual( expect.arrayContaining([0, 1, 2, 100, 65535]) ); }, 2000); test('dataConsumer.getStats() succeeds', async () => { const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); await expect(dataConsumer.getStats()).resolves.toMatchObject([ { type: 'data-consumer', label: dataConsumer.label, protocol: dataConsumer.protocol, messagesSent: 0, bytesSent: 0, }, ]); }, 2000); test('dataConsumer.setSubchannels() succeeds', async () => { const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); await dataConsumer.setSubchannels([999, 999, 998, 65536]); expect(dataConsumer.subchannels).toEqual( expect.arrayContaining([0, 998, 999]) ); }, 2000); test('dataConsumer.addSubchannel() and .removeSubchannel() succeed', async () => { const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); await dataConsumer.setSubchannels([]); expect(dataConsumer.subchannels).toEqual([]); await dataConsumer.addSubchannel(5); expect(dataConsumer.subchannels).toEqual(expect.arrayContaining([5])); await dataConsumer.addSubchannel(10); expect(dataConsumer.subchannels).toEqual(expect.arrayContaining([5, 10])); await dataConsumer.addSubchannel(5); expect(dataConsumer.subchannels).toEqual(expect.arrayContaining([5, 10])); await dataConsumer.removeSubchannel(666); expect(dataConsumer.subchannels).toEqual(expect.arrayContaining([5, 10])); await dataConsumer.removeSubchannel(5); expect(dataConsumer.subchannels).toEqual(expect.arrayContaining([10])); await dataConsumer.setSubchannels([]); expect(dataConsumer.subchannels).toEqual([]); }, 2000); test('transport.consumeData() from a direct DataProducer succeeds', async () => { const onObserverNewDataConsumer = jest.fn(); ctx.webRtcTransport2!.observer.once( 'newdataconsumer', onObserverNewDataConsumer ); const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.directDataProducer!.id, }); expect(onObserverNewDataConsumer).toHaveBeenCalledTimes(1); expect(onObserverNewDataConsumer).toHaveBeenCalledWith(dataConsumer); expect(typeof dataConsumer.id).toBe('string'); expect(dataConsumer.dataProducerId).toBe(ctx.directDataProducer!.id); expect(dataConsumer.closed).toBe(false); expect(dataConsumer.type).toBe('sctp'); expect(typeof dataConsumer.sctpStreamParameters).toBe('object'); expect(typeof dataConsumer.sctpStreamParameters!.streamId).toBe('number'); expect(dataConsumer.sctpStreamParameters!.ordered).toBe(true); expect(dataConsumer.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined(); expect(dataConsumer.sctpStreamParameters!.maxRetransmits).toBeUndefined(); expect(dataConsumer.label).toBe(''); expect(dataConsumer.protocol).toBe(''); expect(dataConsumer.paused).toBe(false); expect(dataConsumer.subchannels).toEqual([]); expect(dataConsumer.appData).toEqual({}); const dump = await ctx.router!.dump(); expect(dump.mapDataProducerIdDataConsumerIds).toEqual( expect.arrayContaining([ { key: ctx.directDataProducer!.id, values: [dataConsumer.id] }, ]) ); expect(dump.mapDataConsumerIdDataProducerId).toEqual( expect.arrayContaining([ { key: dataConsumer.id, value: ctx.directDataProducer!.id }, ]) ); await expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport2!.id, dataProducerIds: [], dataConsumerIds: [dataConsumer.id], }); }, 2000); test('dataConsumer.dump() consuming from a direct DataProducer succeeds', async () => { const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.directDataProducer!.id, ordered: false, maxRetransmits: 2, subchannels: [0, 1], }); const dump = await dataConsumer.dump(); expect(dump.id).toBe(dataConsumer.id); expect(dump.dataProducerId).toBe(dataConsumer.dataProducerId); expect(dump.type).toBe('sctp'); expect(typeof dump.sctpStreamParameters).toBe('object'); expect(dump.sctpStreamParameters!.streamId).toBe( dataConsumer.sctpStreamParameters!.streamId ); expect(dump.sctpStreamParameters!.ordered).toBe(false); expect(dump.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined(); expect(dump.sctpStreamParameters!.maxRetransmits).toBe(2); expect(dump.label).toBe(''); expect(dump.protocol).toBe(''); expect(dump.paused).toBe(false); expect(dump.dataProducerPaused).toBe(false); expect(dump.subchannels).toEqual(expect.arrayContaining([0, 1])); }, 2000); test('transport.consumeData() on a DirectTransport succeeds', async () => { const onObserverNewDataConsumer = jest.fn(); ctx.directTransport!.observer.once( 'newdataconsumer', onObserverNewDataConsumer ); const dataConsumer = await ctx.directTransport!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, paused: true, appData: { hehe: 'HEHE' }, }); expect(onObserverNewDataConsumer).toHaveBeenCalledTimes(1); expect(onObserverNewDataConsumer).toHaveBeenCalledWith(dataConsumer); expect(typeof dataConsumer.id).toBe('string'); expect(dataConsumer.dataProducerId).toBe(ctx.sctpDataProducer!.id); expect(dataConsumer.closed).toBe(false); expect(dataConsumer.type).toBe('direct'); expect(dataConsumer.sctpStreamParameters).toBeUndefined(); expect(dataConsumer.label).toBe('foo'); expect(dataConsumer.protocol).toBe('bar'); expect(dataConsumer.paused).toBe(true); expect(dataConsumer.appData).toEqual({ hehe: 'HEHE' }); await expect(ctx.directTransport!.dump()).resolves.toMatchObject({ id: ctx.directTransport!.id, dataProducerIds: [ctx.directDataProducer!.id], dataConsumerIds: [dataConsumer.id], }); }, 2000); test('dataConsumer.dump() on a DirectTransport succeeds', async () => { const dataConsumer = await ctx.directTransport!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, paused: true, }); const dump = await dataConsumer.dump(); expect(dump.id).toBe(dataConsumer.id); expect(dump.dataProducerId).toBe(dataConsumer.dataProducerId); expect(dump.type).toBe('direct'); expect(dump.sctpStreamParameters).toBeUndefined(); expect(dump.label).toBe('foo'); expect(dump.protocol).toBe('bar'); expect(dump.paused).toBe(true); expect(dump.subchannels).toEqual([]); }, 2000); test('dataConsumer.getStats() on a DirectTransport succeeds', async () => { const dataConsumer = await ctx.directTransport!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); await expect(dataConsumer.getStats()).resolves.toMatchObject([ { type: 'data-consumer', label: dataConsumer.label, protocol: dataConsumer.protocol, messagesSent: 0, bytesSent: 0, }, ]); }, 2000); test('dataConsumer.pause() and resume() succeed', async () => { const onObserverPause = jest.fn(); const onObserverResume = jest.fn(); const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); dataConsumer.observer.on('pause', onObserverPause); dataConsumer.observer.on('resume', onObserverResume); await dataConsumer.pause(); expect(dataConsumer.paused).toBe(true); const dump1 = await dataConsumer.dump(); expect(dump1.paused).toBe(true); await dataConsumer.resume(); expect(dataConsumer.paused).toBe(false); const dump2 = await dataConsumer.dump(); expect(dump2.paused).toBe(false); // Even if we don't await for pause()/resume() completion, the observer must // fire 'pause' and 'resume' events if state was the opposite. void dataConsumer.pause(); void dataConsumer.resume(); void dataConsumer.pause(); void dataConsumer.pause(); void dataConsumer.pause(); await dataConsumer.resume(); expect(onObserverPause).toHaveBeenCalledTimes(3); expect(onObserverResume).toHaveBeenCalledTimes(3); }, 2000); test('dataProducer.pause() and resume() emit events', async () => { const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); const promises = []; const events: string[] = []; dataConsumer.observer.once('resume', () => { events.push('resume'); }); dataConsumer.observer.once('pause', () => { events.push('pause'); }); promises.push(ctx.sctpDataProducer!.pause()); promises.push(ctx.sctpDataProducer!.resume()); await Promise.all(promises); // Must also wait a bit for the corresponding events in the data consumer. await new Promise(resolve => setTimeout(resolve, 100)); expect(events).toEqual(['pause', 'resume']); expect(dataConsumer.paused).toBe(false); }, 2000); test('dataConsumer.close() succeeds', async () => { const onObserverClose = jest.fn(); const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); dataConsumer.observer.once('close', onObserverClose); dataConsumer.close(); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(dataConsumer.closed).toBe(true); const dump = await ctx.router!.dump(); expect(dump.mapDataProducerIdDataConsumerIds).toEqual( expect.arrayContaining([{ key: ctx.sctpDataProducer!.id, values: [] }]) ); expect(dump.mapDataConsumerIdDataProducerId).toEqual([]); await expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport2!.id, dataProducerIds: [], dataConsumerIds: [], }); }, 2000); test('Consumer methods reject if closed', async () => { const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); dataConsumer.close(); await expect(dataConsumer.dump()).rejects.toThrow(Error); await expect(dataConsumer.getStats()).rejects.toThrow(Error); }, 2000); test('DataConsumer emits "dataproducerclose" if DataProducer is closed', async () => { const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); const onObserverClose = jest.fn(); dataConsumer.observer.once('close', onObserverClose); const promise = enhancedOnce( dataConsumer, 'dataproducerclose' ); ctx.sctpDataProducer!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(dataConsumer.closed).toBe(true); }, 2000); test('DataConsumer emits "transportclose" if Transport is closed', async () => { const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: ctx.sctpDataProducer!.id, }); const onObserverClose = jest.fn(); dataConsumer.observer.once('close', onObserverClose); const promise = enhancedOnce( dataConsumer, 'transportclose' ); ctx.webRtcTransport2!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(dataConsumer.closed).toBe(true); await expect(ctx.router!.dump()).resolves.toMatchObject({ mapDataProducerIdDataConsumerIds: {}, mapDataConsumerIdDataProducerId: {}, }); }, 2000); ================================================ FILE: node/src/test/test-DataProducer.ts ================================================ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents, DataProducerEvents } from '../types'; import * as utils from '../utils'; type TestContext = { dataProducerOptions1: mediasoup.types.DataProducerOptions; dataProducerOptions2: mediasoup.types.DataProducerOptions; worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; webRtcTransport1?: mediasoup.types.WebRtcTransport; webRtcTransport2?: mediasoup.types.WebRtcTransport; }; const ctx: TestContext = { dataProducerOptions1: utils.deepFreeze({ sctpStreamParameters: { streamId: 666, }, label: 'foo', protocol: 'bar', appData: { foo: 1, bar: '2' }, }), dataProducerOptions2: utils.deepFreeze({ sctpStreamParameters: { streamId: 777, maxRetransmits: 3, }, label: 'foo', protocol: 'bar', paused: true, appData: { foo: 1, bar: '2' }, }), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); ctx.router = await ctx.worker.createRouter(); ctx.webRtcTransport1 = await ctx.router.createWebRtcTransport({ listenIps: ['127.0.0.1'], enableSctp: true, }); ctx.webRtcTransport2 = await ctx.router.createWebRtcTransport({ listenIps: ['127.0.0.1'], enableSctp: true, }); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('webRtcTransport1.produceData() succeeds', async () => { const onObserverNewDataProducer = jest.fn(); ctx.webRtcTransport1!.observer.once( 'newdataproducer', onObserverNewDataProducer ); const dataProducer1 = await ctx.webRtcTransport1!.produceData( ctx.dataProducerOptions1 ); expect(onObserverNewDataProducer).toHaveBeenCalledTimes(1); expect(onObserverNewDataProducer).toHaveBeenCalledWith(dataProducer1); expect(typeof dataProducer1.id).toBe('string'); expect(dataProducer1.closed).toBe(false); expect(dataProducer1.type).toBe('sctp'); expect(typeof dataProducer1.sctpStreamParameters).toBe('object'); expect(dataProducer1.sctpStreamParameters?.streamId).toBe(666); expect(dataProducer1.sctpStreamParameters?.ordered).toBe(true); expect(dataProducer1.sctpStreamParameters?.maxPacketLifeTime).toBeUndefined(); expect(dataProducer1.sctpStreamParameters?.maxRetransmits).toBeUndefined(); expect(dataProducer1.label).toBe('foo'); expect(dataProducer1.protocol).toBe('bar'); expect(dataProducer1.paused).toBe(false); expect(dataProducer1.appData).toEqual({ foo: 1, bar: '2' }); const dump = await ctx.router!.dump(); expect(dump.mapDataProducerIdDataConsumerIds).toEqual( expect.arrayContaining([{ key: dataProducer1.id, values: [] }]) ); expect(dump.mapDataConsumerIdDataProducerId.length).toBe(0); await expect(ctx.webRtcTransport1!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport1!.id, dataProducerIds: [dataProducer1.id], dataConsumerIds: [], }); }, 2000); test('webRtcTransport2.produceData() succeeds', async () => { const onObserverNewDataProducer = jest.fn(); ctx.webRtcTransport2!.observer.once( 'newdataproducer', onObserverNewDataProducer ); const dataProducer2 = await ctx.webRtcTransport2!.produceData( ctx.dataProducerOptions2 ); expect(onObserverNewDataProducer).toHaveBeenCalledTimes(1); expect(onObserverNewDataProducer).toHaveBeenCalledWith(dataProducer2); expect(typeof dataProducer2.id).toBe('string'); expect(dataProducer2.closed).toBe(false); expect(dataProducer2.type).toBe('sctp'); expect(typeof dataProducer2.sctpStreamParameters).toBe('object'); expect(dataProducer2.sctpStreamParameters?.streamId).toBe(777); expect(dataProducer2.sctpStreamParameters?.ordered).toBe(false); expect(dataProducer2.sctpStreamParameters?.maxPacketLifeTime).toBeUndefined(); expect(dataProducer2.sctpStreamParameters?.maxRetransmits).toBe(3); expect(dataProducer2.label).toBe('foo'); expect(dataProducer2.protocol).toBe('bar'); expect(dataProducer2.paused).toBe(true); expect(dataProducer2.appData).toEqual({ foo: 1, bar: '2' }); const dump = await ctx.router!.dump(); expect(dump.mapDataProducerIdDataConsumerIds).toEqual( expect.arrayContaining([{ key: dataProducer2.id, values: [] }]) ); expect(dump.mapDataConsumerIdDataProducerId.length).toBe(0); await expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport2!.id, dataProducerIds: [dataProducer2.id], dataConsumerIds: [], }); }, 2000); test('webRtcTransport1.produceData() with wrong arguments rejects with TypeError', async () => { await expect(ctx.webRtcTransport1!.produceData({})).rejects.toThrow( TypeError ); // Missing or empty sctpStreamParameters.streamId. await expect( ctx.webRtcTransport1!.produceData({ // @ts-expect-error --- Testing purposes. sctpStreamParameters: { foo: 'foo' }, }) ).rejects.toThrow(TypeError); }, 2000); test('transport.produceData() with already used streamId rejects with Error', async () => { await ctx.webRtcTransport1!.produceData(ctx.dataProducerOptions1); await expect( ctx.webRtcTransport1!.produceData({ sctpStreamParameters: { streamId: 666, }, }) ).rejects.toThrow(Error); }, 2000); test('transport.produceData() with ordered and maxPacketLifeTime rejects with TypeError', async () => { await expect( ctx.webRtcTransport1!.produceData({ sctpStreamParameters: { streamId: 999, ordered: true, maxPacketLifeTime: 4000, }, }) ).rejects.toThrow(TypeError); }, 2000); test('dataProducer.dump() succeeds', async () => { const dataProducer1 = await ctx.webRtcTransport1!.produceData( ctx.dataProducerOptions1 ); const dump1 = await dataProducer1.dump(); expect(dump1.id).toBe(dataProducer1.id); expect(dump1.type).toBe('sctp'); expect(typeof dump1.sctpStreamParameters).toBe('object'); expect(dump1.sctpStreamParameters!.streamId).toBe(666); expect(dump1.sctpStreamParameters!.ordered).toBe(true); expect(dump1.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined(); expect(dump1.sctpStreamParameters!.maxRetransmits).toBeUndefined(); expect(dump1.label).toBe('foo'); expect(dump1.protocol).toBe('bar'); expect(dump1.paused).toBe(false); const dataProducer2 = await ctx.webRtcTransport2!.produceData( ctx.dataProducerOptions2 ); const dump2 = await dataProducer2.dump(); expect(dump2.id).toBe(dataProducer2.id); expect(dump2.type).toBe('sctp'); expect(typeof dump2.sctpStreamParameters).toBe('object'); expect(dump2.sctpStreamParameters!.streamId).toBe(777); expect(dump2.sctpStreamParameters!.ordered).toBe(false); expect(dump2.sctpStreamParameters!.maxPacketLifeTime).toBeUndefined(); expect(dump2.sctpStreamParameters!.maxRetransmits).toBe(3); expect(dump2.label).toBe('foo'); expect(dump2.protocol).toBe('bar'); expect(dump2.paused).toBe(true); }, 2000); test('dataProducer.getStats() succeeds', async () => { const dataProducer1 = await ctx.webRtcTransport1!.produceData( ctx.dataProducerOptions1 ); await expect(dataProducer1.getStats()).resolves.toMatchObject([ { type: 'data-producer', label: dataProducer1.label, protocol: dataProducer1.protocol, messagesReceived: 0, bytesReceived: 0, }, ]); const dataProducer2 = await ctx.webRtcTransport2!.produceData( ctx.dataProducerOptions2 ); await expect(dataProducer2.getStats()).resolves.toMatchObject([ { type: 'data-producer', label: dataProducer2.label, protocol: dataProducer2.protocol, messagesReceived: 0, bytesReceived: 0, }, ]); }, 2000); test('dataProducer.pause() and resume() succeed', async () => { const dataProducer1 = await ctx.webRtcTransport1!.produceData( ctx.dataProducerOptions1 ); const onObserverPause = jest.fn(); const onObserverResume = jest.fn(); dataProducer1.observer.on('pause', onObserverPause); dataProducer1.observer.on('resume', onObserverResume); await dataProducer1.pause(); expect(dataProducer1.paused).toBe(true); const dump1 = await dataProducer1.dump(); expect(dump1.paused).toBe(true); await dataProducer1.resume(); expect(dataProducer1.paused).toBe(false); const dump2 = await dataProducer1.dump(); expect(dump2.paused).toBe(false); // Even if we don't await for pause()/resume() completion, the observer must // fire 'pause' and 'resume' events if state was the opposite. void dataProducer1.pause(); void dataProducer1.resume(); void dataProducer1.pause(); void dataProducer1.pause(); void dataProducer1.pause(); await dataProducer1.resume(); expect(onObserverPause).toHaveBeenCalledTimes(3); expect(onObserverResume).toHaveBeenCalledTimes(3); }, 2000); test('producer.pause() and resume() emit events', async () => { const dataProducer1 = await ctx.webRtcTransport1!.produceData( ctx.dataProducerOptions1 ); const promises = []; const events: string[] = []; dataProducer1.observer.once('resume', () => { events.push('resume'); }); dataProducer1.observer.once('pause', () => { events.push('pause'); }); promises.push(dataProducer1.pause()); promises.push(dataProducer1.resume()); await Promise.all(promises); expect(events).toEqual(['pause', 'resume']); expect(dataProducer1.paused).toBe(false); }, 2000); test('dataProducer.close() succeeds', async () => { const dataProducer1 = await ctx.webRtcTransport1!.produceData( ctx.dataProducerOptions1 ); const onObserverClose = jest.fn(); dataProducer1.observer.once('close', onObserverClose); dataProducer1.close(); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(dataProducer1.closed).toBe(true); await expect(ctx.router!.dump()).resolves.toMatchObject({ mapDataProducerIdDataConsumerIds: {}, mapDataConsumerIdDataProducerId: {}, }); await expect(ctx.webRtcTransport1!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport1!.id, dataProducerIds: [], dataConsumerIds: [], }); }, 2000); test('DataProducer methods reject if closed', async () => { const dataProducer1 = await ctx.webRtcTransport1!.produceData( ctx.dataProducerOptions1 ); dataProducer1.close(); await expect(dataProducer1.dump()).rejects.toThrow(Error); await expect(dataProducer1.getStats()).rejects.toThrow(Error); }, 2000); test('DataProducer emits "transportclose" if Transport is closed', async () => { const dataProducer2 = await ctx.webRtcTransport2!.produceData( ctx.dataProducerOptions2 ); const onObserverClose = jest.fn(); dataProducer2.observer.once('close', onObserverClose); const promise = enhancedOnce( dataProducer2, 'transportclose' ); ctx.webRtcTransport2!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(dataProducer2.closed).toBe(true); }, 2000); ================================================ FILE: node/src/test/test-DirectTransport.ts ================================================ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { DirectTransportEvents } from '../DirectTransportTypes'; import type { WorkerEvents } from '../types'; type TestContext = { worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; }; const ctx: TestContext = {}; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); ctx.router = await ctx.worker.createRouter(); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('router.createDirectTransport() succeeds', async () => { const onObserverNewTransport = jest.fn(); ctx.router!.observer.once('newtransport', onObserverNewTransport); const directTransport = await ctx.router!.createDirectTransport({ maxMessageSize: 1024, appData: { foo: 'bar' }, }); await expect(ctx.router!.dump()).resolves.toMatchObject({ transportIds: [directTransport.id], }); expect(onObserverNewTransport).toHaveBeenCalledTimes(1); expect(onObserverNewTransport).toHaveBeenCalledWith(directTransport); expect(typeof directTransport.id).toBe('string'); expect(directTransport.type).toBe('direct'); expect(directTransport.closed).toBe(false); expect(directTransport.appData).toEqual({ foo: 'bar' }); const dump = await directTransport.dump(); expect(dump.id).toBe(directTransport.id); expect(dump.producerIds).toEqual([]); expect(dump.consumerIds).toEqual([]); expect(dump.dataProducerIds).toEqual([]); expect(dump.dataConsumerIds).toEqual([]); expect(dump.recvRtpHeaderExtensions).toBeDefined(); expect(typeof dump.rtpListener).toBe('object'); directTransport.close(); expect(directTransport.closed).toBe(true); }, 2000); test('router.createDirectTransport() with wrong arguments rejects with TypeError', async () => { await expect( // @ts-expect-error --- Testing purposes. ctx.router!.createDirectTransport({ maxMessageSize: 'foo' }) ).rejects.toThrow(TypeError); await expect( ctx.router!.createDirectTransport({ maxMessageSize: -2000 }) ).rejects.toThrow(TypeError); }, 2000); test('directTransport.getStats() succeeds', async () => { const directTransport = await ctx.router!.createDirectTransport(); const stats = await directTransport.getStats(); expect(Array.isArray(stats)).toBe(true); expect(stats.length).toBe(1); expect(stats[0]!.type).toBe('direct-transport'); expect(stats[0]!.transportId).toBe(directTransport.id); expect(typeof stats[0]!.timestamp).toBe('number'); expect(stats[0]!.bytesReceived).toBe(0); expect(stats[0]!.recvBitrate).toBe(0); expect(stats[0]!.bytesSent).toBe(0); expect(stats[0]!.sendBitrate).toBe(0); expect(stats[0]!.rtpBytesReceived).toBe(0); expect(stats[0]!.rtpRecvBitrate).toBe(0); expect(stats[0]!.rtpBytesSent).toBe(0); expect(stats[0]!.rtpSendBitrate).toBe(0); expect(stats[0]!.rtxBytesReceived).toBe(0); expect(stats[0]!.rtxRecvBitrate).toBe(0); expect(stats[0]!.rtxBytesSent).toBe(0); expect(stats[0]!.rtxSendBitrate).toBe(0); expect(stats[0]!.probationBytesSent).toBe(0); expect(stats[0]!.probationSendBitrate).toBe(0); }, 2000); test('directTransport.connect() succeeds', async () => { const directTransport = await ctx.router!.createDirectTransport(); await expect(directTransport.connect()).resolves.toBeUndefined(); }, 2000); test('dataProducer.send() succeeds', async () => { const directTransport = await ctx.router!.createDirectTransport(); const dataProducer = await directTransport.produceData({ label: 'foo', protocol: 'bar', appData: { foo: 'bar' }, }); const dataConsumer = await directTransport.consumeData({ dataProducerId: dataProducer.id, }); const numMessages = 200; const pauseSendingAtMessage = 10; const resumeSendingAtMessage = 20; const pauseReceivingAtMessage = 40; const resumeReceivingAtMessage = 60; const expectedReceivedNumMessages = numMessages - (resumeSendingAtMessage - pauseSendingAtMessage) - (resumeReceivingAtMessage - pauseReceivingAtMessage); let sentMessageBytes = 0; let effectivelySentMessageBytes = 0; let recvMessageBytes = 0; let numSentMessages = 0; let numReceivedMessages = 0; async function sendNextMessage(): Promise { const id = ++numSentMessages; let message: Buffer | string; if (id === pauseSendingAtMessage) { await dataProducer.pause(); } else if (id === resumeSendingAtMessage) { await dataProducer.resume(); } else if (id === pauseReceivingAtMessage) { await dataConsumer.pause(); } else if (id === resumeReceivingAtMessage) { await dataConsumer.resume(); } let messageSize: number; // Send string (WebRTC DataChannel string). if (id < numMessages / 2) { message = String(id); messageSize = Buffer.from(message).byteLength; } // Send string (WebRTC DataChannel binary). else { message = Buffer.from(String(id)); messageSize = message.byteLength; } dataProducer.send(message); sentMessageBytes += messageSize; if (!dataProducer.paused && !dataConsumer.paused) { effectivelySentMessageBytes += messageSize; } if (id < numMessages) { void sendNextMessage(); } } await new Promise((resolve, reject) => { dataProducer.on('listenererror', (eventName, error) => { reject( new Error( `dataProducer 'listenererror' [eventName:${eventName}]: ${error.toString()}` ) ); }); dataConsumer.on('listenererror', (eventName, error) => { reject( new Error( `dataConsumer 'listenererror' [eventName:${eventName}]: ${error.toString()}` ) ); }); dataConsumer.on('message', (message, ppid) => { ++numReceivedMessages; // message is always a Buffer. recvMessageBytes += message.byteLength; const id = Number(message.toString('utf8')); if (id === numMessages) { resolve(); } // PPID of WebRTC DataChannel string. else if (id < numMessages / 2 && ppid !== 51) { reject( new Error( `ppid in message with id ${id} should be 51 but it is ${ppid}` ) ); } // PPID of WebRTC DataChannel binary. else if (id > numMessages / 2 && ppid !== 53) { reject( new Error( `ppid in message with id ${id} should be 53 but it is ${ppid}` ) ); } }); void sendNextMessage(); }); expect(numSentMessages).toBe(numMessages); expect(numReceivedMessages).toBe(expectedReceivedNumMessages); expect(recvMessageBytes).toBe(effectivelySentMessageBytes); await expect(dataProducer.getStats()).resolves.toMatchObject([ { type: 'data-producer', label: dataProducer.label, protocol: dataProducer.protocol, messagesReceived: numMessages, bytesReceived: sentMessageBytes, }, ]); await expect(dataConsumer.getStats()).resolves.toMatchObject([ { type: 'data-consumer', label: dataConsumer.label, protocol: dataConsumer.protocol, messagesSent: expectedReceivedNumMessages, bytesSent: recvMessageBytes, }, ]); }, 5000); test('dataProducer.send() with subchannels succeeds', async () => { const directTransport = await ctx.router!.createDirectTransport(); const dataProducer = await directTransport.produceData(); const dataConsumer1 = await directTransport.consumeData({ dataProducerId: dataProducer.id, subchannels: [1, 11, 666], }); const dataConsumer2 = await directTransport.consumeData({ dataProducerId: dataProducer.id, subchannels: [2, 22, 666], }); const expectedReceivedNumMessages1 = 7; const expectedReceivedNumMessages2 = 5; const receivedMessages1: string[] = []; const receivedMessages2: string[] = []; await new Promise(resolve => { // Must be received by dataConsumer1 and dataConsumer2. dataProducer.send( 'both', /* ppid */ undefined, /* subchannels */ undefined, /* requiredSubchannel */ undefined ); // Must be received by dataConsumer1 and dataConsumer2. dataProducer.send( 'both', /* ppid */ undefined, /* subchannels */ [1, 2], /* requiredSubchannel */ undefined ); // Must be received by dataConsumer1 and dataConsumer2. dataProducer.send( 'both', /* ppid */ undefined, /* subchannels */ [11, 22, 33], /* requiredSubchannel */ 666 ); // Must not be received by neither dataConsumer1 nor dataConsumer2. dataProducer.send( 'none', /* ppid */ undefined, /* subchannels */ [3], /* requiredSubchannel */ 666 ); // Must not be received by neither dataConsumer1 nor dataConsumer2. dataProducer.send( 'none', /* ppid */ undefined, /* subchannels */ [666], /* requiredSubchannel */ 3 ); // Must be received by dataConsumer1. dataProducer.send( 'dc1', /* ppid */ undefined, /* subchannels */ [1], /* requiredSubchannel */ undefined ); // Must be received by dataConsumer1. dataProducer.send( 'dc1', /* ppid */ undefined, /* subchannels */ [11], /* requiredSubchannel */ 1 ); // Must be received by dataConsumer1. dataProducer.send( 'dc1', /* ppid */ undefined, /* subchannels */ [666], /* requiredSubchannel */ 11 ); // Must be received by dataConsumer2. dataProducer.send( 'dc2', /* ppid */ undefined, /* subchannels */ [666], /* requiredSubchannel */ 2 ); // Make dataConsumer2 also subscribe to subchannel 1. // NOTE: No need to await for this call. void dataConsumer2.setSubchannels([...dataConsumer2.subchannels, 1]); // Must be received by dataConsumer1 and dataConsumer2. dataProducer.send( 'both', /* ppid */ undefined, /* subchannels */ [1], /* requiredSubchannel */ 666 ); dataConsumer1.on('message', message => { receivedMessages1.push(message.toString('utf8')); if ( receivedMessages1.length === expectedReceivedNumMessages1 && receivedMessages2.length === expectedReceivedNumMessages2 ) { resolve(); } }); dataConsumer2.on('message', message => { receivedMessages2.push(message.toString('utf8')); if ( receivedMessages1.length === expectedReceivedNumMessages1 && receivedMessages2.length === expectedReceivedNumMessages2 ) { resolve(); } }); }); expect(receivedMessages1.length).toBe(expectedReceivedNumMessages1); expect(receivedMessages2.length).toBe(expectedReceivedNumMessages2); for (const message of receivedMessages1) { expect(['both', 'dc1'].includes(message)).toBe(true); expect(['dc2'].includes(message)).toBe(false); } for (const message of receivedMessages2) { expect(['both', 'dc2'].includes(message)).toBe(true); expect(['dc1'].includes(message)).toBe(false); } }, 5000); test('DirectTransport methods reject if closed', async () => { const directTransport = await ctx.router!.createDirectTransport(); const onObserverClose = jest.fn(); directTransport.observer.once('close', onObserverClose); directTransport.close(); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(directTransport.closed).toBe(true); await expect(directTransport.dump()).rejects.toThrow(Error); await expect(directTransport.getStats()).rejects.toThrow(Error); }, 2000); test('DirectTransport emits "routerclose" if Router is closed', async () => { const directTransport = await ctx.router!.createDirectTransport(); const onObserverClose = jest.fn(); directTransport.observer.once('close', onObserverClose); const promise = enhancedOnce( directTransport, 'routerclose' ); ctx.router!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(directTransport.closed).toBe(true); }, 2000); test('DirectTransport emits "routerclose" if Worker is closed', async () => { const directTransport = await ctx.router!.createDirectTransport(); const onObserverClose = jest.fn(); directTransport.observer.once('close', onObserverClose); const promise = enhancedOnce( directTransport, 'routerclose' ); ctx.worker!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(directTransport.closed).toBe(true); }, 2000); ================================================ FILE: node/src/test/test-PipeTransport.ts ================================================ import { pickPort } from 'pick-port'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents, ConsumerEvents, ProducerObserverEvents, DataConsumerEvents, } from '../types'; import * as utils from '../utils'; type TestContext = { mediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; audioProducerOptions: mediasoup.types.ProducerOptions; videoProducerOptions: mediasoup.types.ProducerOptions; dataProducerOptions: mediasoup.types.DataProducerOptions; consumerDeviceCapabilities: mediasoup.types.RtpCapabilities; worker1?: mediasoup.types.Worker; worker2?: mediasoup.types.Worker; router1?: mediasoup.types.Router; router2?: mediasoup.types.Router; webRtcTransport1?: mediasoup.types.WebRtcTransport; webRtcTransport2?: mediasoup.types.WebRtcTransport; audioProducer?: mediasoup.types.Producer; videoProducer?: mediasoup.types.Producer; videoConsumer?: mediasoup.types.Consumer; dataProducer?: mediasoup.types.DataProducer; dataConsumer?: mediasoup.types.DataConsumer; }; const ctx: TestContext = { mediaCodecs: utils.deepFreeze([ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, }, { kind: 'video', mimeType: 'video/VP8', clockRate: 90000, }, ]), audioProducerOptions: utils.deepFreeze({ kind: 'audio', rtpParameters: { mid: 'AUDIO', codecs: [ { mimeType: 'audio/opus', payloadType: 111, clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, foo: 'bar1', }, }, ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, }, ], encodings: [{ ssrc: 11111111 }], rtcp: { cname: 'FOOBAR', }, }, appData: { foo: 'bar1' }, }), videoProducerOptions: utils.deepFreeze({ kind: 'video', rtpParameters: { mid: 'VIDEO', codecs: [ { mimeType: 'video/VP8', payloadType: 112, clockRate: 90000, rtcpFeedback: [ { type: 'nack' }, { type: 'nack', parameter: 'pli' }, { type: 'goog-remb' }, { type: 'lalala' }, ], }, ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', id: 11, }, { uri: 'urn:3gpp:video-orientation', id: 13, }, ], encodings: [{ ssrc: 22222222 }, { ssrc: 22222223 }, { ssrc: 22222224 }], rtcp: { cname: 'FOOBAR', }, msid: 'aaaa-bbbb', }, appData: { foo: 'bar2' }, }), dataProducerOptions: utils.deepFreeze({ sctpStreamParameters: { streamId: 666, ordered: false, maxPacketLifeTime: 5000, }, label: 'foo', protocol: 'bar', }), consumerDeviceCapabilities: utils.deepFreeze( { codecs: [ { kind: 'audio', mimeType: 'audio/opus', preferredPayloadType: 100, clockRate: 48000, channels: 2, }, { kind: 'video', mimeType: 'video/VP8', preferredPayloadType: 101, clockRate: 90000, rtcpFeedback: [ { type: 'nack' }, { type: 'ccm', parameter: 'fir' }, { type: 'transport-cc' }, ], }, { kind: 'video', mimeType: 'video/rtx', preferredPayloadType: 102, clockRate: 90000, parameters: { apt: 101, }, rtcpFeedback: [], }, ], headerExtensions: [ { kind: 'video', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', preferredId: 4, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01', preferredId: 5, preferredEncrypt: false, }, { kind: 'audio', uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', preferredId: 6, preferredEncrypt: false, }, ], } ), }; beforeEach(async () => { ctx.worker1 = await mediasoup.createWorker(); ctx.worker2 = await mediasoup.createWorker(); ctx.router1 = await ctx.worker1.createRouter({ mediaCodecs: ctx.mediaCodecs, }); ctx.router2 = await ctx.worker2.createRouter({ mediaCodecs: ctx.mediaCodecs, }); ctx.webRtcTransport1 = await ctx.router1.createWebRtcTransport({ listenInfos: [{ protocol: 'udp', ip: '127.0.0.1' }], enableSctp: true, }); ctx.webRtcTransport2 = await ctx.router2.createWebRtcTransport({ listenIps: ['127.0.0.1'], enableSctp: true, }); ctx.audioProducer = await ctx.webRtcTransport1.produce( ctx.audioProducerOptions ); ctx.videoProducer = await ctx.webRtcTransport1.produce( ctx.videoProducerOptions ); ctx.dataProducer = await ctx.webRtcTransport1.produceData( ctx.dataProducerOptions ); }); afterEach(async () => { ctx.worker1?.close(); ctx.worker2?.close(); if (ctx.worker1?.subprocessClosed === false) { await enhancedOnce(ctx.worker1, 'subprocessclose'); } if (ctx.worker2?.subprocessClosed === false) { await enhancedOnce(ctx.worker2, 'subprocessclose'); } }); test('router.pipeToRouter() succeeds with audio', async () => { const { pipeConsumer, pipeProducer } = (await ctx.router1!.pipeToRouter({ producerId: ctx.audioProducer!.id, router: ctx.router2!, })) as { pipeConsumer: mediasoup.types.Consumer; pipeProducer: mediasoup.types.Producer; }; const dump1 = await ctx.router1!.dump(); // There should be two Transports in router1: // - WebRtcTransport for audioProducer and videoProducer. // - PipeTransport between router1 and router2. expect(dump1.transportIds.length).toBe(2); const dump2 = await ctx.router2!.dump(); // There should be two Transports in router2: // - WebRtcTransport for audioConsumer and videoConsumer. // - PipeTransport between router2 and router1. expect(dump2.transportIds.length).toBe(2); expect(typeof pipeConsumer.id).toBe('string'); expect(pipeConsumer.closed).toBe(false); expect(pipeConsumer.kind).toBe('audio'); expect(typeof pipeConsumer.rtpParameters).toBe('object'); expect(pipeConsumer.rtpParameters.mid).toBeUndefined(); expect(pipeConsumer.rtpParameters.codecs).toEqual([ { mimeType: 'audio/opus', clockRate: 48000, payloadType: 100, channels: 2, parameters: { useinbandfec: 1, foo: 'bar1', }, rtcpFeedback: [], }, ]); expect(pipeConsumer.rtpParameters.headerExtensions).toEqual([ { uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', id: 6, encrypt: false, parameters: {}, }, { encrypt: false, id: 7, parameters: {}, uri: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension', }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time', id: 10, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay', id: 11, encrypt: false, parameters: {}, }, { uri: 'urn:mediasoup:params:rtp-hdrext:packet-id', id: 12, encrypt: false, parameters: {}, }, ]); expect(pipeConsumer.type).toBe('pipe'); expect(pipeConsumer.paused).toBe(false); expect(pipeConsumer.producerPaused).toBe(false); expect(pipeConsumer.score).toEqual({ score: 10, producerScore: 10, producerScores: [], }); expect(pipeConsumer.appData).toEqual({}); expect(pipeProducer.id).toBe(ctx.audioProducer!.id); expect(pipeProducer.closed).toBe(false); expect(pipeProducer.kind).toBe('audio'); expect(typeof pipeProducer.rtpParameters).toBe('object'); expect(pipeProducer.rtpParameters.mid).toBeUndefined(); expect(pipeProducer.rtpParameters.codecs).toEqual([ { mimeType: 'audio/opus', payloadType: 100, clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, foo: 'bar1', }, rtcpFeedback: [], }, ]); expect(pipeProducer.rtpParameters.headerExtensions).toEqual([ { uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', id: 6, encrypt: false, parameters: {}, }, { encrypt: false, id: 7, parameters: {}, uri: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension', }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time', id: 10, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay', id: 11, encrypt: false, parameters: {}, }, { uri: 'urn:mediasoup:params:rtp-hdrext:packet-id', id: 12, encrypt: false, parameters: {}, }, ]); expect(pipeProducer.paused).toBe(false); }, 2000); test('router.pipeToRouter() succeeds with video', async () => { await ctx.videoProducer!.pause(); const { pipeConsumer, pipeProducer } = (await ctx.router1!.pipeToRouter({ producerId: ctx.videoProducer!.id, router: ctx.router2!, })) as { pipeConsumer: mediasoup.types.Consumer; pipeProducer: mediasoup.types.Producer; }; const dump1 = await ctx.router1!.dump(); // No new PipeTransport should has been created. The existing one is used. expect(dump1.transportIds.length).toBe(2); const dump2 = await ctx.router2!.dump(); // No new PipeTransport should has been created. The existing one is used. expect(dump2.transportIds.length).toBe(2); expect(typeof pipeConsumer.id).toBe('string'); expect(pipeConsumer.closed).toBe(false); expect(pipeConsumer.kind).toBe('video'); expect(typeof pipeConsumer.rtpParameters).toBe('object'); expect(pipeConsumer.rtpParameters.mid).toBeUndefined(); expect(pipeConsumer.rtpParameters.codecs).toEqual([ { mimeType: 'video/VP8', payloadType: 101, clockRate: 90000, parameters: {}, rtcpFeedback: [ { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, ], }, ]); expect(pipeConsumer.rtpParameters.headerExtensions).toEqual([ { uri: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension', id: 7, encrypt: false, parameters: {}, }, { uri: 'urn:3gpp:video-orientation', id: 8, encrypt: false, parameters: {}, }, { uri: 'urn:ietf:params:rtp-hdrext:toffset', id: 9, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time', id: 10, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay', id: 11, encrypt: false, parameters: {}, }, { uri: 'urn:mediasoup:params:rtp-hdrext:packet-id', id: 12, encrypt: false, parameters: {}, }, ]); expect(pipeConsumer.type).toBe('pipe'); expect(pipeConsumer.paused).toBe(false); expect(pipeConsumer.producerPaused).toBe(true); expect(pipeConsumer.score).toEqual({ score: 10, producerScore: 10, producerScores: [], }); expect(pipeConsumer.appData).toEqual({}); expect(pipeProducer.id).toBe(ctx.videoProducer!.id); expect(pipeProducer.closed).toBe(false); expect(pipeProducer.kind).toBe('video'); expect(typeof pipeProducer.rtpParameters).toBe('object'); expect(pipeProducer.rtpParameters.mid).toBeUndefined(); expect(pipeProducer.rtpParameters.codecs).toEqual([ { mimeType: 'video/VP8', payloadType: 101, clockRate: 90000, parameters: {}, rtcpFeedback: [ { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, ], }, ]); expect(pipeProducer.rtpParameters.headerExtensions).toEqual([ { uri: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension', id: 7, encrypt: false, parameters: {}, }, { uri: 'urn:3gpp:video-orientation', id: 8, encrypt: false, parameters: {}, }, { uri: 'urn:ietf:params:rtp-hdrext:toffset', id: 9, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time', id: 10, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay', id: 11, encrypt: false, parameters: {}, }, { uri: 'urn:mediasoup:params:rtp-hdrext:packet-id', id: 12, encrypt: false, parameters: {}, }, ]); expect(pipeProducer.paused).toBe(true); }, 2000); test('router.createPipeTransport() with wrong arguments rejects with TypeError', async () => { // @ts-expect-error --- Testing purposes. await expect(ctx.router1!.createPipeTransport({})).rejects.toThrow(TypeError); await expect( ctx.router1!.createPipeTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 4000, max: 3000 }, }, }) ).rejects.toThrow(TypeError); await expect( ctx.router1!.createPipeTransport({ listenIp: '123' }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.router1!.createPipeTransport({ listenIp: ['127.0.0.1'] }) ).rejects.toThrow(TypeError); await expect( ctx.router1!.createPipeTransport({ listenInfo: { protocol: 'tcp', ip: '127.0.0.1' }, }) ).rejects.toThrow(TypeError); await expect( ctx.router1!.createPipeTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1' }, // @ts-expect-error --- Testing purposes. appData: 'NOT-AN-OBJECT', }) ).rejects.toThrow(TypeError); }, 2000); test('router.createPipeTransport() with enableRtx succeeds', async () => { const pipeTransport = await ctx.router1!.createPipeTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 }, }, enableRtx: true, }); expect(pipeTransport.type).toBe('pipe'); const pipeConsumer = await pipeTransport.consume({ producerId: ctx.videoProducer!.id, }); expect(typeof pipeConsumer.id).toBe('string'); expect(pipeConsumer.closed).toBe(false); expect(pipeConsumer.kind).toBe('video'); expect(typeof pipeConsumer.rtpParameters).toBe('object'); expect(pipeConsumer.rtpParameters.mid).toBeUndefined(); expect(pipeConsumer.rtpParameters.codecs).toEqual([ { mimeType: 'video/VP8', payloadType: 101, clockRate: 90000, parameters: {}, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, ], }, { mimeType: 'video/rtx', payloadType: 102, clockRate: 90000, parameters: { apt: 101 }, rtcpFeedback: [], }, ]); expect(pipeConsumer.rtpParameters.headerExtensions).toEqual([ { uri: 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension', id: 7, encrypt: false, parameters: {}, }, { uri: 'urn:3gpp:video-orientation', id: 8, encrypt: false, parameters: {}, }, { uri: 'urn:ietf:params:rtp-hdrext:toffset', id: 9, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time', id: 10, encrypt: false, parameters: {}, }, { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay', id: 11, encrypt: false, parameters: {}, }, { uri: 'urn:mediasoup:params:rtp-hdrext:packet-id', id: 12, encrypt: false, parameters: {}, }, ]); expect(pipeConsumer.type).toBe('pipe'); expect(pipeConsumer.paused).toBe(false); expect(pipeConsumer.producerPaused).toBe(false); expect(pipeConsumer.score).toEqual({ score: 10, producerScore: 10, producerScores: [], }); expect(pipeConsumer.appData).toEqual({}); }, 2000); test('pipeTransport.connect() with valid SRTP parameters succeeds', async () => { const pipeTransport = await ctx.router1!.createPipeTransport({ listenIp: '127.0.0.1', enableSrtp: true, }); expect(typeof pipeTransport.srtpParameters).toBe('object'); // The master length of AEAD_AES_256_GCM. expect(pipeTransport.srtpParameters?.keyBase64.length).toBe(60); // Valid srtpParameters. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, srtpParameters: { cryptoSuite: 'AEAD_AES_256_GCM', keyBase64: 'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=', }, }) ).resolves.toBeUndefined(); }, 2000); test('pipeTransport.connect() with srtpParameters fails if enableSrtp is unset', async () => { const pipeTransport = await ctx.router1!.createPipeTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 }, }, enableRtx: true, }); expect(pipeTransport.srtpParameters).toBeUndefined(); // No SRTP enabled so passing srtpParameters must fail. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, srtpParameters: { cryptoSuite: 'AEAD_AES_256_GCM', keyBase64: 'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=', }, }) ).rejects.toThrow(TypeError); // No SRTP enabled so passing srtpParameters (even if invalid) must fail. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, // @ts-expect-error --- Testing purposes. srtpParameters: 'invalid', }) ).rejects.toThrow(TypeError); }); test('pipeTransport.connect() with invalid srtpParameters fails', async () => { const pipeTransport = await ctx.router1!.createPipeTransport({ listenIp: '127.0.0.1', enableSrtp: true, }); expect(typeof pipeTransport.id).toBe('string'); expect(typeof pipeTransport.srtpParameters).toBe('object'); // The master length of AEAD_AES_256_GCM. expect(pipeTransport.srtpParameters?.keyBase64.length).toBe(60); // Missing srtpParameters. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, }) ).rejects.toThrow(TypeError); // Invalid srtpParameters. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, // @ts-expect-error --- Testing purposes. srtpParameters: 1, }) ).rejects.toThrow(TypeError); // Missing srtpParameters.cryptoSuite. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, // @ts-expect-error --- Testing purposes. srtpParameters: { keyBase64: 'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=', }, }) ).rejects.toThrow(TypeError); // Missing srtpParameters.keyBase64. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, // @ts-expect-error --- Testing purposes. srtpParameters: { cryptoSuite: 'AEAD_AES_256_GCM', }, }) ).rejects.toThrow(TypeError); // Invalid srtpParameters.cryptoSuite. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, srtpParameters: { // @ts-expect-error --- Testing purposes. cryptoSuite: 'FOO', keyBase64: 'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=', }, }) ).rejects.toThrow(TypeError); // Invalid srtpParameters.cryptoSuite. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, srtpParameters: { // @ts-expect-error --- Testing purposes. cryptoSuite: 123, keyBase64: 'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=', }, }) ).rejects.toThrow(TypeError); // Invalid srtpParameters.keyBase64. await expect( pipeTransport.connect({ ip: '127.0.0.2', port: 9999, srtpParameters: { cryptoSuite: 'AEAD_AES_256_GCM', // @ts-expect-error --- Testing purposes. keyBase64: [], }, }) ).rejects.toThrow(TypeError); }, 2000); test('router.createPipeTransport() with fixed port succeeds', async () => { const port = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const pipeTransport = await ctx.router1!.createPipeTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1', port }, }); expect(pipeTransport.tuple.localPort).toEqual(port); }, 2000); test('transport.consume() for a pipe Producer succeeds', async () => { const { pipeProducer } = await ctx.router1!.pipeToRouter({ producerId: ctx.videoProducer!.id, router: ctx.router2!, }); const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: pipeProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); expect(typeof videoConsumer.id).toBe('string'); expect(videoConsumer.closed).toBe(false); expect(videoConsumer.kind).toBe('video'); expect(typeof videoConsumer.rtpParameters).toBe('object'); expect(videoConsumer.rtpParameters.mid).toBe('0'); expect(videoConsumer.rtpParameters.codecs).toEqual([ { mimeType: 'video/VP8', payloadType: 101, clockRate: 90000, parameters: {}, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'ccm', parameter: 'fir' }, { type: 'transport-cc', parameter: '' }, ], }, { mimeType: 'video/rtx', payloadType: 102, clockRate: 90000, parameters: { apt: 101, }, rtcpFeedback: [], }, ]); expect(videoConsumer.rtpParameters.headerExtensions).toEqual([ { uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', id: 4, encrypt: false, parameters: {}, }, { uri: 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01', id: 5, encrypt: false, parameters: {}, }, ]); expect(videoConsumer.rtpParameters.encodings?.length).toBe(1); expect(typeof videoConsumer.rtpParameters.encodings![0]!.ssrc).toBe('number'); expect(typeof videoConsumer.rtpParameters.encodings![0]!.rtx).toBe('object'); expect(typeof videoConsumer.rtpParameters.encodings![0]!.rtx?.ssrc).toBe( 'number' ); expect(videoConsumer.rtpParameters.msid).toBe('aaaa-bbbb'); expect(videoConsumer.type).toBe('simulcast'); expect(videoConsumer.paused).toBe(false); expect(videoConsumer.producerPaused).toBe(false); expect(videoConsumer.score).toEqual({ score: 10, producerScore: 0, producerScores: [0, 0, 0], }); expect(videoConsumer.appData).toEqual({}); }, 2000); test('producer.pause() and producer.resume() are transmitted to pipe Consumer', async () => { await ctx.videoProducer!.pause(); // We need to obtain the pipeProducer to await for its 'puase' and 'resume' // events, otherwise we may get errors like this: // InvalidStateError: Channel closed, pending request aborted [method:PRODUCER_PAUSE, id:8] // See related fixed issue: // https://github.com/versatica/mediasoup/issues/1374 const { pipeProducer: pipeVideoProducer } = await ctx.router1!.pipeToRouter({ producerId: ctx.videoProducer!.id, router: ctx.router2!, }); const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: pipeVideoProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); expect(ctx.videoProducer!.paused).toBe(true); expect(videoConsumer.producerPaused).toBe(true); expect(videoConsumer.paused).toBe(false); // NOTE: Let's use a Promise since otherwise there may be race conditions // between events and await lines below. const promise1 = enhancedOnce( videoConsumer, 'producerresume' ); const promise2 = enhancedOnce( pipeVideoProducer!.observer, 'resume' ); await ctx.videoProducer!.resume(); await Promise.all([promise1, promise2]); expect(videoConsumer.producerPaused).toBe(false); expect(videoConsumer.paused).toBe(false); expect(pipeVideoProducer!.paused).toBe(false); const promise3 = enhancedOnce(videoConsumer, 'producerpause'); const promise4 = enhancedOnce( pipeVideoProducer!.observer, 'pause' ); await ctx.videoProducer!.pause(); await Promise.all([promise3, promise4]); expect(videoConsumer.producerPaused).toBe(true); expect(videoConsumer.paused).toBe(false); expect(pipeVideoProducer!.paused).toBe(true); }, 2000); test('producer.close() is transmitted to pipe Consumer', async () => { const { pipeProducer } = await ctx.router1!.pipeToRouter({ producerId: ctx.videoProducer!.id, router: ctx.router2!, }); const videoConsumer = await ctx.webRtcTransport2!.consume({ producerId: pipeProducer!.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); ctx.videoProducer!.close(); expect(ctx.videoProducer!.closed).toBe(true); if (!videoConsumer.closed) { await enhancedOnce(videoConsumer, 'producerclose'); } expect(videoConsumer.closed).toBe(true); }, 2000); test('router.pipeToRouter() with keepId: true fails if both Routers belong to the same Worker', async () => { const router1bis = await ctx.worker1!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); await expect( ctx.router1!.pipeToRouter({ producerId: ctx.videoProducer!.id, router: router1bis, // Default value is true. keepId: true, }) ).rejects.toThrow(Error); }, 2000); test('router.pipeToRouter() with keepId: false does not fail if both Routers belong to the same Worker', async () => { const router1bis = await ctx.worker1!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); const { pipeProducer } = await ctx.router1!.pipeToRouter({ producerId: ctx.videoProducer!.id, router: router1bis, keepId: false, }); expect(pipeProducer!.id).not.toBe(ctx.videoProducer!.id); }, 2000); test('router.pipeToRouter() succeeds with data', async () => { const { pipeDataConsumer, pipeDataProducer } = (await ctx.router1!.pipeToRouter({ dataProducerId: ctx.dataProducer!.id, router: ctx.router2!, })) as { pipeDataConsumer: mediasoup.types.DataConsumer; pipeDataProducer: mediasoup.types.DataProducer; }; const dump1 = await ctx.router1!.dump(); // There should be two Transports in router1: // - WebRtcTransport for audioProducer, videoProducer and dataProducer. // - PipeTransport between router1 and router2. expect(dump1.transportIds.length).toBe(2); const dump2 = await ctx.router2!.dump(); // There should be two Transports in router2: // - WebRtcTransport for audioConsumer, videoConsumer and dataConsumer. // - PipeTransport between router2 and router1. expect(dump2.transportIds.length).toBe(2); expect(typeof pipeDataConsumer.id).toBe('string'); expect(pipeDataConsumer.closed).toBe(false); expect(pipeDataConsumer.type).toBe('sctp'); expect(typeof pipeDataConsumer.sctpStreamParameters).toBe('object'); expect(typeof pipeDataConsumer.sctpStreamParameters?.streamId).toBe('number'); expect(pipeDataConsumer.sctpStreamParameters?.ordered).toBe(false); expect(pipeDataConsumer.sctpStreamParameters?.maxPacketLifeTime).toBe(5000); expect(pipeDataConsumer.sctpStreamParameters?.maxRetransmits).toBeUndefined(); expect(pipeDataConsumer.label).toBe('foo'); expect(pipeDataConsumer.protocol).toBe('bar'); expect(pipeDataProducer.id).toBe(ctx.dataProducer!.id); expect(pipeDataProducer.closed).toBe(false); expect(pipeDataProducer.type).toBe('sctp'); expect(typeof pipeDataProducer.sctpStreamParameters).toBe('object'); expect(typeof pipeDataProducer.sctpStreamParameters?.streamId).toBe('number'); expect(pipeDataProducer.sctpStreamParameters?.ordered).toBe(false); expect(pipeDataProducer.sctpStreamParameters?.maxPacketLifeTime).toBe(5000); expect(pipeDataProducer.sctpStreamParameters?.maxRetransmits).toBeUndefined(); expect(pipeDataProducer.label).toBe('foo'); expect(pipeDataProducer.protocol).toBe('bar'); }, 2000); test('transport.dataConsume() for a pipe DataProducer succeeds', async () => { const { pipeDataProducer } = await ctx.router1!.pipeToRouter({ dataProducerId: ctx.dataProducer!.id, router: ctx.router2!, }); const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: pipeDataProducer!.id, }); expect(typeof dataConsumer.id).toBe('string'); expect(dataConsumer.closed).toBe(false); expect(dataConsumer.type).toBe('sctp'); expect(typeof dataConsumer.sctpStreamParameters).toBe('object'); expect(typeof dataConsumer.sctpStreamParameters?.streamId).toBe('number'); expect(dataConsumer.sctpStreamParameters?.ordered).toBe(false); expect(dataConsumer.sctpStreamParameters?.maxPacketLifeTime).toBe(5000); expect(dataConsumer.sctpStreamParameters?.maxRetransmits).toBeUndefined(); expect(dataConsumer.label).toBe('foo'); expect(dataConsumer.protocol).toBe('bar'); }, 2000); test('dataProducer.close() is transmitted to pipe DataConsumer', async () => { const { pipeDataProducer } = await ctx.router1!.pipeToRouter({ dataProducerId: ctx.dataProducer!.id, router: ctx.router2!, }); const dataConsumer = await ctx.webRtcTransport2!.consumeData({ dataProducerId: pipeDataProducer!.id, }); ctx.dataProducer!.close(); expect(ctx.dataProducer!.closed).toBe(true); if (!dataConsumer.closed) { await enhancedOnce(dataConsumer, 'dataproducerclose'); } expect(dataConsumer.closed).toBe(true); }, 2000); test('router.pipeToRouter() called twice generates a single PipeTransport pair', async () => { const routerA = await ctx.worker1!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); const routerB = await ctx.worker2!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); const transportA1 = await routerA.createWebRtcTransport({ listenIps: ['127.0.0.1'], }); const transportA2 = await routerA.createWebRtcTransport({ listenIps: ['127.0.0.1'], }); const audioProducerA1 = await transportA1.produce(ctx.audioProducerOptions); const audioProducerA2 = await transportA2.produce(ctx.audioProducerOptions); await Promise.all([ routerA.pipeToRouter({ producerId: audioProducerA1.id, router: routerB, }), routerA.pipeToRouter({ producerId: audioProducerA2.id, router: routerB, }), ]); const dump1 = await routerA.dump(); // There should be 3 Transports in routerA: // - WebRtcTransport for audioProducerA1 and audioProducerA2. // - PipeTransport between routerA and routerB. expect(dump1.transportIds.length).toBe(3); const dump2 = await routerB.dump(); // There should be 1 Transport in routerB: // - PipeTransport between routerA and routerB. expect(dump2.transportIds.length).toBe(1); }, 2000); test('router.pipeToRouter() called in two Routers passing one to each other as argument generates a single PipeTransport pair', async () => { const routerA = await ctx.worker1!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); const routerB = await ctx.worker2!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); const transportA = await routerA.createWebRtcTransport({ listenIps: ['127.0.0.1'], }); const transportB = await routerB.createWebRtcTransport({ listenIps: ['127.0.0.1'], }); const audioProducerA = await transportA.produce(ctx.audioProducerOptions); const audioProducerB = await transportB.produce(ctx.audioProducerOptions); const pipeTransportsA = new Map(); const pipeTransportsB = new Map(); routerA.observer.on('newtransport', transport => { if (transport.constructor.name !== 'PipeTransportImpl') { return; } pipeTransportsA.set(transport.id, transport); transport.observer.on('close', () => pipeTransportsA.delete(transport.id)); }); routerB.observer.on('newtransport', transport => { if (transport.constructor.name !== 'PipeTransportImpl') { return; } pipeTransportsB.set(transport.id, transport); transport.observer.on('close', () => pipeTransportsB.delete(transport.id)); }); await Promise.all([ routerA.pipeToRouter({ producerId: audioProducerA.id, router: routerB, }), routerB.pipeToRouter({ producerId: audioProducerB.id, router: routerA, }), ]); // There should be a single PipeTransport in each Router and they must be // connected. expect(pipeTransportsA.size).toBe(1); expect(pipeTransportsB.size).toBe(1); const pipeTransportA = Array.from(pipeTransportsA.values())[0]; const pipeTransportB = Array.from(pipeTransportsB.values())[0]; expect(pipeTransportA.tuple.localPort).toBe(pipeTransportB.tuple.remotePort); expect(pipeTransportB.tuple.localPort).toBe(pipeTransportA.tuple.remotePort); routerA.close(); expect(pipeTransportsA.size).toBe(0); expect(pipeTransportsB.size).toBe(0); }, 2000); test('router.pipeToRouter() with neither producerId nor dataProducerId fails', async () => { const router1bis = await ctx.worker1!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); await expect( ctx.router1!.pipeToRouter({ router: router1bis, }) ).rejects.toThrow(Error); }, 2000); test('router.pipeToRouter() with both producerId and dataProducerId fails', async () => { const router1bis = await ctx.worker1!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); await expect( ctx.router1!.pipeToRouter({ producerId: '1234', dataProducerId: '5678', router: router1bis, }) ).rejects.toThrow(Error); }, 2000); ================================================ FILE: node/src/test/test-PlainTransport.ts ================================================ import * as os from 'node:os'; import { pickPort } from 'pick-port'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents, PlainTransportEvents } from '../types'; import * as utils from '../utils'; const IS_WINDOWS = os.platform() === 'win32'; const USE_BUILD_IN_SCTP_STACK = false; type TestContext = { mediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; }; const ctx: TestContext = { mediaCodecs: utils.deepFreeze([ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, foo: 'bar', }, }, { kind: 'video', mimeType: 'video/VP8', clockRate: 90000, }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'level-asymmetry-allowed': 1, 'packetization-mode': 1, 'profile-level-id': '4d0032', foo: 'bar', }, rtcpFeedback: [], // Will be ignored. }, ]), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker({ useBuiltInSctpStack: USE_BUILD_IN_SCTP_STACK, }); ctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs }); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('router.createPlainTransport() succeeds', async () => { const plainTransport = await ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 }, }, }); await expect(ctx.router!.dump()).resolves.toMatchObject({ transportIds: [plainTransport.id], }); const onObserverNewTransport = jest.fn(); ctx.router!.observer.once('newtransport', onObserverNewTransport); // Create a separate transport here. const plainTransport2 = await ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1', announcedAddress: '9.9.9.1', portRange: { min: 2000, max: 3000 }, }, enableSctp: true, appData: { foo: 'bar' }, }); expect(onObserverNewTransport).toHaveBeenCalledTimes(1); expect(onObserverNewTransport).toHaveBeenCalledWith(plainTransport2); expect(typeof plainTransport2.id).toBe('string'); expect(plainTransport2.closed).toBe(false); expect(plainTransport2.type).toBe('plain'); expect(plainTransport2.appData).toEqual({ foo: 'bar' }); expect(typeof plainTransport2.tuple).toBe('object'); // @deprecated Use tuple.localAddress instead. expect(plainTransport2.tuple.localIp).toBe('9.9.9.1'); expect(plainTransport2.tuple.localAddress).toBe('9.9.9.1'); expect(typeof plainTransport2.tuple.localPort).toBe('number'); expect(plainTransport2.tuple.protocol).toBe('udp'); expect(plainTransport2.rtcpTuple).toBeUndefined(); expect(plainTransport2.sctpParameters).toMatchObject({ port: 5000, // NOTE: When using the built-in SCTP stack, `numSctpStreams` given to the // transport is ignored. OS: USE_BUILD_IN_SCTP_STACK ? 65535 : 1024, MIS: USE_BUILD_IN_SCTP_STACK ? 65535 : 1024, maxMessageSize: 262144, }); expect(plainTransport2.sctpState).toBe('new'); expect(plainTransport2.srtpParameters).toBeUndefined(); const dump1 = await plainTransport2.dump(); expect(dump1.id).toBe(plainTransport2.id); expect(dump1.producerIds).toEqual([]); expect(dump1.consumerIds).toEqual([]); expect(dump1.tuple).toEqual(plainTransport2.tuple); expect(dump1.rtcpTuple).toEqual(plainTransport2.rtcpTuple); expect(dump1.sctpParameters).toEqual(plainTransport2.sctpParameters); expect(dump1.sctpState).toBe('new'); expect(dump1.recvRtpHeaderExtensions).toBeDefined(); expect(typeof dump1.rtpListener).toBe('object'); plainTransport2.close(); expect(plainTransport2.closed).toBe(true); const anotherTransport = await ctx.router!.createPlainTransport({ listenIp: '127.0.0.1', }); expect(typeof anotherTransport).toBe('object'); const rtpPort = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const rtcpPort = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const transport2 = await ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1', port: rtpPort }, rtcpListenInfo: { protocol: 'udp', ip: '127.0.0.1', port: rtcpPort }, }); expect(typeof transport2.id).toBe('string'); expect(transport2.closed).toBe(false); expect(transport2.appData).toEqual({}); expect(typeof transport2.tuple).toBe('object'); // @deprecated Use tuple.localAddress instead. expect(transport2.tuple.localIp).toBe('127.0.0.1'); expect(transport2.tuple.localAddress).toBe('127.0.0.1'); expect(transport2.tuple.localPort).toBe(rtpPort); expect(transport2.tuple.protocol).toBe('udp'); expect(typeof transport2.rtcpTuple).toBe('object'); // @deprecated Use tuple.localAddress instead. expect(transport2.rtcpTuple?.localIp).toBe('127.0.0.1'); expect(transport2.rtcpTuple?.localAddress).toBe('127.0.0.1'); expect(transport2.rtcpTuple?.localPort).toBe(rtcpPort); expect(transport2.rtcpTuple?.protocol).toBe('udp'); expect(transport2.sctpParameters).toBeUndefined(); expect(transport2.sctpState).toBeUndefined(); const dump2 = await transport2.dump(); expect(dump2.id).toBe(transport2.id); expect(dump2.tuple).toEqual(transport2.tuple); expect(dump2.rtcpTuple).toEqual(transport2.rtcpTuple); expect(dump2.sctpState).toBeUndefined(); }, 2000); test('router.createPlainTransport() with wrong arguments rejects with TypeError', async () => { // @ts-expect-error --- Testing purposes. await expect(ctx.router!.createPlainTransport({})).rejects.toThrow(TypeError); await expect( ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 4000, max: 3000 }, }, }) ).rejects.toThrow(TypeError); await expect( ctx.router!.createPlainTransport({ listenIp: '123' }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.router!.createPlainTransport({ listenIp: ['127.0.0.1'] }) ).rejects.toThrow(TypeError); await expect( ctx.router!.createPlainTransport({ listenInfo: { protocol: 'tcp', ip: '127.0.0.1' }, }) ).rejects.toThrow(TypeError); await expect( ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1' }, // @ts-expect-error --- Testing purposes. appData: 'NOT-AN-OBJECT', }) ).rejects.toThrow(TypeError); }, 2000); test('router.createPlainTransport() with enableSrtp succeeds', async () => { // Use default cryptoSuite: 'AES_CM_128_HMAC_SHA1_80'. const plainTransport = await ctx.router!.createPlainTransport({ listenIp: '127.0.0.1', enableSrtp: true, }); expect(typeof plainTransport.id).toBe('string'); expect(typeof plainTransport.srtpParameters).toBe('object'); expect(plainTransport.srtpParameters?.cryptoSuite).toBe( 'AES_CM_128_HMAC_SHA1_80' ); expect(plainTransport.srtpParameters?.keyBase64.length).toBe(40); // Missing srtpParameters. await expect( plainTransport.connect({ ip: '127.0.0.2', port: 9999, }) ).rejects.toThrow(TypeError); // Invalid srtpParameters. await expect( plainTransport.connect({ ip: '127.0.0.2', port: 9999, // @ts-expect-error --- Testing purposes. srtpParameters: 1, }) ).rejects.toThrow(TypeError); // Missing srtpParameters.cryptoSuite. await expect( plainTransport.connect({ ip: '127.0.0.2', port: 9999, // @ts-expect-error --- Testing purposes. srtpParameters: { keyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv', }, }) ).rejects.toThrow(TypeError); // Missing srtpParameters.keyBase64. await expect( plainTransport.connect({ ip: '127.0.0.2', port: 9999, // @ts-expect-error --- Testing purposes. srtpParameters: { cryptoSuite: 'AES_CM_128_HMAC_SHA1_80', }, }) ).rejects.toThrow(TypeError); // Invalid srtpParameters.cryptoSuite. await expect( plainTransport.connect({ ip: '127.0.0.2', port: 9999, srtpParameters: { // @ts-expect-error --- Testing purposes. cryptoSuite: 'FOO', keyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv', }, }) ).rejects.toThrow(TypeError); // Invalid srtpParameters.cryptoSuite. await expect( plainTransport.connect({ ip: '127.0.0.2', port: 9999, srtpParameters: { // @ts-expect-error --- Testing purposes. cryptoSuite: 123, keyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv', }, }) ).rejects.toThrow(TypeError); // Invalid srtpParameters.keyBase64. await expect( plainTransport.connect({ ip: '127.0.0.2', port: 9999, srtpParameters: { cryptoSuite: 'AES_CM_128_HMAC_SHA1_80', // @ts-expect-error --- Testing purposes. keyBase64: [], }, }) ).rejects.toThrow(TypeError); // Valid srtpParameters. And let's update the crypto suite. await expect( plainTransport.connect({ ip: '127.0.0.2', port: 9999, srtpParameters: { cryptoSuite: 'AEAD_AES_256_GCM', keyBase64: 'YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=', }, }) ).resolves.toBeUndefined(); expect(plainTransport.srtpParameters?.cryptoSuite).toBe('AEAD_AES_256_GCM'); expect(plainTransport.srtpParameters?.keyBase64.length).toBe(60); }, 2000); test('router.createPlainTransport() with non bindable IP rejects with Error', async () => { await expect( ctx.router!.createPlainTransport({ listenIp: '8.8.8.8' }) ).rejects.toThrow(Error); }, 2000); if (!IS_WINDOWS) { test('two transports binding to the same IP:port with udpReusePort flag succeed', async () => { const multicastIp = '224.0.0.1'; const port = await pickPort({ type: 'udp', ip: multicastIp, reserveTimeout: 0, }); await expect( ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: multicastIp, port: port, // NOTE: ipv6Only flag will be ignored since ip is IPv4. flags: { udpReusePort: true, ipv6Only: true }, }, }) ).resolves.toBeDefined(); await expect( ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: multicastIp, port: port, flags: { udpReusePort: true }, }, }) ).resolves.toBeDefined(); }, 2000); test('two transports binding to the same IP:port without udpReusePort flag fail', async () => { const multicastIp = '224.0.0.1'; const port = await pickPort({ type: 'udp', ip: multicastIp, reserveTimeout: 0, }); await expect( ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: multicastIp, port: port, flags: { udpReusePort: false }, }, }) ).resolves.toBeDefined(); await expect( ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: multicastIp, port: port, flags: { udpReusePort: false }, }, }) ).rejects.toThrow(); }, 2000); } test('plainTransport.getStats() succeeds', async () => { const plainTransport = await ctx.router!.createPlainTransport({ listenIp: '127.0.0.1', }); const stats = await plainTransport.getStats(); expect(Array.isArray(stats)).toBe(true); expect(stats.length).toBe(1); expect(stats[0]!.type).toBe('plain-rtp-transport'); expect(stats[0]!.transportId).toBe(plainTransport.id); expect(typeof stats[0]!.timestamp).toBe('number'); expect(stats[0]!.bytesReceived).toBe(0); expect(stats[0]!.recvBitrate).toBe(0); expect(stats[0]!.bytesSent).toBe(0); expect(stats[0]!.sendBitrate).toBe(0); expect(stats[0]!.rtpBytesReceived).toBe(0); expect(stats[0]!.rtpRecvBitrate).toBe(0); expect(stats[0]!.rtpBytesSent).toBe(0); expect(stats[0]!.rtpSendBitrate).toBe(0); expect(stats[0]!.rtxBytesReceived).toBe(0); expect(stats[0]!.rtxRecvBitrate).toBe(0); expect(stats[0]!.rtxBytesSent).toBe(0); expect(stats[0]!.rtxSendBitrate).toBe(0); expect(stats[0]!.probationBytesSent).toBe(0); expect(stats[0]!.probationSendBitrate).toBe(0); expect(typeof stats[0]!.tuple).toBe('object'); // @deprecated Use tuple.localAddress instead. expect(stats[0]!.tuple.localIp).toBe('127.0.0.1'); expect(stats[0]!.tuple.localAddress).toBe('127.0.0.1'); expect(typeof stats[0]!.tuple.localPort).toBe('number'); expect(stats[0]!.tuple.protocol).toBe('udp'); expect(stats[0]!.rtcpTuple).toBeUndefined(); }, 2000); test('plainTransport.connect() succeeds', async () => { const plainTransport = await ctx.router!.createPlainTransport({ listenIp: '127.0.0.1', rtcpMux: false, }); await expect( plainTransport.connect({ ip: '1.2.3.4', port: 1234, rtcpPort: 1235 }) ).resolves.toBeUndefined(); // Must fail if connected. await expect( plainTransport.connect({ ip: '1.2.3.4', port: 1234, rtcpPort: 1235 }) ).rejects.toThrow(Error); expect(plainTransport.tuple.remoteIp).toBe('1.2.3.4'); expect(plainTransport.tuple.remotePort).toBe(1234); expect(plainTransport.tuple.protocol).toBe('udp'); expect(plainTransport.rtcpTuple?.remoteIp).toBe('1.2.3.4'); expect(plainTransport.rtcpTuple?.remotePort).toBe(1235); expect(plainTransport.rtcpTuple?.protocol).toBe('udp'); }, 2000); test('plainTransport.connect() with wrong arguments rejects with TypeError', async () => { const plainTransport = await ctx.router!.createPlainTransport({ listenIp: '127.0.0.1', rtcpMux: false, }); // No SRTP enabled so passing srtpParameters must fail. await expect( plainTransport.connect({ ip: '127.0.0.2', port: 9998, rtcpPort: 9999, srtpParameters: { cryptoSuite: 'AES_CM_128_HMAC_SHA1_80', keyBase64: 'ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv', }, }) ).rejects.toThrow(TypeError); await expect(plainTransport.connect({})).rejects.toThrow(TypeError); await expect(plainTransport.connect({ ip: '::::1234' })).rejects.toThrow( TypeError ); // Must fail because transport has rtcpMux: false so rtcpPort must be given // in connect(). await expect( plainTransport.connect({ ip: '127.0.0.1', port: 1234, // @ts-expect-error --- Testing purposes. __rtcpPort: 1235, }) ).rejects.toThrow(TypeError); await expect( plainTransport.connect({ ip: '127.0.0.1', // @ts-expect-error --- Testing purposes. __port: 'chicken', rtcpPort: 1235, }) ).rejects.toThrow(TypeError); }, 2000); test('PlainTransport methods reject if closed', async () => { const plainTransport = await ctx.router!.createPlainTransport({ listenIp: '127.0.0.1', }); const onObserverClose = jest.fn(); plainTransport.observer.once('close', onObserverClose); plainTransport.close(); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(plainTransport.closed).toBe(true); await expect(plainTransport.dump()).rejects.toThrow(Error); await expect(plainTransport.getStats()).rejects.toThrow(Error); await expect(plainTransport.connect({})).rejects.toThrow(Error); }, 2000); test('router.createPlainTransport() with fixed port succeeds', async () => { const port = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const plainTransport = await ctx.router!.createPlainTransport({ listenInfo: { protocol: 'udp', ip: '127.0.0.1', port }, }); expect(plainTransport.tuple.localPort).toEqual(port); }, 2000); test('PlainTransport emits "routerclose" if Router is closed', async () => { const plainTransport = await ctx.router!.createPlainTransport({ listenIp: '127.0.0.1', }); const onObserverClose = jest.fn(); plainTransport.observer.once('close', onObserverClose); const promise = enhancedOnce( plainTransport, 'routerclose' ); ctx.router!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(plainTransport.closed).toBe(true); }, 2000); test('PlainTransport emits "routerclose" if Worker is closed', async () => { const plainTransport = await ctx.router!.createPlainTransport({ listenIp: '127.0.0.1', }); const onObserverClose = jest.fn(); plainTransport.observer.once('close', onObserverClose); const promise = enhancedOnce( plainTransport, 'routerclose' ); ctx.worker!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(plainTransport.closed).toBe(true); }, 2000); ================================================ FILE: node/src/test/test-Producer.ts ================================================ import * as flatbuffers from 'flatbuffers'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents, ProducerEvents } from '../types'; import type { ProducerImpl } from '../Producer'; import { UnsupportedError } from '../errors'; import * as utils from '../utils'; import { Notification, Body as NotificationBody, Event, } from '../fbs/notification'; import * as FbsProducer from '../fbs/producer'; type TestContext = { mediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; audioProducerOptions: mediasoup.types.ProducerOptions; videoProducerOptions: mediasoup.types.ProducerOptions; worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; webRtcTransport1?: mediasoup.types.WebRtcTransport; webRtcTransport2?: mediasoup.types.WebRtcTransport; }; const ctx: TestContext = { mediaCodecs: utils.deepFreeze([ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, parameters: { foo: '111', }, }, { kind: 'video', mimeType: 'video/VP8', clockRate: 90000, }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'level-asymmetry-allowed': 1, 'packetization-mode': 1, 'profile-level-id': '4d0032', foo: 'bar', }, rtcpFeedback: [], // Will be ignored. }, ]), audioProducerOptions: utils.deepFreeze({ kind: 'audio', rtpParameters: { mid: 'AUDIO', codecs: [ { mimeType: 'audio/opus', payloadType: 0, clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, usedtx: 1, foo: 222.222, bar: '333', }, }, ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, }, { uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', id: 12, }, ], // Missing encodings on purpose. rtcp: { cname: 'audio-1', }, }, appData: { foo: 1, bar: '2' }, }), videoProducerOptions: utils.deepFreeze({ kind: 'video', rtpParameters: { mid: 'VIDEO', codecs: [ { mimeType: 'video/h264', payloadType: 112, clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '4d0032', }, rtcpFeedback: [ { type: 'nack' }, { type: 'nack', parameter: 'pli' }, { type: 'goog-remb' }, ], }, { mimeType: 'video/rtx', payloadType: 113, clockRate: 90000, parameters: { apt: 112 }, }, ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, }, { uri: 'urn:3gpp:video-orientation', id: 13, }, ], encodings: [ { ssrc: 22222222, rtx: { ssrc: 22222223 }, scalabilityMode: 'L1T3' }, { ssrc: 22222224, rtx: { ssrc: 22222225 } }, { ssrc: 22222226, rtx: { ssrc: 22222227 } }, { ssrc: 22222228, rtx: { ssrc: 22222229 } }, ], rtcp: { cname: 'video-1', }, }, appData: { foo: 1, bar: '2' }, }), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); ctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs }); ctx.webRtcTransport1 = await ctx.router.createWebRtcTransport({ listenIps: ['127.0.0.1'], }); ctx.webRtcTransport2 = await ctx.router.createWebRtcTransport({ listenIps: ['127.0.0.1'], }); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('webRtcTransport1.produce() succeeds', async () => { const onObserverNewProducer = jest.fn(); ctx.webRtcTransport1!.observer.once('newproducer', onObserverNewProducer); const audioProducer = await ctx.webRtcTransport1!.produce( ctx.audioProducerOptions ); expect(onObserverNewProducer).toHaveBeenCalledTimes(1); expect(onObserverNewProducer).toHaveBeenCalledWith(audioProducer); expect(typeof audioProducer.id).toBe('string'); expect(audioProducer.closed).toBe(false); expect(audioProducer.kind).toBe('audio'); expect(typeof audioProducer.rtpParameters).toBe('object'); expect(audioProducer.type).toBe('simple'); // Private API. expect(typeof audioProducer.consumableRtpParameters).toBe('object'); expect(audioProducer.paused).toBe(false); expect(audioProducer.score).toEqual([]); expect(audioProducer.appData).toEqual({ foo: 1, bar: '2' }); await expect(ctx.router!.dump()).resolves.toMatchObject({ mapProducerIdConsumerIds: [{ key: audioProducer.id, values: [] }], mapConsumerIdProducerId: [], }); await expect(ctx.webRtcTransport1!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport1!.id, producerIds: [audioProducer.id], consumerIds: [], }); }, 2000); test('webRtcTransport2.produce() succeeds', async () => { const onObserverNewProducer = jest.fn(); ctx.webRtcTransport2!.observer.once('newproducer', onObserverNewProducer); const videoProducer = await ctx.webRtcTransport2!.produce( ctx.videoProducerOptions ); expect(onObserverNewProducer).toHaveBeenCalledTimes(1); expect(onObserverNewProducer).toHaveBeenCalledWith(videoProducer); expect(typeof videoProducer.id).toBe('string'); expect(videoProducer.closed).toBe(false); expect(videoProducer.kind).toBe('video'); expect(typeof videoProducer.rtpParameters).toBe('object'); expect(videoProducer.type).toBe('simulcast'); // Private API. expect(typeof videoProducer.consumableRtpParameters).toBe('object'); expect(videoProducer.paused).toBe(false); expect(videoProducer.score).toEqual([]); expect(videoProducer.appData).toEqual({ foo: 1, bar: '2' }); const dump = await ctx.router!.dump(); expect(dump.mapProducerIdConsumerIds).toEqual( expect.arrayContaining([{ key: videoProducer.id, values: [] }]) ); expect(dump.mapConsumerIdProducerId.length).toBe(0); await expect(ctx.webRtcTransport2!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport2!.id, producerIds: [videoProducer.id], consumerIds: [], }); }, 2000); test('webRtcTransport1.produce() without header extensions and rtcp succeeds', async () => { const onObserverNewProducer = jest.fn(); ctx.webRtcTransport1!.observer.once('newproducer', onObserverNewProducer); const audioProducer = await ctx.webRtcTransport1!.produce({ kind: 'audio', rtpParameters: { mid: 'AUDIO2', codecs: [ { mimeType: 'audio/opus', payloadType: 0, clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, usedtx: 1, foo: 222.222, bar: '333', }, }, ], }, appData: { foo: 1, bar: '2' }, }); expect(onObserverNewProducer).toHaveBeenCalledTimes(1); expect(onObserverNewProducer).toHaveBeenCalledWith(audioProducer!); expect(typeof audioProducer!.id).toBe('string'); expect(audioProducer!.closed).toBe(false); expect(audioProducer!.kind).toBe('audio'); expect(typeof audioProducer!.rtpParameters).toBe('object'); expect(audioProducer!.type).toBe('simple'); // Private API. expect(typeof audioProducer!.consumableRtpParameters).toBe('object'); expect(audioProducer!.paused).toBe(false); expect(audioProducer!.score).toEqual([]); expect(audioProducer!.appData).toEqual({ foo: 1, bar: '2' }); audioProducer.close(); }, 2000); test('webRtcTransport1.produce() with wrong arguments rejects with TypeError', async () => { await expect( ctx.webRtcTransport1!.produce({ // @ts-expect-error --- Testing purposes. kind: 'chicken', // @ts-expect-error --- Testing purposes. rtpParameters: {}, }) ).rejects.toThrow(TypeError); await expect( ctx.webRtcTransport1!.produce({ kind: 'audio', // @ts-expect-error --- Testing purposes. rtpParameters: {}, }) ).rejects.toThrow(TypeError); // Invalid ssrc. await expect( ctx.webRtcTransport1!.produce({ kind: 'audio', rtpParameters: { codecs: [], headerExtensions: [], // @ts-expect-error --- Testing purposes. encodings: [{ ssrc: '1111' }], rtcp: { cname: 'qwerty' }, }, }) ).rejects.toThrow(TypeError); // Missing or empty rtpParameters.encodings. await expect( ctx.webRtcTransport1!.produce({ kind: 'video', rtpParameters: { codecs: [ { mimeType: 'video/h264', payloadType: 112, clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '4d0032', }, }, { mimeType: 'video/rtx', payloadType: 113, clockRate: 90000, parameters: { apt: 112 }, }, ], headerExtensions: [], encodings: [], rtcp: { cname: 'qwerty' }, }, }) ).rejects.toThrow(TypeError); // Wrong apt in RTX codec. await expect( ctx.webRtcTransport1!.produce({ kind: 'audio', rtpParameters: { codecs: [ { mimeType: 'video/h264', payloadType: 112, clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '4d0032', }, }, { mimeType: 'video/rtx', payloadType: 113, clockRate: 90000, parameters: { apt: 111 }, }, ], headerExtensions: [], encodings: [{ ssrc: 6666, rtx: { ssrc: 6667 } }], rtcp: { cname: 'video-1', }, }, }) ).rejects.toThrow(TypeError); }, 2000); test('webRtcTransport1.produce() with unsupported codecs rejects with UnsupportedError', async () => { await expect( ctx.webRtcTransport1!.produce({ kind: 'audio', rtpParameters: { codecs: [ { mimeType: 'audio/ISAC', payloadType: 108, clockRate: 32000, }, ], headerExtensions: [], encodings: [{ ssrc: 1111 }], rtcp: { cname: 'audio' }, }, }) ).rejects.toThrow(UnsupportedError); // Invalid H264 profile-level-id. await expect( ctx.webRtcTransport1!.produce({ kind: 'video', rtpParameters: { codecs: [ { mimeType: 'video/h264', payloadType: 112, clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': 'CHICKEN', }, }, { mimeType: 'video/rtx', payloadType: 113, clockRate: 90000, parameters: { apt: 112 }, }, ], headerExtensions: [], encodings: [{ ssrc: 6666, rtx: { ssrc: 6667 } }], }, }) ).rejects.toThrow(UnsupportedError); }, 2000); test('transport.produce() with already used MID or SSRC rejects with Error', async () => { const audioProducerOptions: mediasoup.types.ProducerOptions = { kind: 'audio', rtpParameters: { mid: 'AUDIO', codecs: [ { mimeType: 'audio/opus', payloadType: 0, clockRate: 48000, channels: 2, }, ], encodings: [{ ssrc: 33333333 }], }, }; const videoProducerOptions: mediasoup.types.ProducerOptions = { kind: 'video', rtpParameters: { mid: 'VIDEO2', codecs: [ { mimeType: 'video/h264', payloadType: 112, clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '4d0032', }, }, ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, }, ], encodings: [{ ssrc: 22222222 }], rtcp: { cname: 'video-1', }, }, }; await ctx.webRtcTransport1!.produce(audioProducerOptions); await expect( ctx.webRtcTransport1!.produce(audioProducerOptions) ).rejects.toThrow(Error); await ctx.webRtcTransport2!.produce(videoProducerOptions); await expect( ctx.webRtcTransport2!.produce(videoProducerOptions) ).rejects.toThrow(Error); }, 2000); test('transport.produce() with no MID and with single encoding without RID or SSRC rejects with Error', async () => { await expect( ctx.webRtcTransport1!.produce({ kind: 'audio', rtpParameters: { codecs: [ { mimeType: 'audio/opus', payloadType: 111, clockRate: 48000, channels: 2, }, ], encodings: [{}], }, }) ).rejects.toThrow(Error); }, 2000); test('producer.dump() succeeds', async () => { const audioProducer = await ctx.webRtcTransport1!.produce( ctx.audioProducerOptions ); const dump1 = await audioProducer.dump(); expect(dump1.id).toBe(audioProducer.id); expect(dump1.kind).toBe(audioProducer.kind); expect(typeof dump1.rtpParameters).toBe('object'); expect(Array.isArray(dump1.rtpParameters.codecs)).toBe(true); expect(dump1.rtpParameters.codecs.length).toBe(1); expect(dump1.rtpParameters.codecs[0]!.mimeType).toBe('audio/opus'); expect(dump1.rtpParameters.codecs[0]!.payloadType).toBe(0); expect(dump1.rtpParameters.codecs[0]!.clockRate).toBe(48000); expect(dump1.rtpParameters.codecs[0]!.channels).toBe(2); expect(dump1.rtpParameters.codecs[0]!.parameters).toEqual({ useinbandfec: 1, usedtx: 1, foo: 222.222, bar: '333', }); expect(dump1.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([]); expect(Array.isArray(dump1.rtpParameters.headerExtensions)).toBe(true); expect(dump1.rtpParameters.headerExtensions!.length).toBe(2); expect(dump1.rtpParameters.headerExtensions).toEqual([ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, parameters: {}, encrypt: false, }, { uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', id: 12, parameters: {}, encrypt: false, }, ]); expect(Array.isArray(dump1.rtpParameters.encodings)).toBe(true); expect(dump1.rtpParameters.encodings!.length).toBe(1); expect(dump1.rtpParameters.encodings![0]).toEqual( expect.objectContaining({ codecPayloadType: 0, }) ); expect(dump1.type).toBe('simple'); const videoProducer = await ctx.webRtcTransport2!.produce( ctx.videoProducerOptions ); const dump2 = await videoProducer.dump(); expect(dump2.id).toBe(videoProducer.id); expect(dump2.kind).toBe(videoProducer.kind); expect(typeof dump2.rtpParameters).toBe('object'); expect(Array.isArray(dump2.rtpParameters.codecs)).toBe(true); expect(dump2.rtpParameters.codecs.length).toBe(2); expect(dump2.rtpParameters.codecs[0]!.mimeType).toBe('video/H264'); expect(dump2.rtpParameters.codecs[0]!.payloadType).toBe(112); expect(dump2.rtpParameters.codecs[0]!.clockRate).toBe(90000); expect(dump2.rtpParameters.codecs[0]!.channels).toBeUndefined(); expect(dump2.rtpParameters.codecs[0]!.parameters).toEqual({ 'packetization-mode': 1, 'profile-level-id': '4d0032', }); expect(dump2.rtpParameters.codecs[0]!.rtcpFeedback).toEqual([ { type: 'nack' }, { type: 'nack', parameter: 'pli' }, { type: 'goog-remb' }, ]); expect(dump2.rtpParameters.codecs[1]!.mimeType).toBe('video/rtx'); expect(dump2.rtpParameters.codecs[1]!.payloadType).toBe(113); expect(dump2.rtpParameters.codecs[1]!.clockRate).toBe(90000); expect(dump2.rtpParameters.codecs[1]!.channels).toBeUndefined(); expect(dump2.rtpParameters.codecs[1]!.parameters).toEqual({ apt: 112 }); expect(dump2.rtpParameters.codecs[1]!.rtcpFeedback).toEqual([]); expect(Array.isArray(dump2.rtpParameters.headerExtensions)).toBe(true); expect(dump2.rtpParameters.headerExtensions!.length).toBe(2); expect(dump2.rtpParameters.headerExtensions).toEqual([ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, parameters: {}, encrypt: false, }, { uri: 'urn:3gpp:video-orientation', id: 13, parameters: {}, encrypt: false, }, ]); expect(Array.isArray(dump2.rtpParameters.encodings)).toBe(true); expect(dump2.rtpParameters.encodings!.length).toBe(4); expect(dump2.rtpParameters.encodings).toMatchObject([ { codecPayloadType: 112, ssrc: 22222222, rtx: { ssrc: 22222223 }, scalabilityMode: 'L1T3', }, { codecPayloadType: 112, ssrc: 22222224, rtx: { ssrc: 22222225 } }, { codecPayloadType: 112, ssrc: 22222226, rtx: { ssrc: 22222227 } }, { codecPayloadType: 112, ssrc: 22222228, rtx: { ssrc: 22222229 } }, ]); expect(dump2.type).toBe('simulcast'); }, 2000); test('producer.getStats() succeeds', async () => { const audioProducer = await ctx.webRtcTransport1!.produce( ctx.audioProducerOptions ); const videoProducer = await ctx.webRtcTransport2!.produce( ctx.videoProducerOptions ); await expect(audioProducer.getStats()).resolves.toEqual([]); await expect(videoProducer.getStats()).resolves.toEqual([]); }, 2000); test('producer.pause() and resume() succeed', async () => { const audioProducer = await ctx.webRtcTransport1!.produce( ctx.audioProducerOptions ); const onObserverPause = jest.fn(); const onObserverResume = jest.fn(); audioProducer.observer.on('pause', onObserverPause); audioProducer.observer.on('resume', onObserverResume); await audioProducer.pause(); expect(audioProducer.paused).toBe(true); await expect(audioProducer.dump()).resolves.toMatchObject({ paused: true }); await audioProducer.resume(); expect(audioProducer.paused).toBe(false); await expect(audioProducer.dump()).resolves.toMatchObject({ paused: false }); // Even if we don't await for pause()/resume() completion, the observer must // fire 'pause' and 'resume' events if state was the opposite. void audioProducer.pause(); void audioProducer.resume(); void audioProducer.pause(); void audioProducer.pause(); void audioProducer.pause(); await audioProducer.resume(); expect(onObserverPause).toHaveBeenCalledTimes(3); expect(onObserverResume).toHaveBeenCalledTimes(3); }, 2000); test('producer.pause() and resume() emit events', async () => { const audioProducer = await ctx.webRtcTransport1!.produce( ctx.audioProducerOptions ); const promises = []; const events: string[] = []; audioProducer.observer.once('resume', () => { events.push('resume'); }); audioProducer.observer.once('pause', () => { events.push('pause'); }); promises.push(audioProducer.pause()); promises.push(audioProducer.resume()); await Promise.all(promises); expect(events).toEqual(['pause', 'resume']); expect(audioProducer.paused).toBe(false); }, 2000); test('producer.enableTraceEvent() succeed', async () => { const audioProducer = await ctx.webRtcTransport1!.produce( ctx.audioProducerOptions ); await audioProducer.enableTraceEvent(['rtp', 'pli']); const dump1 = await audioProducer.dump(); expect(dump1.traceEventTypes).toEqual(expect.arrayContaining(['rtp', 'pli'])); await audioProducer.enableTraceEvent(); const dump2 = await audioProducer.dump(); expect(dump2.traceEventTypes).toEqual(expect.arrayContaining([])); // @ts-expect-error --- Testing purposes. await audioProducer.enableTraceEvent(['nack', 'FOO', 'fir']); const dump3 = await audioProducer.dump(); expect(dump3.traceEventTypes).toEqual( expect.arrayContaining(['nack', 'fir']) ); await audioProducer.enableTraceEvent(); const dump4 = await audioProducer.dump(); expect(dump4.traceEventTypes).toEqual(expect.arrayContaining([])); }, 2000); test('producer.enableTraceEvent() with wrong arguments rejects with TypeError', async () => { const audioProducer = await ctx.webRtcTransport1!.produce( ctx.audioProducerOptions ); // @ts-expect-error --- Testing purposes. await expect(audioProducer.enableTraceEvent(123)).rejects.toThrow(TypeError); // @ts-expect-error --- Testing purposes. await expect(audioProducer.enableTraceEvent('rtp')).rejects.toThrow( TypeError ); await expect( // @ts-expect-error --- Testing purposes. audioProducer.enableTraceEvent(['fir', 123.123]) ).rejects.toThrow(TypeError); }, 2000); test('Producer emits "score"', async () => { const videoProducer = await ctx.webRtcTransport2!.produce( ctx.videoProducerOptions ); // API not exposed in the interface. const channel = (videoProducer as ProducerImpl).channelForTesting; const onScore = jest.fn(); videoProducer.on('score', onScore); // Simulate a 'score' notification coming through the channel. const builder = new flatbuffers.Builder(); const producerScoreNotification = new FbsProducer.ScoreNotificationT([ new FbsProducer.ScoreT( /* encodingIdx */ 0, /* ssrc */ 11, /* rid */ undefined, /* score */ 10 ), new FbsProducer.ScoreT( /* encodingIdx */ 1, /* ssrc */ 22, /* rid */ undefined, /* score */ 9 ), ]); const notificationOffset = Notification.createNotification( builder, builder.createString(videoProducer.id), Event.PRODUCER_SCORE, NotificationBody.Producer_ScoreNotification, producerScoreNotification.pack(builder) ); builder.finish(notificationOffset); const notification = Notification.getRootAsNotification( new flatbuffers.ByteBuffer(builder.asUint8Array()) ); channel.emit(videoProducer.id, Event.PRODUCER_SCORE, notification); channel.emit(videoProducer.id, Event.PRODUCER_SCORE, notification); channel.emit(videoProducer.id, Event.PRODUCER_SCORE, notification); expect(onScore).toHaveBeenCalledTimes(3); expect(videoProducer.score).toEqual([ { ssrc: 11, rid: undefined, score: 10, encodingIdx: 0 }, { ssrc: 22, rid: undefined, score: 9, encodingIdx: 1 }, ]); }, 2000); test('producer.close() succeeds', async () => { const audioProducer = await ctx.webRtcTransport1!.produce( ctx.audioProducerOptions ); const onObserverClose = jest.fn(); audioProducer.observer.once('close', onObserverClose); audioProducer.close(); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(audioProducer.closed).toBe(true); await expect(ctx.router!.dump()).resolves.toMatchObject({ mapProducerIdConsumerIds: [], mapConsumerIdProducerId: [], }); await expect(ctx.webRtcTransport1!.dump()).resolves.toMatchObject({ id: ctx.webRtcTransport1!.id, producerIds: [], consumerIds: [], }); }, 2000); test('Producer methods reject if closed', async () => { const audioProducer = await ctx.webRtcTransport1!.produce( ctx.audioProducerOptions ); audioProducer.close(); await expect(audioProducer.dump()).rejects.toThrow(Error); await expect(audioProducer.getStats()).rejects.toThrow(Error); await expect(audioProducer.pause()).rejects.toThrow(Error); await expect(audioProducer.resume()).rejects.toThrow(Error); }, 2000); test('Producer emits "transportclose" if Transport is closed', async () => { const videoProducer = await ctx.webRtcTransport2!.produce( ctx.videoProducerOptions ); const onObserverClose = jest.fn(); videoProducer.observer.once('close', onObserverClose); const promise = enhancedOnce(videoProducer, 'transportclose'); ctx.webRtcTransport2!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(videoProducer.closed).toBe(true); }, 2000); ================================================ FILE: node/src/test/test-Router.ts ================================================ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerImpl } from '../Worker'; import type { WorkerEvents, RouterEvents } from '../types'; import { InvalidStateError, UnsupportedError } from '../errors'; import * as utils from '../utils'; type TestContext = { mediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; unsupportedMediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; worker?: mediasoup.types.Worker; }; const ctx: TestContext = { mediaCodecs: utils.deepFreeze([ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, foo: 'bar', }, }, { kind: 'video', mimeType: 'video/VP8', clockRate: 90000, }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'level-asymmetry-allowed': 1, 'packetization-mode': 1, 'profile-level-id': '4d0032', }, rtcpFeedback: [], // Will be ignored. }, ]), unsupportedMediaCodecs: utils.deepFreeze< mediasoup.types.RouterRtpCodecCapability[] >([ { kind: 'audio', mimeType: 'audio/x-aiff', clockRate: 8000, channels: 1, }, { kind: 'video', mimeType: 'video/3gpp', clockRate: 90000, }, ]), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('worker.createRouter() succeeds', async () => { const onObserverNewRouter = jest.fn(); ctx.worker!.observer.once('newrouter', onObserverNewRouter); const router = await ctx.worker!.createRouter<{ foo: number; bar?: string }>({ mediaCodecs: ctx.mediaCodecs, appData: { foo: 123 }, }); expect(onObserverNewRouter).toHaveBeenCalledTimes(1); expect(onObserverNewRouter).toHaveBeenCalledWith(router); expect(typeof router.id).toBe('string'); expect(router.closed).toBe(false); expect(typeof router.rtpCapabilities).toBe('object'); expect(Array.isArray(router.rtpCapabilities.codecs)).toBe(true); // 3 codecs + 2 RTX codecs. expect(router.rtpCapabilities.codecs?.length).toBe(5); expect(Array.isArray(router.rtpCapabilities.headerExtensions)).toBe(true); expect(router.appData).toEqual({ foo: 123 }); expect(() => (router.appData = { foo: 222, bar: 'BBB' })).not.toThrow(); await expect(ctx.worker!.dump()).resolves.toMatchObject({ pid: ctx.worker!.pid, webRtcServerIds: [], routerIds: [router.id], channelMessageHandlers: { channelRequestHandlers: [router.id], channelNotificationHandlers: [], }, }); await expect(router.dump()).resolves.toMatchObject({ id: router.id, transportIds: [], rtpObserverIds: [], mapProducerIdConsumerIds: {}, mapConsumerIdProducerId: {}, mapProducerIdObserverIds: {}, mapDataProducerIdDataConsumerIds: {}, mapDataConsumerIdDataProducerId: {}, }); // API not exposed in the interface. expect((ctx.worker! as WorkerImpl).routersForTesting.size).toBe(1); ctx.worker!.close(); expect(router.closed).toBe(true); // API not exposed in the interface. expect((ctx.worker! as WorkerImpl).routersForTesting.size).toBe(0); }, 2000); test('worker.createRouter() with invalid codecs rejects with UnsupportedError', async () => { await expect( ctx.worker!.createRouter({ mediaCodecs: ctx.unsupportedMediaCodecs }) ).rejects.toThrow(UnsupportedError); }, 2000); test('worker.createRouter() with wrong arguments rejects with TypeError', async () => { // @ts-expect-error --- Testing purposes. await expect(ctx.worker!.createRouter({ mediaCodecs: {} })).rejects.toThrow( TypeError ); await expect( // @ts-expect-error --- Testing purposes. ctx.worker!.createRouter({ appData: 'NOT-AN-OBJECT' }) ).rejects.toThrow(TypeError); }, 2000); test('worker.createRouter() rejects with InvalidStateError if Worker is closed', async () => { ctx.worker!.close(); await expect( ctx.worker!.createRouter({ mediaCodecs: ctx.mediaCodecs }) ).rejects.toThrow(InvalidStateError); }, 2000); test('router.updateMediaCodecs() succeeds', async () => { const router = await ctx.worker!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); expect(typeof router.rtpCapabilities).toBe('object'); expect(Array.isArray(router.rtpCapabilities.codecs)).toBe(true); // 3 codecs + 2 RTX codecs. expect(router.rtpCapabilities.codecs?.length).toBe(5); expect(Array.isArray(router.rtpCapabilities.headerExtensions)).toBe(true); router.updateMediaCodecs([]); expect(typeof router.rtpCapabilities).toBe('object'); expect(Array.isArray(router.rtpCapabilities.codecs)).toBe(true); expect(router.rtpCapabilities.codecs?.length).toBe(0); expect(Array.isArray(router.rtpCapabilities.headerExtensions)).toBe(true); }, 2000); test('router.close() succeeds', async () => { const router = await ctx.worker!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); const onObserverClose = jest.fn(); router.observer.once('close', onObserverClose); router.close(); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(router.closed).toBe(true); }, 2000); test('Router emits "workerclose" if Worker is closed', async () => { const router = await ctx.worker!.createRouter({ mediaCodecs: ctx.mediaCodecs, }); const onObserverClose = jest.fn(); router.observer.once('close', onObserverClose); const promise = enhancedOnce(router, 'workerclose'); ctx.worker!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(router.closed).toBe(true); }, 2000); ================================================ FILE: node/src/test/test-WebRtcServer.ts ================================================ import { pickPort } from 'pick-port'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerImpl } from '../Worker'; import type { WorkerEvents, WebRtcServerEvents } from '../types'; import type { WebRtcServerImpl } from '../WebRtcServer'; import type { RouterImpl } from '../Router'; import { InvalidStateError } from '../errors'; type TestContext = { worker?: mediasoup.types.Worker; }; const ctx: TestContext = {}; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('worker.createWebRtcServer() succeeds', async () => { const onObserverNewWebRtcServer = jest.fn(); ctx.worker!.observer.once('newwebrtcserver', onObserverNewWebRtcServer); const port1 = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const port2 = await pickPort({ type: 'tcp', ip: '127.0.0.1', reserveTimeout: 0, }); const webRtcServer = await ctx.worker!.createWebRtcServer<{ foo?: number }>({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', port: port1, }, { protocol: 'tcp', ip: '127.0.0.1', announcedAddress: 'foo.bar.org', port: port2, }, ], appData: { foo: 123 }, }); expect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1); expect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer); expect(typeof webRtcServer.id).toBe('string'); expect(webRtcServer.closed).toBe(false); expect(webRtcServer.appData).toEqual({ foo: 123 }); await expect(ctx.worker!.dump()).resolves.toMatchObject({ pid: ctx.worker!.pid, webRtcServerIds: [webRtcServer.id], routerIds: [], channelMessageHandlers: { channelRequestHandlers: [webRtcServer.id], channelNotificationHandlers: [], }, }); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, udpSockets: [{ ip: '127.0.0.1', port: port1 }], tcpServers: [{ ip: '127.0.0.1', port: port2 }], webRtcTransportIds: [], localIceUsernameFragments: [], tupleHashes: [], }); // API not exposed in the interface. expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(1); ctx.worker!.close(); expect(webRtcServer.closed).toBe(true); // API not exposed in the interface. expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(0); }, 2000); test('worker.createWebRtcServer() with portRange succeeds', async () => { const onObserverNewWebRtcServer = jest.fn(); ctx.worker!.observer.once('newwebrtcserver', onObserverNewWebRtcServer); const port1 = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const port2 = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const webRtcServer = await ctx.worker!.createWebRtcServer({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: port1, max: port1 }, }, { protocol: 'tcp', ip: '127.0.0.1', announcedAddress: '1.2.3.4', portRange: { min: port2, max: port2 }, }, ], appData: { foo: 123 }, }); expect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1); expect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer); expect(typeof webRtcServer.id).toBe('string'); expect(webRtcServer.closed).toBe(false); expect(webRtcServer.appData).toEqual({ foo: 123 }); await expect(ctx.worker!.dump()).resolves.toMatchObject({ pid: ctx.worker!.pid, webRtcServerIds: [webRtcServer.id], routerIds: [], channelMessageHandlers: { channelRequestHandlers: [webRtcServer.id], channelNotificationHandlers: [], }, }); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, udpSockets: [{ ip: '127.0.0.1', port: port1 }], tcpServers: [{ ip: '127.0.0.1', port: port2 }], webRtcTransportIds: [], localIceUsernameFragments: [], tupleHashes: [], }); // API not exposed in the interface. expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(1); ctx.worker!.close(); expect(webRtcServer.closed).toBe(true); // API not exposed in the interface. expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(0); }, 2000); test('worker.createWebRtcServer() without specifying port/portRange succeeds', async () => { const onObserverNewWebRtcServer = jest.fn(); ctx.worker!.observer.once('newwebrtcserver', onObserverNewWebRtcServer); const webRtcServer = await ctx.worker!.createWebRtcServer({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', }, { protocol: 'tcp', ip: '127.0.0.1', announcedAddress: '1.2.3.4', }, ], appData: { foo: 123 }, }); expect(onObserverNewWebRtcServer).toHaveBeenCalledTimes(1); expect(onObserverNewWebRtcServer).toHaveBeenCalledWith(webRtcServer); expect(typeof webRtcServer.id).toBe('string'); expect(webRtcServer.closed).toBe(false); expect(webRtcServer.appData).toEqual({ foo: 123 }); await expect(ctx.worker!.dump()).resolves.toMatchObject({ pid: ctx.worker!.pid, webRtcServerIds: [webRtcServer.id], routerIds: [], channelMessageHandlers: { channelRequestHandlers: [webRtcServer.id], channelNotificationHandlers: [], }, }); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, udpSockets: [{ ip: '127.0.0.1', port: expect.any(Number) }], tcpServers: [{ ip: '127.0.0.1', port: expect.any(Number) }], webRtcTransportIds: [], localIceUsernameFragments: [], tupleHashes: [], }); // API not exposed in the interface. expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(1); ctx.worker!.close(); expect(webRtcServer.closed).toBe(true); // API not exposed in the interface. expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(0); }, 2000); test('worker.createWebRtcServer() with wrong arguments rejects with TypeError', async () => { // @ts-expect-error --- Testing purposes. await expect(ctx.worker!.createWebRtcServer({})).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.worker!.createWebRtcServer({ listenInfos: 'NOT-AN-ARRAY' }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.worker!.createWebRtcServer({ listenInfos: ['NOT-AN-OBJECT'] }) ).rejects.toThrow(Error); // Empty listenInfos so should fail. await expect( ctx.worker!.createWebRtcServer({ listenInfos: [] }) ).rejects.toThrow(TypeError); }, 2000); test('worker.createWebRtcServer() with unavailable listenInfos rejects with Error', async () => { const worker2 = await mediasoup.createWorker(); const port1 = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const port2 = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); // Using an unavailable listen IP. await expect( ctx.worker!.createWebRtcServer({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', port: port1, }, { protocol: 'udp', ip: '1.2.3.4', port: port2, }, ], }) ).rejects.toThrow(Error); // Using the same UDP port in two listenInfos. await expect( ctx.worker!.createWebRtcServer({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', port: port1, }, { protocol: 'udp', ip: '127.0.0.1', announcedAddress: '1.2.3.4', port: port1, }, ], }) ).rejects.toThrow(Error); await ctx.worker!.createWebRtcServer({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', port: port1, }, ], }); // Using the same UDP port in a second Worker. await expect( worker2.createWebRtcServer({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', port: port1, }, ], }) ).rejects.toThrow(Error); worker2.close(); }, 2000); test('worker.createWebRtcServer() rejects with InvalidStateError if Worker is closed', async () => { ctx.worker!.close(); const port = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); await expect( ctx.worker!.createWebRtcServer({ listenInfos: [{ protocol: 'udp', ip: '127.0.0.1', port }], }) ).rejects.toThrow(InvalidStateError); }, 2000); test('webRtcServer.close() succeeds', async () => { const port = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const webRtcServer = await ctx.worker!.createWebRtcServer({ listenInfos: [{ protocol: 'udp', ip: '127.0.0.1', port }], }); const onObserverClose = jest.fn(); webRtcServer.observer.once('close', onObserverClose); webRtcServer.close(); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(webRtcServer.closed).toBe(true); }, 2000); test('WebRtcServer emits "workerclose" if Worker is closed', async () => { const port = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const webRtcServer = await ctx.worker!.createWebRtcServer({ listenInfos: [{ protocol: 'tcp', ip: '127.0.0.1', port }], }); const onObserverClose = jest.fn(); webRtcServer.observer.once('close', onObserverClose); const promise = enhancedOnce(webRtcServer, 'workerclose'); ctx.worker!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(webRtcServer.closed).toBe(true); }, 2000); test('router.createWebRtcTransport() with webRtcServer succeeds and transport is closed', async () => { const port1 = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const port2 = await pickPort({ type: 'tcp', ip: '127.0.0.1', reserveTimeout: 0, }); const webRtcServer = await ctx.worker!.createWebRtcServer({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', port: port1 }, { protocol: 'tcp', ip: '127.0.0.1', announcedAddress: 'media1.foo.org', exposeInternalIp: true, port: port2, }, ], }); const onObserverWebRtcTransportHandled = jest.fn(); const onObserverWebRtcTransportUnhandled = jest.fn(); webRtcServer.observer.once( 'webrtctransporthandled', onObserverWebRtcTransportHandled ); webRtcServer.observer.once( 'webrtctransportunhandled', onObserverWebRtcTransportUnhandled ); const router = await ctx.worker!.createRouter(); const onObserverNewTransport = jest.fn(); router.observer.once('newtransport', onObserverNewTransport); const transport = await router.createWebRtcTransport({ webRtcServer, // Let's disable UDP so resulting ICE candidates should only contain TCP. enableUdp: false, appData: { foo: 'bar' }, }); await expect(router.dump()).resolves.toMatchObject({ transportIds: [transport.id], }); expect(onObserverWebRtcTransportHandled).toHaveBeenCalledTimes(1); expect(onObserverWebRtcTransportHandled).toHaveBeenCalledWith(transport); expect(onObserverNewTransport).toHaveBeenCalledTimes(1); expect(onObserverNewTransport).toHaveBeenCalledWith(transport); expect(typeof transport.id).toBe('string'); expect(transport.closed).toBe(false); expect(transport.appData).toEqual({ foo: 'bar' }); const iceCandidates = transport.iceCandidates; expect(iceCandidates.length).toBe(2); expect(iceCandidates[0]!.address).toBe('media1.foo.org'); expect(iceCandidates[0]!.ip).toBe('media1.foo.org'); expect(iceCandidates[0]!.port).toBe(port2); expect(iceCandidates[0]!.protocol).toBe('tcp'); expect(iceCandidates[0]!.type).toBe('host'); expect(iceCandidates[0]!.tcpType).toBe('passive'); expect(iceCandidates[1]!.address).toBe('127.0.0.1'); expect(iceCandidates[1]!.ip).toBe('127.0.0.1'); expect(iceCandidates[1]!.port).toBe(port2); expect(iceCandidates[1]!.protocol).toBe('tcp'); expect(iceCandidates[1]!.type).toBe('host'); expect(iceCandidates[1]!.tcpType).toBe('passive'); expect(iceCandidates[0]!.priority).toBeGreaterThan( iceCandidates[1]!.priority ); expect(transport.iceState).toBe('new'); expect(transport.iceSelectedTuple).toBeUndefined(); // API not exposed in the interface. expect( (webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size ).toBe(1); // API not exposed in the interface. expect((router as RouterImpl).transportsForTesting.size).toBe(1); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, udpSockets: [{ ip: '127.0.0.1', port: port1 }], tcpServers: [{ ip: '127.0.0.1', port: port2 }], webRtcTransportIds: [transport.id], localIceUsernameFragments: [ { /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id }, ], tupleHashes: [], }); transport.close(); expect(transport.closed).toBe(true); expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledTimes(1); expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledWith(transport); // API not exposed in the interface. expect( (webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size ).toBe(0); // API not exposed in the interface. expect((router as RouterImpl).transportsForTesting.size).toBe(0); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, udpSockets: [{ ip: '127.0.0.1', port: port1 }], tcpServers: [{ ip: '127.0.0.1', port: port2 }], webRtcTransportIds: [], localIceUsernameFragments: [], tupleHashes: [], }); }, 2000); test('router.createWebRtcTransport() with webRtcServer succeeds and webRtcServer is closed', async () => { const port1 = await pickPort({ type: 'udp', ip: '127.0.0.1', reserveTimeout: 0, }); const port2 = await pickPort({ type: 'tcp', ip: '127.0.0.1', reserveTimeout: 0, }); const webRtcServer = await ctx.worker!.createWebRtcServer({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', port: port1 }, { protocol: 'tcp', ip: '127.0.0.1', port: port2 }, ], }); const onObserverWebRtcTransportHandled = jest.fn(); const onObserverWebRtcTransportUnhandled = jest.fn(); webRtcServer.observer.once( 'webrtctransporthandled', onObserverWebRtcTransportHandled ); webRtcServer.observer.once( 'webrtctransportunhandled', onObserverWebRtcTransportUnhandled ); const router = await ctx.worker!.createRouter(); const transport = await router.createWebRtcTransport({ webRtcServer, appData: { foo: 'bar' }, }); expect(onObserverWebRtcTransportHandled).toHaveBeenCalledTimes(1); expect(onObserverWebRtcTransportHandled).toHaveBeenCalledWith(transport); await expect(router.dump()).resolves.toMatchObject({ transportIds: [transport.id], }); expect(typeof transport.id).toBe('string'); expect(transport.closed).toBe(false); expect(transport.appData).toEqual({ foo: 'bar' }); const iceCandidates = transport.iceCandidates; expect(iceCandidates.length).toBe(2); expect(iceCandidates[0]!.ip).toBe('127.0.0.1'); expect(iceCandidates[0]!.port).toBe(port1); expect(iceCandidates[0]!.protocol).toBe('udp'); expect(iceCandidates[0]!.type).toBe('host'); expect(iceCandidates[0]!.tcpType).toBeUndefined(); expect(iceCandidates[1]!.ip).toBe('127.0.0.1'); expect(iceCandidates[1]!.port).toBe(port2); expect(iceCandidates[1]!.protocol).toBe('tcp'); expect(iceCandidates[1]!.type).toBe('host'); expect(iceCandidates[1]!.tcpType).toBe('passive'); expect(transport.iceState).toBe('new'); expect(transport.iceSelectedTuple).toBeUndefined(); // API not exposed in the interface. expect( (webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size ).toBe(1); // API not exposed in the interface. expect((router as RouterImpl).transportsForTesting.size).toBe(1); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, udpSockets: [{ ip: '127.0.0.1', port: port1 }], tcpServers: [{ ip: '127.0.0.1', port: port2 }], webRtcTransportIds: [transport.id], localIceUsernameFragments: [ { /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id }, ], tupleHashes: [], }); // Let's restart ICE in the transport so it should add a new entry in // localIceUsernameFragments in the WebRtcServer. await transport.restartIce(); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, udpSockets: [{ ip: '127.0.0.1', port: port1 }], tcpServers: [{ ip: '127.0.0.1', port: port2 }], webRtcTransportIds: [transport.id], localIceUsernameFragments: [ { /* localIceUsernameFragment: xxx, */ webRtcTransportId: transport.id }, { /* localIceUsernameFragment: yyy, */ webRtcTransportId: transport.id }, ], tupleHashes: [], }); const onObserverClose = jest.fn(); webRtcServer.observer.once('close', onObserverClose); const onListenServerClose = jest.fn(); transport.once('listenserverclose', onListenServerClose); webRtcServer.close(); expect(webRtcServer.closed).toBe(true); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(onListenServerClose).toHaveBeenCalledTimes(1); expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledTimes(1); expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledWith(transport); expect(transport.closed).toBe(true); expect(transport.iceState).toBe('closed'); expect(transport.iceSelectedTuple).toBe(undefined); expect(transport.dtlsState).toBe('closed'); expect(transport.sctpState).toBe(undefined); // API not exposed in the interface. expect( (webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size ).toBe(0); // API not exposed in the interface. expect((router as RouterImpl).transportsForTesting.size).toBe(0); await expect(ctx.worker!.dump()).resolves.toMatchObject({ pid: ctx.worker!.pid, webRtcServerIds: [], routerIds: [router.id], channelMessageHandlers: { channelRequestHandlers: [router.id], channelNotificationHandlers: [], }, }); await expect(router.dump()).resolves.toMatchObject({ id: router.id, transportIds: [], }); }, 2000); ================================================ FILE: node/src/test/test-WebRtcTransport.ts ================================================ import { pickPort } from 'pick-port'; import * as flatbuffers from 'flatbuffers'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents, WebRtcTransportEvents } from '../types'; import type { WebRtcTransportImpl } from '../WebRtcTransport'; import type { TransportTuple } from '../TransportTypes'; import { serializeProtocol } from '../Transport'; import * as utils from '../utils'; import { Notification, Body as NotificationBody, Event, } from '../fbs/notification'; import * as FbsTransport from '../fbs/transport'; import * as FbsWebRtcTransport from '../fbs/web-rtc-transport'; const USE_BUILD_IN_SCTP_STACK = false; type TestContext = { mediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; }; const ctx: TestContext = { mediaCodecs: utils.deepFreeze([ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, foo: 'bar', }, }, { kind: 'video', mimeType: 'video/VP8', clockRate: 90000, }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'level-asymmetry-allowed': 1, 'packetization-mode': 1, 'profile-level-id': '4d0032', foo: 'bar', }, }, ]), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker({ useBuiltInSctpStack: USE_BUILD_IN_SCTP_STACK, }); ctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs }); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('router.createWebRtcTransport() succeeds', async () => { const onObserverNewTransport = jest.fn(); ctx.router!.observer.once('newtransport', onObserverNewTransport); const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', announcedAddress: '9.9.9.1', // |exposeInternalIp| will generate an extra ICE candidate with |ip| // value. exposeInternalIp: true, portRange: { min: 2000, max: 3000 }, }, { protocol: 'tcp', ip: '127.0.0.1', announcedAddress: '9.9.9.1', portRange: { min: 2000, max: 3000 }, }, { protocol: 'udp', ip: '0.0.0.0', announcedAddress: 'foo1.bar.org', portRange: { min: 2000, max: 3000 }, }, { protocol: 'tcp', ip: '0.0.0.0', announcedAddress: 'foo2.bar.org', portRange: { min: 2000, max: 3000 }, }, { protocol: 'udp', ip: '127.0.0.1', announcedAddress: undefined, portRange: { min: 2000, max: 3000 }, }, { protocol: 'tcp', ip: '127.0.0.1', announcedAddress: undefined, portRange: { min: 2000, max: 3000 }, }, ], enableTcp: true, preferUdp: true, enableSctp: true, numSctpStreams: { OS: 2048, MIS: 4096 }, maxSctpMessageSize: 1000000, appData: { foo: 'bar' }, }); await expect(ctx.router!.dump()).resolves.toMatchObject({ transportIds: [webRtcTransport.id], }); expect(onObserverNewTransport).toHaveBeenCalledTimes(1); expect(onObserverNewTransport).toHaveBeenCalledWith(webRtcTransport); expect(typeof webRtcTransport.id).toBe('string'); expect(webRtcTransport.closed).toBe(false); expect(webRtcTransport.type).toBe('webrtc'); expect(webRtcTransport.appData).toEqual({ foo: 'bar' }); expect(webRtcTransport.iceRole).toBe('controlled'); expect(typeof webRtcTransport.iceParameters).toBe('object'); expect(webRtcTransport.iceParameters.iceLite).toBe(true); expect(typeof webRtcTransport.iceParameters.usernameFragment).toBe('string'); expect(typeof webRtcTransport.iceParameters.password).toBe('string'); expect(webRtcTransport.sctpParameters).toMatchObject({ port: 5000, // NOTE: When using the built-in SCTP stack, `numSctpStreams` given to the // transport is ignored. OS: USE_BUILD_IN_SCTP_STACK ? 65535 : 2048, MIS: USE_BUILD_IN_SCTP_STACK ? 65535 : 4096, maxMessageSize: 1000000, }); expect(Array.isArray(webRtcTransport.iceCandidates)).toBe(true); expect(webRtcTransport.iceCandidates.length).toBe(7); const iceCandidates = webRtcTransport.iceCandidates; expect(iceCandidates[0]!.ip).toBe('9.9.9.1'); expect(iceCandidates[0]!.protocol).toBe('udp'); expect(iceCandidates[0]!.type).toBe('host'); expect(iceCandidates[0]!.tcpType).toBeUndefined(); expect(iceCandidates[1]!.ip).toBe('127.0.0.1'); expect(iceCandidates[1]!.protocol).toBe('udp'); expect(iceCandidates[1]!.type).toBe('host'); expect(iceCandidates[1]!.tcpType).toBeUndefined(); expect(iceCandidates[2]!.ip).toBe('9.9.9.1'); expect(iceCandidates[2]!.protocol).toBe('tcp'); expect(iceCandidates[2]!.type).toBe('host'); expect(iceCandidates[2]!.tcpType).toBe('passive'); expect(iceCandidates[3]!.ip).toBe('foo1.bar.org'); expect(iceCandidates[3]!.protocol).toBe('udp'); expect(iceCandidates[3]!.type).toBe('host'); expect(iceCandidates[3]!.tcpType).toBeUndefined(); expect(iceCandidates[4]!.ip).toBe('foo2.bar.org'); expect(iceCandidates[4]!.protocol).toBe('tcp'); expect(iceCandidates[4]!.type).toBe('host'); expect(iceCandidates[4]!.tcpType).toBe('passive'); expect(iceCandidates[5]!.ip).toBe('127.0.0.1'); expect(iceCandidates[5]!.protocol).toBe('udp'); expect(iceCandidates[5]!.type).toBe('host'); expect(iceCandidates[5]!.tcpType).toBeUndefined(); expect(iceCandidates[6]!.ip).toBe('127.0.0.1'); expect(iceCandidates[6]!.protocol).toBe('tcp'); expect(iceCandidates[6]!.type).toBe('host'); expect(iceCandidates[6]!.tcpType).toBe('passive'); expect(iceCandidates[0]!.priority).toBeGreaterThan( iceCandidates[1]!.priority ); expect(iceCandidates[1]!.priority).toBeGreaterThan( iceCandidates[2]!.priority ); expect(iceCandidates[2]!.priority).toBeGreaterThan( iceCandidates[3]!.priority ); expect(iceCandidates[3]!.priority).toBeGreaterThan( iceCandidates[4]!.priority ); expect(iceCandidates[4]!.priority).toBeGreaterThan( iceCandidates[5]!.priority ); expect(iceCandidates[5]!.priority).toBeGreaterThan( iceCandidates[6]!.priority ); expect(webRtcTransport.iceState).toBe('new'); expect(webRtcTransport.iceSelectedTuple).toBeUndefined(); expect(typeof webRtcTransport.dtlsParameters).toBe('object'); expect(Array.isArray(webRtcTransport.dtlsParameters.fingerprints)).toBe(true); expect(webRtcTransport.dtlsParameters.role).toBe('auto'); expect(webRtcTransport.dtlsState).toBe('new'); expect(webRtcTransport.dtlsRemoteCert).toBeUndefined(); expect(webRtcTransport.sctpState).toBe('new'); const dump = await webRtcTransport.dump(); expect(dump.id).toBe(webRtcTransport.id); expect(dump.producerIds).toEqual([]); expect(dump.consumerIds).toEqual([]); expect(dump.iceRole).toBe(webRtcTransport.iceRole); expect(dump.iceParameters).toEqual(webRtcTransport.iceParameters); expect(dump.iceCandidates).toEqual(webRtcTransport.iceCandidates); expect(dump.iceState).toBe(webRtcTransport.iceState); expect(dump.iceSelectedTuple).toEqual(webRtcTransport.iceSelectedTuple); expect(dump.dtlsParameters).toEqual(webRtcTransport.dtlsParameters); expect(dump.dtlsState).toBe(webRtcTransport.dtlsState); expect(dump.sctpParameters).toEqual(webRtcTransport.sctpParameters); expect(dump.sctpState).toBe(webRtcTransport.sctpState); expect(dump.recvRtpHeaderExtensions).toBeDefined(); expect(typeof dump.rtpListener).toBe('object'); webRtcTransport.close(); expect(webRtcTransport.closed).toBe(true); }, 2000); test('router.createWebRtcTransport() with deprecated listenIps succeeds', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenIps: [{ ip: '127.0.0.1', announcedIp: undefined }], enableUdp: true, enableTcp: true, preferUdp: false, initialAvailableOutgoingBitrate: 1000000, }); expect(Array.isArray(webRtcTransport.iceCandidates)).toBe(true); expect(webRtcTransport.iceCandidates.length).toBe(2); const iceCandidates = webRtcTransport.iceCandidates; expect(iceCandidates[0]!.ip).toBe('127.0.0.1'); expect(iceCandidates[0]!.protocol).toBe('udp'); expect(iceCandidates[0]!.type).toBe('host'); expect(iceCandidates[0]!.tcpType).toBeUndefined(); expect(iceCandidates[1]!.ip).toBe('127.0.0.1'); expect(iceCandidates[1]!.protocol).toBe('tcp'); expect(iceCandidates[1]!.type).toBe('host'); expect(iceCandidates[1]!.tcpType).toBe('passive'); expect(iceCandidates[0]!.priority).toBeGreaterThan( iceCandidates[1]!.priority ); }, 2000); test('router.createWebRtcTransport() with fixed port succeeds', async () => { const port = await pickPort({ type: 'tcp', ip: '127.0.0.1', reserveTimeout: 0, }); const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ // NOTE: udpReusePort flag will be ignored since protocol is TCP. { protocol: 'tcp', ip: '127.0.0.1', port, flags: { udpReusePort: true } }, ], }); expect(webRtcTransport.iceCandidates[0]!.port).toEqual(port); }, 2000); test('router.createWebRtcTransport() with portRange succeeds', async () => { const portRange = { min: 11111, max: 11112 }; const webRtcTransport1 = await ctx.router!.createWebRtcTransport({ listenInfos: [{ protocol: 'udp', ip: '127.0.0.1', portRange }], }); const iceCandidate1 = webRtcTransport1.iceCandidates[0]!; expect(iceCandidate1.ip).toBe('127.0.0.1'); expect( iceCandidate1.port >= portRange.min && iceCandidate1.port <= portRange.max ).toBe(true); expect(iceCandidate1.protocol).toBe('udp'); const webRtcTransport2 = await ctx.router!.createWebRtcTransport({ listenInfos: [{ protocol: 'udp', ip: '127.0.0.1', portRange }], }); const iceCandidate2 = webRtcTransport2.iceCandidates[0]!; expect(iceCandidate2.ip).toBe('127.0.0.1'); expect( iceCandidate1.port >= portRange.min && iceCandidate1.port <= portRange.max ).toBe(true); expect(iceCandidate2.protocol).toBe('udp'); // No more available ports so it must fail. await expect( ctx.router!.createWebRtcTransport({ listenInfos: [{ protocol: 'udp', ip: '127.0.0.1', portRange }], }) ).rejects.toThrow(Error); }, 2000); test('router.createWebRtcTransport() with wrong arguments rejects with TypeError', async () => { // @ts-expect-error --- Testing purposes. await expect(ctx.router!.createWebRtcTransport({})).rejects.toThrow( TypeError ); await expect( ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 4000, max: 3000 }, }, ], }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.router!.createWebRtcTransport({ listenIps: [123] }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.router!.createWebRtcTransport({ listenInfos: '127.0.0.1' }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. ctx.router!.createWebRtcTransport({ listenIps: '127.0.0.1' }) ).rejects.toThrow(TypeError); await expect( ctx.router!.createWebRtcTransport({ listenIps: ['127.0.0.1'], // @ts-expect-error --- Testing purposes. appData: 'NOT-AN-OBJECT', }) ).rejects.toThrow(TypeError); await expect( ctx.router!.createWebRtcTransport({ listenIps: ['127.0.0.1'], enableSctp: true, // @ts-expect-error --- Testing purposes. numSctpStreams: 'foo', }) ).rejects.toThrow(TypeError); }, 2000); test('router.createWebRtcTransport() with non bindable IP rejects with Error', async () => { await expect( ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '8.8.8.8', portRange: { min: 2000, max: 3000 } }, ], }) ).rejects.toThrow(Error); }, 2000); test('webRtcTransport.getStats() succeeds', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', announcedAddress: '9.9.9.1', portRange: { min: 2000, max: 3000 }, }, ], }); const stats = await webRtcTransport.getStats(); expect(Array.isArray(stats)).toBe(true); expect(stats.length).toBe(1); expect(stats[0]!.type).toBe('webrtc-transport'); expect(stats[0]!.transportId).toBe(webRtcTransport.id); expect(typeof stats[0]!.timestamp).toBe('number'); expect(stats[0]!.iceRole).toBe('controlled'); expect(stats[0]!.iceState).toBe('new'); expect(stats[0]!.dtlsState).toBe('new'); expect(stats[0]!.sctpState).toBeUndefined(); expect(stats[0]!.bytesReceived).toBe(0); expect(stats[0]!.recvBitrate).toBe(0); expect(stats[0]!.bytesSent).toBe(0); expect(stats[0]!.sendBitrate).toBe(0); expect(stats[0]!.rtpBytesReceived).toBe(0); expect(stats[0]!.rtpRecvBitrate).toBe(0); expect(stats[0]!.rtpBytesSent).toBe(0); expect(stats[0]!.rtpSendBitrate).toBe(0); expect(stats[0]!.rtxBytesReceived).toBe(0); expect(stats[0]!.rtxRecvBitrate).toBe(0); expect(stats[0]!.rtxBytesSent).toBe(0); expect(stats[0]!.rtxSendBitrate).toBe(0); expect(stats[0]!.probationBytesSent).toBe(0); expect(stats[0]!.probationSendBitrate).toBe(0); expect(stats[0]!.iceSelectedTuple).toBeUndefined(); expect(stats[0]!.maxIncomingBitrate).toBeUndefined(); }, 2000); test('webRtcTransport.connect() succeeds', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', announcedAddress: '9.9.9.1', portRange: { min: 2000, max: 3000 }, }, ], }); const dtlsRemoteParameters: mediasoup.types.DtlsParameters = { fingerprints: [ { algorithm: 'sha-256', value: '82:5A:68:3D:36:C3:0A:DE:AF:E7:32:43:D2:88:83:57:AC:2D:65:E5:80:C4:B6:FB:AF:1A:A0:21:9F:6D:0C:AD', }, ], role: 'client', }; await expect( webRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters, }) ).resolves.toBeUndefined(); // Must fail if connected. await expect( webRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters, }) ).rejects.toThrow(Error); expect(webRtcTransport.dtlsParameters.role).toBe('server'); }, 2000); test('webRtcTransport.connect() with wrong arguments rejects with TypeError', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', announcedAddress: '9.9.9.1', portRange: { min: 2000, max: 3000 }, }, ], }); let dtlsRemoteParameters: mediasoup.types.DtlsParameters; // @ts-expect-error --- Testing purposes. await expect(webRtcTransport.connect({})).rejects.toThrow(TypeError); dtlsRemoteParameters = { fingerprints: [ { // @ts-expect-error --- Testing purposes.. algorithm: 'sha-256000', value: '82:5A:68:3D:36:C3:0A:DE:AF:E7:32:43:D2:88:83:57:AC:2D:65:E5:80:C4:B6:FB:AF:1A:A0:21:9F:6D:0C:AD', }, ], role: 'client', }; await expect( webRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters }) ).rejects.toThrow(TypeError); dtlsRemoteParameters = { fingerprints: [ { algorithm: 'sha-256', value: '82:5A:68:3D:36:C3:0A:DE:AF:E7:32:43:D2:88:83:57:AC:2D:65:E5:80:C4:B6:FB:AF:1A:A0:21:9F:6D:0C:AD', }, ], // @ts-expect-error --- Testing purposes. role: 'chicken', }; await expect( webRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters }) ).rejects.toThrow(TypeError); dtlsRemoteParameters = { fingerprints: [], role: 'client', }; await expect( webRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters }) ).rejects.toThrow(TypeError); await expect( webRtcTransport.connect({ dtlsParameters: dtlsRemoteParameters }) ).rejects.toThrow(TypeError); expect(webRtcTransport.dtlsParameters.role).toBe('auto'); }, 2000); test('webRtcTransport.setMaxIncomingBitrate() succeeds', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', announcedAddress: '9.9.9.1', portRange: { min: 2000, max: 3000 }, }, ], }); await expect( webRtcTransport.setMaxIncomingBitrate(1000000) ).resolves.toBeUndefined(); // Remove limit. await expect( webRtcTransport.setMaxIncomingBitrate(0) ).resolves.toBeUndefined(); }, 2000); test('webRtcTransport.setMaxOutgoingBitrate() succeeds', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); await expect( webRtcTransport.setMaxOutgoingBitrate(2000000) ).resolves.toBeUndefined(); // Remove limit. await expect( webRtcTransport.setMaxOutgoingBitrate(0) ).resolves.toBeUndefined(); }, 2000); test('webRtcTransport.setMinOutgoingBitrate() succeeds', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); await expect( webRtcTransport.setMinOutgoingBitrate(100000) ).resolves.toBeUndefined(); // Remove limit. await expect( webRtcTransport.setMinOutgoingBitrate(0) ).resolves.toBeUndefined(); }, 2000); test('webRtcTransport.setMaxOutgoingBitrate() fails if value is lower than current min limit', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); await expect( webRtcTransport.setMinOutgoingBitrate(3000000) ).resolves.toBeUndefined(); await expect(webRtcTransport.setMaxOutgoingBitrate(2000000)).rejects.toThrow( Error ); // Remove limit. await expect( webRtcTransport.setMinOutgoingBitrate(0) ).resolves.toBeUndefined(); }, 2000); test('webRtcTransport.setMinOutgoingBitrate() fails if value is higher than current max limit', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); await expect( webRtcTransport.setMaxOutgoingBitrate(2000000) ).resolves.toBeUndefined(); await expect(webRtcTransport.setMinOutgoingBitrate(3000000)).rejects.toThrow( Error ); // Remove limit. await expect( webRtcTransport.setMaxOutgoingBitrate(0) ).resolves.toBeUndefined(); }, 2000); test('webRtcTransport.restartIce() succeeds', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); const previousIceUsernameFragment = webRtcTransport.iceParameters.usernameFragment; const previousIcePassword = webRtcTransport.iceParameters.password; await expect(webRtcTransport.restartIce()).resolves.toMatchObject({ usernameFragment: expect.any(String), password: expect.any(String), iceLite: true, }); expect(typeof webRtcTransport.iceParameters.usernameFragment).toBe('string'); expect(typeof webRtcTransport.iceParameters.password).toBe('string'); expect(webRtcTransport.iceParameters.usernameFragment).not.toBe( previousIceUsernameFragment ); expect(webRtcTransport.iceParameters.password).not.toBe(previousIcePassword); }, 2000); test('transport.enableTraceEvent() succeed', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); // @ts-expect-error --- Testing purposes. await webRtcTransport.enableTraceEvent(['foo', 'probation']); await expect(webRtcTransport.dump()).resolves.toMatchObject({ traceEventTypes: ['probation'], }); await webRtcTransport.enableTraceEvent(); await expect(webRtcTransport.dump()).resolves.toMatchObject({ traceEventTypes: [], }); // @ts-expect-error --- Testing purposes. await webRtcTransport.enableTraceEvent(['probation', 'FOO', 'bwe', 'BAR']); await expect(webRtcTransport.dump()).resolves.toMatchObject({ traceEventTypes: ['probation', 'bwe'], }); await webRtcTransport.enableTraceEvent(); await expect(webRtcTransport.dump()).resolves.toMatchObject({ traceEventTypes: [], }); }, 2000); test('transport.enableTraceEvent() with wrong arguments rejects with TypeError', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); // @ts-expect-error --- Testing purposes. await expect(webRtcTransport.enableTraceEvent(123)).rejects.toThrow( TypeError ); // @ts-expect-error --- Testing purposes. await expect(webRtcTransport.enableTraceEvent('probation')).rejects.toThrow( TypeError ); await expect( // @ts-expect-error --- Testing purposes. webRtcTransport.enableTraceEvent(['probation', 123.123]) ).rejects.toThrow(TypeError); }, 2000); test('WebRtcTransport events succeed', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); // API not exposed in the interface. const channel = (webRtcTransport as WebRtcTransportImpl).channelForTesting; const onIceStateChange = jest.fn(); webRtcTransport.on('icestatechange', onIceStateChange); // Simulate a 'iceselectedtuplechange' notification coming through the // channel. const builder = new flatbuffers.Builder(); const iceStateChangeNotification = new FbsWebRtcTransport.IceStateChangeNotificationT( FbsWebRtcTransport.IceState.COMPLETED ); let notificationOffset = Notification.createNotification( builder, builder.createString(webRtcTransport.id), Event.WEBRTCTRANSPORT_ICE_STATE_CHANGE, NotificationBody.WebRtcTransport_IceStateChangeNotification, iceStateChangeNotification.pack(builder) ); builder.finish(notificationOffset); let notification = Notification.getRootAsNotification( new flatbuffers.ByteBuffer(builder.asUint8Array()) ); channel.emit( webRtcTransport.id, Event.WEBRTCTRANSPORT_ICE_STATE_CHANGE, notification ); expect(onIceStateChange).toHaveBeenCalledTimes(1); expect(onIceStateChange).toHaveBeenCalledWith('completed'); expect(webRtcTransport.iceState).toBe('completed'); builder.clear(); const onIceSelectedTuple = jest.fn(); const iceSelectedTuple: TransportTuple = { // @deprecated Use localAddress. localIp: '1.1.1.1', localAddress: '1.1.1.1', localPort: 1111, remoteIp: '2.2.2.2', remotePort: 2222, protocol: 'udp', }; webRtcTransport.on('iceselectedtuplechange', onIceSelectedTuple); // Simulate a 'icestatechange' notification coming through the channel. const iceSelectedTupleChangeNotification = new FbsWebRtcTransport.IceSelectedTupleChangeNotificationT( new FbsTransport.TupleT( iceSelectedTuple.localAddress, iceSelectedTuple.localPort, iceSelectedTuple.remoteIp, iceSelectedTuple.remotePort, serializeProtocol(iceSelectedTuple.protocol) ) ); notificationOffset = Notification.createNotification( builder, builder.createString(webRtcTransport.id), Event.WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE, NotificationBody.WebRtcTransport_IceSelectedTupleChangeNotification, iceSelectedTupleChangeNotification.pack(builder) ); builder.finish(notificationOffset); notification = Notification.getRootAsNotification( new flatbuffers.ByteBuffer(builder.asUint8Array()) ); channel.emit( webRtcTransport.id, Event.WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE, notification ); expect(onIceSelectedTuple).toHaveBeenCalledTimes(1); expect(onIceSelectedTuple).toHaveBeenCalledWith(iceSelectedTuple); expect(webRtcTransport.iceSelectedTuple).toEqual(iceSelectedTuple); builder.clear(); const onDtlsStateChange = jest.fn(); webRtcTransport.on('dtlsstatechange', onDtlsStateChange); // Simulate a 'dtlsstatechange' notification coming through the channel. const dtlsStateChangeNotification = new FbsWebRtcTransport.DtlsStateChangeNotificationT( FbsWebRtcTransport.DtlsState.CONNECTING ); notificationOffset = Notification.createNotification( builder, builder.createString(webRtcTransport.id), Event.WEBRTCTRANSPORT_DTLS_STATE_CHANGE, NotificationBody.WebRtcTransport_DtlsStateChangeNotification, dtlsStateChangeNotification.pack(builder) ); builder.finish(notificationOffset); notification = Notification.getRootAsNotification( new flatbuffers.ByteBuffer(builder.asUint8Array()) ); channel.emit( webRtcTransport.id, Event.WEBRTCTRANSPORT_DTLS_STATE_CHANGE, notification ); expect(onDtlsStateChange).toHaveBeenCalledTimes(1); expect(onDtlsStateChange).toHaveBeenCalledWith('connecting'); expect(webRtcTransport.dtlsState).toBe('connecting'); }, 2000); test('WebRtcTransport methods reject if closed', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); const onObserverClose = jest.fn(); webRtcTransport.observer.once('close', onObserverClose); webRtcTransport.close(); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(webRtcTransport.closed).toBe(true); expect(webRtcTransport.iceState).toBe('closed'); expect(webRtcTransport.iceSelectedTuple).toBeUndefined(); expect(webRtcTransport.dtlsState).toBe('closed'); expect(webRtcTransport.sctpState).toBeUndefined(); await expect(webRtcTransport.dump()).rejects.toThrow(Error); await expect(webRtcTransport.getStats()).rejects.toThrow(Error); // @ts-expect-error --- Testing purposes. await expect(webRtcTransport.connect({})).rejects.toThrow(Error); await expect(webRtcTransport.setMaxIncomingBitrate(200000)).rejects.toThrow( Error ); await expect(webRtcTransport.setMaxOutgoingBitrate(200000)).rejects.toThrow( Error ); await expect(webRtcTransport.setMinOutgoingBitrate(100000)).rejects.toThrow( Error ); await expect(webRtcTransport.restartIce()).rejects.toThrow(Error); }, 2000); test('WebRtcTransport emits "routerclose" if Router is closed', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenIps: ['127.0.0.1'], enableSctp: true, }); const onObserverClose = jest.fn(); webRtcTransport.observer.once('close', onObserverClose); const promise = enhancedOnce( webRtcTransport, 'routerclose' ); ctx.router!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(webRtcTransport.closed).toBe(true); expect(webRtcTransport.iceState).toBe('closed'); expect(webRtcTransport.iceSelectedTuple).toBeUndefined(); expect(webRtcTransport.dtlsState).toBe('closed'); expect(webRtcTransport.sctpState).toBe('closed'); }, 2000); test('WebRtcTransport emits "routerclose" if Worker is closed', async () => { const webRtcTransport = await ctx.router!.createWebRtcTransport({ listenInfos: [ { protocol: 'udp', ip: '127.0.0.1', portRange: { min: 2000, max: 3000 } }, ], }); const onObserverClose = jest.fn(); webRtcTransport.observer.once('close', onObserverClose); const promise = enhancedOnce( webRtcTransport, 'routerclose' ); ctx.worker!.close(); await promise; expect(onObserverClose).toHaveBeenCalledTimes(1); expect(webRtcTransport.closed).toBe(true); expect(webRtcTransport.iceState).toBe('closed'); expect(webRtcTransport.iceSelectedTuple).toBeUndefined(); expect(webRtcTransport.dtlsState).toBe('closed'); expect(webRtcTransport.sctpState).toBeUndefined(); }, 2000); ================================================ FILE: node/src/test/test-Worker.ts ================================================ import * as os from 'node:os'; import * as process from 'node:process'; import * as path from 'node:path'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents } from '../types'; import { InvalidStateError } from '../errors'; test('mediasoup.workerBin matches mediasoup-worker absolute path', () => { const workerBin = process.env['MEDIASOUP_WORKER_BIN'] ? process.env['MEDIASOUP_WORKER_BIN'] : process.env['MEDIASOUP_BUILDTYPE'] === 'Debug' ? path.join( __dirname, '..', '..', '..', 'worker', 'out', 'Debug', 'mediasoup-worker' ) : path.join( __dirname, '..', '..', '..', 'worker', 'out', 'Release', 'mediasoup-worker' ); expect(mediasoup.workerBin).toBe(workerBin); }); test('mediasoup.createWorker() succeeds', async () => { const onObserverNewWorker = jest.fn(); mediasoup.observer.once('newworker', onObserverNewWorker); const worker1 = await mediasoup.createWorker(); expect(onObserverNewWorker).toHaveBeenCalledTimes(1); expect(onObserverNewWorker).toHaveBeenCalledWith(worker1); expect(worker1.constructor.name).toBe('WorkerImpl'); expect(typeof worker1.pid).toBe('number'); expect(worker1.closed).toBe(false); expect(worker1.subprocessClosed).toBe(false); expect(worker1.died).toBe(false); worker1.close(); await enhancedOnce(worker1, 'subprocessclose'); expect(worker1.closed).toBe(true); expect(worker1.subprocessClosed).toBe(true); expect(worker1.died).toBe(false); const worker2 = await mediasoup.createWorker<{ foo: number; bar?: string }>({ logLevel: 'debug', logTags: ['info'], rtcMinPort: 0, rtcMaxPort: 9999, dtlsCertificateFile: path.join(__dirname, 'data', 'dtls-cert.pem'), dtlsPrivateKeyFile: path.join(__dirname, 'data', 'dtls-key.pem'), libwebrtcFieldTrials: 'WebRTC-Bwe-AlrLimitedBackoff/Disabled/', disableLiburing: true, appData: { foo: 456 }, }); expect(worker2.constructor.name).toBe('WorkerImpl'); expect(typeof worker2.pid).toBe('number'); expect(worker2.closed).toBe(false); expect(worker2.subprocessClosed).toBe(false); expect(worker2.died).toBe(false); expect(worker2.appData).toEqual({ foo: 456 }); worker2.close(); await enhancedOnce(worker2, 'subprocessclose'); expect(worker2.closed).toBe(true); expect(worker2.subprocessClosed).toBe(true); expect(worker2.died).toBe(false); }, 2000); test('mediasoup.createWorker() with wrong settings rejects with TypeError', async () => { // @ts-expect-error --- Testing purposes. await expect(mediasoup.createWorker({ logLevel: 'chicken' })).rejects.toThrow( TypeError ); await expect( mediasoup.createWorker({ rtcMinPort: 1000, rtcMaxPort: 999 }) ).rejects.toThrow(TypeError); // Port is from 0 to 65535. await expect( mediasoup.createWorker({ rtcMinPort: 1000, rtcMaxPort: 65536 }) ).rejects.toThrow(TypeError); await expect( mediasoup.createWorker({ dtlsCertificateFile: '/notfound/cert.pem' }) ).rejects.toThrow(TypeError); await expect( mediasoup.createWorker({ dtlsPrivateKeyFile: '/notfound/priv.pem' }) ).rejects.toThrow(TypeError); await expect( // @ts-expect-error --- Testing purposes. mediasoup.createWorker({ appData: 'NOT-AN-OBJECT' }) ).rejects.toBeInstanceOf(TypeError); }, 2000); test('mediasoup.createWorker() with wrong `workerBin` rejects with Error', async () => { await expect( mediasoup.createWorker({ workerBin: '/tmp/foo/mediasoup-worker' }) ).rejects.toBeInstanceOf(Error); }, 2000); test('worker.updateSettings() succeeds', async () => { const worker = await mediasoup.createWorker(); await expect( worker.updateSettings({ logLevel: 'debug', logTags: ['ice'] }) ).resolves.toBeUndefined(); worker.close(); await enhancedOnce(worker, 'subprocessclose'); }, 2000); test('worker.updateSettings() with wrong settings rejects with TypeError', async () => { const worker = await mediasoup.createWorker(); // @ts-expect-error --- Testing purposes. await expect(worker.updateSettings({ logLevel: 'chicken' })).rejects.toThrow( TypeError ); worker.close(); await enhancedOnce(worker, 'subprocessclose'); }, 2000); test('worker.updateSettings() rejects with InvalidStateError if closed', async () => { const worker = await mediasoup.createWorker(); worker.close(); await enhancedOnce(worker, 'subprocessclose'); await expect(worker.updateSettings({ logLevel: 'error' })).rejects.toThrow( InvalidStateError ); }, 2000); test('worker.dump() succeeds', async () => { const worker = await mediasoup.createWorker({ // Just for testing purposes. This does nothing since by default // `mediasoup.workerBin` is used. workerBin: mediasoup.workerBin, }); await expect(worker.dump()).resolves.toMatchObject({ pid: worker.pid, webRtcServerIds: [], routerIds: [], channelMessageHandlers: { channelRequestHandlers: [], channelNotificationHandlers: [], }, }); worker.close(); }, 2000); test('worker.dump() rejects with InvalidStateError if closed', async () => { const worker = await mediasoup.createWorker(); worker.close(); await enhancedOnce(worker, 'subprocessclose'); await expect(worker.dump()).rejects.toThrow(InvalidStateError); }, 2000); test('worker.getResourceUsage() succeeds', async () => { const worker = await mediasoup.createWorker(); await expect(worker.getResourceUsage()).resolves.toMatchObject({}); worker.close(); await enhancedOnce(worker, 'subprocessclose'); }, 2000); test('worker.close() succeeds', async () => { const worker = await mediasoup.createWorker({ logLevel: 'warn' }); const onObserverClose = jest.fn(); worker.observer.once('close', onObserverClose); worker.close(); await enhancedOnce(worker, 'subprocessclose'); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(worker.closed).toBe(true); expect(worker.subprocessClosed).toBe(true); expect(worker.died).toBe(false); }, 2000); test('Worker emits "died" if mediasoup-worker process died unexpectedly', async () => { let onDied: ReturnType; let onObserverClose: ReturnType; const worker1 = await mediasoup.createWorker({ logLevel: 'warn' }); onDied = jest.fn(); onObserverClose = jest.fn(); worker1.observer.once('close', onObserverClose); await new Promise((resolve, reject) => { worker1.on('died', () => { onDied(); if (onObserverClose.mock.calls.length > 0) { reject( new Error('observer "close" event emitted before worker "died" event') ); } else if (worker1.closed) { resolve(); } else { reject(new Error('worker.closed is false')); } }); process.kill(worker1.pid, 'SIGINT'); }); expect(onDied).toHaveBeenCalledTimes(1); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(worker1.closed).toBe(true); expect(worker1.subprocessClosed).toBe(true); expect(worker1.died).toBe(true); const worker2 = await mediasoup.createWorker({ logLevel: 'warn' }); onDied = jest.fn(); onObserverClose = jest.fn(); worker2.observer.once('close', onObserverClose); await new Promise((resolve, reject) => { worker2.on('died', () => { onDied(); if (onObserverClose.mock.calls.length > 0) { reject( new Error('observer "close" event emitted before worker "died" event') ); } else if (worker2.closed) { resolve(); } else { reject(new Error('worker.closed is false')); } }); process.kill(worker2.pid, 'SIGTERM'); }); expect(onDied).toHaveBeenCalledTimes(1); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(worker2.closed).toBe(true); expect(worker2.subprocessClosed).toBe(true); expect(worker2.died).toBe(true); const worker3 = await mediasoup.createWorker({ logLevel: 'warn' }); onDied = jest.fn(); onObserverClose = jest.fn(); worker3.observer.once('close', onObserverClose); await new Promise((resolve, reject) => { worker3.on('died', () => { onDied(); if (onObserverClose.mock.calls.length > 0) { reject( new Error('observer "close" event emitted before worker "died" event') ); } else if (worker3.closed) { resolve(); } else { reject(new Error('worker.closed is false')); } }); process.kill(worker3.pid, 'SIGKILL'); }); expect(onDied).toHaveBeenCalledTimes(1); expect(onObserverClose).toHaveBeenCalledTimes(1); expect(worker3.closed).toBe(true); expect(worker3.subprocessClosed).toBe(true); expect(worker3.died).toBe(true); }, 5000); // Windows doesn't have some signals such as SIGPIPE, SIGALRM, SIGUSR1, SIGUSR2 // so we just skip this test in Windows. if (os.platform() !== 'win32') { test('mediasoup-worker process ignores PIPE, HUP, ALRM, USR1 and USR2 signals', async () => { const worker = await mediasoup.createWorker({ logLevel: 'warn' }); await new Promise((resolve, reject) => { worker.on('died', reject); process.kill(worker.pid, 'SIGPIPE'); process.kill(worker.pid, 'SIGHUP'); process.kill(worker.pid, 'SIGALRM'); process.kill(worker.pid, 'SIGUSR1'); process.kill(worker.pid, 'SIGUSR2'); setTimeout(() => { expect(worker.closed).toBe(false); expect(worker.subprocessClosed).toBe(false); expect(worker.died).toBe(false); worker.on('subprocessclose', resolve); worker.close(); }, 2000); }); }, 4000); } ================================================ FILE: node/src/test/test-mediasoup.ts ================================================ import * as fs from 'node:fs'; import * as path from 'node:path'; import { enhancedOnce } from '../enhancedEvents'; import * as mediasoup from '../'; import type { WorkerEvents } from '../types'; const PKG = JSON.parse( fs.readFileSync(path.join(__dirname, '..', '..', '..', 'package.json'), { encoding: 'utf-8', }) ); const { version, getSupportedRtpCapabilities, parseScalabilityMode } = mediasoup; test('mediasoup.version matches version field in package.json', () => { expect(version).toBe(PKG.version); }); test('mediasoup.setLoggerEventListeners() succeeds', async () => { const onDebug = jest.fn(); mediasoup.setLogEventListeners({ ondebug: onDebug, onwarn: undefined, onerror: undefined, }); const worker = await mediasoup.createWorker(); worker.close(); expect(onDebug).toHaveBeenCalled(); if (worker.subprocessClosed === false) { await enhancedOnce(worker, 'subprocessclose'); } }, 2000); test('mediasoup.getSupportedRtpCapabilities() returns the mediasoup RTP capabilities', () => { const rtpCapabilities = getSupportedRtpCapabilities(); expect(typeof rtpCapabilities).toBe('object'); // Mangle retrieved codecs to check that, if called again, // getSupportedRtpCapabilities() returns a cloned object. // @ts-expect-error --- Testing purposes. rtpCapabilities.codecs = 'bar'; const rtpCapabilities2 = getSupportedRtpCapabilities(); expect(rtpCapabilities2).not.toEqual(rtpCapabilities); }); test('mediasoup.parseScalabilityMode() succeeds', () => { expect(parseScalabilityMode('L1T3')).toEqual({ spatialLayers: 1, temporalLayers: 3, ksvc: false, }); expect(parseScalabilityMode('L3T2_KEY')).toEqual({ spatialLayers: 3, temporalLayers: 2, ksvc: true, }); expect(parseScalabilityMode('S2T3')).toEqual({ spatialLayers: 2, temporalLayers: 3, ksvc: false, }); expect(parseScalabilityMode('foo')).toEqual({ spatialLayers: 1, temporalLayers: 1, ksvc: false, }); expect(parseScalabilityMode(undefined)).toEqual({ spatialLayers: 1, temporalLayers: 1, ksvc: false, }); expect(parseScalabilityMode('S0T3')).toEqual({ spatialLayers: 1, temporalLayers: 1, ksvc: false, }); expect(parseScalabilityMode('S1T0')).toEqual({ spatialLayers: 1, temporalLayers: 1, ksvc: false, }); expect(parseScalabilityMode('L20T3')).toEqual({ spatialLayers: 20, temporalLayers: 3, ksvc: false, }); expect(parseScalabilityMode('S200T3')).toEqual({ spatialLayers: 1, temporalLayers: 1, ksvc: false, }); expect(parseScalabilityMode('L4T7_KEY_SHIFT')).toEqual({ spatialLayers: 4, temporalLayers: 7, ksvc: true, }); }); ================================================ FILE: node/src/test/test-multiopus.ts ================================================ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents } from '../types'; import { UnsupportedError } from '../errors'; import * as utils from '../utils'; type TestContext = { mediaCodecs: mediasoup.types.RtpCodecCapability[]; audioProducerOptions: mediasoup.types.ProducerOptions; consumerDeviceCapabilities: mediasoup.types.RtpCapabilities; worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; webRtcTransport?: mediasoup.types.WebRtcTransport; }; const ctx: TestContext = { mediaCodecs: utils.deepFreeze([ { kind: 'audio', mimeType: 'audio/multiopus', preferredPayloadType: 100, clockRate: 48000, channels: 6, parameters: { useinbandfec: 1, channel_mapping: '0,4,1,2,3,5', num_streams: 4, coupled_streams: 2, }, }, ]), audioProducerOptions: utils.deepFreeze({ kind: 'audio', rtpParameters: { mid: 'AUDIO', codecs: [ { mimeType: 'audio/multiopus', payloadType: 0, clockRate: 48000, channels: 6, parameters: { useinbandfec: 1, channel_mapping: '0,4,1,2,3,5', num_streams: 4, coupled_streams: 2, }, }, ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 10, }, { uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', id: 12, }, ], }, }), consumerDeviceCapabilities: utils.deepFreeze( { codecs: [ { mimeType: 'audio/multiopus', kind: 'audio', preferredPayloadType: 100, clockRate: 48000, channels: 6, parameters: { channel_mapping: '0,4,1,2,3,5', num_streams: 4, coupled_streams: 2, }, }, ], headerExtensions: [ { kind: 'audio', uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', preferredId: 1, preferredEncrypt: false, }, { kind: 'audio', uri: 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time', preferredId: 4, preferredEncrypt: false, }, { kind: 'audio', uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', preferredId: 10, preferredEncrypt: false, }, ], } ), }; beforeEach(async () => { ctx.worker = await mediasoup.createWorker(); ctx.router = await ctx.worker.createRouter({ mediaCodecs: ctx.mediaCodecs }); ctx.webRtcTransport = await ctx.router.createWebRtcTransport({ listenInfos: [{ protocol: 'udp', ip: '127.0.0.1' }], }); }); afterEach(async () => { ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('produce() and consume() succeed', async () => { const audioProducer = await ctx.webRtcTransport!.produce( ctx.audioProducerOptions ); expect(audioProducer.rtpParameters.codecs).toEqual([ { mimeType: 'audio/multiopus', payloadType: 0, clockRate: 48000, channels: 6, parameters: { useinbandfec: 1, channel_mapping: '0,4,1,2,3,5', num_streams: 4, coupled_streams: 2, }, rtcpFeedback: [], }, ]); expect( ctx.router!.canConsume({ producerId: audioProducer.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }) ).toBe(true); const audioConsumer = await ctx.webRtcTransport!.consume({ producerId: audioProducer.id, rtpCapabilities: ctx.consumerDeviceCapabilities, }); expect(audioConsumer.rtpParameters.codecs).toEqual([ { mimeType: 'audio/multiopus', payloadType: 100, clockRate: 48000, channels: 6, parameters: { useinbandfec: 1, channel_mapping: '0,4,1,2,3,5', num_streams: 4, coupled_streams: 2, }, rtcpFeedback: [], }, ]); }, 2000); test('fails to produce wrong parameters', async () => { await expect( ctx.webRtcTransport!.produce({ kind: 'audio', rtpParameters: { mid: 'AUDIO', codecs: [ { mimeType: 'audio/multiopus', payloadType: 0, clockRate: 48000, channels: 6, parameters: { channel_mapping: '0,4,1,2,3,5', num_streams: 2, coupled_streams: 2, }, }, ], }, }) ).rejects.toThrow(UnsupportedError); await expect( ctx.webRtcTransport!.produce({ kind: 'audio', rtpParameters: { mid: 'AUDIO', codecs: [ { mimeType: 'audio/multiopus', payloadType: 0, clockRate: 48000, channels: 6, parameters: { channel_mapping: '0,4,1,2,3,5', num_streams: 4, coupled_streams: 1, }, }, ], }, }) ).rejects.toThrow(UnsupportedError); }, 2000); test('fails to consume wrong channels', async () => { const audioProducer = await ctx.webRtcTransport!.produce( ctx.audioProducerOptions ); const localConsumerDeviceCapabilities: mediasoup.types.RtpCapabilities = { codecs: [ { mimeType: 'audio/multiopus', kind: 'audio', preferredPayloadType: 100, clockRate: 48000, channels: 8, parameters: { channel_mapping: '0,4,1,2,3,5', num_streams: 4, coupled_streams: 2, }, }, ], }; expect( !ctx.router!.canConsume({ producerId: audioProducer.id, rtpCapabilities: localConsumerDeviceCapabilities, }) ).toBe(true); await expect( ctx.webRtcTransport!.consume({ producerId: audioProducer.id, rtpCapabilities: localConsumerDeviceCapabilities, }) ).rejects.toThrow(Error); }, 2000); ================================================ FILE: node/src/test/test-ortc.ts ================================================ import * as mediasoup from '../'; import * as ortc from '../ortc'; import { UnsupportedError } from '../errors'; test('generateRouterRtpCapabilities() succeeds', () => { const mediaCodecs: mediasoup.types.RouterRtpCodecCapability[] = [ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, foo: 'bar', }, }, { kind: 'video', mimeType: 'video/VP8', preferredPayloadType: 125, // Let's force it. clockRate: 90000, }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'level-asymmetry-allowed': 1, 'profile-level-id': '42e01f', foo: 'bar', }, rtcpFeedback: [], // Will be ignored. }, ]; const rtpCapabilities = ortc.generateRouterRtpCapabilities(mediaCodecs); expect(rtpCapabilities.codecs?.length).toBe(5); // opus. expect(rtpCapabilities.codecs?.[0]).toEqual({ kind: 'audio', mimeType: 'audio/opus', preferredPayloadType: 100, // 100 is the first available dynamic PT. clockRate: 48000, channels: 2, parameters: { useinbandfec: 1, foo: 'bar', }, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'transport-cc', parameter: '' }, ], }); // VP8. expect(rtpCapabilities.codecs?.[1]).toEqual({ kind: 'video', mimeType: 'video/VP8', preferredPayloadType: 125, clockRate: 90000, parameters: {}, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb', parameter: '' }, { type: 'transport-cc', parameter: '' }, ], }); // VP8 RTX. expect(rtpCapabilities.codecs?.[2]).toEqual({ kind: 'video', mimeType: 'video/rtx', preferredPayloadType: 101, // 101 is the second available dynamic PT. clockRate: 90000, parameters: { apt: 125, }, rtcpFeedback: [], }); // H264. expect(rtpCapabilities.codecs?.[3]).toEqual({ kind: 'video', mimeType: 'video/H264', preferredPayloadType: 102, // 102 is the third available dynamic PT. clockRate: 90000, parameters: { // Since packetization-mode param was not included in the H264 codec // and it's default value is 0, it's not added by ortc file. // 'packetization-mode' : 0, 'level-asymmetry-allowed': 1, 'profile-level-id': '42e01f', foo: 'bar', }, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, { type: 'goog-remb', parameter: '' }, { type: 'transport-cc', parameter: '' }, ], }); // H264 RTX. expect(rtpCapabilities.codecs?.[4]).toEqual({ kind: 'video', mimeType: 'video/rtx', preferredPayloadType: 103, clockRate: 90000, parameters: { apt: 102, }, rtcpFeedback: [], }); }); test('generateRouterRtpCapabilities() with unsupported codecs throws UnsupportedError', () => { let mediaCodecs: mediasoup.types.RouterRtpCodecCapability[]; mediaCodecs = [ { kind: 'audio', mimeType: 'audio/chicken', clockRate: 8000, channels: 4, }, ]; expect(() => ortc.generateRouterRtpCapabilities(mediaCodecs)).toThrow( UnsupportedError ); mediaCodecs = [ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 1, }, ]; expect(() => ortc.generateRouterRtpCapabilities(mediaCodecs)).toThrow( UnsupportedError ); }); test('generateRouterRtpCapabilities() with too many codecs throws', () => { const mediaCodecs: mediasoup.types.RouterRtpCodecCapability[] = []; for (let i = 0; i < 100; ++i) { mediaCodecs.push({ kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, }); } expect(() => ortc.generateRouterRtpCapabilities(mediaCodecs)).toThrow( 'cannot allocate' ); }); test('getProducerRtpParametersMapping(), getConsumableRtpParameters(), getConsumerRtpParameters() and getPipeConsumerRtpParameters() succeed', () => { const mediaCodecs: mediasoup.types.RouterRtpCodecCapability[] = [ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'level-asymmetry-allowed': 1, 'packetization-mode': 1, 'profile-level-id': '4d0032', bar: 'lalala', }, }, ]; const routerRtpCapabilities = ortc.generateRouterRtpCapabilities(mediaCodecs); const rtpParameters: mediasoup.types.RtpParameters = { codecs: [ { mimeType: 'video/H264', payloadType: 111, clockRate: 90000, parameters: { foo: 1234, 'packetization-mode': 1, 'profile-level-id': '4d0032', }, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'goog-remb', parameter: '' }, ], }, { mimeType: 'video/rtx', payloadType: 112, clockRate: 90000, parameters: { apt: 111, }, rtcpFeedback: [], }, ], headerExtensions: [ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 1, }, { uri: 'urn:3gpp:video-orientation', id: 2, }, ], encodings: [ { ssrc: 11111111, rtx: { ssrc: 11111112 }, maxBitrate: 111111, scalabilityMode: 'L1T3', }, { ssrc: 21111111, rtx: { ssrc: 21111112 }, maxBitrate: 222222, scalabilityMode: 'L1T3', }, { rid: 'high', maxBitrate: 333333, scalabilityMode: 'L1T3', }, ], rtcp: { cname: 'qwerty1234', }, }; const rtpMapping = ortc.getProducerRtpParametersMapping( rtpParameters, routerRtpCapabilities ); expect(rtpMapping.codecs).toEqual([ { payloadType: 111, mappedPayloadType: 101 }, { payloadType: 112, mappedPayloadType: 102 }, ]); expect(rtpMapping.encodings[0]!.ssrc).toBe(11111111); expect(rtpMapping.encodings[0]!.rid).toBeUndefined(); expect(typeof rtpMapping.encodings[0]!.mappedSsrc).toBe('number'); expect(rtpMapping.encodings[1]!.ssrc).toBe(21111111); expect(rtpMapping.encodings[1]!.rid).toBeUndefined(); expect(typeof rtpMapping.encodings[1]!.mappedSsrc).toBe('number'); expect(rtpMapping.encodings[2]!.ssrc).toBeUndefined(); expect(rtpMapping.encodings[2]!.rid).toBe('high'); expect(typeof rtpMapping.encodings[2]!.mappedSsrc).toBe('number'); const consumableRtpParameters = ortc.getConsumableRtpParameters( 'video', rtpParameters, routerRtpCapabilities, rtpMapping ); expect(consumableRtpParameters.codecs[0]!.mimeType).toBe('video/H264'); expect(consumableRtpParameters.codecs[0]!.payloadType).toBe(101); expect(consumableRtpParameters.codecs[0]!.clockRate).toBe(90000); expect(consumableRtpParameters.codecs[0]!.parameters).toEqual({ foo: 1234, 'packetization-mode': 1, 'profile-level-id': '4d0032', }); expect(consumableRtpParameters.codecs[1]!.mimeType).toBe('video/rtx'); expect(consumableRtpParameters.codecs[1]!.payloadType).toBe(102); expect(consumableRtpParameters.codecs[1]!.clockRate).toBe(90000); expect(consumableRtpParameters.codecs[1]!.parameters).toEqual({ apt: 101 }); expect(consumableRtpParameters.encodings?.[0]).toEqual({ ssrc: rtpMapping.encodings[0]!.mappedSsrc, maxBitrate: 111111, scalabilityMode: 'L1T3', }); expect(consumableRtpParameters.encodings?.[1]).toEqual({ ssrc: rtpMapping.encodings[1]!.mappedSsrc, maxBitrate: 222222, scalabilityMode: 'L1T3', }); expect(consumableRtpParameters.encodings?.[2]).toEqual({ ssrc: rtpMapping.encodings[2]!.mappedSsrc, maxBitrate: 333333, scalabilityMode: 'L1T3', }); expect(consumableRtpParameters.rtcp).toEqual({ cname: rtpParameters.rtcp?.cname, reducedSize: true, }); const remoteRtpCapabilities: mediasoup.types.RtpCapabilities = { codecs: [ { kind: 'audio', mimeType: 'audio/opus', preferredPayloadType: 100, clockRate: 48000, channels: 2, parameters: {}, rtcpFeedback: [], }, { kind: 'video', mimeType: 'video/H264', preferredPayloadType: 101, clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '4d0032', baz: 'LOLOLO', }, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'foo', parameter: 'FOO' }, ], }, { kind: 'video', mimeType: 'video/rtx', preferredPayloadType: 102, clockRate: 90000, parameters: { apt: 101, }, rtcpFeedback: [], }, ], headerExtensions: [ { kind: 'audio', uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', preferredId: 1, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', preferredId: 1, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id', preferredId: 2, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'audio', uri: 'urn:ietf:params:rtp-hdrext:ssrc-audio-level', preferredId: 6, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'urn:3gpp:video-orientation', preferredId: 8, preferredEncrypt: false, direction: 'sendrecv', }, { kind: 'video', uri: 'urn:ietf:params:rtp-hdrext:toffset', preferredId: 9, preferredEncrypt: false, direction: 'sendrecv', }, ], }; const consumerRtpParameters = ortc.getConsumerRtpParameters({ consumableRtpParameters, remoteRtpCapabilities, pipe: false, enableRtx: true, }); expect(consumerRtpParameters.codecs.length).toEqual(2); expect(consumerRtpParameters.codecs[0]).toEqual({ mimeType: 'video/H264', payloadType: 101, clockRate: 90000, parameters: { foo: 1234, 'packetization-mode': 1, 'profile-level-id': '4d0032', }, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'pli' }, { type: 'foo', parameter: 'FOO' }, ], }); expect(consumerRtpParameters.codecs[1]).toEqual({ mimeType: 'video/rtx', payloadType: 102, clockRate: 90000, parameters: { apt: 101, }, rtcpFeedback: [], }); expect(consumerRtpParameters.encodings!.length).toBe(1); expect(typeof consumerRtpParameters.encodings![0]!.ssrc).toBe('number'); expect(typeof consumerRtpParameters.encodings![0]!.rtx).toBe('object'); expect(typeof consumerRtpParameters.encodings![0]!.rtx?.ssrc).toBe('number'); expect(consumerRtpParameters.encodings![0]!.scalabilityMode).toBe('L3T3'); expect(consumerRtpParameters.encodings![0]!.maxBitrate).toBe(333333); expect(consumerRtpParameters.headerExtensions).toEqual([ { uri: 'urn:ietf:params:rtp-hdrext:sdes:mid', id: 1, encrypt: false, parameters: {}, }, { uri: 'urn:3gpp:video-orientation', id: 8, encrypt: false, parameters: {}, }, { uri: 'urn:ietf:params:rtp-hdrext:toffset', id: 9, encrypt: false, parameters: {}, }, ]); expect(consumerRtpParameters.rtcp).toEqual({ cname: rtpParameters.rtcp?.cname, reducedSize: true, }); const pipeConsumerRtpParameters = ortc.getPipeConsumerRtpParameters({ consumableRtpParameters, enableRtx: false, }); expect(pipeConsumerRtpParameters.codecs.length).toEqual(1); expect(pipeConsumerRtpParameters.codecs[0]).toEqual({ mimeType: 'video/H264', payloadType: 101, clockRate: 90000, parameters: { foo: 1234, 'packetization-mode': 1, 'profile-level-id': '4d0032', }, rtcpFeedback: [ { type: 'nack', parameter: 'pli' }, { type: 'ccm', parameter: 'fir' }, ], }); expect(pipeConsumerRtpParameters.encodings!.length).toBe(3); expect(typeof pipeConsumerRtpParameters.encodings![0]!.ssrc).toBe('number'); expect(pipeConsumerRtpParameters.encodings![0]!.rtx).toBeUndefined(); expect(typeof pipeConsumerRtpParameters.encodings![0]!.maxBitrate).toBe( 'number' ); expect(pipeConsumerRtpParameters.encodings![0]!.scalabilityMode).toBe('L1T3'); expect(typeof pipeConsumerRtpParameters.encodings![1]!.ssrc).toBe('number'); expect(pipeConsumerRtpParameters.encodings![1]!.rtx).toBeUndefined(); expect(typeof pipeConsumerRtpParameters.encodings![1]!.maxBitrate).toBe( 'number' ); expect(pipeConsumerRtpParameters.encodings![1]!.scalabilityMode).toBe('L1T3'); expect(typeof pipeConsumerRtpParameters.encodings![2]!.ssrc).toBe('number'); expect(pipeConsumerRtpParameters.encodings![2]!.rtx).toBeUndefined(); expect(typeof pipeConsumerRtpParameters.encodings![2]!.maxBitrate).toBe( 'number' ); expect(pipeConsumerRtpParameters.encodings![2]!.scalabilityMode).toBe('L1T3'); expect(pipeConsumerRtpParameters.rtcp).toEqual({ cname: rtpParameters.rtcp?.cname, reducedSize: true, }); }); test('getProducerRtpParametersMapping() with incompatible params throws UnsupportedError', () => { const mediaCodecs: mediasoup.types.RouterRtpCodecCapability[] = [ { kind: 'audio', mimeType: 'audio/opus', clockRate: 48000, channels: 2, }, { kind: 'video', mimeType: 'video/H264', clockRate: 90000, parameters: { 'packetization-mode': 1, 'profile-level-id': '640032', }, }, ]; const routerRtpCapabilities = ortc.generateRouterRtpCapabilities(mediaCodecs); const rtpParameters = { codecs: [ { mimeType: 'video/VP8', payloadType: 120, clockRate: 90000, rtcpFeedback: [ { type: 'nack', parameter: '' }, { type: 'nack', parameter: 'fir' }, ], }, ], headerExtensions: [], encodings: [{ ssrc: 11111111 }], rtcp: { cname: 'qwerty1234', }, }; expect(() => ortc.getProducerRtpParametersMapping(rtpParameters, routerRtpCapabilities) ).toThrow(UnsupportedError); }); ================================================ FILE: node/src/test/test-werift-sctp.ts ================================================ import { createSocket } from 'node:dgram'; import { SCTP, SCTP_STATE, WEBRTC_PPID, createUdpTransport as createSctpUdpTransport, } from 'werift-sctp'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; import type { WorkerEvents } from '../types'; type TestContext = { worker?: mediasoup.types.Worker; router?: mediasoup.types.Router; plainTransport?: mediasoup.types.PlainTransport; dataProducer?: mediasoup.types.DataProducer; dataConsumer?: mediasoup.types.DataConsumer; sctpClient?: SCTP; sctpSendStreamId?: number; }; const ctx: TestContext = {}; beforeEach(async () => { ctx.worker = await mediasoup.createWorker({ disableLiburing: true, }); ctx.router = await ctx.worker.createRouter(); ctx.plainTransport = await ctx.router.createPlainTransport({ // https://github.com/nodejs/node/issues/14900. listenIp: '127.0.0.1', // So we don't need to call plainTransport.connect(). comedia: true, enableSctp: true, numSctpStreams: { OS: 256, MIS: 256 }, }); // Create an explicit SCTP outgoing stream id. ctx.sctpSendStreamId = 123; ctx.sctpClient = SCTP.client( createSctpUdpTransport(createSocket('udp4'), { port: ctx.plainTransport.tuple.localPort, address: ctx.plainTransport.tuple.localAddress, }) ); // Create a DataProducer with the corresponding SCTP stream id. ctx.dataProducer = await ctx.plainTransport.produceData({ sctpStreamParameters: { streamId: ctx.sctpSendStreamId, ordered: true, }, label: 'node-sctp', protocol: 'foo & bar 😀😀😀', }); // Create a DataConsumer to receive messages from the DataProducer over the // same plainTransport. ctx.dataConsumer = await ctx.plainTransport.consumeData({ dataProducerId: ctx.dataProducer.id, }); let connectionTimeoutTimer: NodeJS.Timeout | undefined; await Promise.race([ // Wait for SCTP to become connected in both the PlainTransport and in the // werift-sctp client. Promise.all([ // Connect werift-sctp client (this resolves once SCTP is connected). ctx.sctpClient.start(5000), // This resolves once connected too. ctx.sctpClient.stateChanged.connected.asPromise(), // Wait for SCTP state in the mediasoup PlainTransport to be "connected". new Promise((resolve, reject) => { if (ctx.plainTransport?.sctpState === 'connected') { resolve(); } else { ctx.plainTransport?.on('sctpstatechange', state => { if (state === 'connected') { resolve(); } else if (state === 'failed' || state === 'closed') { reject( new Error( 'SCTP connection in PlainTransport failed or was closed' ) ); } }); } }), ]), new Promise((resolve, reject) => { connectionTimeoutTimer = setTimeout( () => reject(new Error('SCTP connection timeout')), 3000 ); }), ]); clearTimeout(connectionTimeoutTimer); }, 5000); afterEach(async () => { await ctx.sctpClient?.stop(); ctx.sctpClient?.transport.close(); ctx.worker?.close(); if (ctx.worker?.subprocessClosed === false) { await enhancedOnce(ctx.worker, 'subprocessclose'); } }); test('SCTP state is connected', () => { expect(ctx.plainTransport!.sctpState).toBe('connected'); expect(ctx.sctpClient!.associationState).toBe(SCTP_STATE.ESTABLISHED); }); test('ordered DataProducer delivers all SCTP messages to the DataConsumer', async () => { const numMessages = 200; let sentMessageBytes = 0; let recvMessageBytes = 0; let numSentMessages = 0; let numReceivedMessages = 0; // It must be zero because it's the first DataConsumer on the plainTransport. expect(ctx.dataConsumer!.sctpStreamParameters?.streamId).toBe(0); await new Promise((resolve, reject) => { sendNextMessage(); function sendNextMessage(): void { const id = ++numSentMessages; const data = Buffer.from(String(id)); let ppid: WEBRTC_PPID; // Set ppid of type WebRTC DataChannel string. if (id < numMessages / 2) { ppid = WEBRTC_PPID.STRING; } // Set ppid of type WebRTC DataChannel binary. else { ppid = WEBRTC_PPID.BINARY; } void ctx.sctpClient!.send(ctx.sctpSendStreamId!, ppid, data); sentMessageBytes += data.byteLength; if (id < numMessages) { sendNextMessage(); } } ctx.sctpClient!.onReceive.subscribe( (streamId: number, ppid: WEBRTC_PPID, data: Buffer) => { // `streamId` must be zero because it's the first SCTP incoming stream // (so first DataConsumer). if (streamId !== 0) { reject(new Error(`streamId should be 0 but it is ${streamId}`)); return; } ++numReceivedMessages; recvMessageBytes += data.byteLength; const id = Number(data.toString('utf8')); if (id !== numReceivedMessages) { reject( new Error( `id ${id} in message should match numReceivedMessages ${numReceivedMessages}` ) ); } else if (id === numMessages) { resolve(); } else if (id < numMessages / 2 && ppid !== WEBRTC_PPID.STRING) { reject( new Error( `ppid in message with id ${id} should be ${WEBRTC_PPID.STRING} but it is ${ppid}` ) ); } else if (id > numMessages / 2 && ppid !== WEBRTC_PPID.BINARY) { reject( new Error( `ppid in message with id ${id} should be ${WEBRTC_PPID.BINARY} but it is ${ppid}` ) ); return; } } ); }); expect(numSentMessages).toBe(numMessages); expect(numReceivedMessages).toBe(numMessages); expect(recvMessageBytes).toBe(sentMessageBytes); await expect(ctx.dataProducer!.getStats()).resolves.toMatchObject([ { type: 'data-producer', label: ctx.dataProducer!.label, protocol: ctx.dataProducer!.protocol, messagesReceived: numMessages, bytesReceived: sentMessageBytes, }, ]); await expect(ctx.dataConsumer!.getStats()).resolves.toMatchObject([ { type: 'data-consumer', label: ctx.dataConsumer!.label, protocol: ctx.dataConsumer!.protocol, messagesSent: numMessages, bytesSent: recvMessageBytes, }, ]); }, 10000); ================================================ FILE: node/src/types.ts ================================================ export type * from './indexTypes'; export type * from './WorkerTypes'; export type * from './WebRtcServerTypes'; export type * from './RouterTypes'; export type * from './TransportTypes'; export type * from './WebRtcTransportTypes'; export type * from './PlainTransportTypes'; export type * from './PipeTransportTypes'; export type * from './DirectTransportTypes'; export type * from './ProducerTypes'; export type * from './ConsumerTypes'; export type * from './DataProducerTypes'; export type * from './DataConsumerTypes'; export type * from './RtpObserverTypes'; export type * from './ActiveSpeakerObserverTypes'; export type * from './AudioLevelObserverTypes'; export type * from './rtpParametersTypes'; export type * from './rtpStreamStatsTypes'; export type * from './sctpParametersTypes'; export type * from './srtpParametersTypes'; export type * from './scalabilityModesTypes'; export type * from './errors'; type Only = { [P in keyof T]: T[P]; } & { [P in keyof U]?: never; }; export type Either = Only | Only; export type AppData = { [key: string]: unknown; }; ================================================ FILE: node/src/utils.ts ================================================ import { randomUUID, randomInt } from 'node:crypto'; /** * Clones the given value. */ export function clone(value: T): T { if (value === undefined) { return undefined as unknown as T; } else if (Number.isNaN(value)) { return NaN as unknown as T; } else if (typeof structuredClone === 'function') { // Available in Node >= 18. return structuredClone(value); } else { return JSON.parse(JSON.stringify(value)); } } /** * Generates a random UUID v4. */ export function generateUUIDv4(): string { return randomUUID(); } /** * Generates a random positive integer. */ export function generateRandomNumber(): number { return randomInt(100_000_000, 999_999_999); } /** * Make an object or array recursively immutable. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze. */ export function deepFreeze(data: T): T { // Retrieve the property names defined on object. const propNames = Reflect.ownKeys(data as Record); // Freeze properties before freezing self. for (const name of propNames) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const value = (data as any)[name]; if ((value && typeof value === 'object') || typeof value === 'function') { deepFreeze(value); } } return Object.freeze(data); } ================================================ FILE: npm-scripts.mjs ================================================ import * as process from 'node:process'; import * as os from 'node:os'; import * as fs from 'node:fs'; import * as path from 'node:path'; import { execSync } from 'node:child_process'; import fetch from 'node-fetch'; import * as tar from 'tar'; import pkg from './package.json' with { type: 'json' }; const IS_WINDOWS = os.platform() === 'win32'; const MAYOR_VERSION = pkg.version.split('.')[0]; const PYTHON = getPython(); const PIP_INVOKE_DIR = path.resolve('worker/pip_invoke'); const WORKER_RELEASE_DIR = 'worker/out/Release'; const WORKER_RELEASE_BIN = IS_WINDOWS ? 'mediasoup-worker.exe' : 'mediasoup-worker'; const WORKER_RELEASE_BIN_PATH = `${WORKER_RELEASE_DIR}/${WORKER_RELEASE_BIN}`; const WORKER_PREBUILD_DIR = 'worker/prebuild'; const GH_OWNER = 'versatica'; const GH_REPO = 'mediasoup'; // Paths for ESLint to check. const ESLINT_PATHS = [ 'eslint.config.mjs', 'jest.config.mjs', 'knip.config.mjs', 'node/src', 'npm-scripts.mjs', 'worker/scripts', ]; // Paths for ESLint to ignore. const ESLINT_IGNORE_PATHS = ['node/src/fbs']; // Paths for Prettier to check/write. // NOTE: Prettier ignores paths in .gitignore so we don't need to care about // node/src/fbs. const PRETTIER_PATHS = [ 'CHANGELOG.md', 'CONTRIBUTING.md', 'README.md', 'doc', 'eslint.config.mjs', 'jest.config.mjs', 'knip.config.mjs', 'node/src', 'npm-scripts.mjs', 'package.json', 'tsconfig.json', 'worker/scripts', ]; const task = process.argv[2]; const taskArgs = process.argv.slice(3).join(' '); // PYTHONPATH env must be updated now so all invoke calls below will find the // pip invoke module. if (process.env.PYTHONPATH) { if (IS_WINDOWS) { process.env.PYTHONPATH = `${PIP_INVOKE_DIR};${process.env.PYTHONPATH}`; } else { process.env.PYTHONPATH = `${PIP_INVOKE_DIR}:${process.env.PYTHONPATH}`; } } else { process.env.PYTHONPATH = PIP_INVOKE_DIR; } void run(); async function run() { logInfo(taskArgs ? `[args:"${taskArgs}"]` : ''); switch (task) { // As per NPM documentation (https://docs.npmjs.com/cli/v9/using-npm/scripts) // `prepare` script: // // - Runs BEFORE the package is packed, i.e. during `npm publish` and // `npm pack`. // - Runs on local `npm install` without any arguments. // - NOTE: If a package being installed through git contains a `prepare` // script, its dependencies and devDependencies will be installed, and // the `prepare` script will be run, before the package is packaged and // installed. // // So here we generate flatbuffers definitions for TypeScript and compile // TypeScript to JavaScript. case 'prepare': { await flatcNode(); buildTypescript({ force: false }); break; } case 'postinstall': { // If the user/app provides us with a custom mediasoup-worker binary then // don't do anything. if (process.env.MEDIASOUP_WORKER_BIN) { logInfo('MEDIASOUP_WORKER_BIN environment variable given, skipping'); break; } // If MEDIASOUP_LOCAL_DEV is given, or if MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD // env is given, or if mediasoup package is being installed via git+ssh // (instead of via npm), and if MEDIASOUP_FORCE_PREBUILT_WORKER_DOWNLOAD // env is not set, then skip mediasoup-worker prebuilt download. else if ( (process.env.MEDIASOUP_LOCAL_DEV || process.env.MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD || process.env.npm_package_resolved?.startsWith('git+ssh://')) && !process.env.MEDIASOUP_FORCE_WORKER_PREBUILT_DOWNLOAD ) { logInfo( 'skipping mediasoup-worker prebuilt download, building it locally' ); buildWorker(); if (!process.env.MEDIASOUP_LOCAL_DEV) { cleanWorkerArtifacts(); } } // Attempt to download a prebuilt binary. Fallback to building locally. else if (!(await downloadPrebuiltWorker())) { logInfo( `couldn't fetch any mediasoup-worker prebuilt binary, building it locally` ); buildWorker(); if (!process.env.MEDIASOUP_LOCAL_DEV) { cleanWorkerArtifacts(); } } break; } case 'typescript:build': { buildTypescript({ force: true }); break; } case 'typescript:watch': { watchTypescript(); break; } case 'worker:build': { buildWorker(); break; } case 'worker:prebuild-name': { getWorkerPrebuildTarName(); break; } case 'worker:prebuild': { await prebuildWorker(); break; } case 'lint:node': { lintNode(); break; } case 'lint:worker': { lintWorker(); break; } case 'format:node': { formatNode(); break; } case 'format:worker': { formatWorker(); break; } case 'tidy:worker': { tidyWorker({ fix: false }); break; } case 'tidy:worker:fix': { tidyWorker({ fix: true }); break; } case 'flatc:node': { await flatcNode(); break; } case 'flatc:worker': { flatcWorker(); break; } case 'test:node': { testNode(); break; } case 'test:worker': { testWorker(); break; } case 'coverage:node': { coverageNode(); break; } case 'release:check': { await checkRelease(); break; } case 'release': { await release(); break; } default: { logError('unknown task'); exitWithError(); } } } function getPython() { let python = process.env.PYTHON; if (!python) { try { execSync('python3 --version', { stdio: ['ignore', 'ignore', 'ignore'] }); python = 'python3'; } catch (error) { python = 'python'; } } return python; } function getWorkerPrebuildTarName() { let workerPrebuildTarName = `mediasoup-worker-${pkg.version}-${os.platform()}-${os.arch()}`; // In Linux we want to know about kernel version since kernel >= 6 supports // io-uring. if (os.platform() === 'linux') { const kernelMajorVersion = Number(os.release().split('.')[0]); workerPrebuildTarName += `-kernel${kernelMajorVersion}`; } workerPrebuildTarName = `${workerPrebuildTarName}.tgz`; logInfo( `getWorkerPrebuildTarName() [workerPrebuildTarName:${workerPrebuildTarName}]` ); return workerPrebuildTarName; } function installInvoke() { if (fs.existsSync(PIP_INVOKE_DIR)) { return; } logInfo('installInvoke()'); // Install pip invoke into custom location, so we don't depend on system-wide // installation. executeCmd( `"${PYTHON}" -m pip install --upgrade --no-user --target "${PIP_INVOKE_DIR}" invoke` ); } function deleteNodeLib() { if (!fs.existsSync('node/lib')) { return; } logInfo('deleteNodeLib()'); fs.rmSync('node/lib', { recursive: true, force: true }); } function buildTypescript({ force }) { if (!force && fs.existsSync('node/lib')) { return; } logInfo(`buildTypescript() [force:${force}]`); deleteNodeLib(); executeCmd(`tsc ${taskArgs}`); } function watchTypescript() { logInfo('watchTypescript()'); deleteNodeLib(); executeCmd(`tsc --watch ${taskArgs}`); } function buildWorker() { logInfo('buildWorker()'); installInvoke(); executeCmd(`"${PYTHON}" -m invoke -r worker mediasoup-worker`); } function cleanWorkerArtifacts() { logInfo('cleanWorkerArtifacts()'); installInvoke(); // Clean build artifacts except `mediasoup-worker`. executeCmd(`"${PYTHON}" -m invoke -r worker clean-build`); // Clean downloaded dependencies. executeCmd(`"${PYTHON}" -m invoke -r worker clean-subprojects`); // Clean PIP/Meson/Ninja. executeCmd(`"${PYTHON}" -m invoke -r worker clean-pip`); } function lintNode() { logInfo('lintNode()'); // Ensure there are no rules that are unnecessary or conflict with Prettier // rules. executeCmd('eslint-config-prettier eslint.config.mjs'); const eslintIgnorePatternArgs = ESLINT_IGNORE_PATHS.map( entry => `--ignore-pattern ${entry}` ).join(' '); const eslintFiles = ESLINT_PATHS.join(' '); executeCmd( `eslint -c eslint.config.mjs --max-warnings 0 ${eslintIgnorePatternArgs} ${eslintFiles}` ); const prettierFiles = PRETTIER_PATHS.join(' '); executeCmd(`prettier --check ${prettierFiles}`); executeCmd('knip --config knip.config.mjs --treat-config-hints-as-errors'); } function lintWorker() { logInfo('lintWorker()'); installInvoke(); executeCmd(`"${PYTHON}" -m invoke -r worker lint`); } function formatNode() { logInfo('formatNode()'); const prettierFiles = PRETTIER_PATHS.join(' '); executeCmd(`prettier --write ${prettierFiles}`); } function formatWorker() { logInfo('formatWorker()'); installInvoke(); executeCmd(`"${PYTHON}" -m invoke -r worker format`); } function tidyWorker({ fix }) { logInfo(`tidyWorker() [fix:${fix}]`); installInvoke(); if (fix) { executeCmd(`"${PYTHON}" -m invoke -r worker tidy-fix`); } else { executeCmd(`"${PYTHON}" -m invoke -r worker tidy`); } } async function flatcNode() { logInfo('flatcNode()'); // NOTE: Load dep on demand since it's a devDependency. const ini = await import('ini'); installInvoke(); // Build flatc if needed. executeCmd(`"${PYTHON}" -m invoke -r worker flatc`); const buildType = process.env.MEDIASOUP_BUILDTYPE || 'Release'; const extension = IS_WINDOWS ? '.exe' : ''; const flatbuffersWrapFilePath = path.join( 'worker', 'subprojects', 'flatbuffers.wrap' ); const flatbuffersWrap = ini.parse( fs.readFileSync(flatbuffersWrapFilePath, { encoding: 'utf-8', }) ); const flatbuffersDir = flatbuffersWrap['wrap-file']['directory']; const flatc = path.resolve( path.join( 'worker', 'out', buildType, 'build', 'subprojects', flatbuffersDir, `flatc${extension}` ) ); const out = path.resolve(path.join('node', 'src')); for (const dirent of fs.readdirSync(path.join('worker', 'fbs'), { withFileTypes: true, })) { if (!dirent.isFile() || path.parse(dirent.name).ext !== '.fbs') { continue; } const filePath = path.resolve(path.join('worker', 'fbs', dirent.name)); executeCmd( `"${flatc}" --ts --ts-no-import-ext --gen-object-api -o "${out}" "${filePath}"` ); } } function flatcWorker() { logInfo('flatcWorker()'); installInvoke(); executeCmd(`"${PYTHON}" -m invoke -r worker flatc`); } function testNode() { logInfo('testNode()'); executeCmd(`jest --silent false --detectOpenHandles ${taskArgs}`); } function testWorker() { logInfo('testWorker()'); installInvoke(); executeCmd(`"${PYTHON}" -m invoke -r worker test`); } function coverageNode() { logInfo('coverageNode()'); executeCmd(`jest --coverage ${taskArgs}`); executeCmd('open-cli coverage/lcov-report/index.html'); } function installNodeDeps() { logInfo('installNodeDeps()'); // Install/update Node deps. executeCmd('npm ci --ignore-scripts'); // Update package-lock.json. executeCmd('npm install --package-lock-only --ignore-scripts'); // Check vulnerabilities in deps. executeCmd('npm audit --omit dev'); executeCmd('npm audit --prefix worker/scripts'); } async function checkRelease() { logInfo('checkRelease()'); installNodeDeps(); await flatcNode(); buildTypescript({ force: true }); buildWorker(); lintNode(); lintWorker(); testNode(); testWorker(); } async function release() { logInfo('release()'); let octokit; let versionChanges; try { octokit = await getOctokit(); versionChanges = await getVersionChanges(); } catch (error) { logError(error.message); exitWithError(); } await checkRelease(); executeCmd(`git commit -am '${pkg.version}'`); executeCmd(`git tag -a ${pkg.version} -m '${pkg.version}'`); executeCmd(`git push origin v${MAYOR_VERSION}`); executeCmd(`git push origin '${pkg.version}'`); logInfo('creating release in GitHub'); await octokit.repos.createRelease({ owner: GH_OWNER, repo: GH_REPO, name: pkg.version, body: versionChanges, tag_name: pkg.version, draft: false, }); executeInteractiveCmd('npm publish'); } function ensureDir(dir) { logInfo(`ensureDir() [dir:${dir}]`); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } async function prebuildWorker() { logInfo('prebuildWorker()'); ensureDir(WORKER_PREBUILD_DIR); const workerPrebuildTar = getWorkerPrebuildTarName(); const workerPrebuildTarPath = `${WORKER_PREBUILD_DIR}/${workerPrebuildTar}`; try { await new Promise((resolve, reject) => { // Generate a gzip file which just contains mediasoup-worker binary // without any folder. tar .create( { cwd: WORKER_RELEASE_DIR, gzip: true, strict: true, }, [WORKER_RELEASE_BIN] ) // This is needed for the case in which tar.create() fails before // invoking pipe() on its result. .on('error', reject) .pipe(fs.createWriteStream(workerPrebuildTarPath)) .on('finish', resolve) .on('error', reject); }); } catch (error) { logError( 'prebuildWorker() | failed to create mediasoup-worker prebuilt tar file:', error ); exitWithError(); } } // Returns a Promise resolving to true if a mediasoup-worker prebuilt binary // was downloaded and uncompressed, false otherwise. async function downloadPrebuiltWorker() { const releaseBase = process.env.MEDIASOUP_WORKER_PREBUILT_DOWNLOAD_BASE_URL || `${pkg.repository.url .replace(/^git\+/, '') .replace(/\.git$/, '')}/releases/download`; const workerPrebuildTar = getWorkerPrebuildTarName(); const workerPrebuildTarUrl = `${releaseBase}/${pkg.version}/${workerPrebuildTar}`; logInfo( `downloadPrebuiltWorker() [workerPrebuildTarUrl:${workerPrebuildTarUrl}]` ); ensureDir(WORKER_PREBUILD_DIR); let res; try { res = await fetch(workerPrebuildTarUrl); if (res.status === 404) { logInfo( 'downloadPrebuiltWorker() | no available mediasoup-worker prebuilt binary for current architecture' ); return false; } else if (!res.ok) { logError( `downloadPrebuiltWorker() | failed to download mediasoup-worker prebuilt binary: ${res.status} ${res.statusText}` ); return false; } } catch (error) { logError( `downloadPrebuiltWorker() | failed to download mediasoup-worker prebuilt binary: ${error}` ); return false; } ensureDir(WORKER_RELEASE_DIR); return new Promise(resolve => { // Extract mediasoup-worker in the official mediasoup-worker path. res.body .pipe( tar.extract({ cwd: WORKER_RELEASE_DIR, newer: false, strict: true, }) ) .on('finish', () => { logInfo( 'downloadPrebuiltWorker() | got mediasoup-worker prebuilt binary' ); try { // Give execution permission to the binary. fs.chmodSync(WORKER_RELEASE_BIN_PATH, 0o775); } catch (error) { logWarn( `downloadPrebuiltWorker() | failed to give execution permissions to the mediasoup-worker prebuilt binary: ${error}` ); } // Let's confirm that the fetched mediasoup-worker prebuit binary does // run in current host. This is to prevent weird issues related to // different versions of libc in the system and so on. // So run mediasoup-worker without the required MEDIASOUP_VERSION env // and expect exit code 41 (see main.cpp). logInfo( 'downloadPrebuiltWorker() | checking fetched mediasoup-worker prebuilt binary in current host' ); try { const resolvedBinPath = path.resolve(WORKER_RELEASE_BIN_PATH); // This will always fail on purpose, but if status code is 41 then // it's good. execSync(`"${resolvedBinPath}"`, { stdio: ['ignore', 'ignore', 'ignore'], // Ensure no env is passed to avoid accidents. env: {}, }); } catch (error) { if (error.status === 41) { logInfo( 'downloadPrebuiltWorker() | fetched mediasoup-worker prebuilt binary is valid for current host' ); resolve(true); } else { logError( `downloadPrebuiltWorker() | fetched mediasoup-worker prebuilt binary fails to run in this host [status:${error.status}]` ); try { fs.unlinkSync(WORKER_RELEASE_BIN_PATH); } catch (error2) {} resolve(false); } } }) .on('error', error => { logError( `downloadPrebuiltWorker() | failed to extract downloaded mediasoup-worker prebuilt binary:`, error ); resolve(false); }); }); } async function getOctokit() { if (!process.env.GITHUB_TOKEN) { throw new Error('missing GITHUB_TOKEN environment variable'); } // NOTE: Load dep on demand since it's a devDependency. const { Octokit } = await import('@octokit/rest'); const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN, }); return octokit; } async function getVersionChanges() { logInfo('getVersionChanges()'); // NOTE: Load dep on demand since it's a devDependency. const marked = await import('marked'); const changelog = fs.readFileSync('./CHANGELOG.md', { encoding: 'utf-8' }); const entries = marked.lexer(changelog); for (let idx = 0; idx < entries.length; ++idx) { const entry = entries[idx]; if (entry.type === 'heading' && entry.text === pkg.version) { const changes = entries[idx + 1].raw; return changes; } } // This should not happen (unless author forgot to update CHANGELOG). throw new Error( `no entry found in CHANGELOG.md for version '${pkg.version}'` ); } function executeCmd(command) { logInfo(`executeCmd(): ${command}`); try { execSync(command, { stdio: ['ignore', process.stdout, process.stderr] }); } catch (error) { logError(`executeCmd() failed, exiting: ${error}`); exitWithError(); } } function executeInteractiveCmd(command) { logInfo(`executeInteractiveCmd(): ${command}`); try { execSync(command, { stdio: 'inherit', env: process.env }); } catch (error) { logError(`executeInteractiveCmd() failed, exiting: ${error}`); exitWithError(); } } function logInfo(...args) { // eslint-disable-next-line no-console console.log(`npm-scripts.mjs \x1b[36m[INFO] [${task}]\x1b[0m`, ...args); } function logWarn(...args) { // eslint-disable-next-line no-console console.warn(`npm-scripts.mjs \x1b[33m[WARN] [${task}]\x1b\0m`, ...args); } function logError(...args) { // eslint-disable-next-line no-console console.error(`npm-scripts.mjs \x1b[31m[ERROR] [${task}]\x1b[0m`, ...args); } function exitWithError() { process.exit(1); } ================================================ FILE: package.json ================================================ { "name": "mediasoup", "version": "3.19.22", "description": "Cutting Edge WebRTC Video Conferencing", "contributors": [ "Iñaki Baz Castillo (https://inakibaz.me)", "José Luis Millán (https://github.com/jmillan)", "Nazar Mokynskyi (https://github.com/nazar-pc)" ], "license": "ISC", "homepage": "https://mediasoup.org", "repository": { "type": "git", "url": "git+https://github.com/versatica/mediasoup.git" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/mediasoup" }, "main": "node/lib/index.js", "types": "node/lib/index.d.ts", "exports": { ".": { "types": "./node/lib/index.d.ts", "default": "./node/lib/index.js" }, "./types": { "types": "./node/lib/types.d.ts", "default": "./node/lib/types.js" }, "./ortc": { "types": "./node/lib/ortc.d.ts", "default": "./node/lib/ortc.js" }, "./extras": { "types": "./node/lib/extras.d.ts", "default": "./node/lib/extras.js" } }, "files": [ "LICENSE", "README.md", "node/lib", "npm-scripts.mjs", "worker/Makefile", "worker/deps/libwebrtc", "worker/fbs", "worker/fuzzer/include", "worker/fuzzer/src", "worker/include", "worker/mocks/include", "worker/mocks/src", "worker/src", "worker/scripts/*.json", "worker/scripts/*.mjs", "worker/scripts/*.py", "worker/scripts/*.sh", "worker/subprojects/*.wrap", "worker/test/include", "worker/test/src", "worker/meson.build", "worker/meson_options.txt", "worker/tasks.py" ], "engines": { "node": ">=22" }, "keywords": [ "webrtc", "ortc", "sfu", "nodejs" ], "scripts": { "prepare": "node npm-scripts.mjs prepare", "postinstall": "node npm-scripts.mjs postinstall", "typescript:build": "node npm-scripts.mjs typescript:build", "typescript:watch": "node npm-scripts.mjs typescript:watch", "worker:build": "node npm-scripts.mjs worker:build", "worker:prebuild-name": "node npm-scripts.mjs worker:prebuild-name", "worker:prebuild": "node npm-scripts.mjs worker:prebuild", "lint": "node npm-scripts.mjs lint:node && node npm-scripts.mjs lint:worker", "lint:node": "node npm-scripts.mjs lint:node", "lint:worker": "node npm-scripts.mjs lint:worker", "format": "node npm-scripts.mjs format:node && node npm-scripts.mjs format:worker", "format:node": "node npm-scripts.mjs format:node", "format:worker": "node npm-scripts.mjs format:worker", "tidy:worker": "node npm-scripts.mjs tidy:worker", "tidy:worker:fix": "node npm-scripts.mjs tidy:worker:fix", "flatc": "node npm-scripts.mjs flatc:node && node npm-scripts.mjs flatc:worker", "flatc:node": "node npm-scripts.mjs flatc:node", "flatc:worker": "node npm-scripts.mjs flatc:worker", "test": "node npm-scripts.mjs test:node && node npm-scripts.mjs test:worker", "test:node": "node npm-scripts.mjs test:node", "test:worker": "node npm-scripts.mjs test:worker", "coverage": "node npm-scripts.mjs coverage:node", "coverage:node": "node npm-scripts.mjs coverage:node", "release:check": "node npm-scripts.mjs release:check", "release": "node npm-scripts.mjs release" }, "dependencies": { "debug": "^4.4.3", "flatbuffers": "^25.9.23", "h264-profile-level-id": "^2.3.2", "node-fetch": "^3.3.2", "supports-color": "^10.2.2", "tar": "^7.5.15" }, "devDependencies": { "@eslint/js": "^10.0.1", "@octokit/rest": "^22.0.1", "@types/debug": "^4.1.13", "@types/ini": "^4.1.1", "@types/jest": "^30.0.0", "@types/node": "^25.6.2", "eslint": "^10.3.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-jest": "^29.15.2", "eslint-plugin-prettier": "^5.5.5", "globals": "^17.6.0", "ini": "^6.0.0", "jest": "^30.4.2", "knip": "^6.14.0", "marked": "^18.0.3", "open-cli": "^9.0.0", "pick-port": "^2.2.0", "prettier": "^3.8.3", "ts-jest": "^29.4.9", "typescript": "^6.0.3", "typescript-eslint": "^8.59.3", "werift-sctp": "^0.0.11" } } ================================================ FILE: rust/CHANGELOG.md ================================================ # Changelog ### NEXT - Worker: Add `use_built_in_sctp_stack` setting (defaults to `false`) to enable mediasoup built-in SCTP stack (PR #1777). ### 0.21.0 - `router.pipe_producer_to_router()` and `router.pipe_data_producer_to_router()` can now connect two `Routers` in the same `Worker` if `keep_id` is set to `false` (PR #1604). - Updates from mediasoup TypeScript `3.18.1.=3.19.0`. - Ensure that the order of acquiring the `paused` and `producer_paused` locks in `consumer.rs` is consistent at all times to avoid deadlock (PR #1605). - Convert `WORKER_CLOSE` into a notification (PR #1729). ### 0.20.0 - Make `parameters` and `rtcp_feedback` optional in `RtpCodecParameters` and `RtpCodecCapability` during deserialization (PR #1597). - Make codec `mime_type` case insensitive during deserialization (PR #1599). - Only expose `data_structures`, `rtp_parameters`, `sctp_parameters` and `srtp_parameters` through the `mediasoup-types` crate (PR #1600). ### 0.19.1 - Fix installation in paths with spaces (PR #1596). - Updates from mediasoup TypeScript `3.17.1.=3.18.1`. ### 0.19.0 - Enable AV1 codec (PR #1563). - Remove H265 codec and deprecated frame-marking RTP extension (PR #1564). - Remove H264-SVC codec (PR #1568). - Add `Router::update_media_codecs()` to dynamically change Router's RTP capabilities (#1571). - `TransportListenInfo`: Add `expose_internal_ip` which, if set to `true` and `announced_address` is set, exposes an additional ICE candidate in `WebRtcTransport` whose IP is `listen_info.ip` rather than `listen_info.announced_address` (PR #1583). - Updates from mediasoup TypeScript `3.14.11.=3.17.0`. ### 0.18.2 - Don't log error if `close()` on an object fails because channel is closed already (PR #1560). - General mediasoup changes: - Sign self generated DTLS certificate with SHA256 (PR #1450). - `SimulcastConsumer`: Fix cannot switch layers if initial `tsReferenceSpatialLayer disappears` disappears (PR #1459). - Worker: Fix crash when using colliding `portRange` values in different transports (PR #1469). - Worker: Drop VP8 packets with a higher temporal layer than the current one (PR #1009). - Fix the problem of the TCC package being omitted from being sent (PR #1492). - `Consumer`: Fix sequence number gap (PR #1494). - Fix VP9 out of order packets forwarding (PR #1486). - Fix wrong SCTP stream parameters in SCTP `DataConsumer` that consumes from a direct `DataProducer` (PR #1516). - Worker: Fix encode retransmitted packets with the corresponding data (PR #1527). - `SvcConsumer`: Fix K-SVC bitrate in `IncreaseLayer()` method (PR #1535). - `Consumer` classes: Only drop packets in RTP sequence manager when they belong to current spatial layer (PR #1549). - `Consumer` classes: Add target layer retransmission buffer to avoid PLIs/FIRs when RTP packets containing a key frame arrive out of order (PR #1550 and PR #1558). ### 0.18.1 - FBS: Provide proper data upon panic (#1523). ### 0.18.0 - Fix wrong SCTP stream parameters in SCTP `DataConsumer` that consumes from a direct `DataProducer` (PR #1516). - New enum variant was added in 0.17.2. ### 0.17.2 - Fix `PipeConsumer::get_stats()` (PR #1511). ### 0.17.1 - Update Rust toolchain channel to version 1.79.0 (PR #1409). - Updates from mediasoup TypeScript `3.14.7..=3.14.10`. - General mediasoup changes: - Worker: Add `enable_liburing` boolean option (`true` by default) to disable `io_uring` even if it's supported by the prebuilt `mediasoup-worker` and by current host (PR #1442). ### 0.17.0 - Updates from mediasoup TypeScript `3.13.18..=3.14.6`. - General mediasoup changes: - Worker: Fix crash when closing `WebRtcServer` with active `WebRtcTransports` (PR #1390). - `Worker: Fix memory leak when using `WebRtcServer` with TCP enabled (PR #1389). - OPUS: Fix DTX detection (PR #1357). - `TransportListenInfo`: Add `portRange` (deprecate worker port range) (PR #1365). - Update worker FlatBuffers to 24.3.6-1 (fix cannot set temporal layer 0) (PR #1348). - Fix DTLS packets do not honor configured DTLS MTU (attempt 3) (PR #1345). - Add server side ICE consent checks to detect silent WebRTC disconnections (PR #1332). - `TransportListenInfo`: "announced ip" can also be a hostname (PR #1322). - `TransportListenInfo`: Rename "announced ip" to "announced address" (PR #1324). ### 0.16.0 - Updates from mediasoup TypeScript `3.13.13..=3.13.17`. - General mediasoup changes: - `TransportListenInfo.announced_ip` can also be a hostname (PR #1322). - `TransportListenInfo.announced_ip` is now `announced_address`, `IceCandidate.ip` is now `IceCandidate.address` and `TransportTuple.local_ip` is not `TransportTuple.local_address` (PR #1324). ### 0.15.0 - Expose DataChannel string message as binary (PR #1289). ### 0.14.0 - Updates from mediasoup TypeScript `3.13.8..=3.13.12`. - Update h264-profile-level-id dependency to 0.2.0. - Fix docs build (PR #1271). - Rename `data_consumer::on_producer_resume` to `data_consumer::on_data_producer_resume` (PR #1271). ### 0.13.0 - Updates from mediasoup TypeScript `3.13.0..=3.13.7`. - General mediasoup changes: - Switch from JSON based messages to `flatbuffers` (PR #1064). - Enable `liburing` usage for Linux (kernel versions >= 6) (PR #1218). - Add pause/resume API in `DataProducer` and `DataConsumer` (PR #1104). - DataChannel subchannels feature (PR #1152). - `Worker`: Make DTLS fragment stay within MTU size range (PR #1156). - Replace make + Makefile with Python Invoke library + tasks.py (also fix installation under path with whitespaces) (PR #1239). ### 0.12.0 - Updates from mediasoup TypeScript `3.11.9..=3.12.16`. ### 0.11.4 - Fix consuming data producer from direct transport by data consumer on non-direct transport. ### 0.11.3 - Updates from mediasoup TypeScript `3.11.3..=3.11.8`. ### 0.11.2 - Updates from mediasoup TypeScript `3.10.11..=3.11.2`. ### 0.11.1 - Updates from mediasoup TypeScript `3.10.7..=3.10.10`. ### 0.11.0 - Updates from mediasoup TypeScript `3.10.2..=3.10.6`. ### 0.10.0 - Updates from mediasoup TypeScript `3.9.10..=3.10.1`. - `WebRtcServer`: A new class that brings to `WebRtcTransports` the ability to listen on a single UDP/TCP port (PR #834, PR #845). - Minor API breaking changes. ### 0.9.3 - Fix a segfaults in tests and under multithreaded executor. - Fix another racy deadlock situation. - Expose hierarchical dependencies of ownership of Rust data structures, now it is possible to call `consumer.transport().router().worker().worker_manager()`. - General mediasoup changes: - ICE renomination support (PR #756). - Update `libuv` to 1.43.0. - TCC client optimizations for faster and more stable BWE (PR #712 by @ggarber). - Added support for RTP abs-capture-time header (PR #761 by @oto313). - Fix VP9 kSVC forwarding logic to not forward lower unneded layers (PR #778 by @ggarber). - Fix update bandwidth estimation configuration and available bitrate when updating max outgoing bitrate (PR #779 by @ggarber). - Optimize RTP header extension handling (PR #786). - `RateCalculator`: Reset optimization (PR #785). - Fix frozen video due to double call to `Consumer::UserOnTransportDisconnected()` (PR #788, thanks to @ggarber for exposing this issue in PR #787). ### 0.9.2 - Update `lru` dependency to fix security vulnerability ### 0.9.1 - Fix cleanup of build artifacts. - Make `Transport` implement `Send`. - Another fix to rare deadlock. - Improved Windows support (doesn't require MSVS activation). ### 0.9.0 - Fix for receiving data over payload channel. - Support thread initializer function for worker threads, can be used for pinning worker threads to CPU cores. - Significant worker communication optimizations (especially latency). - Switch from file descriptors to function calls when communicating with worker. - Various optimizations that caused minor breaking changes to public API. - Requests no longer have internal timeout, but they can now be cancelled, add your own timeouts on top if needed. - Windows support. - General mediasoup changes: - Replaces GYP build system with fully-functional Meson build system (PR #622). - `Consumer`: Modification of bitrate allocation algorithm (PR #708). - Single H264/H265 codec configuration in `supportedRtpCapabilities` (PR #718). ### 0.8.5 - Fix types for `round_trip_time` and `bitrate_by_layer` fields `ProducerStat` and `ConsumerStat`. - Accumulation of worker fixes. ### 0.8.4 - Add Active Speaker Observer to prelude. - Fix consumers preventing producers from being closed (regression introduced in 0.8.3). ### 0.8.3 - prelude module containing traits and structs that should be sufficient for most basic mediasoup-based apps. - Dominant Speaker Event (PR #603 by @SteveMcFarlin). ### 0.8.2 - Support for optional fixed port on transports. ### 0.8.1 - Add convenience methods for getting information from `TransportTuple` enum, especially local IP/port. - Add `mid` option in `ConsumerOptions` to provide way to override MID - Add convenience method `ConsumerStats::consumer_stat()`. ### 0.8.0 - `NonClosingProducer` removed (use `PipedProducer` instead, they were identical). - `RtpHeaderExtensionUri::as_str()` now takes `self` instead of `&self`. - `kind` field of `RtpHeaderExtension` is no longer optional. - Refactor `ScalabilityMode` from being a string to enum, make sure layers are not zero on type system level. - Concrete types for info field of tracing events. ### 0.7.2 - Thread and memory safety fixes in mediasoup-sys. - macOS support. - `NonClosingProducer` renamed into `PipedProducer` with better docs. - Internal restructuring of modules for better compatibility with IDEs. - Feature level updated to mediasoup `3.7.6`. ### 0.7.0 - Switch from running C++ worker processes to worker threads using mediasoup-sys that wraps mediasoup-worker into library. - Simplify `WorkerManager::new()` and `WorkerManager::with_executor()` API as the result of above. - Support `rtxPacketsDiscarded` in `Producer` stats. - Enable Rust 2018 idioms warnings. - Make sure all public types have `Debug` implementation on them. - Enforce docs on public types and add missing documentation. - Remove `RtpCodecParametersParameters::new()` (`RtpCodecParametersParameters::default()` does the same thing). ### 0.6.0 Initial upstreamed release. ================================================ FILE: rust/Cargo.toml ================================================ [package] name = "mediasoup" version = "0.21.0" description = "Cutting Edge WebRTC Video Conferencing in Rust" categories = ["api-bindings", "multimedia", "network-programming"] authors = [ "Nazar Mokrynskyi ", "José Luis Millán ", "Iñaki Baz Castillo " ] edition = "2021" license = "ISC" keywords = ["webrtc", "ortc", "sfu"] documentation = "https://docs.rs/mediasoup" repository = "https://github.com/versatica/mediasoup" readme = "README.md" include = ["/benches", "/src", "/README.md"] [package.metadata.docs.rs] default-target = "x86_64-unknown-linux-gnu" targets = [] [dependencies] async-channel = "1.7.1" async-executor = "1.4.1" async-lock = "2.6.0" async-oneshot = "0.5.0" async-trait = "0.1.58" atomic-take = "1.0.0" event-listener-primitives = "2.0.1" fastrand = "1.8.0" futures-lite = "1.12.0" h264-profile-level-id = "0.2.0" hash_hasher = "2.0.3" log = "0.4.17" nohash-hasher = "0.2.0" once_cell = "1.16.0" planus = "0.4.0" serde_json = "1.0.87" serde_repr = "0.1.9" thiserror = "1.0.37" [dependencies.lru] default-features = false version = "0.8.1" [dependencies.mediasoup-sys] path = "../worker" version = "0.11.0" [dependencies.mediasoup-types] path = "./types" version = "0.3.0" [dependencies.parking_lot] version = "0.12.1" features = ["serde"] [dependencies.regex] default-features = false features = ["std", "perf"] version = "1.6.0" [dependencies.serde] features = ["derive"] version = "1.0.190" [dependencies.uuid] features = ["serde", "v4"] version = "1.2.1" [dev-dependencies] actix = "0.13.0" actix-web-actors = "4.2.0" async-io = "1.10.0" criterion = "0.4.0" env_logger = "0.9.1" portpicker = "0.1.1" [dev-dependencies.actix-web] default-features = false features = ["macros", "ws"] version = "4.9.0" [[bench]] name = "direct_data" harness = false [[bench]] name = "producer" harness = false ================================================ FILE: rust/benches/direct_data.rs ================================================ use criterion::{criterion_group, criterion_main, Criterion}; use mediasoup::prelude::*; use std::borrow::Cow; use std::sync::mpsc; async fn create_data_producer_consumer_pair( ) -> Result<(DataProducer, DataConsumer), Box> { let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await?; let router = worker.create_router(RouterOptions::default()).await?; let direct_transport = router .create_direct_transport(DirectTransportOptions::default()) .await?; let data_producer = direct_transport .produce_data(DataProducerOptions::new_direct()) .await?; let data_consumer = direct_transport .consume_data(DataConsumerOptions::new_direct(data_producer.id(), None)) .await?; Ok((data_producer, data_consumer)) } pub fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("direct_data"); let data = std::iter::repeat_with(|| fastrand::u8(..)) .take(512) .collect::>(); { let (data_producer, data_consumer) = futures_lite::future::block_on(async { create_data_producer_consumer_pair().await.unwrap() }); let direct_data_producer = if let DataProducer::Direct(direct_data_producer) = data_producer { direct_data_producer } else { unreachable!() }; group.bench_with_input("recv", &data, |b, data| { b.iter(|| { let (sender, receiver) = mpsc::sync_channel(1); let _handler_id = data_consumer.on_message(move |_message| { let _ = sender.send(()); }); let _ = direct_data_producer.send(WebRtcMessage::Binary(Cow::from(data)), None, None); let _ = receiver.recv(); }) }); } group.finish(); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: rust/benches/producer.rs ================================================ use criterion::{criterion_group, criterion_main, Criterion}; use mediasoup::prelude::*; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; fn create_ssrc() -> u32 { fastrand::u32(100_000_000..999_999_999) } fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([("foo", "111".into())]), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("level-asymmetry-allowed", 1_u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }, ] } async fn init() -> (Worker, Router, WebRtcTransport, WebRtcTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); let transport_1 = router .create_webrtc_transport(transport_options.clone()) .await .expect("Failed to create transport1"); let transport_2 = router .create_webrtc_transport(transport_options) .await .expect("Failed to create transport2"); (worker, router, transport_1, transport_2) } fn audio_producer_options() -> ProducerOptions { ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some(fastrand::u32(100_000_000..999_999_999).to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 0, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("usedtx", 1_u32.into()), ("foo", "222.222".into()), ("bar", "333".into()), ]), rtcp_feedback: vec![], }], header_extensions: vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::SsrcAudioLevel, id: 12, encrypt: false, }, ], // Missing encodings on purpose. encodings: vec![], rtcp: RtcpParameters { cname: Some("audio-1".to_string()), ..RtcpParameters::default() }, msid: None, }, ) } fn video_producer_options() -> ProducerOptions { ProducerOptions::new( MediaKind::Video, RtpParameters { mid: Some(fastrand::u32(100_000_000..999_999_999).to_string()), codecs: vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::GoogRemb, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 113, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 112u32.into())]), rtcp_feedback: vec![], }, ], header_extensions: vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 13, encrypt: false, }, ], encodings: vec![ RtpEncodingParameters { ssrc: Some(create_ssrc()), rtx: Some(RtpEncodingParametersRtx { ssrc: create_ssrc(), }), scalability_mode: "L1T3".parse().unwrap(), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(create_ssrc()), rtx: Some(RtpEncodingParametersRtx { ssrc: create_ssrc(), }), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(create_ssrc()), rtx: Some(RtpEncodingParametersRtx { ssrc: create_ssrc(), }), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(create_ssrc()), rtx: Some(RtpEncodingParametersRtx { ssrc: create_ssrc(), }), ..RtpEncodingParameters::default() }, ], rtcp: RtcpParameters { cname: Some("video-1".to_string()), ..RtcpParameters::default() }, msid: None, }, ) } pub fn criterion_benchmark(c: &mut Criterion) { let mut group = c.benchmark_group("producer"); { let (_worker, _router, transport_1, _transport_2) = futures_lite::future::block_on(async { init().await }); { let audio_producer = futures_lite::future::block_on(async { let (_worker, _router, transport_1, _transport_2) = init().await; transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio") }); group.bench_function("create/audio", |b| { b.iter(|| { let _ = futures_lite::future::block_on(async { transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio") }); }) }); group.bench_function("dump/audio", |b| { b.iter(|| { let _ = futures_lite::future::block_on(async { audio_producer.dump().await }); }) }); group.bench_function("stats/audio", |b| { b.iter(|| { let _ = futures_lite::future::block_on(async { audio_producer.get_stats().await }); }) }); } { let video_producer = futures_lite::future::block_on(async { let (_worker, _router, transport_1, _transport_2) = init().await; transport_1 .produce(video_producer_options()) .await .expect("Failed to produce video") }); group.bench_function("create/video", |b| { b.iter(|| { let _ = futures_lite::future::block_on(async { transport_1 .produce(video_producer_options()) .await .expect("Failed to produce video") }); }) }); group.bench_function("dump/video", |b| { b.iter(|| { let _ = futures_lite::future::block_on(async { video_producer.dump().await }); }) }); group.bench_function("stats/video", |b| { b.iter(|| { let _ = futures_lite::future::block_on(async { video_producer.get_stats().await }); }) }); } } group.finish(); } criterion_group!(benches, criterion_benchmark); criterion_main!(benches); ================================================ FILE: rust/examples/echo.rs ================================================ use actix::prelude::*; use actix_web::web::{Data, Payload}; use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; use actix_web_actors::ws; use mediasoup::prelude::*; use mediasoup::worker::{WorkerLogLevel, WorkerLogTag}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; /// List of codecs that SFU will accept from clients fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([("useinbandfec", 1_u32.into())]), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, ] } /// Data structure containing all the necessary information about transport options required from /// the server to establish transport connection on the client #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct TransportOptions { id: TransportId, dtls_parameters: DtlsParameters, ice_candidates: Vec, ice_parameters: IceParameters, } /// Server messages sent to the client #[derive(Serialize, Message)] #[serde(tag = "action")] #[rtype(result = "()")] #[allow(clippy::large_enum_variant)] enum ServerMessage { /// Initialization message with consumer/producer transport options and Router's RTP /// capabilities necessary to establish WebRTC transport connection client-side #[serde(rename_all = "camelCase")] Init { consumer_transport_options: TransportOptions, producer_transport_options: TransportOptions, router_rtp_capabilities: RtpCapabilitiesFinalized, }, /// Notification that producer transport was connected successfully (in case of error connection /// is just dropped, in real-world application you probably want to handle it better) ConnectedProducerTransport, /// Notification that producer was created on the server, in this simple example client will try /// to consume it right away, hence `echo` example #[serde(rename_all = "camelCase")] Produced { id: ProducerId }, /// Notification that consumer transport was connected successfully (in case of error connection /// is just dropped, in real-world application you probably want to handle it better) ConnectedConsumerTransport, /// Notification that consumer was successfully created server-side, client can resume the /// consumer after this #[serde(rename_all = "camelCase")] Consumed { id: ConsumerId, producer_id: ProducerId, kind: MediaKind, rtp_parameters: RtpParameters, }, } /// Client messages sent to the server #[derive(Deserialize, Message)] #[serde(tag = "action")] #[rtype(result = "()")] enum ClientMessage { /// Client-side initialization with its RTP capabilities, in this simple case we expect those to /// match server Router's RTP capabilities #[serde(rename_all = "camelCase")] Init { rtp_capabilities: RtpCapabilities }, /// Request to connect producer transport with client-side DTLS parameters #[serde(rename_all = "camelCase")] ConnectProducerTransport { dtls_parameters: DtlsParameters }, /// Request to produce a new audio or video track with specified RTP parameters #[serde(rename_all = "camelCase")] Produce { kind: MediaKind, rtp_parameters: RtpParameters, }, /// Request to connect consumer transport with client-side DTLS parameters #[serde(rename_all = "camelCase")] ConnectConsumerTransport { dtls_parameters: DtlsParameters }, /// Request to consume specified producer #[serde(rename_all = "camelCase")] Consume { producer_id: ProducerId }, /// Request to resume consumer that was previously created #[serde(rename_all = "camelCase")] ConsumerResume { id: ConsumerId }, } /// Internal actor messages for convenience #[derive(Message)] #[rtype(result = "()")] enum InternalMessage { /// Save producer in connection-specific hashmap to prevent it from being destroyed SaveProducer(Producer), /// Save consumer in connection-specific hashmap to prevent it from being destroyed SaveConsumer(Consumer), /// Stop/close the WebSocket connection Stop, } /// Consumer/producer transports pair for the client struct Transports { consumer: WebRtcTransport, producer: WebRtcTransport, } /// Actor that will represent WebSocket connection from the client, it will handle inbound and /// outbound WebSocket messages in JSON. /// /// See https://actix.rs/docs/websockets/ for official `actix-web` documentation. struct EchoConnection { /// RTP capabilities received from the client client_rtp_capabilities: Option, /// Consumers associated with this client, preventing them from being destroyed consumers: HashMap, /// Producers associated with this client, preventing them from being destroyed producers: Vec, /// Router associated with this client, useful to get its RTP capabilities later router: Router, /// Consumer and producer transports associated with this client transports: Transports, } impl EchoConnection { /// Create a new instance representing WebSocket connection async fn new(worker_manager: &WorkerManager) -> Result { let worker = worker_manager .create_worker({ let mut settings = WorkerSettings::default(); settings.log_level = WorkerLogLevel::Debug; settings.log_tags = vec![ WorkerLogTag::Info, WorkerLogTag::Ice, WorkerLogTag::Dtls, WorkerLogTag::Rtp, WorkerLogTag::Srtp, WorkerLogTag::Rtcp, WorkerLogTag::Rtx, WorkerLogTag::Bwe, WorkerLogTag::Score, WorkerLogTag::Simulcast, WorkerLogTag::Svc, WorkerLogTag::Sctp, WorkerLogTag::Message, ]; settings }) .await .map_err(|error| format!("Failed to create worker: {error}"))?; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .map_err(|error| format!("Failed to create router: {error}"))?; // We know that for echo example we'll need 2 transports, so we can create both right away. // This may not be the case for real-world applications or you may create this at a // different time and/or in different order. let transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); let producer_transport = router .create_webrtc_transport(transport_options.clone()) .await .map_err(|error| format!("Failed to create producer transport: {error}"))?; let consumer_transport = router .create_webrtc_transport(transport_options) .await .map_err(|error| format!("Failed to create consumer transport: {error}"))?; Ok(Self { client_rtp_capabilities: None, consumers: HashMap::new(), producers: vec![], router, transports: Transports { consumer: consumer_transport, producer: producer_transport, }, }) } } impl Actor for EchoConnection { type Context = ws::WebsocketContext; fn started(&mut self, ctx: &mut Self::Context) { println!("WebSocket connection created"); // We know that both consumer and producer transports will be used, so we sent server // information about both in an initialization message alongside with router capabilities // to the client right after WebSocket connection is established let server_init_message = ServerMessage::Init { consumer_transport_options: TransportOptions { id: self.transports.consumer.id(), dtls_parameters: self.transports.consumer.dtls_parameters(), ice_candidates: self.transports.consumer.ice_candidates().clone(), ice_parameters: self.transports.consumer.ice_parameters().clone(), }, producer_transport_options: TransportOptions { id: self.transports.producer.id(), dtls_parameters: self.transports.producer.dtls_parameters(), ice_candidates: self.transports.producer.ice_candidates().clone(), ice_parameters: self.transports.producer.ice_parameters().clone(), }, router_rtp_capabilities: self.router.rtp_capabilities().clone(), }; ctx.address().do_send(server_init_message); } fn stopped(&mut self, _ctx: &mut Self::Context) { println!("WebSocket connection closed"); } } impl StreamHandler> for EchoConnection { fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { // Here we handle incoming WebSocket messages, intentionally not handling continuation // messages since we know all messages will fit into a single frame, but in real-world apps // you need to handle continuation frames too (`ws::Message::Continuation`) match msg { Ok(ws::Message::Ping(msg)) => { ctx.pong(&msg); } Ok(ws::Message::Pong(_)) => {} Ok(ws::Message::Text(text)) => match serde_json::from_str::(&text) { Ok(message) => { // Parse JSON into an enum and just send it back to the actor to be processed // by another handler below, it is much more convenient to just parse it in one // place and have typed data structure everywhere else ctx.address().do_send(message); } Err(error) => { eprintln!("Failed to parse client message: {error}\n{text}"); } }, Ok(ws::Message::Binary(bin)) => { eprintln!("Unexpected binary message: {bin:?}"); } Ok(ws::Message::Close(reason)) => { ctx.close(reason); ctx.stop(); } _ => ctx.stop(), } } } impl Handler for EchoConnection { type Result = (); fn handle(&mut self, message: ClientMessage, ctx: &mut Self::Context) { match message { ClientMessage::Init { rtp_capabilities } => { // We need to know client's RTP capabilities, those are sent using initialization // message and are stored in connection struct for future use self.client_rtp_capabilities.replace(rtp_capabilities); } ClientMessage::ConnectProducerTransport { dtls_parameters } => { let address = ctx.address(); let transport = self.transports.producer.clone(); // Establish connection for producer transport using DTLS parameters received // from the client, but doing so in a background task since this handler is // synchronous actix::spawn(async move { match transport .connect(WebRtcTransportRemoteParameters { dtls_parameters }) .await { Ok(_) => { address.do_send(ServerMessage::ConnectedProducerTransport); println!("Producer transport connected"); } Err(error) => { eprintln!("Failed to connect producer transport: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::Produce { kind, rtp_parameters, } => { let address = ctx.address(); let transport = self.transports.producer.clone(); // Use producer transport to create a new producer on the server with given RTP // parameters actix::spawn(async move { match transport .produce(ProducerOptions::new(kind, rtp_parameters)) .await { Ok(producer) => { let id = producer.id(); address.do_send(ServerMessage::Produced { id }); // Producer is stored in a hashmap since if we don't do it, it will get // destroyed as soon as its instance goes out out scope address.do_send(InternalMessage::SaveProducer(producer)); println!("{kind:?} producer created: {id}"); } Err(error) => { eprintln!("Failed to create {kind:?} producer: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::ConnectConsumerTransport { dtls_parameters } => { let address = ctx.address(); let transport = self.transports.consumer.clone(); // The same as producer transport, but for consumer transport actix::spawn(async move { match transport .connect(WebRtcTransportRemoteParameters { dtls_parameters }) .await { Ok(_) => { address.do_send(ServerMessage::ConnectedConsumerTransport); println!("Consumer transport connected"); } Err(error) => { eprintln!("Failed to connect consumer transport: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::Consume { producer_id } => { let address = ctx.address(); let transport = self.transports.consumer.clone(); let rtp_capabilities = match self.client_rtp_capabilities.clone() { Some(rtp_capabilities) => rtp_capabilities, None => { eprintln!("Client should send RTP capabilities before consuming"); return; } }; // Create consumer for given producer ID, while first making sure that RTP // capabilities were sent by the client prior to that actix::spawn(async move { let mut options = ConsumerOptions::new(producer_id, rtp_capabilities); options.paused = true; match transport.consume(options).await { Ok(consumer) => { let id = consumer.id(); let kind = consumer.kind(); let rtp_parameters = consumer.rtp_parameters().clone(); address.do_send(ServerMessage::Consumed { id, producer_id, kind, rtp_parameters, }); // Consumer is stored in a hashmap since if we don't do it, it will get // destroyed as soon as its instance goes out out scope address.do_send(InternalMessage::SaveConsumer(consumer)); println!("{kind:?} consumer created: {id}"); } Err(error) => { eprintln!("Failed to create consumer: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::ConsumerResume { id } => { if let Some(consumer) = self.consumers.get(&id).cloned() { actix::spawn(async move { match consumer.resume().await { Ok(_) => { println!( "Successfully resumed {:?} consumer {}", consumer.kind(), consumer.id(), ); } Err(error) => { println!( "Failed to resume {:?} consumer {}: {}", consumer.kind(), consumer.id(), error, ); } } }); } } } } } /// Simple handler that will transform typed server messages into JSON and send them over to the /// client over WebSocket connection impl Handler for EchoConnection { type Result = (); fn handle(&mut self, message: ServerMessage, ctx: &mut Self::Context) { ctx.text(serde_json::to_string(&message).unwrap()); } } /// Convenience handler for internal messages, these actions require mutable access to the /// connection struct and having such message handler makes it easy to use from background tasks /// where otherwise Mutex would have to be used instead impl Handler for EchoConnection { type Result = (); fn handle(&mut self, message: InternalMessage, ctx: &mut Self::Context) { match message { InternalMessage::Stop => { ctx.stop(); } InternalMessage::SaveProducer(producer) => { // Retain producer to prevent it from being destroyed self.producers.push(producer); } InternalMessage::SaveConsumer(consumer) => { self.consumers.insert(consumer.id(), consumer); } } } } /// Function that receives HTTP request on WebSocket route and upgrades it to WebSocket connection. /// /// See https://actix.rs/docs/websockets/ for official `actix-web` documentation. async fn ws_index( request: HttpRequest, worker_manager: Data, stream: Payload, ) -> Result { match EchoConnection::new(&worker_manager).await { Ok(echo_server) => ws::start(echo_server, &request, stream), Err(error) => { eprintln!("{error}"); Ok(HttpResponse::InternalServerError().finish()) } } } #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init(); // We will reuse the same worker manager across all connections, this is more than enough for // this use case let worker_manager = Data::new(WorkerManager::new()); HttpServer::new(move || { App::new() .app_data(worker_manager.clone()) .route("/ws", web::get().to(ws_index)) }) // 2 threads is plenty for this example, default is to have as many threads as CPU cores .workers(2) .bind("127.0.0.1:3000")? .run() .await } ================================================ FILE: rust/examples/multiopus.rs ================================================ use actix::prelude::*; use actix_web::web::{Data, Payload}; use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; use actix_web_actors::ws; use mediasoup::prelude::*; use mediasoup::worker::{WorkerLogLevel, WorkerLogTag}; use mediasoup_types::rtp_parameters::{RtpCodecParameters, RtpEncodingParameters}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; /// List of codecs that SFU will accept from clients fn media_codecs() -> Vec { vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("channel_mapping", "0,4,1,2,3,5".into()), ("num_streams", 4_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![RtcpFeedback::TransportCc], }] } /// Data structure containing all the necessary information about transport options required from /// the server to establish transport connection on the client #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct TransportOptions { id: TransportId, dtls_parameters: DtlsParameters, ice_candidates: Vec, ice_parameters: IceParameters, } /// Server messages sent to the client #[derive(Serialize, Message)] #[serde(tag = "action")] #[rtype(result = "()")] enum ServerMessage { /// Initialization message with consumer transport options and Router's RTP /// capabilities necessary to establish WebRTC transport connection client-side #[serde(rename_all = "camelCase")] Init { consumer_transport_options: TransportOptions, router_rtp_capabilities: RtpCapabilitiesFinalized, rtp_producer_id: ProducerId, }, /// Notification that consumer transport was connected successfully (in case of error connection /// is just dropped, in real-world application you probably want to handle it better) ConnectedConsumerTransport, /// Notification that consumer was successfully created server-side, client can resume the /// consumer after this #[serde(rename_all = "camelCase")] Consumed { id: ConsumerId, producer_id: ProducerId, kind: MediaKind, rtp_parameters: RtpParameters, }, } /// Client messages sent to the server #[derive(Deserialize, Message)] #[serde(tag = "action")] #[rtype(result = "()")] enum ClientMessage { /// Client-side initialization with its RTP capabilities, in this simple case we expect those to /// match server Router's RTP capabilities #[serde(rename_all = "camelCase")] Init { rtp_capabilities: RtpCapabilities }, /// Request to connect consumer transport with client-side DTLS parameters #[serde(rename_all = "camelCase")] ConnectConsumerTransport { dtls_parameters: DtlsParameters }, /// Request to consume specified producer #[serde(rename_all = "camelCase")] Consume { producer_id: ProducerId }, } /// Internal actor messages for convenience #[derive(Message)] #[rtype(result = "()")] enum InternalMessage { /// Save consumer in connection-specific hashmap to prevent it from being destroyed SaveConsumer(Consumer), /// Stop/close the WebSocket connection Stop, } /// Actor that will represent WebSocket connection from the client, it will handle inbound and /// outbound WebSocket messages in JSON. /// /// See https://actix.rs/docs/websockets/ for official `actix-web` documentation. struct EchoConnection { /// RTP capabilities received from the client client_rtp_capabilities: Option, /// Consumers associated with this client, preventing them from being destroyed consumers: HashMap, /// RTP producer associated with this client, preventing it from being destroyed and useful to /// get its ID later rtp_producer: Producer, /// Router associated with this client, useful to get its RTP capabilities later router: Router, /// Consumer transport associated with this client consumer_transport: WebRtcTransport, } impl EchoConnection { /// Create a new instance representing WebSocket connection async fn new(worker_manager: &WorkerManager) -> Result { let worker = worker_manager .create_worker({ let mut settings = WorkerSettings::default(); settings.log_level = WorkerLogLevel::Debug; settings.log_tags = vec![ WorkerLogTag::Info, WorkerLogTag::Ice, WorkerLogTag::Dtls, WorkerLogTag::Rtp, WorkerLogTag::Srtp, WorkerLogTag::Rtcp, WorkerLogTag::Rtx, WorkerLogTag::Bwe, WorkerLogTag::Score, WorkerLogTag::Simulcast, WorkerLogTag::Svc, WorkerLogTag::Sctp, WorkerLogTag::Message, ]; settings }) .await .map_err(|error| format!("Failed to create worker: {error}"))?; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .map_err(|error| format!("Failed to create router: {error}"))?; // For simplicity we will create plain transport for audio producer right away let plain_transport = router .create_plain_transport({ let mut options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); options.comedia = true; options.rtcp_mux = false; options }) .await .map_err(|error| format!("Failed to create plain transport: {error}"))?; // And creating audio producer that will be consumed over WebRTC later let rtp_producer = plain_transport .produce(ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, payload_type: 100, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("channel_mapping", "0,1,4,5,2,3".into()), ("num_streams", 4_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![], }], encodings: vec![RtpEncodingParameters { ssrc: Some(1111), ..RtpEncodingParameters::default() }], ..RtpParameters::default() }, )) .await .map_err(|error| format!("Failed to create audio producer: {error}"))?; println!( "Plain transport created:\n \ RTP listening on {}:{}\n \ RTCP listening on {}:{}\n \ PT=100\n \ SSRC=1111", plain_transport.tuple().local_address(), plain_transport.tuple().local_port(), plain_transport.rtcp_tuple().unwrap().local_address(), plain_transport.rtcp_tuple().unwrap().local_port(), ); println!( "Use following command with GStreamer (1.20+) to play a sample audio:\n\ gst-launch-1.0 \\\n \ rtpbin name=rtpbin \\\n \ souphttpsrc location=https://www2.iis.fraunhofer.de/AAC/ChID-BLITS-EBU-Narration.mp4 ! \\\n \ queue ! \\\n \ decodebin ! \\\n \ audioresample ! \\\n \ audioconvert ! \\\n \ opusenc inband-fec=true ! \\\n \ queue ! \\\n \ clocksync ! \\\n \ rtpopuspay pt=100 ssrc=1111 ! \\\n \ rtpbin.send_rtp_sink_0 \\\n \ rtpbin.send_rtp_src_0 ! udpsink host={} port={} sync=false async=false \\\n \ rtpbin.send_rtcp_src_0 ! udpsink host={} port={} sync=false async=false", plain_transport.tuple().local_address(), plain_transport.tuple().local_port(), plain_transport.rtcp_tuple().unwrap().local_address(), plain_transport.rtcp_tuple().unwrap().local_port(), ); // We know that for multiopus example we'll need just consumer transport, so we can create // it right away. This may not be the case for real-world applications or you may create // this at a different time and/or in different order. let consumer_transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .map_err(|error| format!("Failed to create consumer transport: {error}"))?; Ok(Self { client_rtp_capabilities: None, consumers: HashMap::new(), rtp_producer, router, consumer_transport, }) } } impl Actor for EchoConnection { type Context = ws::WebsocketContext; fn started(&mut self, ctx: &mut Self::Context) { println!("WebSocket connection created"); // We know that only consumer transport will be used, so we sent server information about it // in an initialization message alongside with router capabilities to the client right after // WebSocket connection is established let server_init_message = ServerMessage::Init { consumer_transport_options: TransportOptions { id: self.consumer_transport.id(), dtls_parameters: self.consumer_transport.dtls_parameters(), ice_candidates: self.consumer_transport.ice_candidates().clone(), ice_parameters: self.consumer_transport.ice_parameters().clone(), }, router_rtp_capabilities: self.router.rtp_capabilities().clone(), rtp_producer_id: self.rtp_producer.id(), }; ctx.address().do_send(server_init_message); } fn stopped(&mut self, _ctx: &mut Self::Context) { println!("WebSocket connection closed"); } } impl StreamHandler> for EchoConnection { fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { // Here we handle incoming WebSocket messages, intentionally not handling continuation // messages since we know all messages will fit into a single frame, but in real-world apps // you need to handle continuation frames too (`ws::Message::Continuation`) match msg { Ok(ws::Message::Ping(msg)) => { ctx.pong(&msg); } Ok(ws::Message::Pong(_)) => {} Ok(ws::Message::Text(text)) => match serde_json::from_str::(&text) { Ok(message) => { // Parse JSON into an enum and just send it back to the actor to be processed // by another handler below, it is much more convenient to just parse it in one // place and have typed data structure everywhere else ctx.address().do_send(message); } Err(error) => { eprintln!("Failed to parse client message: {error}\n{text}"); } }, Ok(ws::Message::Binary(bin)) => { eprintln!("Unexpected binary message: {bin:?}"); } Ok(ws::Message::Close(reason)) => { ctx.close(reason); ctx.stop(); } _ => ctx.stop(), } } } impl Handler for EchoConnection { type Result = (); fn handle(&mut self, message: ClientMessage, ctx: &mut Self::Context) { match message { ClientMessage::Init { rtp_capabilities } => { // We need to know client's RTP capabilities, those are sent using initialization // message and are stored in connection struct for future use self.client_rtp_capabilities.replace(rtp_capabilities); } ClientMessage::ConnectConsumerTransport { dtls_parameters } => { let address = ctx.address(); let transport = self.consumer_transport.clone(); // The same as producer transport, but for consumer transport actix::spawn(async move { match transport .connect(WebRtcTransportRemoteParameters { dtls_parameters }) .await { Ok(_) => { address.do_send(ServerMessage::ConnectedConsumerTransport); println!("Consumer transport connected"); } Err(error) => { eprintln!("Failed to connect consumer transport: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::Consume { producer_id } => { let address = ctx.address(); let transport = self.consumer_transport.clone(); let rtp_capabilities = match self.client_rtp_capabilities.clone() { Some(rtp_capabilities) => rtp_capabilities, None => { eprintln!("Client should send RTP capabilities before consuming"); return; } }; // Create consumer for given producer ID, while first making sure that RTP // capabilities were sent by the client prior to that actix::spawn(async move { match transport .consume(ConsumerOptions::new(producer_id, rtp_capabilities)) .await { Ok(consumer) => { let id = consumer.id(); let kind = consumer.kind(); let rtp_parameters = consumer.rtp_parameters().clone(); address.do_send(ServerMessage::Consumed { id, producer_id, kind, rtp_parameters, }); // Consumer is stored in a hashmap since if we don't do it, it will get // destroyed as soon as its instance goes out out scope address.do_send(InternalMessage::SaveConsumer(consumer)); println!("{kind:?} consumer created: {id}"); } Err(error) => { eprintln!("Failed to create consumer: {error}"); address.do_send(InternalMessage::Stop); } } }); } } } } /// Simple handler that will transform typed server messages into JSON and send them over to the /// client over WebSocket connection impl Handler for EchoConnection { type Result = (); fn handle(&mut self, message: ServerMessage, ctx: &mut Self::Context) { ctx.text(serde_json::to_string(&message).unwrap()); } } /// Convenience handler for internal messages, these actions require mutable access to the /// connection struct and having such message handler makes it easy to use from background tasks /// where otherwise Mutex would have to be used instead impl Handler for EchoConnection { type Result = (); fn handle(&mut self, message: InternalMessage, ctx: &mut Self::Context) { match message { InternalMessage::Stop => { ctx.stop(); } InternalMessage::SaveConsumer(consumer) => { self.consumers.insert(consumer.id(), consumer); } } } } /// Function that receives HTTP request on WebSocket route and upgrades it to WebSocket connection. /// /// See https://actix.rs/docs/websockets/ for official `actix-web` documentation. async fn ws_index( request: HttpRequest, worker_manager: Data, stream: Payload, ) -> Result { match EchoConnection::new(&worker_manager).await { Ok(echo_server) => ws::start(echo_server, &request, stream), Err(error) => { eprintln!("{error}"); Ok(HttpResponse::InternalServerError().finish()) } } } #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init(); // We will reuse the same worker manager across all connections, this is more than enough for // this use case let worker_manager = Data::new(WorkerManager::new()); HttpServer::new(move || { App::new() .app_data(worker_manager.clone()) .route("/ws", web::get().to(ws_index)) }) // 2 threads is plenty for this example, default is to have as many threads as CPU cores .workers(2) .bind("127.0.0.1:3000")? .run() .await } ================================================ FILE: rust/examples/readme.md ================================================ # Examples This directory contains examples of using mediasoup Rust library. NOTE: These examples are simplified and provided for demo purposes only, they are by no means complete or representative of production-grade software, use for educational purposes only and refer to documentation for all possible options. In order to run server-side part of examples (this directory) use `cargo run --example EXAMPLE_NAME`. # Echo Simple echo example that receives audio+video from the client and sends audio+video back to the client via different transport. Frontend part of this example is in `examples-frontend/echo` directory. Check WebSocket messages in browser DevTools for better understanding of what is happening under the hood, also source code has a bunch of comments about what is happening and where changes would be needed for production use. # Video room A bit more advanced example that allow multiple participants to join the same virtual room and receive audio+video from other participants, resulting in a simple video conferencing setup. Frontend part of this example is in `examples-frontend/videoroom` directory. Check WebSocket messages in browser DevTools for better understanding of what is happening under the hood, also source code has a bunch of comments about what is happening and where changes would be needed for production use. # Multiopus Demonstration of surround sound in WebRTC using Chromium-specific "multiopus" audio. This one is very simple in a sense that it only supports one participant playing back sample audio and nothing else. NOTE: This requires GStreamer 1.20, which at the moment of writing isn't released yet. On Linux `docker run --rm -it --net=host restreamio/gstreamer:latest-prod-dbg` can be used to enter environment with working GStreamer version, or you can compile one from upstream sources. Check WebSocket messages in browser DevTools for better understanding of what is happening under the hood, also source code has a bunch of comments about what is happening and where changes would be needed for production use. ================================================ FILE: rust/examples/svc-simulcast.rs ================================================ use actix::prelude::*; use actix_web::web::{Data, Payload}; use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; use actix_web_actors::ws; use mediasoup::prelude::*; use mediasoup::worker::{WorkerLogLevel, WorkerLogTag}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; /// List of codecs that SFU will accept from clients fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([("useinbandfec", 1_u32.into())]), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp9, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, ] } /// Data structure containing all the necessary information about transport options required from /// the server to establish transport connection on the client #[derive(Serialize)] #[serde(rename_all = "camelCase")] struct TransportOptions { id: TransportId, dtls_parameters: DtlsParameters, ice_candidates: Vec, ice_parameters: IceParameters, } /// Server messages sent to the client #[derive(Serialize, Message)] #[serde(tag = "action")] #[rtype(result = "()")] #[allow(clippy::large_enum_variant)] enum ServerMessage { /// Initialization message with consumer/producer transport options and Router's RTP /// capabilities necessary to establish WebRTC transport connection client-side #[serde(rename_all = "camelCase")] Init { consumer_transport_options: TransportOptions, producer_transport_options: TransportOptions, router_rtp_capabilities: RtpCapabilitiesFinalized, }, /// Notification that producer transport was connected successfully (in case of error connection /// is just dropped, in real-world application you probably want to handle it better) ConnectedProducerTransport, /// Notification that producer was created on the server, in this simple example client will try /// to consume it right away #[serde(rename_all = "camelCase")] Produced { id: ProducerId }, /// Notification that consumer transport was connected successfully (in case of error connection /// is just dropped, in real-world application you probably want to handle it better) ConnectedConsumerTransport, /// Notification that consumer was successfully created server-side, client can resume the /// consumer after this #[serde(rename_all = "camelCase")] Consumed { id: ConsumerId, producer_id: ProducerId, kind: MediaKind, rtp_parameters: RtpParameters, }, } /// Client messages sent to the server #[derive(Deserialize, Message)] #[serde(tag = "action")] #[rtype(result = "()")] enum ClientMessage { /// Client-side initialization with its RTP capabilities, in this simple case we expect those to /// match server Router's RTP capabilities #[serde(rename_all = "camelCase")] Init { rtp_capabilities: RtpCapabilities }, /// Request to connect producer transport with client-side DTLS parameters #[serde(rename_all = "camelCase")] ConnectProducerTransport { dtls_parameters: DtlsParameters }, /// Request to produce a new audio or video track with specified RTP parameters #[serde(rename_all = "camelCase")] Produce { kind: MediaKind, rtp_parameters: RtpParameters, }, /// Request to connect consumer transport with client-side DTLS parameters #[serde(rename_all = "camelCase")] ConnectConsumerTransport { dtls_parameters: DtlsParameters }, /// Request to consume specified producer #[serde(rename_all = "camelCase")] Consume { producer_id: ProducerId }, /// Request to resume consumer that was previously created #[serde(rename_all = "camelCase")] ConsumerResume { id: ConsumerId }, /// Request to set preferred spatial and temporal layers #[serde(rename_all = "camelCase")] SetConsumerPreferredLayers { id: ConsumerId, preferred_layers: ConsumerLayers, }, } /// Internal actor messages for convenience #[derive(Message)] #[rtype(result = "()")] enum InternalMessage { /// Save producer in connection-specific hashmap to prevent it from being destroyed SaveProducer(Producer), /// Save consumer in connection-specific hashmap to prevent it from being destroyed SaveConsumer(Consumer), /// Stop/close the WebSocket connection Stop, } /// Consumer/producer transports pair for the client struct Transports { consumer: WebRtcTransport, producer: WebRtcTransport, } /// Actor that will represent WebSocket connection from the client, it will handle inbound and /// outbound WebSocket messages in JSON. /// /// See https://actix.rs/docs/websockets/ for official `actix-web` documentation. struct SvcSimulcastConnection { /// RTP capabilities received from the client client_rtp_capabilities: Option, /// Consumers associated with this client, preventing them from being destroyed consumers: HashMap, /// Producers associated with this client, preventing them from being destroyed producers: Vec, /// Router associated with this client, useful to get its RTP capabilities later router: Router, /// Consumer and producer transports associated with this client transports: Transports, } impl SvcSimulcastConnection { /// Create a new instance representing WebSocket connection async fn new(worker_manager: &WorkerManager) -> Result { let worker = worker_manager .create_worker({ let mut settings = WorkerSettings::default(); settings.log_level = WorkerLogLevel::Debug; settings.log_tags = vec![ WorkerLogTag::Info, WorkerLogTag::Ice, WorkerLogTag::Dtls, WorkerLogTag::Rtp, WorkerLogTag::Srtp, WorkerLogTag::Rtcp, WorkerLogTag::Rtx, WorkerLogTag::Bwe, WorkerLogTag::Score, WorkerLogTag::Simulcast, WorkerLogTag::Svc, WorkerLogTag::Sctp, WorkerLogTag::Message, ]; settings }) .await .map_err(|error| format!("Failed to create worker: {error}"))?; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .map_err(|error| format!("Failed to create router: {error}"))?; // We know that for svc-simulcast example we'll need 2 transports, so we can create both // right away. // This may not be the case for real-world applications or you may create this at a // different time and/or in different order. let transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); let producer_transport = router .create_webrtc_transport(transport_options.clone()) .await .map_err(|error| format!("Failed to create producer transport: {error}"))?; let consumer_transport = router .create_webrtc_transport(transport_options) .await .map_err(|error| format!("Failed to create consumer transport: {error}"))?; Ok(Self { client_rtp_capabilities: None, consumers: HashMap::new(), producers: vec![], router, transports: Transports { consumer: consumer_transport, producer: producer_transport, }, }) } } impl Actor for SvcSimulcastConnection { type Context = ws::WebsocketContext; fn started(&mut self, ctx: &mut Self::Context) { println!("WebSocket connection created"); // We know that both consumer and producer transports will be used, so we sent server // information about both in an initialization message alongside with router capabilities // to the client right after WebSocket connection is established let server_init_message = ServerMessage::Init { consumer_transport_options: TransportOptions { id: self.transports.consumer.id(), dtls_parameters: self.transports.consumer.dtls_parameters(), ice_candidates: self.transports.consumer.ice_candidates().clone(), ice_parameters: self.transports.consumer.ice_parameters().clone(), }, producer_transport_options: TransportOptions { id: self.transports.producer.id(), dtls_parameters: self.transports.producer.dtls_parameters(), ice_candidates: self.transports.producer.ice_candidates().clone(), ice_parameters: self.transports.producer.ice_parameters().clone(), }, router_rtp_capabilities: self.router.rtp_capabilities().clone(), }; ctx.address().do_send(server_init_message); } fn stopped(&mut self, _ctx: &mut Self::Context) { println!("WebSocket connection closed"); } } impl StreamHandler> for SvcSimulcastConnection { fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { // Here we handle incoming WebSocket messages, intentionally not handling continuation // messages since we know all messages will fit into a single frame, but in real-world apps // you need to handle continuation frames too (`ws::Message::Continuation`) match msg { Ok(ws::Message::Ping(msg)) => { ctx.pong(&msg); } Ok(ws::Message::Pong(_)) => {} Ok(ws::Message::Text(text)) => match serde_json::from_str::(&text) { Ok(message) => { // Parse JSON into an enum and just send it back to the actor to be processed // by another handler below, it is much more convenient to just parse it in one // place and have typed data structure everywhere else ctx.address().do_send(message); } Err(error) => { eprintln!("Failed to parse client message: {text}\n{error}"); } }, Ok(ws::Message::Binary(bin)) => { eprintln!("Unexpected binary message: {bin:?}"); } Ok(ws::Message::Close(reason)) => { ctx.close(reason); ctx.stop(); } _ => ctx.stop(), } } } impl Handler for SvcSimulcastConnection { type Result = (); fn handle(&mut self, message: ClientMessage, ctx: &mut Self::Context) { match message { ClientMessage::Init { rtp_capabilities } => { // We need to know client's RTP capabilities, those are sent using initialization // message and are stored in connection struct for future use self.client_rtp_capabilities.replace(rtp_capabilities); } ClientMessage::SetConsumerPreferredLayers { id, preferred_layers, } => { if let Some(consumer) = self.consumers.get(&id).cloned() { actix::spawn(async move { match consumer.set_preferred_layers(preferred_layers).await { Ok(_) => { println!( "Successfully set preferred layers {:?} consumer {}", preferred_layers, consumer.id(), ); } Err(error) => { println!( "Failed to set preferred layers {:?} consumer {}: {}", preferred_layers, consumer.id(), error, ); } } }); } } ClientMessage::ConnectProducerTransport { dtls_parameters } => { let address = ctx.address(); let transport = self.transports.producer.clone(); // Establish connection for producer transport using DTLS parameters received // from the client, but doing so in a background task since this handler is // synchronous actix::spawn(async move { match transport .connect(WebRtcTransportRemoteParameters { dtls_parameters }) .await { Ok(_) => { address.do_send(ServerMessage::ConnectedProducerTransport); println!("Producer transport connected"); } Err(error) => { eprintln!("Failed to connect producer transport: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::Produce { kind, rtp_parameters, } => { let address = ctx.address(); let transport = self.transports.producer.clone(); // Use producer transport to create a new producer on the server with given RTP // parameters actix::spawn(async move { match transport .produce(ProducerOptions::new(kind, rtp_parameters)) .await { Ok(producer) => { let id = producer.id(); address.do_send(ServerMessage::Produced { id }); // Producer is stored in a hashmap since if we don't do it, it will get // destroyed as soon as its instance goes out out scope address.do_send(InternalMessage::SaveProducer(producer)); println!("{kind:?} producer created: {id}"); } Err(error) => { eprintln!("Failed to create {kind:?} producer: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::ConnectConsumerTransport { dtls_parameters } => { let address = ctx.address(); let transport = self.transports.consumer.clone(); // The same as producer transport, but for consumer transport actix::spawn(async move { match transport .connect(WebRtcTransportRemoteParameters { dtls_parameters }) .await { Ok(_) => { address.do_send(ServerMessage::ConnectedConsumerTransport); println!("Consumer transport connected"); } Err(error) => { eprintln!("Failed to connect consumer transport: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::Consume { producer_id } => { let address = ctx.address(); let transport = self.transports.consumer.clone(); let rtp_capabilities = match self.client_rtp_capabilities.clone() { Some(rtp_capabilities) => rtp_capabilities, None => { eprintln!("Client should send RTP capabilities before consuming"); return; } }; // Create consumer for given producer ID, while first making sure that RTP // capabilities were sent by the client prior to that actix::spawn(async move { let mut options = ConsumerOptions::new(producer_id, rtp_capabilities); options.paused = true; match transport.consume(options).await { Ok(consumer) => { let id = consumer.id(); let kind = consumer.kind(); let rtp_parameters = consumer.rtp_parameters().clone(); address.do_send(ServerMessage::Consumed { id, producer_id, kind, rtp_parameters, }); // Consumer is stored in a hashmap since if we don't do it, it will get // destroyed as soon as its instance goes out out scope address.do_send(InternalMessage::SaveConsumer(consumer)); println!("{kind:?} consumer created: {id}"); } Err(error) => { eprintln!("Failed to create consumer: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::ConsumerResume { id } => { if let Some(consumer) = self.consumers.get(&id).cloned() { actix::spawn(async move { match consumer.resume().await { Ok(_) => { println!( "Successfully resumed {:?} consumer {}", consumer.kind(), consumer.id(), ); } Err(error) => { println!( "Failed to resume {:?} consumer {}: {}", consumer.kind(), consumer.id(), error, ); } } }); } } } } } /// Simple handler that will transform typed server messages into JSON and send them over to the /// client over WebSocket connection impl Handler for SvcSimulcastConnection { type Result = (); fn handle(&mut self, message: ServerMessage, ctx: &mut Self::Context) { ctx.text(serde_json::to_string(&message).unwrap()); } } /// Convenience handler for internal messages, these actions require mutable access to the /// connection struct and having such message handler makes it easy to use from background tasks /// where otherwise Mutex would have to be used instead impl Handler for SvcSimulcastConnection { type Result = (); fn handle(&mut self, message: InternalMessage, ctx: &mut Self::Context) { match message { InternalMessage::Stop => { ctx.stop(); } InternalMessage::SaveProducer(producer) => { // Retain producer to prevent it from being destroyed self.producers.push(producer); } InternalMessage::SaveConsumer(consumer) => { self.consumers.insert(consumer.id(), consumer); } } } } /// Function that receives HTTP request on WebSocket route and upgrades it to WebSocket connection. /// /// See https://actix.rs/docs/websockets/ for official `actix-web` documentation. async fn ws_index( request: HttpRequest, worker_manager: Data, stream: Payload, ) -> Result { match SvcSimulcastConnection::new(&worker_manager).await { Ok(svc_simulcast_connection) => ws::start(svc_simulcast_connection, &request, stream), Err(error) => { eprintln!("{error}"); Ok(HttpResponse::InternalServerError().finish()) } } } #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init(); // We will reuse the same worker manager across all connections, this is more than enough for // this use case let worker_manager = Data::new(WorkerManager::new()); HttpServer::new(move || { App::new() .app_data(worker_manager.clone()) .route("/ws", web::get().to(ws_index)) }) // 2 threads is plenty for this example, default is to have as many threads as CPU cores .workers(2) .bind("127.0.0.1:3000")? .run() .await } ================================================ FILE: rust/examples/videoroom.rs ================================================ use crate::participant::ParticipantConnection; use crate::room::RoomId; use crate::rooms_registry::RoomsRegistry; use actix_web::web::{Data, Payload, Query}; use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer}; use actix_web_actors::ws; use mediasoup::prelude::*; use serde::Deserialize; use std::num::{NonZeroU32, NonZeroU8}; mod room { use crate::participant::ParticipantId; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use mediasoup::prelude::*; use mediasoup::worker::{WorkerLogLevel, WorkerLogTag}; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt; use std::sync::{Arc, Weak}; use uuid::Uuid; #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)] pub struct RoomId(Uuid); impl fmt::Display for RoomId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl RoomId { pub fn new() -> Self { Self(Uuid::new_v4()) } } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { producer_add: Bag, ParticipantId, Producer>, producer_remove: Bag, ParticipantId, ProducerId>, close: BagOnce>, } struct Inner { id: RoomId, router: Router, handlers: Handlers, clients: Mutex>>, } impl fmt::Debug for Inner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Inner") .field("id", &self.id) .field("handlers", &"...") .field("clients", &self.clients) .finish() } } impl Drop for Inner { fn drop(&mut self) { println!("Room {} closed", self.id); self.handlers.close.call_simple(); } } /// Room holds producers of the participants such that other participants can consume audio and /// video tracks of each other #[derive(Debug, Clone)] pub struct Room { inner: Arc, } impl Room { /// Create new `Room` with random `RoomId` pub async fn new(worker_manager: &WorkerManager) -> Result { Self::new_with_id(worker_manager, RoomId::new()).await } /// Create new `Room` with a specific `RoomId` pub async fn new_with_id( worker_manager: &WorkerManager, id: RoomId, ) -> Result { let worker = worker_manager .create_worker({ let mut settings = WorkerSettings::default(); settings.log_level = WorkerLogLevel::Debug; settings.log_tags = vec![ WorkerLogTag::Info, WorkerLogTag::Ice, WorkerLogTag::Dtls, WorkerLogTag::Rtp, WorkerLogTag::Srtp, WorkerLogTag::Rtcp, WorkerLogTag::Rtx, WorkerLogTag::Bwe, WorkerLogTag::Score, WorkerLogTag::Simulcast, WorkerLogTag::Svc, WorkerLogTag::Sctp, WorkerLogTag::Message, ]; settings }) .await .map_err(|error| format!("Failed to create worker: {error}"))?; let router = worker .create_router(RouterOptions::new(crate::media_codecs())) .await .map_err(|error| format!("Failed to create router: {error}"))?; println!("Room {id} created"); Ok(Self { inner: Arc::new(Inner { id, router, handlers: Handlers::default(), clients: Mutex::default(), }), }) } /// ID of the room pub fn id(&self) -> RoomId { self.inner.id } /// Get router associated with this room pub fn router(&self) -> &Router { &self.inner.router } /// Add producer to the room, this will trigger notifications to other participants that /// will be able to consume it pub fn add_producer(&self, participant_id: ParticipantId, producer: Producer) { self.inner .clients .lock() .entry(participant_id) .or_default() .push(producer.clone()); self.inner .handlers .producer_add .call_simple(&participant_id, &producer); } /// Remove participant and all of its associated producers pub fn remove_participant(&self, participant_id: &ParticipantId) { let producers = self.inner.clients.lock().remove(participant_id); for producer in producers.unwrap_or_default() { let producer_id = &producer.id(); self.inner .handlers .producer_remove .call_simple(participant_id, producer_id); } } /// Get all producers of all participants, useful when new participant connects and needs to /// consume tracks of everyone who is already in the room pub fn get_all_producers(&self) -> Vec<(ParticipantId, ProducerId)> { self.inner .clients .lock() .iter() .flat_map(|(participant_id, producers)| { let participant_id = *participant_id; producers .iter() .map(move |producer| (participant_id, producer.id())) }) .collect() } /// Subscribe to notifications when new producer is added to the room pub fn on_producer_add( &self, callback: F, ) -> HandlerId { self.inner.handlers.producer_add.add(Arc::new(callback)) } /// Subscribe to notifications when producer is removed from the room pub fn on_producer_remove( &self, callback: F, ) -> HandlerId { self.inner.handlers.producer_remove.add(Arc::new(callback)) } /// Subscribe to notification when room is closed pub fn on_close(&self, callback: F) -> HandlerId { self.inner.handlers.close.add(Box::new(callback)) } /// Get `WeakRoom` that can later be upgraded to `Room`, but will not prevent room from /// being destroyed pub fn downgrade(&self) -> WeakRoom { WeakRoom { inner: Arc::downgrade(&self.inner), } } } /// Similar to `Room`, but doesn't prevent room from being destroyed #[derive(Debug, Clone)] pub struct WeakRoom { inner: Weak, } impl WeakRoom { /// Upgrade `WeakRoom` to `Room`, may return `None` if underlying room was destroyed already pub fn upgrade(&self) -> Option { self.inner.upgrade().map(|inner| Room { inner }) } } } mod rooms_registry { use crate::room::{Room, RoomId, WeakRoom}; use async_lock::Mutex; use mediasoup::prelude::*; use std::collections::hash_map::Entry; use std::collections::HashMap; use std::sync::Arc; #[derive(Debug, Default, Clone)] pub struct RoomsRegistry { // We store `WeakRoom` instead of full `Room` to avoid cycles and to not prevent rooms from // being destroyed when last participant disconnects rooms: Arc>>, } impl RoomsRegistry { /// Retrieves existing room or creates a new one with specified `RoomId` pub async fn get_or_create_room( &self, worker_manager: &WorkerManager, room_id: RoomId, ) -> Result { let mut rooms = self.rooms.lock().await; match rooms.entry(room_id) { Entry::Occupied(mut entry) => match entry.get().upgrade() { Some(room) => Ok(room), None => { let room = Room::new_with_id(worker_manager, room_id).await?; entry.insert(room.downgrade()); room.on_close({ let room_id = room.id(); let rooms = Arc::clone(&self.rooms); move || { std::thread::spawn(move || { futures_lite::future::block_on(async move { rooms.lock().await.remove(&room_id); }); }); } }) .detach(); Ok(room) } }, Entry::Vacant(entry) => { let room = Room::new_with_id(worker_manager, room_id).await?; entry.insert(room.downgrade()); room.on_close({ let room_id = room.id(); let rooms = Arc::clone(&self.rooms); move || { std::thread::spawn(move || { futures_lite::future::block_on(async move { rooms.lock().await.remove(&room_id); }); }); } }) .detach(); Ok(room) } } } /// Create new room with random `RoomId` pub async fn create_room(&self, worker_manager: &WorkerManager) -> Result { let mut rooms = self.rooms.lock().await; let room = Room::new(worker_manager).await?; rooms.insert(room.id(), room.downgrade()); room.on_close({ let room_id = room.id(); let rooms = Arc::clone(&self.rooms); move || { std::thread::spawn(move || { futures_lite::future::block_on(async move { rooms.lock().await.remove(&room_id); }); }); } }) .detach(); Ok(room) } } } mod participant { use crate::participant::messages::{ ClientMessage, InternalMessage, ServerMessage, TransportOptions, }; use crate::room::Room; use actix::prelude::*; use actix_web_actors::ws; use event_listener_primitives::HandlerId; use mediasoup::prelude::*; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt; use std::net::{IpAddr, Ipv4Addr}; use uuid::Uuid; mod messages { use crate::participant::ParticipantId; use crate::room::RoomId; use actix::prelude::*; use mediasoup::prelude::*; use serde::{Deserialize, Serialize}; /// Data structure containing all the necessary information about transport options required /// from the server to establish transport connection on the client #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct TransportOptions { pub id: TransportId, pub dtls_parameters: DtlsParameters, pub ice_candidates: Vec, pub ice_parameters: IceParameters, } /// Server messages sent to the client #[derive(Serialize, Message)] #[serde(tag = "action")] #[rtype(result = "()")] #[allow(clippy::large_enum_variant)] pub enum ServerMessage { /// Initialization message with consumer/producer transport options and Router's RTP /// capabilities necessary to establish WebRTC transport connection client-side #[serde(rename_all = "camelCase")] Init { room_id: RoomId, consumer_transport_options: TransportOptions, producer_transport_options: TransportOptions, router_rtp_capabilities: RtpCapabilitiesFinalized, }, /// Notification that new producer was added to the room #[serde(rename_all = "camelCase")] ProducerAdded { participant_id: ParticipantId, producer_id: ProducerId, }, /// Notification that producer was removed from the room #[serde(rename_all = "camelCase")] ProducerRemoved { participant_id: ParticipantId, producer_id: ProducerId, }, /// Notification that producer transport was connected successfully (in case of error /// connection is just dropped, in real-world application you probably want to handle it /// better) ConnectedProducerTransport, /// Notification that producer was created on the server #[serde(rename_all = "camelCase")] Produced { id: ProducerId }, /// Notification that consumer transport was connected successfully (in case of error /// connection is just dropped, in real-world application you probably want to handle it /// better) ConnectedConsumerTransport, /// Notification that consumer was successfully created server-side, client can resume /// the consumer after this #[serde(rename_all = "camelCase")] Consumed { id: ConsumerId, producer_id: ProducerId, kind: MediaKind, rtp_parameters: RtpParameters, }, } /// Client messages sent to the server #[derive(Deserialize, Message)] #[serde(tag = "action")] #[rtype(result = "()")] pub enum ClientMessage { /// Client-side initialization with its RTP capabilities, in this simple case we expect /// those to match server Router's RTP capabilities #[serde(rename_all = "camelCase")] Init { rtp_capabilities: RtpCapabilities }, /// Request to connect producer transport with client-side DTLS parameters #[serde(rename_all = "camelCase")] ConnectProducerTransport { dtls_parameters: DtlsParameters }, /// Request to produce a new audio or video track with specified RTP parameters #[serde(rename_all = "camelCase")] Produce { kind: MediaKind, rtp_parameters: RtpParameters, }, /// Request to connect consumer transport with client-side DTLS parameters #[serde(rename_all = "camelCase")] ConnectConsumerTransport { dtls_parameters: DtlsParameters }, /// Request to consume specified producer #[serde(rename_all = "camelCase")] Consume { producer_id: ProducerId }, /// Request to resume consumer that was previously created #[serde(rename_all = "camelCase")] ConsumerResume { id: ConsumerId }, } /// Internal actor messages for convenience #[derive(Message)] #[rtype(result = "()")] pub enum InternalMessage { /// Save producer in connection-specific hashmap to prevent it from being destroyed SaveProducer(Producer), /// Save consumer in connection-specific hashmap to prevent it from being destroyed SaveConsumer(Consumer), /// Stop/close the WebSocket connection Stop, } } #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)] pub struct ParticipantId(Uuid); impl fmt::Display for ParticipantId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl ParticipantId { fn new() -> Self { Self(Uuid::new_v4()) } } /// Consumer/producer transports pair for the client struct Transports { consumer: WebRtcTransport, producer: WebRtcTransport, } /// Actor that will represent WebSocket connection from the client, it will handle inbound and /// outbound WebSocket messages in JSON. /// /// See https://actix.rs/docs/websockets/ for official `actix-web` documentation. pub struct ParticipantConnection { id: ParticipantId, /// RTP capabilities received from the client client_rtp_capabilities: Option, /// Consumers associated with this client, preventing them from being destroyed consumers: HashMap, /// Producers associated with this client, preventing them from being destroyed producers: Vec, /// Consumer and producer transports associated with this client transports: Transports, /// Room to which the client belongs room: Room, /// Event handlers that were attached and need to be removed when participant connection is /// destroyed attached_handlers: Vec, } impl Drop for ParticipantConnection { fn drop(&mut self) { self.room.remove_participant(&self.id); } } impl ParticipantConnection { /// Create a new instance representing WebSocket connection pub async fn new(room: Room) -> Result { // We know that for videoroom example we'll need 2 transports, so we can create both // right away. This may not be the case for real-world applications or you may create // this at a different time and/or in different order. let transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); let producer_transport = room .router() .create_webrtc_transport(transport_options.clone()) .await .map_err(|error| format!("Failed to create producer transport: {error}"))?; let consumer_transport = room .router() .create_webrtc_transport(transport_options) .await .map_err(|error| format!("Failed to create consumer transport: {error}"))?; Ok(Self { id: ParticipantId::new(), client_rtp_capabilities: None, consumers: HashMap::new(), producers: vec![], transports: Transports { consumer: consumer_transport, producer: producer_transport, }, room, attached_handlers: Vec::new(), }) } } impl Actor for ParticipantConnection { type Context = ws::WebsocketContext; fn started(&mut self, ctx: &mut Self::Context) { println!("[participant_id {}] WebSocket connection created", self.id); // We know that both consumer and producer transports will be used, so we sent server // information about both in an initialization message alongside with router // capabilities to the client right after WebSocket connection is established let server_init_message = ServerMessage::Init { room_id: self.room.id(), consumer_transport_options: TransportOptions { id: self.transports.consumer.id(), dtls_parameters: self.transports.consumer.dtls_parameters(), ice_candidates: self.transports.consumer.ice_candidates().clone(), ice_parameters: self.transports.consumer.ice_parameters().clone(), }, producer_transport_options: TransportOptions { id: self.transports.producer.id(), dtls_parameters: self.transports.producer.dtls_parameters(), ice_candidates: self.transports.producer.ice_candidates().clone(), ice_parameters: self.transports.producer.ice_parameters().clone(), }, router_rtp_capabilities: self.room.router().rtp_capabilities().clone(), }; let address = ctx.address(); address.do_send(server_init_message); // Listen for new producers added to the room self.attached_handlers.push(self.room.on_producer_add({ let own_participant_id = self.id; let address = address.clone(); move |participant_id, producer| { if &own_participant_id == participant_id { return; } address.do_send(ServerMessage::ProducerAdded { participant_id: *participant_id, producer_id: producer.id(), }); } })); // Listen for producers removed from the the room self.attached_handlers.push(self.room.on_producer_remove({ let own_participant_id = self.id; let address = address.clone(); move |participant_id, producer_id| { if &own_participant_id == participant_id { return; } address.do_send(ServerMessage::ProducerRemoved { participant_id: *participant_id, producer_id: *producer_id, }); } })); // Notify client about any producers that already exist in the room for (participant_id, producer_id) in self.room.get_all_producers() { address.do_send(ServerMessage::ProducerAdded { participant_id, producer_id, }); } } fn stopped(&mut self, _ctx: &mut Self::Context) { println!("[participant_id {0}] WebSocket connection closed", self.id); } } impl StreamHandler> for ParticipantConnection { fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { // Here we handle incoming WebSocket messages, intentionally not handling continuation // messages since we know all messages will fit into a single frame, but in real-world // apps you need to handle continuation frames too (`ws::Message::Continuation`) match msg { Ok(ws::Message::Ping(msg)) => { ctx.pong(&msg); } Ok(ws::Message::Pong(_)) => {} Ok(ws::Message::Text(text)) => match serde_json::from_str::(&text) { Ok(message) => { // Parse JSON into an enum and just send it back to the actor to be // processed by another handler below, it is much more convenient to just // parse it in one place and have typed data structure everywhere else ctx.address().do_send(message); } Err(error) => { eprintln!("Failed to parse client message: {error}\n{text}"); } }, Ok(ws::Message::Binary(bin)) => { eprintln!("Unexpected binary message: {bin:?}"); } Ok(ws::Message::Close(reason)) => { ctx.close(reason); ctx.stop(); } _ => ctx.stop(), } } } impl Handler for ParticipantConnection { type Result = (); fn handle(&mut self, message: ClientMessage, ctx: &mut Self::Context) { match message { ClientMessage::Init { rtp_capabilities } => { // We need to know client's RTP capabilities, those are sent using // initialization message and are stored in connection struct for future use self.client_rtp_capabilities.replace(rtp_capabilities); } ClientMessage::ConnectProducerTransport { dtls_parameters } => { let participant_id = self.id; let address = ctx.address(); let transport = self.transports.producer.clone(); // Establish connection for producer transport using DTLS parameters received // from the client, but doing so in a background task since this handler is // synchronous actix::spawn(async move { match transport .connect(WebRtcTransportRemoteParameters { dtls_parameters }) .await { Ok(_) => { address.do_send(ServerMessage::ConnectedProducerTransport); println!( "[participant_id {participant_id}] Producer transport connected" ); } Err(error) => { eprintln!("Failed to connect producer transport: {error}"); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::Produce { kind, rtp_parameters, } => { let participant_id = self.id; let address = ctx.address(); let transport = self.transports.producer.clone(); let room = self.room.clone(); // Use producer transport to create a new producer on the server with given RTP // parameters actix::spawn(async move { match transport .produce(ProducerOptions::new(kind, rtp_parameters)) .await { Ok(producer) => { let id = producer.id(); address.do_send(ServerMessage::Produced { id }); // Add producer to the room so that others can consume it room.add_producer(participant_id, producer.clone()); // Producer is stored in a hashmap since if we don't do it, it will // get destroyed as soon as its instance goes out out scope address.do_send(InternalMessage::SaveProducer(producer)); println!( "[participant_id {participant_id}] {kind:?} producer created: {id}" ); } Err(error) => { eprintln!( "[participant_id {participant_id}] Failed to create {kind:?} producer: {error}" ); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::ConnectConsumerTransport { dtls_parameters } => { let participant_id = self.id; let address = ctx.address(); let transport = self.transports.consumer.clone(); // The same as producer transport, but for consumer transport actix::spawn(async move { match transport .connect(WebRtcTransportRemoteParameters { dtls_parameters }) .await { Ok(_) => { address.do_send(ServerMessage::ConnectedConsumerTransport); println!( "[participant_id {participant_id}] Consumer transport connected" ); } Err(error) => { eprintln!( "[participant_id {participant_id}] Failed to connect consumer transport: {error}" ); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::Consume { producer_id } => { let participant_id = self.id; let address = ctx.address(); let transport = self.transports.consumer.clone(); let rtp_capabilities = match self.client_rtp_capabilities.clone() { Some(rtp_capabilities) => rtp_capabilities, None => { eprintln!( "[participant_id {participant_id}] Client should send RTP capabilities before \ consuming" ); return; } }; // Create consumer for given producer ID, while first making sure that RTP // capabilities were sent by the client prior to that actix::spawn(async move { let mut options = ConsumerOptions::new(producer_id, rtp_capabilities); options.paused = true; match transport.consume(options).await { Ok(consumer) => { let id = consumer.id(); let kind = consumer.kind(); let rtp_parameters = consumer.rtp_parameters().clone(); address.do_send(ServerMessage::Consumed { id, producer_id, kind, rtp_parameters, }); // Consumer is stored in a hashmap since if we don't do it, it will // get destroyed as soon as its instance goes out out scope address.do_send(InternalMessage::SaveConsumer(consumer)); println!( "[participant_id {participant_id}] {kind:?} consumer created: {id}" ); } Err(error) => { eprintln!( "[participant_id {participant_id}] Failed to create consumer: {error}" ); address.do_send(InternalMessage::Stop); } } }); } ClientMessage::ConsumerResume { id } => { if let Some(consumer) = self.consumers.get(&id).cloned() { let participant_id = self.id; actix::spawn(async move { match consumer.resume().await { Ok(_) => { println!( "[participant_id {}] Successfully resumed {:?} consumer {}", participant_id, consumer.kind(), consumer.id(), ); } Err(error) => { println!( "[participant_id {}] Failed to resume {:?} consumer {}: {}", participant_id, consumer.kind(), consumer.id(), error, ); } } }); } } } } } /// Simple handler that will transform typed server messages into JSON and send them over to the /// client over WebSocket connection impl Handler for ParticipantConnection { type Result = (); fn handle(&mut self, message: ServerMessage, ctx: &mut Self::Context) { ctx.text(serde_json::to_string(&message).unwrap()); } } /// Convenience handler for internal messages, these actions require mutable access to the /// connection struct and having such message handler makes it easy to use from background tasks /// where otherwise Mutex would have to be used instead impl Handler for ParticipantConnection { type Result = (); fn handle(&mut self, message: InternalMessage, ctx: &mut Self::Context) { match message { InternalMessage::Stop => { ctx.stop(); } InternalMessage::SaveProducer(producer) => { // Retain producer to prevent it from being destroyed self.producers.push(producer); } InternalMessage::SaveConsumer(consumer) => { self.consumers.insert(consumer.id(), consumer); } } } } } /// List of codecs that SFU will accept from clients fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([("useinbandfec", 1_u32.into())]), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, ] } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct QueryParameters { room_id: Option, } /// Function that receives HTTP request on WebSocket route and upgrades it to WebSocket connection. /// /// See https://actix.rs/docs/websockets/ for official `actix-web` documentation. async fn ws_index( query_parameters: Query, request: HttpRequest, worker_manager: Data, rooms_registry: Data, stream: Payload, ) -> Result { let room = match query_parameters.room_id { Some(room_id) => { rooms_registry .get_or_create_room(&worker_manager, room_id) .await } None => rooms_registry.create_room(&worker_manager).await, }; let room = match room { Ok(room) => room, Err(error) => { eprintln!("{error}"); return Ok(HttpResponse::InternalServerError().finish()); } }; match ParticipantConnection::new(room).await { Ok(echo_server) => ws::start(echo_server, &request, stream), Err(error) => { eprintln!("{error}"); Ok(HttpResponse::InternalServerError().finish()) } } } #[actix_web::main] async fn main() -> std::io::Result<()> { env_logger::init(); // We will reuse the same worker manager across all connections, this is more than enough for // this use case let worker_manager = Data::new(WorkerManager::new()); // Rooms registry will hold all the active rooms let rooms_registry = Data::new(RoomsRegistry::default()); HttpServer::new(move || { App::new() .app_data(worker_manager.clone()) .app_data(rooms_registry.clone()) .route("/ws", web::get().to(ws_index)) }) // 2 threads is plenty for this example, default is to have as many threads as CPU cores .workers(2) .bind("127.0.0.1:3000")? .run() .await } ================================================ FILE: rust/examples-frontend/echo/.eslintrc.js ================================================ module.exports = { env : { browser : true, es6 : true }, parserOptions : { project : 'tsconfig.json', sourceType : 'module' } }; ================================================ FILE: rust/examples-frontend/echo/index.html ================================================
Send preview
Receive preview
================================================ FILE: rust/examples-frontend/echo/package.json ================================================ { "version": "0.0.0", "private": true, "engines": { "node": ">=14.8" }, "scripts": { "start": "webpack serve" }, "author": "Nazar Mokrynskyi ", "dependencies": { "mediasoup-client": "^3.6.51" }, "devDependencies": { "ts-loader": "^9.2.8", "typescript": "^4.6.3", "webpack": "^5.71.0", "webpack-cli": "^4.9.2", "webpack-dev-server": "^5.2.2" } } ================================================ FILE: rust/examples-frontend/echo/src/index.ts ================================================ /* eslint-disable no-console */ import { Device } from 'mediasoup-client'; import { MediaKind, RtpCapabilities, RtpParameters } from 'mediasoup-client/lib/RtpParameters'; import { DtlsParameters, TransportOptions, Transport } from 'mediasoup-client/lib/Transport'; import { ConsumerOptions } from 'mediasoup-client/lib/Consumer'; type Brand = K & { __brand: T }; type ConsumerId = Brand; type ProducerId = Brand; interface ServerInit { action: 'Init'; consumerTransportOptions: TransportOptions; producerTransportOptions: TransportOptions; routerRtpCapabilities: RtpCapabilities; } interface ServerConnectedProducerTransport { action: 'ConnectedProducerTransport'; } interface ServerProduced { action: 'Produced'; id: ProducerId; } interface ServerConnectedConsumerTransport { action: 'ConnectedConsumerTransport'; } interface ServerConsumed { action: 'Consumed'; id: ConsumerId; kind: MediaKind; rtpParameters: RtpParameters; } type ServerMessage = ServerInit | ServerConnectedProducerTransport | ServerProduced | ServerConnectedConsumerTransport | ServerConsumed; interface ClientInit { action: 'Init'; rtpCapabilities: RtpCapabilities; } interface ClientConnectProducerTransport { action: 'ConnectProducerTransport'; dtlsParameters: DtlsParameters; } interface ClientConnectConsumerTransport { action: 'ConnectConsumerTransport'; dtlsParameters: DtlsParameters; } interface ClientProduce { action: 'Produce'; kind: MediaKind; rtpParameters: RtpParameters; } interface ClientConsume { action: 'Consume'; producerId: ProducerId; } interface ClientConsumerResume { action: 'ConsumerResume'; id: ConsumerId; } type ClientMessage = ClientInit | ClientConnectProducerTransport | ClientProduce | ClientConnectConsumerTransport | ClientConsume | ClientConsumerResume; async function init() { const sendPreview = document.querySelector('#preview-send') as HTMLVideoElement; const receivePreview = document.querySelector('#preview-receive') as HTMLVideoElement; sendPreview.onloadedmetadata = () => { sendPreview.play(); }; receivePreview.onloadedmetadata = () => { receivePreview.play(); }; const receiveMediaStream = new MediaStream(); const ws = new WebSocket('ws://localhost:3000/ws'); function send(message: ClientMessage) { ws.send(JSON.stringify(message)); } const device = new Device(); let producerTransport: Transport | undefined; let consumerTransport: Transport | undefined; { const waitingForResponse: Map = new Map(); ws.onmessage = async (message) => { const decodedMessage: ServerMessage = JSON.parse(message.data); switch (decodedMessage.action) { case 'Init': { // It is expected that server will send initialization message right after // WebSocket connection is established await device.load({ routerRtpCapabilities : decodedMessage.routerRtpCapabilities }); // Send client-side initialization message back right away send({ action : 'Init', rtpCapabilities : device.rtpCapabilities }); // Producer transport is needed to send audio and video to SFU producerTransport = device.createSendTransport( decodedMessage.producerTransportOptions ); producerTransport .on('connect', ({ dtlsParameters }, success) => { // Send request to establish producer transport connection send({ action : 'ConnectProducerTransport', dtlsParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('ConnectedProducerTransport', () => { success(); console.log('Producer transport connected'); }); }) .on('produce', ({ kind, rtpParameters }, success) => { // Once connection is established, send request to produce // audio or video track send({ action : 'Produce', kind, rtpParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('Produced', ({ id }: {id: string}) => { success({ id }); }); }); // Request microphone and camera access, in real-world apps you may want // to do this separately so that audio-only and video-only cases are // handled nicely instead of failing completely const mediaStream = await navigator.mediaDevices.getUserMedia({ audio : true, video : { width : { ideal : 1280 }, height : { ideal : 720 }, frameRate : { ideal : 60 } } }); sendPreview.srcObject = mediaStream; const producers = []; // And create producers for all tracks that were previously requested for (const track of mediaStream.getTracks()) { const producer = await producerTransport.produce({ track }); producers.push(producer); console.log(`${track.kind} producer created:`, producer); } // Consumer transport is now needed to receive previously produced // tracks back consumerTransport = device.createRecvTransport( decodedMessage.consumerTransportOptions ); consumerTransport .on('connect', ({ dtlsParameters }, success) => { // Send request to establish consumer transport connection send({ action : 'ConnectConsumerTransport', dtlsParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('ConnectedConsumerTransport', () => { success(); console.log('Consumer transport connected'); }); }); // For simplicity of this example producers were stored in an array // and are now all consumed one at a time for (const producer of producers) { await new Promise((resolve) => { // Send request to consume producer send({ action : 'Consume', producerId : producer.id as ProducerId }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('Consumed', async (consumerOptions: ConsumerOptions) => { // Once confirmation is received, corresponding consumer // can be created client-side const consumer = await (consumerTransport as Transport).consume( consumerOptions ); console.log(`${consumer.kind} consumer created:`, consumer); // Consumer needs to be resumed after being created in // paused state (see official documentation about why: // https://mediasoup.org/documentation/v3/mediasoup/api/#transport-consume) send({ action : 'ConsumerResume', id : consumer.id as ConsumerId }); receiveMediaStream.addTrack(consumer.track); receivePreview.srcObject = receiveMediaStream; resolve(undefined); }); }); } break; } default: { // All messages other than initialization go here and are assumed // to be notifications that correspond to previously sent requests const callback = waitingForResponse.get(decodedMessage.action); if (callback) { waitingForResponse.delete(decodedMessage.action); callback(decodedMessage); } else { console.error('Received unexpected message', decodedMessage); } } } }; } ws.onerror = console.error; } init(); ================================================ FILE: rust/examples-frontend/echo/tsconfig.json ================================================ { "compilerOptions": { "module": "es6", "target": "es2018", "sourceMap": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "moduleResolution": "node", "allowSyntheticDefaultImports": false, "sourceRoot": "src", "baseUrl": "src", "outDir": "dist", "strict": true, "noUnusedParameters": true, "noUnusedLocals": true, "lib": [ "es5", "es2015", "es2019", "dom" ] }, "include": [ "src", ".eslintrc.js", "webpack.config.js" ] } ================================================ FILE: rust/examples-frontend/echo/webpack.config.js ================================================ const path = require('path'); module.exports = { mode : 'development', entry : './src/index.ts', module : { rules : [ { test : /\.ts$/, use : 'ts-loader', exclude : /node_modules/ } ] }, resolve : { extensions : [ '.ts', '.js' ] }, output : { filename : 'dist/bundle.js', path : path.resolve(__dirname, 'dist') }, devServer : { liveReload : false, port : 3001, static : { directory: __dirname } } }; ================================================ FILE: rust/examples-frontend/multiopus/.eslintrc.js ================================================ module.exports = { env : { browser : true, es6 : true }, parserOptions : { project : 'tsconfig.json', sourceType : 'module' } }; ================================================ FILE: rust/examples-frontend/multiopus/index.html ================================================
Receive preview (see server-side example logs for sending audio instructions)
================================================ FILE: rust/examples-frontend/multiopus/package.json ================================================ { "version": "0.0.0", "private": true, "engines": { "node": ">=14.8" }, "scripts": { "start": "webpack serve" }, "author": "Nazar Mokrynskyi ", "dependencies": { "mediasoup-client": "^3.6.51" }, "devDependencies": { "ts-loader": "^9.2.8", "typescript": "^4.6.3", "webpack": "^5.71.0", "webpack-cli": "^4.9.2", "webpack-dev-server": "^5.2.2" } } ================================================ FILE: rust/examples-frontend/multiopus/src/index.ts ================================================ /* eslint-disable no-console */ import { Device } from 'mediasoup-client'; import { MediaKind, RtpCapabilities, RtpParameters } from 'mediasoup-client/lib/RtpParameters'; import { DtlsParameters, TransportOptions, Transport } from 'mediasoup-client/lib/Transport'; import { ConsumerOptions } from 'mediasoup-client/lib/Consumer'; import { getExtendedRtpCapabilities } from 'mediasoup-client/lib/ortc'; type Brand = K & { __brand: T }; type ConsumerId = Brand; type ProducerId = Brand; interface ServerInit { action: 'Init'; consumerTransportOptions: TransportOptions; routerRtpCapabilities: RtpCapabilities; rtpProducerId: ProducerId; } interface ServerConnectedConsumerTransport { action: 'ConnectedConsumerTransport'; } interface ServerConsumed { action: 'Consumed'; id: ConsumerId; kind: MediaKind; rtpParameters: RtpParameters; } type ServerMessage = ServerInit | ServerConnectedConsumerTransport | ServerConsumed; interface ClientInit { action: 'Init'; rtpCapabilities: RtpCapabilities; } interface ClientConnectConsumerTransport { action: 'ConnectConsumerTransport'; dtlsParameters: DtlsParameters; } interface ClientConsume { action: 'Consume'; producerId: ProducerId; } type ClientMessage = ClientInit | ClientConnectConsumerTransport | ClientConsume; async function init() { const receivePreview = document.querySelector('#preview-receive') as HTMLAudioElement; receivePreview.onloadedmetadata = () => { console.log('onloadedmetadata'); receivePreview.play(); }; const receiveMediaStream = new MediaStream(); const ws = new WebSocket('ws://localhost:3000/ws'); function send(message: ClientMessage) { ws.send(JSON.stringify(message)); } const device = new Device(); let consumerTransport: Transport | undefined; { const waitingForResponse: Map = new Map(); ws.onmessage = async (message) => { const decodedMessage: ServerMessage = JSON.parse(message.data); switch (decodedMessage.action) { case 'Init': { // It is expected that server will send initialization message right after // WebSocket connection is established await device.load({ routerRtpCapabilities : decodedMessage.routerRtpCapabilities }); // TODO: Here we hardcode RTP capabilities, as Chromium doesn't really expose // multiopus support in a meaningful way, so we need to hack it like this for // now until `mediasoup-client` is updated to handle this case properly { // @ts-ignore device._recvRtpCapabilities = { codecs : [ { kind : 'audio', mimeType : 'audio/multiopus', clockRate : 48000, channels : 6, parameters : { useinbandfec : 1, 'channel_mapping' : '0,1,4,5,2,3', 'num_streams' : 4, 'coupled_streams' : 2 }, rtcpFeedback : [ { type : 'transport-cc', parameter : '' } ] } ], headerExtensions : device.rtpCapabilities.headerExtensions }; // @ts-ignore device._extendedRtpCapabilities = getExtendedRtpCapabilities( device.rtpCapabilities, decodedMessage.routerRtpCapabilities ); } // Send client-side initialization message back right away. send({ action : 'Init', rtpCapabilities : device.rtpCapabilities }); // Consumer transport is now needed to receive producer created on the server consumerTransport = device.createRecvTransport( decodedMessage.consumerTransportOptions ); // TODO: Here we patch RTCPeerConnection in such a way that we can intercept // and munge SDP answer until until `mediasoup-client` is updated to handle // this case properly { // @ts-ignore const pc = consumerTransport._handler._pc; const setLocalDescription = pc.setLocalDescription; pc.setLocalDescription = (answer: {type: 'answer'; sdp: string}) => { answer.sdp = answer.sdp.replace( 'm=audio 9 UDP/TLS/RTP/SAVPF 0', `m=audio 9 UDP/TLS/RTP/SAVPF 100 a=rtpmap:100 multiopus/48000/6 a=fmtp:100 channel_mapping=0,4,1,2,3,5;coupled_streams=2;num_streams=4;useinbandfec=1` ); return setLocalDescription.call(pc, answer); }; } consumerTransport .on('connect', ({ dtlsParameters }, success) => { // Send request to establish consumer transport connection send({ action : 'ConnectConsumerTransport', dtlsParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('ConnectedConsumerTransport', () => { success(); console.log('Consumer transport connected'); }); }); // We have just one producer and it is already created, so consuming is easy await new Promise((resolve) => { // Send request to consume producer send({ action : 'Consume', producerId : decodedMessage.rtpProducerId }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('Consumed', async (consumerOptions: ConsumerOptions) => { // Once confirmation is received, corresponding consumer // can be created client-side const consumer = await (consumerTransport as Transport).consume( consumerOptions ); console.log(`${consumer.kind} consumer created:`, consumer); receiveMediaStream.addTrack(consumer.track); receivePreview.srcObject = receiveMediaStream; resolve(undefined); }); }); break; } default: { // All messages other than initialization go here and are assumed // to be notifications that correspond to previously sent requests const callback = waitingForResponse.get(decodedMessage.action); if (callback) { waitingForResponse.delete(decodedMessage.action); callback(decodedMessage); } else { console.error('Received unexpected message', decodedMessage); } } } }; } ws.onerror = console.error; } document.querySelector('#connect')! .addEventListener('click', () => { const container = document.querySelector('#container')!; container.removeAttribute('waiting-for-click'); init(); }); ================================================ FILE: rust/examples-frontend/multiopus/tsconfig.json ================================================ { "compilerOptions": { "module": "es6", "target": "es2018", "sourceMap": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "moduleResolution": "node", "allowSyntheticDefaultImports": false, "sourceRoot": "src", "baseUrl": "src", "outDir": "dist", "strict": true, "noUnusedParameters": true, "noUnusedLocals": true, "lib": [ "es5", "es2015", "es2019", "dom" ] }, "include": [ "src", ".eslintrc.js", "webpack.config.js" ] } ================================================ FILE: rust/examples-frontend/multiopus/webpack.config.js ================================================ const path = require('path'); module.exports = { mode : 'development', entry : './src/index.ts', module : { rules : [ { test : /\.ts$/, use : 'ts-loader', exclude : /node_modules/ } ] }, resolve : { extensions : [ '.ts', '.js' ] }, output : { filename : 'dist/bundle.js', path : path.resolve(__dirname, 'dist') }, devServer : { liveReload : false, port : 3001, static : { directory: __dirname } } }; ================================================ FILE: rust/examples-frontend/readme.md ================================================ # Examples This directory contains complementary frontend examples of using mediasoup Rust library. NOTE: These examples are simplified and provided for demo purposes only, they are by no means complete or representative of production-grade software, use for educational purposes only and refer to documentation for all possible options. In order to run client-side part of examples (this directory) go to the directory with example and run following: ```bash npm install npm start ``` ================================================ FILE: rust/examples-frontend/svc-simulcast/.eslintrc.js ================================================ module.exports = { env : { browser : true, es6 : true }, parserOptions : { project : 'tsconfig.json', sourceType : 'module' } }; ================================================ FILE: rust/examples-frontend/svc-simulcast/index.html ================================================
Send preview
Receive preview

Video codec: ?

Switch layers: S?T?
================================================ FILE: rust/examples-frontend/svc-simulcast/package.json ================================================ { "version": "0.0.0", "private": true, "engines": { "node": ">=14.8" }, "scripts": { "start": "webpack serve" }, "author": "Tarsis Maksym ", "dependencies": { "mediasoup-client": "^3.6.51" }, "devDependencies": { "ts-loader": "^9.2.8", "typescript": "^4.6.3", "webpack": "^5.71.0", "webpack-cli": "^4.9.2", "webpack-dev-server": "^5.2.2" } } ================================================ FILE: rust/examples-frontend/svc-simulcast/src/index.ts ================================================ /* eslint-disable no-console */ import { Device, parseScalabilityMode } from 'mediasoup-client'; import { MediaKind, RtpCapabilities, RtpParameters } from 'mediasoup-client/lib/RtpParameters'; import { DtlsParameters, TransportOptions, Transport } from 'mediasoup-client/lib/Transport'; import { Consumer } from 'mediasoup-client/lib/types'; import { ConsumerOptions } from 'mediasoup-client/lib/Consumer'; type Brand = K & { __brand: T }; type ConsumerId = Brand; type ProducerId = Brand; interface ServerInit { action: 'Init'; consumerTransportOptions: TransportOptions; producerTransportOptions: TransportOptions; routerRtpCapabilities: RtpCapabilities; } interface ServerConnectedProducerTransport { action: 'ConnectedProducerTransport'; } interface ServerProduced { action: 'Produced'; id: ProducerId; } interface ServerConnectedConsumerTransport { action: 'ConnectedConsumerTransport'; } interface ServerConsumed { action: 'Consumed'; id: ConsumerId; kind: MediaKind; rtpParameters: RtpParameters; } type ServerMessage = ServerInit | ServerConnectedProducerTransport | ServerProduced | ServerConnectedConsumerTransport | ServerConsumed; interface ClientInit { action: 'Init'; rtpCapabilities: RtpCapabilities; } interface ClientConnectProducerTransport { action: 'ConnectProducerTransport'; dtlsParameters: DtlsParameters; } interface ClientConnectConsumerTransport { action: 'ConnectConsumerTransport'; dtlsParameters: DtlsParameters; } interface ClientProduce { action: 'Produce'; kind: MediaKind; rtpParameters: RtpParameters; } interface ClientConsume { action: 'Consume'; producerId: ProducerId; } interface ClientConsumerResume { action: 'ConsumerResume'; id: ConsumerId; } interface ClientSetConsumerPreferredLayers { action: 'SetConsumerPreferredLayers'; id: ConsumerId; preferredLayers: { spatialLayer: number temporalLayer: number } } type ClientMessage = ClientInit | ClientConnectProducerTransport | ClientProduce | ClientConnectConsumerTransport | ClientConsume | ClientConsumerResume | ClientSetConsumerPreferredLayers; async function init() { const isFirefox = navigator.userAgent.toLowerCase().includes('firefox') const sendPreview = document.querySelector('#preview-send') as HTMLVideoElement; const receivePreview = document.querySelector('#preview-receive') as HTMLVideoElement; const videoCodec = document.querySelector('#video-codec') as HTMLSpanElement; sendPreview.onloadedmetadata = () => { sendPreview.play(); }; receivePreview.onloadedmetadata = () => { receivePreview.play(); }; const decreaseLayer = document.querySelector('#decreaseLayer') as HTMLButtonElement; const increaseLayer = document.querySelector('#increaseLayer') as HTMLButtonElement; const spatialLayerNode = document.querySelector('#spatial') as HTMLSpanElement const temporalLayerNode = document.querySelector('#temporal') as HTMLSpanElement temporalLayerNode.innerText = 'none' let videoConsumer: Consumer | null = null; let maxSpatialLayer = 0; let maxTemporalLayer = 0; let preferredSpatialLayer = 0; let preferredTemporalLayer = 0; decreaseLayer.addEventListener('click', () => { let newPreferredSpatialLayer: number; let newPreferredTemporalLayer: number; if (preferredTemporalLayer > 0) { newPreferredSpatialLayer = preferredSpatialLayer; newPreferredTemporalLayer = preferredTemporalLayer - 1; } else if (preferredSpatialLayer > 0) { newPreferredSpatialLayer = preferredSpatialLayer - 1; newPreferredTemporalLayer = maxTemporalLayer; } else { newPreferredSpatialLayer = maxSpatialLayer; newPreferredTemporalLayer = maxTemporalLayer; } setPreferredLayers(newPreferredSpatialLayer, newPreferredTemporalLayer) }); increaseLayer.addEventListener('click', () => { let newPreferredSpatialLayer: number; let newPreferredTemporalLayer: number; if (preferredTemporalLayer < 2) { newPreferredSpatialLayer = preferredSpatialLayer; newPreferredTemporalLayer = preferredTemporalLayer + 1; } else if (preferredSpatialLayer < 2) { newPreferredSpatialLayer = preferredSpatialLayer + 1; newPreferredTemporalLayer = 0; } else { newPreferredSpatialLayer = 0; newPreferredTemporalLayer = 0; } setPreferredLayers(newPreferredSpatialLayer, newPreferredTemporalLayer) }); const setPreferredLayers = (spatialLayer: number, temporalLayer: number = 0): void => { if (!videoConsumer) { throw new Error('Failed to update preferred layers: video consumer not found.') } preferredSpatialLayer = spatialLayer preferredTemporalLayer = temporalLayer spatialLayerNode.innerText = String(spatialLayer); temporalLayerNode.innerText = String(temporalLayer); send({ action: 'SetConsumerPreferredLayers', id: videoConsumer.id as ConsumerId, preferredLayers: {spatialLayer, temporalLayer} }); } const receiveMediaStream = new MediaStream(); const ws = new WebSocket('ws://localhost:3000/ws'); function send(message: ClientMessage) { ws.send(JSON.stringify(message)); } const device = new Device(); let producerTransport: Transport | undefined; let consumerTransport: Transport | undefined; { const waitingForResponse: Map = new Map(); ws.onmessage = async (message) => { const decodedMessage: ServerMessage = JSON.parse(message.data); switch (decodedMessage.action) { case 'Init': { // It is expected that server will send initialization message right after // WebSocket connection is established await device.load({ routerRtpCapabilities : decodedMessage.routerRtpCapabilities }); // Send client-side initialization message back right away send({ action : 'Init', rtpCapabilities : device.rtpCapabilities }); // Producer transport is needed to send audio and video to SFU producerTransport = device.createSendTransport( decodedMessage.producerTransportOptions ); producerTransport .on('connect', ({ dtlsParameters }, success) => { // Send request to establish producer transport connection send({ action : 'ConnectProducerTransport', dtlsParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('ConnectedProducerTransport', () => { success(); console.log('Producer transport connected'); }); }) .on('produce', ({ kind, rtpParameters }, success) => { // Once connection is established, send request to produce // audio or video track send({ action : 'Produce', kind, rtpParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('Produced', ({ id }: {id: string}) => { success({ id }); }); }); // Request microphone and camera access, in real-world apps you may want // to do this separately so that audio-only and video-only cases are // handled nicely instead of failing completely const mediaStream = await navigator.mediaDevices.getUserMedia({ audio : true, video : { width : { ideal : 1280 }, height : { ideal : 720 }, frameRate : { ideal : 60 } } }); sendPreview.srcObject = mediaStream; const producers = []; // And create producers for all tracks that were previously requested for (const track of mediaStream.getTracks()) { const codec = track.kind === 'video' ? device.rtpCapabilities.codecs?.find((codec) => { // Firefox supports VP9, but not SVC return codec.mimeType.toLowerCase() === 'video/vp9' && !isFirefox; }) ?? device.rtpCapabilities.codecs?.find((codec) => codec.mimeType.toLowerCase() === 'video/vp8') : undefined; if (track.kind === 'video') { videoCodec.innerText = codec?.mimeType?.split('/')[1] ?? '?'; } let encodings; if (codec?.mimeType.toLowerCase() === 'video/vp8') { encodings = [ {scaleResolutionDownBy: 4, maxBitrate: 500000}, {scaleResolutionDownBy: 2, maxBitrate: 1000000}, {scaleResolutionDownBy: 1, maxBitrate: 5000000} ]; } else if (codec?.mimeType.toLowerCase() === 'video/vp9') { encodings = [ {scalabilityMode: 'S3T3'}, ]; } const producer = await producerTransport.produce({ track, encodings, codec }); producers.push(producer); console.log(`${track.kind} producer created:`, producer); } // Consumer transport is now needed to receive previously produced // tracks back consumerTransport = device.createRecvTransport( decodedMessage.consumerTransportOptions ); consumerTransport .on('connect', ({ dtlsParameters }, success) => { // Send request to establish consumer transport connection send({ action : 'ConnectConsumerTransport', dtlsParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('ConnectedConsumerTransport', () => { success(); console.log('Consumer transport connected'); }); }); // For simplicity of this example producers were stored in an array // and are now all consumed one at a time for (const producer of producers) { await new Promise((resolve) => { // Send request to consume producer send({ action : 'Consume', producerId : producer.id as ProducerId }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('Consumed', async (consumerOptions: ConsumerOptions) => { // Once confirmation is received, corresponding consumer // can be created client-side const consumer = await (consumerTransport as Transport).consume( consumerOptions ); console.log(`${consumer.kind} consumer created:`, consumer); // Consumer needs to be resumed after being created in // paused state (see official documentation about why: // https://mediasoup.org/documentation/v3/mediasoup/api/#transport-consume) send({ action : 'ConsumerResume', id : consumer.id as ConsumerId }); receiveMediaStream.addTrack(consumer.track); receivePreview.srcObject = receiveMediaStream; if (consumer.kind === 'video') { videoConsumer = consumer const encodings = videoConsumer.rtpParameters.encodings ?? []; if (encodings[0]) { const scalabilityMode = parseScalabilityMode(encodings[0].scalabilityMode) maxSpatialLayer = scalabilityMode.spatialLayers - 1; maxTemporalLayer = scalabilityMode.temporalLayers -1; preferredSpatialLayer = maxSpatialLayer; preferredTemporalLayer = maxTemporalLayer; setPreferredLayers(preferredSpatialLayer, preferredTemporalLayer); } } resolve(undefined); }); }); } break; } default: { // All messages other than initialization go here and are assumed // to be notifications that correspond to previously sent requests const callback = waitingForResponse.get(decodedMessage.action); if (callback) { waitingForResponse.delete(decodedMessage.action); callback(decodedMessage); } else { console.error('Received unexpected message', decodedMessage); } } } }; } ws.onerror = console.error; } init(); ================================================ FILE: rust/examples-frontend/svc-simulcast/tsconfig.json ================================================ { "compilerOptions": { "module": "es6", "target": "es2018", "sourceMap": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "moduleResolution": "node", "allowSyntheticDefaultImports": false, "sourceRoot": "src", "baseUrl": "src", "outDir": "dist", "strict": true, "noUnusedParameters": true, "noUnusedLocals": true, "lib": [ "es5", "es2015", "es2019", "dom" ] }, "include": [ "src", ".eslintrc.js", "webpack.config.js" ] } ================================================ FILE: rust/examples-frontend/svc-simulcast/webpack.config.js ================================================ const path = require('path'); module.exports = { mode : 'development', entry : './src/index.ts', module : { rules : [ { test : /\.ts$/, use : 'ts-loader', exclude : /node_modules/ } ] }, resolve : { extensions : [ '.ts', '.js' ] }, output : { filename : 'dist/bundle.js', path : path.resolve(__dirname, 'dist') }, devServer : { liveReload : false, port : 3001, static : { directory: __dirname } } }; ================================================ FILE: rust/examples-frontend/videoroom/.eslintrc.js ================================================ module.exports = { env : { browser : true, es6 : true }, parserOptions : { project : 'tsconfig.json', sourceType : 'module' } }; ================================================ FILE: rust/examples-frontend/videoroom/index.html ================================================
You
================================================ FILE: rust/examples-frontend/videoroom/package.json ================================================ { "version": "0.0.0", "private": true, "engines": { "node": ">=14.8" }, "scripts": { "start": "webpack serve" }, "author": "Nazar Mokrynskyi ", "dependencies": { "mediasoup-client": "^3.6.51" }, "devDependencies": { "ts-loader": "^9.2.8", "typescript": "^4.6.3", "webpack": "^5.71.0", "webpack-cli": "^4.9.2", "webpack-dev-server": "^5.2.2" } } ================================================ FILE: rust/examples-frontend/videoroom/src/index.ts ================================================ /* eslint-disable no-console */ import { Device } from 'mediasoup-client'; import { MediaKind, RtpCapabilities, RtpParameters } from 'mediasoup-client/lib/RtpParameters'; import { DtlsParameters, TransportOptions, Transport } from 'mediasoup-client/lib/Transport'; import { ConsumerOptions } from 'mediasoup-client/lib/Consumer'; type Brand = K & { __brand: T }; type RoomId = Brand; type ParticipantId = Brand; type ConsumerId = Brand; type ProducerId = Brand; interface ServerInit { action: 'Init'; roomId: RoomId; consumerTransportOptions: TransportOptions; producerTransportOptions: TransportOptions; routerRtpCapabilities: RtpCapabilities; } interface ServerProducerAdded { action: 'ProducerAdded'; participantId: ParticipantId; producerId: ProducerId; } interface ServerProducerRemoved { action: 'ProducerRemoved'; participantId: ParticipantId; producerId: ProducerId; } interface ServerConnectedProducerTransport { action: 'ConnectedProducerTransport'; } interface ServerProduced { action: 'Produced'; id: ProducerId; } interface ServerConnectedConsumerTransport { action: 'ConnectedConsumerTransport'; } interface ServerConsumed { action: 'Consumed'; id: ConsumerId; kind: MediaKind; rtpParameters: RtpParameters; } type ServerMessage = ServerInit | ServerProducerAdded | ServerProducerRemoved | ServerConnectedProducerTransport | ServerProduced | ServerConnectedConsumerTransport | ServerConsumed; interface ClientInit { action: 'Init'; rtpCapabilities: RtpCapabilities; } interface ClientConnectProducerTransport { action: 'ConnectProducerTransport'; dtlsParameters: DtlsParameters; } interface ClientConnectConsumerTransport { action: 'ConnectConsumerTransport'; dtlsParameters: DtlsParameters; } interface ClientProduce { action: 'Produce'; kind: MediaKind; rtpParameters: RtpParameters; } interface ClientConsume { action: 'Consume'; producerId: ProducerId; } interface ClientConsumerResume { action: 'ConsumerResume'; id: ConsumerId; } type ClientMessage = ClientInit | ClientConnectProducerTransport | ClientProduce | ClientConnectConsumerTransport | ClientConsume | ClientConsumerResume; class Participant { private readonly figure: HTMLElement; private readonly preview: HTMLVideoElement; private readonly mediaStream = new MediaStream(); constructor( public readonly id: ParticipantId) { const container = document.querySelector('#container')!; this.figure = document.createElement('figure'); this.preview = document.createElement('video'); this.preview.muted = true; this.preview.controls= true; this.preview.onloadedmetadata = () => { this.preview.play(); }; const figcaption = document.createElement('figcaption'); figcaption.innerText = `Participant ${id}`; this.figure.append(this.preview, figcaption); container.append(this.figure); } public addTrack(track: MediaStreamTrack): void { this.mediaStream.addTrack(track); this.preview.srcObject = this.mediaStream; } public deleteTrack(track: MediaStreamTrack): void { this.mediaStream.removeTrack(track); this.preview.srcObject = this.mediaStream; } public hasTracks(): boolean { return this.mediaStream.getTracks().length > 0; } public destroy(): void { this.preview.srcObject = null; this.figure.remove(); } } class Participants { private participants = new Map(); private producerIdToTrack = new Map(); public addTrack( participantId: ParticipantId, producerId: ProducerId, track: MediaStreamTrack): void { this.producerIdToTrack.set(producerId, track); this.getOrCreateParticipant(participantId).addTrack(track); } public deleteTrack(participantId: ParticipantId, producerId: ProducerId) { const track = this.producerIdToTrack.get(producerId); if (track) { const participant = this.getOrCreateParticipant(participantId); participant.deleteTrack(track); if (!participant.hasTracks()) { this.participants.delete(participantId); participant.destroy(); } } // TODO } getOrCreateParticipant(id: ParticipantId): Participant { let participant = this.participants.get(id); if (!participant) { participant =new Participant(id); this.participants.set(id, participant); } return participant; } } async function init() { const sendPreview = document.querySelector('#preview-send') as HTMLVideoElement; sendPreview.onloadedmetadata = () => { sendPreview.play(); }; const participants = new Participants(); const roomId = (new URL(location.href)).searchParams.get('roomId') as RoomId | undefined; const wsUrl = new URL('ws://localhost:3000/ws'); if (roomId) { wsUrl.searchParams.set('roomId', roomId); } const ws = new WebSocket(wsUrl.toString()); function send(message: ClientMessage) { ws.send(JSON.stringify(message)); } const device = new Device(); let producerTransport: Transport | undefined; let consumerTransport: Transport | undefined; let sequentialMessages: Promise = Promise.resolve(); const waitingForResponse: Map = new Map(); const onmessage = async (message: ServerMessage) => { switch (message.action) { case 'Init': { if (!roomId) { const url = new URL(location.href); url.searchParams.set('roomId', message.roomId); history.pushState({}, '', url.toString()); } // It is expected that server will send initialization message right after // WebSocket connection is established await device.load({ routerRtpCapabilities : message.routerRtpCapabilities }); // Send client-side initialization message back right away send({ action : 'Init', rtpCapabilities : device.rtpCapabilities }); // Producer transport is needed to send audio and video to SFU producerTransport = device.createSendTransport( message.producerTransportOptions ); producerTransport .on('connect', ({ dtlsParameters }, success) => { // Send request to establish producer transport connection send({ action : 'ConnectProducerTransport', dtlsParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('ConnectedProducerTransport', () => { success(); console.log('Producer transport connected'); }); }) .on('produce', ({ kind, rtpParameters }, success) => { // Once connection is established, send request to produce // audio or video track send({ action : 'Produce', kind, rtpParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('Produced', ({ id }: { id: string }) => { success({ id }); }); }); // Request microphone and camera access, in real-world apps you may want // to do this separately so that audio-only and video-only cases are // handled nicely instead of failing completely const mediaStream = await navigator.mediaDevices.getUserMedia({ audio : true, video : { width : { ideal : 1280 }, height : { ideal : 720 }, frameRate : { ideal : 60 } } }); sendPreview.srcObject = mediaStream; // And create producers for all tracks that were previously requested for (const track of mediaStream.getTracks()) { const producer = await producerTransport.produce({ track }); console.log(`${track.kind} producer created:`, producer); } // Producer transport will be needed to receive produced tracks consumerTransport = device.createRecvTransport( message.consumerTransportOptions ); consumerTransport .on('connect', ({ dtlsParameters }, success) => { // Send request to establish consumer transport connection send({ action : 'ConnectConsumerTransport', dtlsParameters }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('ConnectedConsumerTransport', () => { success(); console.log('Consumer transport connected'); }); }); break; } case 'ProducerAdded': { await new Promise((resolve) => { // Send request to consume producer send({ action : 'Consume', producerId : message.producerId }); // And wait for confirmation, but, obviously, no error handling, // which you should definitely have in real-world applications waitingForResponse.set('Consumed', async (consumerOptions: ConsumerOptions) => { // Once confirmation is received, corresponding consumer // can be created client-side const consumer = await (consumerTransport as Transport).consume( consumerOptions ); console.log(`${consumer.kind} consumer created:`, consumer); // Consumer needs to be resumed after being created in // paused state (see official documentation about why: // https://mediasoup.org/documentation/v3/mediasoup/api/#transport-consume) send({ action : 'ConsumerResume', id : consumer.id as ConsumerId }); participants .addTrack(message.participantId, message.producerId, consumer.track); resolve(undefined); }); }); break; } case 'ProducerRemoved': { participants .deleteTrack(message.participantId, message.producerId); break; } default: { console.error('Received unexpected message', message); } } }; ws.onmessage = (message) => { const decodedMessage: ServerMessage = JSON.parse(message.data); // All other messages go here and are assumed to be notifications // that correspond to previously sent requests const callback = waitingForResponse.get(decodedMessage.action); if (callback) { waitingForResponse.delete(decodedMessage.action); callback(decodedMessage); } else { // Simple hack to make sure we process all messages in order, in real-world apps // messages it would be useful to have messages being processed concurrently sequentialMessages = sequentialMessages .then(() => { return onmessage(decodedMessage); }) .catch((error) => { console.error('Unexpected error during message handling:', error); }); } }; ws.onerror = console.error; } init(); ================================================ FILE: rust/examples-frontend/videoroom/tsconfig.json ================================================ { "compilerOptions": { "module": "es6", "target": "es2018", "sourceMap": true, "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "moduleResolution": "node", "allowSyntheticDefaultImports": false, "sourceRoot": "src", "baseUrl": "src", "outDir": "dist", "strict": true, "noUnusedParameters": true, "noUnusedLocals": true, "lib": [ "es5", "es2015", "es2019", "dom" ] }, "include": [ "src", ".eslintrc.js", "webpack.config.js" ] } ================================================ FILE: rust/examples-frontend/videoroom/webpack.config.js ================================================ const path = require('path'); module.exports = { mode : 'development', entry : './src/index.ts', module : { rules : [ { test : /\.ts$/, use : 'ts-loader', exclude : /node_modules/ } ] }, resolve : { extensions : [ '.ts', '.js' ] }, output : { filename : 'dist/bundle.js', path : path.resolve(__dirname, 'dist') }, devServer : { liveReload : false, port : 3001, static : { directory: __dirname } } }; ================================================ FILE: rust/src/data_structures.rs ================================================ //! Miscellaneous data structures. use mediasoup_sys::fbs::{ common, producer, rtp_packet, sctp_association, transport, web_rtc_transport, }; pub use mediasoup_types::data_structures::*; use crate::fbs::{FromFbs, ToFbs}; impl ToFbs for ListenInfo { type FbsType = transport::ListenInfo; fn to_fbs(&self) -> Self::FbsType { transport::ListenInfo { protocol: match self.protocol { Protocol::Tcp => transport::Protocol::Tcp, Protocol::Udp => transport::Protocol::Udp, }, ip: self.ip.to_string(), announced_address: self .announced_address .as_ref() .map(|address| address.to_string()), expose_internal_ip: self.expose_internal_ip, port: self.port.unwrap_or(0), port_range: match &self.port_range { Some(port_range) => Box::new(transport::PortRange { min: *port_range.start(), max: *port_range.end(), }), None => Box::new(transport::PortRange { min: 0, max: 0 }), }, flags: Box::new(self.flags.unwrap_or_default().to_fbs()), send_buffer_size: self.send_buffer_size.unwrap_or(0), recv_buffer_size: self.recv_buffer_size.unwrap_or(0), } } } impl ToFbs for SocketFlags { type FbsType = transport::SocketFlags; fn to_fbs(&self) -> Self::FbsType { transport::SocketFlags { ipv6_only: self.ipv6_only, udp_reuse_port: self.udp_reuse_port, } } } impl ToFbs for DtlsRole { type FbsType = web_rtc_transport::DtlsRole; fn to_fbs(&self) -> Self::FbsType { match self { DtlsRole::Auto => web_rtc_transport::DtlsRole::Auto, DtlsRole::Client => web_rtc_transport::DtlsRole::Client, DtlsRole::Server => web_rtc_transport::DtlsRole::Server, } } } impl FromFbs for DtlsRole { type FbsType = web_rtc_transport::DtlsRole; fn from_fbs(role: &Self::FbsType) -> Self { match role { web_rtc_transport::DtlsRole::Auto => DtlsRole::Auto, web_rtc_transport::DtlsRole::Client => DtlsRole::Client, web_rtc_transport::DtlsRole::Server => DtlsRole::Server, } } } impl FromFbs for DtlsState { type FbsType = web_rtc_transport::DtlsState; fn from_fbs(state: &Self::FbsType) -> Self { match state { web_rtc_transport::DtlsState::New => DtlsState::New, web_rtc_transport::DtlsState::Connecting => DtlsState::Connecting, web_rtc_transport::DtlsState::Connected => DtlsState::Connected, web_rtc_transport::DtlsState::Failed => DtlsState::Failed, web_rtc_transport::DtlsState::Closed => DtlsState::Closed, } } } impl ToFbs for DtlsParameters { type FbsType = web_rtc_transport::DtlsParameters; fn to_fbs(&self) -> Self::FbsType { web_rtc_transport::DtlsParameters { role: self.role.to_fbs(), fingerprints: self .fingerprints .iter() .map(DtlsFingerprint::to_fbs) .collect(), } } } impl FromFbs for DtlsParameters { type FbsType = web_rtc_transport::DtlsParameters; fn from_fbs(parameters: &Self::FbsType) -> Self { DtlsParameters { role: DtlsRole::from_fbs(¶meters.role), fingerprints: parameters .fingerprints .iter() .map(|fingerprint| DtlsFingerprint::from_fbs(&fingerprint.clone())) .collect(), } } } impl ToFbs for DtlsFingerprint { type FbsType = web_rtc_transport::Fingerprint; fn to_fbs(&self) -> Self::FbsType { match self { DtlsFingerprint::Sha1 { .. } => web_rtc_transport::Fingerprint { algorithm: web_rtc_transport::FingerprintAlgorithm::Sha1, value: self.value_string(), }, DtlsFingerprint::Sha224 { .. } => web_rtc_transport::Fingerprint { algorithm: web_rtc_transport::FingerprintAlgorithm::Sha224, value: self.value_string(), }, DtlsFingerprint::Sha256 { .. } => web_rtc_transport::Fingerprint { algorithm: web_rtc_transport::FingerprintAlgorithm::Sha256, value: self.value_string(), }, DtlsFingerprint::Sha384 { .. } => web_rtc_transport::Fingerprint { algorithm: web_rtc_transport::FingerprintAlgorithm::Sha384, value: self.value_string(), }, DtlsFingerprint::Sha512 { .. } => web_rtc_transport::Fingerprint { algorithm: web_rtc_transport::FingerprintAlgorithm::Sha512, value: self.value_string(), }, } } } /// Parses a series of hex bytes into a byte array. fn hex_as_bytes(input: &str) -> [u8; N] { let mut output = [0_u8; N]; for (i, o) in input.split(':').zip(&mut output.iter_mut()) { *o = u8::from_str_radix(i, 16).unwrap_or_else(|error| { panic!("Failed to parse value {i} as series of hex bytes: {error}") }); } output } impl FromFbs for DtlsFingerprint { type FbsType = web_rtc_transport::Fingerprint; fn from_fbs(fingerprint: &Self::FbsType) -> Self { match fingerprint.algorithm { web_rtc_transport::FingerprintAlgorithm::Sha1 => { let value_result = hex_as_bytes::<20>(fingerprint.value.as_str()); DtlsFingerprint::Sha1 { value: value_result, } } web_rtc_transport::FingerprintAlgorithm::Sha224 => { let value_result = hex_as_bytes::<28>(fingerprint.value.as_str()); DtlsFingerprint::Sha224 { value: value_result, } } web_rtc_transport::FingerprintAlgorithm::Sha256 => { let value_result = hex_as_bytes::<32>(fingerprint.value.as_str()); DtlsFingerprint::Sha256 { value: value_result, } } web_rtc_transport::FingerprintAlgorithm::Sha384 => { let value_result = hex_as_bytes::<48>(fingerprint.value.as_str()); DtlsFingerprint::Sha384 { value: value_result, } } web_rtc_transport::FingerprintAlgorithm::Sha512 => { let value_result = hex_as_bytes::<64>(fingerprint.value.as_str()); DtlsFingerprint::Sha512 { value: value_result, } } } } } impl FromFbs for IceRole { type FbsType = web_rtc_transport::IceRole; fn from_fbs(role: &Self::FbsType) -> Self { match role { web_rtc_transport::IceRole::Controlled => IceRole::Controlled, web_rtc_transport::IceRole::Controlling => IceRole::Controlling, } } } impl FromFbs for IceParameters { type FbsType = web_rtc_transport::IceParameters; fn from_fbs(parameters: &Self::FbsType) -> Self { Self { username_fragment: parameters.username_fragment.to_string(), password: parameters.password.to_string(), ice_lite: Some(parameters.ice_lite), } } } impl FromFbs for SctpState { type FbsType = sctp_association::SctpState; fn from_fbs(state: &Self::FbsType) -> Self { match state { sctp_association::SctpState::New => Self::New, sctp_association::SctpState::Connecting => Self::Connecting, sctp_association::SctpState::Connected => Self::Connected, sctp_association::SctpState::Failed => Self::Failed, sctp_association::SctpState::Closed => Self::Closed, } } } impl FromFbs for IceCandidateType { type FbsType = web_rtc_transport::IceCandidateType; fn from_fbs(candidate_type: &Self::FbsType) -> Self { match candidate_type { web_rtc_transport::IceCandidateType::Host => IceCandidateType::Host, } } } impl FromFbs for IceCandidate { type FbsType = web_rtc_transport::IceCandidate; fn from_fbs(candidate: &Self::FbsType) -> Self { Self { foundation: candidate.foundation.clone(), priority: candidate.priority, address: candidate.address.clone(), protocol: Protocol::from_fbs(&candidate.protocol), port: candidate.port, r#type: IceCandidateType::from_fbs(&candidate.type_), tcp_type: FromFbs::from_fbs(&candidate.tcp_type), } } } impl FromFbs for IceCandidateTcpType { type FbsType = web_rtc_transport::IceCandidateTcpType; fn from_fbs(candidate_type: &Self::FbsType) -> Self { match candidate_type { web_rtc_transport::IceCandidateTcpType::Passive => IceCandidateTcpType::Passive, } } } impl FromFbs for IceState { type FbsType = web_rtc_transport::IceState; fn from_fbs(state: &Self::FbsType) -> Self { match state { web_rtc_transport::IceState::New => IceState::New, web_rtc_transport::IceState::Connected => IceState::Connected, web_rtc_transport::IceState::Completed => IceState::Completed, web_rtc_transport::IceState::Disconnected => IceState::Disconnected, } } } impl FromFbs for Protocol { type FbsType = transport::Protocol; fn from_fbs(protocol: &Self::FbsType) -> Self { match protocol { transport::Protocol::Tcp => Protocol::Tcp, transport::Protocol::Udp => Protocol::Udp, } } } impl FromFbs for TransportTuple { type FbsType = transport::Tuple; fn from_fbs(tuple: &Self::FbsType) -> Self { match &tuple.remote_ip { Some(_remote_ip) => TransportTuple::WithRemote { local_address: tuple .local_address .parse() .expect("Error parsing local address"), local_port: tuple.local_port, remote_ip: tuple .remote_ip .as_ref() .unwrap() .parse() .expect("Error parsing remote IP address"), remote_port: tuple.remote_port, protocol: Protocol::from_fbs(&tuple.protocol), }, None => TransportTuple::LocalOnly { local_address: tuple .local_address .parse() .expect("Error parsing local address"), local_port: tuple.local_port, protocol: Protocol::from_fbs(&tuple.protocol), }, } } } impl FromFbs for TraceEventDirection { type FbsType = common::TraceDirection; fn from_fbs(event_type: &Self::FbsType) -> Self { match event_type { common::TraceDirection::DirectionIn => TraceEventDirection::In, common::TraceDirection::DirectionOut => TraceEventDirection::Out, } } } impl FromFbs for SrTraceInfo { type FbsType = producer::SrTraceInfo; fn from_fbs(info: &Self::FbsType) -> Self { Self { ssrc: info.ssrc, ntp_sec: info.ntp_sec, ntp_frac: info.ntp_frac, rtp_ts: info.rtp_ts, packet_count: info.packet_count, octet_count: info.octet_count, } } } impl FromFbs for BweType { type FbsType = transport::BweType; fn from_fbs(info: &Self::FbsType) -> Self { match info { transport::BweType::TransportCc => BweType::TransportCc, transport::BweType::Remb => BweType::Remb, } } } impl FromFbs for BweTraceInfo { type FbsType = transport::BweTraceInfo; fn from_fbs(info: &Self::FbsType) -> Self { Self { r#type: BweType::from_fbs(&info.bwe_type), desired_bitrate: info.desired_bitrate, effective_desired_bitrate: info.effective_desired_bitrate, min_bitrate: info.min_bitrate, max_bitrate: info.max_bitrate, start_bitrate: info.start_bitrate, max_padding_bitrate: info.max_padding_bitrate, available_bitrate: info.available_bitrate, } } } impl FromFbs for RtpPacketTraceInfo { type FbsType = rtp_packet::Dump; fn from_fbs(rtp_packet: &Self::FbsType) -> Self { Self { payload_type: rtp_packet.payload_type, sequence_number: rtp_packet.sequence_number, timestamp: rtp_packet.timestamp, marker: rtp_packet.marker, ssrc: rtp_packet.ssrc, is_key_frame: rtp_packet.is_key_frame, size: rtp_packet.size, payload_size: rtp_packet.payload_size, spatial_layer: rtp_packet.spatial_layer, temporal_layer: rtp_packet.temporal_layer, mid: rtp_packet.mid.clone(), rid: rtp_packet.rid.clone(), rrid: rtp_packet.rrid.clone(), wide_sequence_number: rtp_packet.wide_sequence_number, is_rtx: false, } } } ================================================ FILE: rust/src/fbs.rs ================================================ //! Traits for converting between Rust and FlatBuffers data structures. pub(crate) trait TryFromFbs<'a>: Sized { type FbsType; type Error; fn try_from_fbs(fbs: Self::FbsType) -> Result; } pub(crate) trait FromFbs: Sized { type FbsType; fn from_fbs(fbs: &Self::FbsType) -> Self; } impl<'a, T> TryFromFbs<'a> for Vec where T: TryFromFbs<'a>, { type FbsType = Vec; type Error = T::Error; fn try_from_fbs(fbs: Self::FbsType) -> Result { fbs.into_iter().map(T::try_from_fbs).collect() } } pub(crate) trait ToFbs: Sized { type FbsType; fn to_fbs(&self) -> Self::FbsType; } impl FromFbs for Option where T: FromFbs, { type FbsType = Option; fn from_fbs(value: &Self::FbsType) -> Self { value.as_ref().map(T::from_fbs) } } impl FromFbs for Vec where T: FromFbs, { type FbsType = Vec; fn from_fbs(fbs: &Self::FbsType) -> Self { fbs.iter().map(T::from_fbs).collect() } } impl ToFbs for Vec where T: ToFbs, { type FbsType = Vec; fn to_fbs(&self) -> Self::FbsType { self.iter().map(|item| item.to_fbs()).collect() } } impl ToFbs for Option where T: ToFbs, { type FbsType = Option; fn to_fbs(&self) -> Self::FbsType { self.as_ref().map(T::to_fbs) } } ================================================ FILE: rust/src/lib.rs ================================================ #![warn(rust_2018_idioms, missing_debug_implementations, missing_docs)] //! Rust port of [mediasoup](https://github.com/versatica/mediasoup) TypeScript library! //! //! For general information go to readme in repository. //! //! # For TypeScript users //! If you were using mediasoup in TypeScript before, most of the API should be familiar to you. //! However, this is not one-to-one port, API was adjusted to more idiomatic Rust style leveraging //! powerful type system and ownership system to make API more robust and more misuse-resistant. //! //! So you will find specific types in most places where plain strings were used, instead of //! `close()` you will see `Drop` implementation for major entities that will close everything //! gracefully when it goes out of scope. //! //! # Before you start //! This is very low-level **library**. Which means it doesn't come with a ready to use signaling //! mechanism or easy to customize app scaffold (see //! [design goals](https://github.com/versatica/mediasoup/tree/v3/rust/readme.md#design-goals)). //! //! It is recommended to visit mediasoup website and read //! [design overview](https://mediasoup.org/documentation/v3/mediasoup/design/) first. //! //! There are some requirements for building underlying C++ `mediasoup-worker`, please find them in //! [installation instructions](https://mediasoup.org/documentation/v3/mediasoup/installation/) //! //! # Examples //! There are some examples in `examples` and `examples-frontend` directories (for server- and //! client-side respectively), you may want to look at those to get a general idea of what API looks //! like and what needs to be done in what order (check WebSocket messages in browser DevTools for //! better understanding of what is happening under the hood). //! //! # How to start //! With that in mind, you want start with creating [`WorkerManager`](worker_manager::WorkerManager) //! instance and then 1 or more workers. Workers a responsible for low-level job of sending media //! and data back and forth. Each worker is backed by single-core C++ worker thread. On each worker //! you create one or more routers that enable injection, selection and forwarding of media and data //! through [`transport`] instances. There are a few different transports available, but most likely //! you'll want to use [`WebRtcTransport`](webrtc_transport::WebRtcTransport) most often. With //! transport created you can start creating [`Producer`](producer::Producer)s to send data to //! [`Router`](router::Router) and [`Consumer`](consumer::Consumer) instances to extract data from //! [`Router`](router::Router). //! //! Some of the more advanced cases involve multiple routers and even workers that can user more //! than one core on the machine or even scale beyond single host. Check //! [scalability page](https://mediasoup.org/documentation/v3/scalability/) of the official //! documentation. //! //! Please check integration and unit tests for usage examples, they cover all major functionality //! and are a good place to start until we have demo apps built in Rust). pub use mediasoup_types as types; mod data_structures; pub(crate) mod fbs; mod macros; mod messages; #[doc(hidden)] pub mod ortc; pub mod prelude; pub mod router; mod rtp_parameters; mod sctp_parameters; mod srtp_parameters; pub mod supported_rtp_capabilities; pub mod webrtc_server; pub mod worker; pub mod worker_manager; pub mod audio_level_observer { //! An audio level observer monitors the volume of the selected audio producers. pub use crate::router::audio_level_observer::*; } pub mod active_speaker_observer { //! An active speaker observer monitors the speaking activity of the selected audio producers. pub use crate::router::active_speaker_observer::*; } pub mod consumer { //! A consumer represents an audio or video source being forwarded from a mediasoup router to an //! endpoint. It's created on top of a transport that defines how the media packets are carried. pub use crate::router::consumer::*; } pub mod data_consumer { //! A data consumer represents an endpoint capable of receiving data messages from a mediasoup //! [`Router`](router::Router). //! //! A data consumer can use [SCTP](https://tools.ietf.org/html/rfc4960) (AKA //! DataChannel) to receive those messages, or can directly receive them in the Rust application //! if the data consumer was created on top of a //! [`DirectTransport`](direct_transport::DirectTransport). #[cfg(doc)] use super::*; pub use crate::router::data_consumer::*; } pub mod producer { //! A producer represents an audio or video source being injected into a mediasoup router. It's //! created on top of a transport that defines how the media packets are carried. pub use crate::router::producer::*; } pub mod data_producer { //! A data producer represents an endpoint capable of injecting data messages into a mediasoup //! [`Router`](router::Router). //! //! A data producer can use [SCTP](https://tools.ietf.org/html/rfc4960) (AKA DataChannel) to //! deliver those messages, or can directly send them from the Rust application if the data //! producer was created on top of a [`DirectTransport`](direct_transport::DirectTransport). #[cfg(doc)] use super::*; pub use crate::router::data_producer::*; } pub mod transport { //! A transport connects an endpoint with a mediasoup router and enables transmission of media //! in both directions by means of [`Producer`](producer::Producer), //! [`Consumer`](consumer::Consumer), [`DataProducer`](data_producer::DataProducer) and //! [`DataConsumer`](data_consumer::DataConsumer) instances created on it. //! //! mediasoup implements the following transports: //! * [`WebRtcTransport`](webrtc_transport::WebRtcTransport) //! * [`PlainTransport`](plain_transport::PlainTransport) //! * [`PipeTransport`](pipe_transport::PipeTransport) //! * [`DirectTransport`](direct_transport::DirectTransport) #[cfg(doc)] use super::*; pub use crate::router::transport::*; } pub mod direct_transport { //! A direct transport represents a direct connection between the mediasoup Rust process and a //! [`Router`](router::Router) instance in a mediasoup-worker thread. //! //! A direct transport can be used to directly send and receive data messages from/to Rust by //! means of [`DataProducer`](data_producer::DataProducer)s and //! [`DataConsumer`](data_consumer::DataConsumer)s of type `Direct` created on a direct //! transport. //! Direct messages sent by a [`DataProducer`](data_producer::DataProducer) in a direct //! transport can be consumed by endpoints connected through a SCTP capable transport //! ([`WebRtcTransport`](webrtc_transport::WebRtcTransport), //! [`PlainTransport`](plain_transport::PlainTransport), //! [`PipeTransport`](pipe_transport::PipeTransport) and also by the Rust application by means //! of a [`DataConsumer`](data_consumer::DataConsumer) created on a [`DirectTransport`] (and //! vice-versa: messages sent over SCTP/DataChannel can be consumed by the Rust application by //! means of a [`DataConsumer`](data_consumer::DataConsumer) created on a [`DirectTransport`]). //! //! A direct transport can also be used to inject and directly consume RTP and RTCP packets in //! Rust by using the [`DirectProducer::send`](producer::DirectProducer::send) and //! [`Consumer::on_rtp`](consumer::Consumer::on_rtp) API (plus [`DirectTransport::send_rtcp`] //! and [`DirectTransport::on_rtcp`] API). #[cfg(doc)] use super::*; pub use crate::router::direct_transport::*; } pub mod pipe_transport { //! A pipe transport represents a network path through which RTP, RTCP (optionally secured with //! SRTP) and SCTP (DataChannel) is transmitted. Pipe transports are intended to //! intercommunicate two [`Router`](router::Router) instances collocated on the same host or on //! separate hosts. //! //! # Notes on usage //! When calling [`PipeTransport::consume`](transport::Transport::consume), all RTP streams of //! the [`Producer`](producer::Producer) are transmitted verbatim (in contrast to what happens //! in [`WebRtcTransport`](webrtc_transport::WebRtcTransport) and //! [`PlainTransport`](plain_transport::PlainTransport) in which a single and continuous RTP //! stream is sent to the consuming endpoint). #[cfg(doc)] use super::*; pub use crate::router::pipe_transport::*; } pub mod plain_transport { //! A plain transport represents a network path through which RTP, RTCP (optionally secured with //! SRTP) and SCTP (DataChannel) is transmitted. pub use crate::router::plain_transport::*; } pub mod rtp_observer { //! An RTP observer inspects the media received by a set of selected producers. //! //! mediasoup implements the following RTP observers: //! * [`AudioLevelObserver`](audio_level_observer::AudioLevelObserver) #[cfg(doc)] use super::*; pub use crate::router::rtp_observer::*; } pub mod webrtc_transport { //! A WebRTC transport represents a network path negotiated by both, a WebRTC endpoint and //! mediasoup, via ICE and DTLS procedures. A WebRTC transport may be used to receive media, to //! send media or to both receive and send. There is no limitation in mediasoup. However, due to //! their design, mediasoup-client and libmediasoupclient require separate WebRTC transports for //! sending and receiving. //! //! # Notes on usage //! The WebRTC transport implementation of mediasoup is //! [ICE Lite](https://tools.ietf.org/html/rfc5245#section-2.7), meaning that it does not //! initiate ICE connections but expects ICE Binding Requests from endpoints. pub use crate::router::webrtc_transport::*; } ================================================ FILE: rust/src/macros.rs ================================================ #[doc(hidden)] #[macro_export] macro_rules! uuid_based_wrapper_type { ( $(#[$outer:meta])* $struct_name: ident ) => { $(#[$outer])* #[derive( Debug, Copy, Clone, serde::Deserialize, serde::Serialize, Hash, Ord, PartialOrd, Eq, PartialEq, )] pub struct $struct_name(::uuid::Uuid); impl std::fmt::Display for $struct_name { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.0, f) } } impl ::std::str::FromStr for $struct_name { type Err = ::uuid::Error; fn from_str(s: &str) -> Result { ::uuid::Uuid::from_str(s).map(Self) } } impl From<$struct_name> for ::uuid::Uuid { fn from(id: $struct_name) -> Self { id.0 } } impl $struct_name { pub(super) fn new() -> Self { $struct_name(::uuid::Uuid::new_v4()) } } impl From<$struct_name> for $crate::worker::SubscriptionTarget { fn from(id: $struct_name) -> Self { Self::Uuid(id.0) } } }; } ================================================ FILE: rust/src/messages.rs ================================================ use crate::active_speaker_observer::ActiveSpeakerObserverOptions; use crate::audio_level_observer::AudioLevelObserverOptions; use crate::consumer::{ ConsumerId, ConsumerLayers, ConsumerScore, ConsumerTraceEventType, ConsumerType, }; use crate::data_consumer::{DataConsumerId, DataConsumerType}; use crate::data_producer::{DataProducerId, DataProducerType}; use crate::direct_transport::DirectTransportOptions; use crate::fbs::{FromFbs, ToFbs, TryFromFbs}; use crate::ortc::RtpMapping; use crate::pipe_transport::PipeTransportOptions; use crate::plain_transport::PlainTransportOptions; use crate::producer::{ProducerId, ProducerTraceEventType, ProducerType}; use crate::router::consumer::ConsumerDump; use crate::router::producer::ProducerDump; use crate::router::{RouterDump, RouterId}; use crate::rtp_observer::RtpObserverId; use crate::transport::{TransportId, TransportTraceEventType}; use crate::webrtc_server::{ WebRtcServerDump, WebRtcServerIceUsernameFragment, WebRtcServerId, WebRtcServerIpPort, WebRtcServerListenInfos, WebRtcServerTupleHash, }; use crate::webrtc_transport::{ WebRtcTransportListen, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use crate::worker::{ChannelMessageHandlers, LibUringDump, WorkerDump, WorkerUpdateSettings}; use mediasoup_sys::fbs::{ active_speaker_observer, audio_level_observer, consumer, data_consumer, data_producer, direct_transport, message, notification, pipe_transport, plain_transport, producer, request, response, router, rtp_observer, transport, web_rtc_server, web_rtc_transport, worker, }; use mediasoup_types::data_structures::{ DtlsParameters, DtlsRole, DtlsState, IceCandidate, IceParameters, IceRole, IceState, ListenInfo, SctpState, TransportTuple, }; use mediasoup_types::rtp_parameters::{MediaKind, RtpEncodingParameters, RtpParameters}; use mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters, SctpStreamParameters}; use mediasoup_types::srtp_parameters::{SrtpCryptoSuite, SrtpParameters}; use parking_lot::Mutex; use planus::Builder; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt::{Debug, Display}; use std::net::IpAddr; use std::num::NonZeroU16; use std::ops::Deref; pub(crate) trait Request where Self: Debug, { /// Request method to call on worker. const METHOD: request::Method; type HandlerId: Display; type Response; /// Get a serialized message out of this request. fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec; /// Default response to return in case of soft error, such as channel already closed, entity /// doesn't exist on worker during closing. fn default_for_soft_error() -> Option { None } /// Convert generic response into specific type of this request. fn convert_response( response: Option>, ) -> Result>; } pub(crate) trait Notification: Debug { /// Notification event to call on worker. const EVENT: notification::Event; type HandlerId: Display; /// Get a serialized message out of this notification. fn into_bytes(self, handler_id: Self::HandlerId) -> Vec; } #[derive(Debug)] pub(crate) struct WorkerDumpRequest {} impl Request for WorkerDumpRequest { const METHOD: request::Method = request::Method::WorkerDump; type HandlerId = &'static str; type Response = WorkerDump; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::WorkerDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = worker::DumpResponse::try_from(data)?; Ok(WorkerDump { router_ids: data .router_ids .into_iter() .map(|id| id.parse()) .collect::>()?, webrtc_server_ids: data .web_rtc_server_ids .into_iter() .map(|id| id.parse()) .collect::>()?, channel_message_handlers: ChannelMessageHandlers { channel_request_handlers: data .channel_message_handlers .channel_request_handlers .into_iter() .map(|id| id.parse()) .collect::>()?, channel_notification_handlers: data .channel_message_handlers .channel_notification_handlers .into_iter() .map(|id| id.parse()) .collect::>()?, }, liburing: data.liburing.map(|liburing| LibUringDump { sqe_process_count: liburing.sqe_process_count, sqe_miss_count: liburing.sqe_miss_count, user_data_miss_count: liburing.user_data_miss_count, }), }) } } #[derive(Debug)] pub(crate) struct WorkerUpdateSettingsRequest { pub(crate) data: WorkerUpdateSettings, } impl Request for WorkerUpdateSettingsRequest { const METHOD: request::Method = request::Method::WorkerUpdateSettings; type HandlerId = &'static str; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = worker::UpdateSettingsRequest::create( &mut builder, self.data .log_level .as_ref() .map(|log_level| log_level.as_str()), self.data.log_tags.as_ref().map(|log_tags| { log_tags .iter() .map(|log_tag| log_tag.as_str()) .collect::>() }), ); let request_body = request::Body::create_worker_update_settings_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct WorkerCreateWebRtcServerRequest { pub(crate) webrtc_server_id: WebRtcServerId, pub(crate) listen_infos: WebRtcServerListenInfos, } impl Request for WorkerCreateWebRtcServerRequest { const METHOD: request::Method = request::Method::WorkerCreateWebrtcserver; type HandlerId = &'static str; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = worker::CreateWebRtcServerRequest::create( &mut builder, self.webrtc_server_id.to_string(), self.listen_infos.to_fbs(), ); let request_body = request::Body::create_worker_create_web_rtc_server_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct WorkerCloseNotification {} impl Notification for WorkerCloseNotification { const EVENT: notification::Event = notification::Event::WorkerClose; type HandlerId = &'static str; fn into_bytes(self, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let notification = notification::Notification::create( &mut builder, handler_id.to_string(), Self::EVENT, None::, ); let message_body = message::Body::create_notification(&mut builder, notification); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } } #[derive(Debug)] pub(crate) struct WebRtcServerCloseRequest { pub(crate) webrtc_server_id: WebRtcServerId, } impl Request for WebRtcServerCloseRequest { const METHOD: request::Method = request::Method::WorkerWebrtcserverClose; type HandlerId = &'static str; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = worker::CloseWebRtcServerRequest::create( &mut builder, self.webrtc_server_id.to_string(), ); let request_body = request::Body::create_worker_close_web_rtc_server_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct WebRtcServerDumpRequest {} impl Request for WebRtcServerDumpRequest { const METHOD: request::Method = request::Method::WebrtcserverDump; type HandlerId = WebRtcServerId; type Response = WebRtcServerDump; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::WebRtcServerDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = web_rtc_server::DumpResponse::try_from(data)?; Ok(WebRtcServerDump { id: data.id.parse()?, udp_sockets: data .udp_sockets .into_iter() .map(|ip_port| WebRtcServerIpPort { ip: ip_port.ip.parse().unwrap(), port: ip_port.port, }) .collect(), tcp_servers: data .tcp_servers .into_iter() .map(|ip_port| WebRtcServerIpPort { ip: ip_port.ip.parse().unwrap(), port: ip_port.port, }) .collect(), webrtc_transport_ids: data .web_rtc_transport_ids .into_iter() .map(|id| id.parse()) .collect::>()?, local_ice_username_fragments: data .local_ice_username_fragments .into_iter() .map(|username_fragment| WebRtcServerIceUsernameFragment { local_ice_username_fragment: username_fragment .local_ice_username_fragment .parse() .unwrap(), webrtc_transport_id: username_fragment.web_rtc_transport_id.parse().unwrap(), }) .collect(), tuple_hashes: data .tuple_hashes .into_iter() .map(|tuple_hash| WebRtcServerTupleHash { tuple_hash: tuple_hash.tuple_hash, webrtc_transport_id: tuple_hash.web_rtc_transport_id.parse().unwrap(), }) .collect(), }) } } #[derive(Debug)] pub(crate) struct WorkerCreateRouterRequest { pub(crate) router_id: RouterId, } impl Request for WorkerCreateRouterRequest { const METHOD: request::Method = request::Method::WorkerCreateRouter; type HandlerId = &'static str; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = worker::CreateRouterRequest::create(&mut builder, self.router_id.to_string()); let request_body = request::Body::create_worker_create_router_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct RouterCloseRequest { pub(crate) router_id: RouterId, } impl Request for RouterCloseRequest { const METHOD: request::Method = request::Method::WorkerCloseRouter; type HandlerId = &'static str; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = worker::CloseRouterRequest::create(&mut builder, self.router_id.to_string()); let request_body = request::Body::create_worker_close_router_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct RouterDumpRequest {} impl Request for RouterDumpRequest { const METHOD: request::Method = request::Method::RouterDump; type HandlerId = RouterId; type Response = RouterDump; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::RouterDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = router::DumpResponse::try_from(data)?; Ok(RouterDump { id: data.id.parse()?, map_consumer_id_producer_id: data .map_consumer_id_producer_id .into_iter() .map(|key_value| Ok((key_value.key.parse()?, key_value.value.parse()?))) .collect::>>()?, map_data_consumer_id_data_producer_id: data .map_data_consumer_id_data_producer_id .into_iter() .map(|key_value| Ok((key_value.key.parse()?, key_value.value.parse()?))) .collect::>>()?, map_data_producer_id_data_consumer_ids: data .map_data_producer_id_data_consumer_ids .into_iter() .map(|key_values| { Ok(( key_values.key.parse()?, key_values .values .into_iter() .map(|value| value.parse()) .collect::>()?, )) }) .collect::>>()?, map_producer_id_consumer_ids: data .map_producer_id_consumer_ids .into_iter() .map(|key_values| { Ok(( key_values.key.parse()?, key_values .values .into_iter() .map(|value| value.parse()) .collect::>()?, )) }) .collect::>>()?, map_producer_id_observer_ids: data .map_producer_id_observer_ids .into_iter() .map(|key_values| { Ok(( key_values.key.parse()?, key_values .values .into_iter() .map(|value| value.parse()) .collect::>()?, )) }) .collect::>>()?, rtp_observer_ids: data .rtp_observer_ids .into_iter() .map(|id| id.parse()) .collect::>()?, transport_ids: data .transport_ids .into_iter() .map(|id| id.parse()) .collect::>()?, }) } } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RouterCreateDirectTransportData { transport_id: TransportId, direct: bool, max_message_size: u32, } impl RouterCreateDirectTransportData { pub(crate) fn from_options( transport_id: TransportId, direct_transport_options: &DirectTransportOptions, ) -> Self { Self { transport_id, direct: true, max_message_size: direct_transport_options.max_message_size, } } } impl ToFbs for RouterCreateDirectTransportData { type FbsType = direct_transport::DirectTransportOptions; fn to_fbs(&self) -> Self::FbsType { direct_transport::DirectTransportOptions { base: Box::new(transport::Options { direct: true, max_message_size: Some(self.max_message_size), initial_available_outgoing_bitrate: None, enable_sctp: false, num_sctp_streams: None, max_sctp_message_size: 0, sctp_send_buffer_size: 0, is_data_channel: false, }), } } } #[derive(Debug)] pub(crate) struct RouterCreateDirectTransportRequest { pub(crate) data: RouterCreateDirectTransportData, } impl Request for RouterCreateDirectTransportRequest { const METHOD: request::Method = request::Method::RouterCreateDirecttransport; type HandlerId = RouterId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = router::CreateDirectTransportRequest::create( &mut builder, self.data.transport_id.to_string(), self.data.to_fbs(), ); let request_body = request::Body::create_router_create_direct_transport_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] #[serde(untagged)] enum RouterCreateWebrtcTransportListen { #[serde(rename_all = "camelCase")] Individual { listen_infos: WebRtcTransportListenInfos, }, Server { #[serde(rename = "webRtcServerId")] webrtc_server_id: WebRtcServerId, }, } impl ToFbs for RouterCreateWebrtcTransportListen { type FbsType = web_rtc_transport::Listen; fn to_fbs(&self) -> Self::FbsType { match self { RouterCreateWebrtcTransportListen::Individual { listen_infos } => { web_rtc_transport::Listen::ListenIndividual(Box::new( web_rtc_transport::ListenIndividual { listen_infos: ToFbs::to_fbs(listen_infos.deref()), }, )) } RouterCreateWebrtcTransportListen::Server { webrtc_server_id } => { web_rtc_transport::Listen::ListenServer(Box::new(web_rtc_transport::ListenServer { web_rtc_server_id: webrtc_server_id.to_string(), })) } } } } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RouterCreateWebrtcTransportData { transport_id: TransportId, #[serde(flatten)] listen: RouterCreateWebrtcTransportListen, initial_available_outgoing_bitrate: u32, enable_udp: bool, enable_tcp: bool, prefer_udp: bool, prefer_tcp: bool, ice_consent_timeout: u8, enable_sctp: bool, num_sctp_streams: NumSctpStreams, max_sctp_message_size: u32, sctp_send_buffer_size: u32, is_data_channel: bool, } impl RouterCreateWebrtcTransportData { pub(crate) fn from_options( transport_id: TransportId, webrtc_transport_options: &WebRtcTransportOptions, ) -> Self { Self { transport_id, listen: match &webrtc_transport_options.listen { WebRtcTransportListen::Individual { listen_infos } => { RouterCreateWebrtcTransportListen::Individual { listen_infos: listen_infos.clone(), } } WebRtcTransportListen::Server { webrtc_server } => { RouterCreateWebrtcTransportListen::Server { webrtc_server_id: webrtc_server.id(), } } }, initial_available_outgoing_bitrate: webrtc_transport_options .initial_available_outgoing_bitrate, enable_udp: webrtc_transport_options.enable_udp, enable_tcp: webrtc_transport_options.enable_tcp, prefer_udp: webrtc_transport_options.prefer_udp, prefer_tcp: webrtc_transport_options.prefer_tcp, ice_consent_timeout: webrtc_transport_options.ice_consent_timeout, enable_sctp: webrtc_transport_options.enable_sctp, num_sctp_streams: webrtc_transport_options.num_sctp_streams, max_sctp_message_size: webrtc_transport_options.max_sctp_message_size, sctp_send_buffer_size: webrtc_transport_options.sctp_send_buffer_size, is_data_channel: true, } } } impl ToFbs for RouterCreateWebrtcTransportData { type FbsType = web_rtc_transport::WebRtcTransportOptions; fn to_fbs(&self) -> Self::FbsType { web_rtc_transport::WebRtcTransportOptions { base: Box::new(transport::Options { direct: false, max_message_size: None, initial_available_outgoing_bitrate: Some(self.initial_available_outgoing_bitrate), enable_sctp: self.enable_sctp, num_sctp_streams: Some(Box::new(self.num_sctp_streams.to_fbs())), max_sctp_message_size: self.max_sctp_message_size, sctp_send_buffer_size: self.sctp_send_buffer_size, is_data_channel: true, }), listen: self.listen.to_fbs(), enable_udp: self.enable_udp, enable_tcp: self.enable_tcp, prefer_udp: self.prefer_udp, prefer_tcp: self.prefer_tcp, ice_consent_timeout: self.ice_consent_timeout, } } } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub(crate) struct WebRtcTransportData { pub(crate) ice_role: IceRole, pub(crate) ice_parameters: IceParameters, pub(crate) ice_candidates: Vec, pub(crate) ice_state: Mutex, pub(crate) ice_selected_tuple: Mutex>, pub(crate) dtls_parameters: Mutex, pub(crate) dtls_state: Mutex, pub(crate) dtls_remote_cert: Mutex>, pub(crate) sctp_parameters: Option, pub(crate) sctp_state: Mutex>, } #[derive(Debug)] pub(crate) struct RouterCreateWebRtcTransportRequest { pub(crate) data: RouterCreateWebrtcTransportData, } impl Request for RouterCreateWebRtcTransportRequest { const METHOD: request::Method = request::Method::RouterCreateWebrtctransport; type HandlerId = RouterId; type Response = WebRtcTransportData; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let RouterCreateWebrtcTransportListen::Individual { listen_infos: _ } = self.data.listen else { panic!("RouterCreateWebrtcTransportListen variant must be Individual"); }; let mut builder = Builder::new(); let data = router::CreateWebRtcTransportRequest::create( &mut builder, self.data.transport_id.to_string(), self.data.to_fbs(), ); let request_body = request::Body::create_router_create_web_rtc_transport_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::WebRtcTransportDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = web_rtc_transport::DumpResponse::try_from(data)?; Ok(WebRtcTransportData { ice_role: IceRole::from_fbs(&data.ice_role), ice_parameters: IceParameters::from_fbs(data.ice_parameters.as_ref()), ice_candidates: FromFbs::from_fbs(&data.ice_candidates), ice_state: Mutex::new(IceState::from_fbs(&data.ice_state)), ice_selected_tuple: Mutex::new( data.ice_selected_tuple .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())), ), dtls_parameters: Mutex::new(DtlsParameters::from_fbs(data.dtls_parameters.as_ref())), dtls_state: Mutex::new(DtlsState::from_fbs(&data.dtls_state)), dtls_remote_cert: Mutex::new(None), sctp_parameters: data .base .sctp_parameters .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())), sctp_state: Mutex::new(FromFbs::from_fbs(&data.base.sctp_state)), }) } } #[derive(Debug)] pub(crate) struct RouterCreateWebRtcTransportWithServerRequest { pub(crate) data: RouterCreateWebrtcTransportData, } impl Request for RouterCreateWebRtcTransportWithServerRequest { const METHOD: request::Method = request::Method::RouterCreateWebrtctransportWithServer; type HandlerId = RouterId; type Response = WebRtcTransportData; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let RouterCreateWebrtcTransportListen::Server { webrtc_server_id: _, } = self.data.listen else { panic!("RouterCreateWebrtcTransportListen variant must be Server"); }; let mut builder = Builder::new(); let data = router::CreateWebRtcTransportRequest::create( &mut builder, self.data.transport_id.to_string(), self.data.to_fbs(), ); let request_body = request::Body::create_router_create_web_rtc_transport_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::WebRtcTransportDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = web_rtc_transport::DumpResponse::try_from(data)?; Ok(WebRtcTransportData { ice_role: IceRole::from_fbs(&data.ice_role), ice_parameters: IceParameters::from_fbs(data.ice_parameters.as_ref()), ice_candidates: FromFbs::from_fbs(&data.ice_candidates), ice_state: Mutex::new(IceState::from_fbs(&data.ice_state)), ice_selected_tuple: Mutex::new( data.ice_selected_tuple .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())), ), dtls_parameters: Mutex::new(DtlsParameters::from_fbs(data.dtls_parameters.as_ref())), dtls_state: Mutex::new(DtlsState::from_fbs(&data.dtls_state)), dtls_remote_cert: Mutex::new(None), sctp_parameters: data .base .sctp_parameters .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())), sctp_state: Mutex::new(FromFbs::from_fbs(&data.base.sctp_state)), }) } } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RouterCreatePlainTransportData { transport_id: TransportId, listen_info: ListenInfo, rtcp_listen_info: Option, rtcp_mux: bool, comedia: bool, enable_sctp: bool, num_sctp_streams: NumSctpStreams, max_sctp_message_size: u32, sctp_send_buffer_size: u32, enable_srtp: bool, srtp_crypto_suite: SrtpCryptoSuite, is_data_channel: bool, } impl RouterCreatePlainTransportData { pub(crate) fn from_options( transport_id: TransportId, plain_transport_options: &PlainTransportOptions, ) -> Self { Self { transport_id, listen_info: plain_transport_options.listen_info.clone(), rtcp_listen_info: plain_transport_options.rtcp_listen_info.clone(), rtcp_mux: plain_transport_options.rtcp_mux, comedia: plain_transport_options.comedia, enable_sctp: plain_transport_options.enable_sctp, num_sctp_streams: plain_transport_options.num_sctp_streams, max_sctp_message_size: plain_transport_options.max_sctp_message_size, sctp_send_buffer_size: plain_transport_options.sctp_send_buffer_size, enable_srtp: plain_transport_options.enable_srtp, srtp_crypto_suite: plain_transport_options.srtp_crypto_suite, is_data_channel: false, } } } impl ToFbs for RouterCreatePlainTransportData { type FbsType = plain_transport::PlainTransportOptions; fn to_fbs(&self) -> Self::FbsType { plain_transport::PlainTransportOptions { base: Box::new(transport::Options { direct: false, max_message_size: None, initial_available_outgoing_bitrate: None, enable_sctp: self.enable_sctp, num_sctp_streams: Some(Box::new(self.num_sctp_streams.to_fbs())), max_sctp_message_size: self.max_sctp_message_size, sctp_send_buffer_size: self.sctp_send_buffer_size, is_data_channel: self.is_data_channel, }), listen_info: Box::new(self.listen_info.clone().to_fbs()), rtcp_listen_info: self .rtcp_listen_info .clone() .map(|listen_info| Box::new(listen_info.to_fbs())), rtcp_mux: self.rtcp_mux, comedia: self.comedia, enable_srtp: self.enable_srtp, srtp_crypto_suite: Some(SrtpCryptoSuite::to_fbs(&self.srtp_crypto_suite)), } } } #[derive(Debug)] pub(crate) struct RouterCreatePlainTransportRequest { pub(crate) data: RouterCreatePlainTransportData, } impl Request for RouterCreatePlainTransportRequest { const METHOD: request::Method = request::Method::RouterCreatePlaintransport; type HandlerId = RouterId; type Response = PlainTransportData; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = router::CreatePlainTransportRequest::create( &mut builder, self.data.transport_id.to_string(), self.data.to_fbs(), ); let request_body = request::Body::create_router_create_plain_transport_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::PlainTransportDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = plain_transport::DumpResponse::try_from(data)?; Ok(PlainTransportData { tuple: Mutex::new(TransportTuple::from_fbs(data.tuple.as_ref())), rtcp_tuple: Mutex::new( data.rtcp_tuple .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())), ), sctp_parameters: data .base .sctp_parameters .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())), sctp_state: Mutex::new(FromFbs::from_fbs(&data.base.sctp_state)), srtp_parameters: Mutex::new( data.srtp_parameters .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())), ), }) } } pub(crate) struct PlainTransportData { // The following fields are present, but unused // rtcp_mux: bool, // comedia: bool, pub(crate) tuple: Mutex, pub(crate) rtcp_tuple: Mutex>, pub(crate) sctp_parameters: Option, pub(crate) sctp_state: Mutex>, pub(crate) srtp_parameters: Mutex>, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RouterCreatePipeTransportData { transport_id: TransportId, listen_info: ListenInfo, enable_sctp: bool, num_sctp_streams: NumSctpStreams, max_sctp_message_size: u32, sctp_send_buffer_size: u32, enable_rtx: bool, enable_srtp: bool, is_data_channel: bool, } impl RouterCreatePipeTransportData { pub(crate) fn from_options( transport_id: TransportId, pipe_transport_options: &PipeTransportOptions, ) -> Self { Self { transport_id, listen_info: pipe_transport_options.listen_info.clone(), enable_sctp: pipe_transport_options.enable_sctp, num_sctp_streams: pipe_transport_options.num_sctp_streams, max_sctp_message_size: pipe_transport_options.max_sctp_message_size, sctp_send_buffer_size: pipe_transport_options.sctp_send_buffer_size, enable_rtx: pipe_transport_options.enable_rtx, enable_srtp: pipe_transport_options.enable_srtp, is_data_channel: false, } } } impl ToFbs for RouterCreatePipeTransportData { type FbsType = pipe_transport::PipeTransportOptions; fn to_fbs(&self) -> Self::FbsType { pipe_transport::PipeTransportOptions { base: Box::new(transport::Options { direct: false, max_message_size: None, initial_available_outgoing_bitrate: None, enable_sctp: self.enable_sctp, num_sctp_streams: Some(Box::new(self.num_sctp_streams.to_fbs())), max_sctp_message_size: self.max_sctp_message_size, sctp_send_buffer_size: self.sctp_send_buffer_size, is_data_channel: self.is_data_channel, }), listen_info: Box::new(self.listen_info.clone().to_fbs()), enable_rtx: self.enable_rtx, enable_srtp: self.enable_srtp, } } } #[derive(Debug)] pub(crate) struct RouterCreatePipeTransportRequest { pub(crate) data: RouterCreatePipeTransportData, } impl Request for RouterCreatePipeTransportRequest { const METHOD: request::Method = request::Method::RouterCreatePipetransport; type HandlerId = RouterId; type Response = PipeTransportData; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = router::CreatePipeTransportRequest::create( &mut builder, self.data.transport_id.to_string(), self.data.to_fbs(), ); let request_body = request::Body::create_router_create_pipe_transport_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::PipeTransportDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = pipe_transport::DumpResponse::try_from(data)?; Ok(PipeTransportData { tuple: Mutex::new(TransportTuple::from_fbs(data.tuple.as_ref())), sctp_parameters: data .base .sctp_parameters .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())), sctp_state: Mutex::new(FromFbs::from_fbs(&data.base.sctp_state)), rtx: data.rtx, srtp_parameters: Mutex::new( data.srtp_parameters .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())), ), }) } } pub(crate) struct PipeTransportData { pub(crate) tuple: Mutex, pub(crate) sctp_parameters: Option, pub(crate) sctp_state: Mutex>, pub(crate) rtx: bool, pub(crate) srtp_parameters: Mutex>, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RouterCreateAudioLevelObserverRequest { pub(crate) data: RouterCreateAudioLevelObserverData, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RouterCreateAudioLevelObserverData { rtp_observer_id: RtpObserverId, max_entries: NonZeroU16, threshold: i8, interval: u16, } impl RouterCreateAudioLevelObserverData { pub(crate) fn from_options( rtp_observer_id: RtpObserverId, audio_level_observer_options: &AudioLevelObserverOptions, ) -> Self { Self { rtp_observer_id, max_entries: audio_level_observer_options.max_entries, threshold: audio_level_observer_options.threshold, interval: audio_level_observer_options.interval, } } } impl Request for RouterCreateAudioLevelObserverRequest { const METHOD: request::Method = request::Method::RouterCreateAudiolevelobserver; type HandlerId = RouterId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let options = audio_level_observer::AudioLevelObserverOptions::create( &mut builder, u16::from(self.data.max_entries), self.data.threshold, self.data.interval, ); let data = router::CreateAudioLevelObserverRequest::create( &mut builder, self.data.rtp_observer_id.to_string(), options, ); let request_body = request::Body::create_router_create_audio_level_observer_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RouterCreateActiveSpeakerObserverRequest { pub(crate) data: RouterCreateActiveSpeakerObserverData, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RouterCreateActiveSpeakerObserverData { rtp_observer_id: RtpObserverId, interval: u16, } impl RouterCreateActiveSpeakerObserverData { pub(crate) fn from_options( rtp_observer_id: RtpObserverId, active_speaker_observer_options: &ActiveSpeakerObserverOptions, ) -> Self { Self { rtp_observer_id, interval: active_speaker_observer_options.interval, } } } impl Request for RouterCreateActiveSpeakerObserverRequest { const METHOD: request::Method = request::Method::RouterCreateActivespeakerobserver; type HandlerId = RouterId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let options = active_speaker_observer::ActiveSpeakerObserverOptions::create( &mut builder, self.data.interval, ); let data = router::CreateActiveSpeakerObserverRequest::create( &mut builder, self.data.rtp_observer_id.to_string(), options, ); let request_body = request::Body::create_router_create_active_speaker_observer_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct TransportDumpRequest {} impl Request for TransportDumpRequest { const METHOD: request::Method = request::Method::TransportDump; type HandlerId = TransportId; type Response = response::Body; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { match response { Some(data) => Ok(data.try_into().unwrap()), _ => { panic!("Wrong message from worker: {response:?}"); } } } } #[derive(Debug)] pub(crate) struct TransportGetStatsRequest {} impl Request for TransportGetStatsRequest { const METHOD: request::Method = request::Method::TransportGetStats; type HandlerId = TransportId; type Response = response::Body; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { match response { Some(data) => Ok(data.try_into().unwrap()), _ => { panic!("Wrong message from worker: {response:?}"); } } } } #[derive(Debug)] pub(crate) struct TransportCloseRequest { pub(crate) transport_id: TransportId, } impl Request for TransportCloseRequest { const METHOD: request::Method = request::Method::RouterCloseTransport; type HandlerId = RouterId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = router::CloseTransportRequest::create(&mut builder, self.transport_id.to_string()); let request_body = request::Body::create_router_close_transport_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct WebRtcTransportConnectResponse { pub(crate) dtls_local_role: DtlsRole, } #[derive(Debug)] pub(crate) struct WebRtcTransportConnectRequest { pub(crate) dtls_parameters: DtlsParameters, } impl Request for WebRtcTransportConnectRequest { const METHOD: request::Method = request::Method::WebrtctransportConnect; type HandlerId = TransportId; type Response = WebRtcTransportConnectResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = web_rtc_transport::ConnectRequest::create(&mut builder, self.dtls_parameters.to_fbs()); let request_body = request::Body::create_web_rtc_transport_connect_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::WebRtcTransportConnectResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = web_rtc_transport::ConnectResponse::try_from(data)?; Ok(WebRtcTransportConnectResponse { dtls_local_role: DtlsRole::from_fbs(&data.dtls_local_role), }) } } #[derive(Debug)] pub(crate) struct PipeTransportConnectResponse { pub(crate) tuple: TransportTuple, } #[derive(Debug)] pub(crate) struct PipeTransportConnectRequest { pub(crate) ip: IpAddr, pub(crate) port: u16, pub(crate) srtp_parameters: Option, } impl Request for PipeTransportConnectRequest { const METHOD: request::Method = request::Method::PipetransportConnect; type HandlerId = TransportId; type Response = PipeTransportConnectResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = pipe_transport::ConnectRequest::create( &mut builder, self.ip.to_string(), self.port, ToFbs::to_fbs(&self.srtp_parameters), ); let request_body = request::Body::create_pipe_transport_connect_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::PipeTransportConnectResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = pipe_transport::ConnectResponse::try_from(data)?; Ok(PipeTransportConnectResponse { tuple: TransportTuple::from_fbs(data.tuple.as_ref()), }) } } #[derive(Debug)] pub(crate) struct PlainTransportConnectResponse { pub(crate) tuple: TransportTuple, pub(crate) rtcp_tuple: Option, pub(crate) srtp_parameters: Option, } #[derive(Debug)] pub(crate) struct TransportConnectPlainRequest { pub(crate) ip: Option, pub(crate) port: Option, pub(crate) rtcp_port: Option, pub(crate) srtp_parameters: Option, } impl Request for TransportConnectPlainRequest { const METHOD: request::Method = request::Method::PlaintransportConnect; type HandlerId = TransportId; type Response = PlainTransportConnectResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = plain_transport::ConnectRequest::create( &mut builder, self.ip.map(|ip| ip.to_string()), self.port, self.rtcp_port, ToFbs::to_fbs(&self.srtp_parameters), ); let request_body = request::Body::create_plain_transport_connect_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::PlainTransportConnectResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = plain_transport::ConnectResponse::try_from(data)?; Ok(PlainTransportConnectResponse { tuple: TransportTuple::from_fbs(data.tuple.as_ref()), rtcp_tuple: data .rtcp_tuple .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())), srtp_parameters: data .srtp_parameters .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())), }) } } #[derive(Debug)] pub(crate) struct TransportSetMaxIncomingBitrateRequest { pub(crate) bitrate: u32, } impl Request for TransportSetMaxIncomingBitrateRequest { const METHOD: request::Method = request::Method::TransportSetMaxIncomingBitrate; type HandlerId = TransportId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::SetMaxIncomingBitrateRequest::create(&mut builder, self.bitrate); let request_body = request::Body::create_transport_set_max_incoming_bitrate_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct TransportSetMaxOutgoingBitrateRequest { pub(crate) bitrate: u32, } impl Request for TransportSetMaxOutgoingBitrateRequest { const METHOD: request::Method = request::Method::TransportSetMaxOutgoingBitrate; type HandlerId = TransportId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::SetMaxOutgoingBitrateRequest::create(&mut builder, self.bitrate); let request_body = request::Body::create_transport_set_max_outgoing_bitrate_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct TransportSetMinOutgoingBitrateRequest { pub(crate) bitrate: u32, } impl Request for TransportSetMinOutgoingBitrateRequest { const METHOD: request::Method = request::Method::TransportSetMinOutgoingBitrate; type HandlerId = TransportId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::SetMinOutgoingBitrateRequest::create(&mut builder, self.bitrate); let request_body = request::Body::create_transport_set_min_outgoing_bitrate_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct TransportRestartIceRequest {} impl Request for TransportRestartIceRequest { const METHOD: request::Method = request::Method::TransportRestartIce; type HandlerId = TransportId; type Response = IceParameters; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::TransportRestartIceResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = transport::RestartIceResponse::try_from(data)?; Ok(IceParameters::from_fbs(&web_rtc_transport::IceParameters { username_fragment: data.username_fragment, password: data.password, ice_lite: data.ice_lite, })) } } #[derive(Debug)] pub(crate) struct TransportProduceRequest { pub(crate) producer_id: ProducerId, pub(crate) kind: MediaKind, pub(crate) rtp_parameters: RtpParameters, pub(crate) rtp_mapping: RtpMapping, pub(crate) paused: bool, pub(crate) key_frame_request_delay: u32, pub(crate) enable_mediasoup_packet_id_header_extension: bool, } #[derive(Debug)] pub(crate) struct TransportProduceResponse { pub(crate) r#type: ProducerType, } impl Request for TransportProduceRequest { const METHOD: request::Method = request::Method::TransportProduce; type HandlerId = TransportId; type Response = TransportProduceResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::ProduceRequest::create( &mut builder, self.producer_id.to_string(), self.kind.to_fbs(), Box::new(self.rtp_parameters.to_fbs()), Box::new(self.rtp_mapping.to_fbs()), self.paused, self.key_frame_request_delay, self.enable_mediasoup_packet_id_header_extension, ); let request_body = request::Body::create_transport_produce_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::TransportProduceResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = transport::ProduceResponse::try_from(data)?; Ok(TransportProduceResponse { r#type: ProducerType::from_fbs(&data.type_), }) } } #[derive(Debug)] pub(crate) struct TransportConsumeRequest { pub(crate) consumer_id: ConsumerId, pub(crate) producer_id: ProducerId, pub(crate) kind: MediaKind, pub(crate) rtp_parameters: RtpParameters, pub(crate) r#type: ConsumerType, pub(crate) consumable_rtp_encodings: Vec, pub(crate) paused: bool, pub(crate) preferred_layers: Option, pub(crate) ignore_dtx: bool, } #[derive(Debug)] pub(crate) struct TransportConsumeResponse { pub(crate) paused: bool, pub(crate) producer_paused: bool, pub(crate) score: ConsumerScore, pub(crate) preferred_layers: Option, } impl Request for TransportConsumeRequest { const METHOD: request::Method = request::Method::TransportConsume; type HandlerId = TransportId; type Response = TransportConsumeResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::ConsumeRequest::create( &mut builder, self.consumer_id.to_string(), self.producer_id.to_string(), self.kind.to_fbs(), Box::new(self.rtp_parameters.to_fbs()), self.r#type.to_fbs(), ToFbs::to_fbs(&self.consumable_rtp_encodings), self.paused, ToFbs::to_fbs(&self.preferred_layers), // self.preferred_layers.map(ConsumerLayers::to_fbs), self.ignore_dtx, ); let request_body = request::Body::create_transport_consume_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::TransportConsumeResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = transport::ConsumeResponse::try_from(data)?; Ok(TransportConsumeResponse { paused: data.paused, producer_paused: data.producer_paused, score: ConsumerScore::from_fbs(data.score.as_ref()), preferred_layers: data .preferred_layers .map(|preferred_layers| ConsumerLayers::from_fbs(preferred_layers.as_ref())), }) } } #[derive(Debug)] pub(crate) struct TransportProduceDataRequest { pub(crate) data_producer_id: DataProducerId, pub(crate) r#type: DataProducerType, // #[serde(skip_serializing_if = "Option::is_none")] pub(crate) sctp_stream_parameters: Option, pub(crate) label: String, pub(crate) protocol: String, pub(crate) paused: bool, } #[derive(Debug)] pub(crate) struct TransportProduceDataResponse { pub(crate) r#type: DataProducerType, pub(crate) sctp_stream_parameters: Option, pub(crate) label: String, pub(crate) protocol: String, pub(crate) paused: bool, } impl Request for TransportProduceDataRequest { const METHOD: request::Method = request::Method::TransportProduceData; type HandlerId = TransportId; type Response = TransportProduceDataResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::ProduceDataRequest::create( &mut builder, self.data_producer_id.to_string(), match self.r#type { DataProducerType::Sctp => data_producer::Type::Sctp, DataProducerType::Direct => data_producer::Type::Direct, }, ToFbs::to_fbs(&self.sctp_stream_parameters), if self.label.is_empty() { None } else { Some(self.label) }, if self.protocol.is_empty() { None } else { Some(self.protocol) }, self.paused, ); let request_body = request::Body::create_transport_produce_data_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::DataProducerDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = data_producer::DumpResponse::try_from(data)?; Ok(TransportProduceDataResponse { r#type: match data.type_ { data_producer::Type::Sctp => DataProducerType::Sctp, data_producer::Type::Direct => DataProducerType::Direct, }, sctp_stream_parameters: data.sctp_stream_parameters.map(|stream_parameters| { SctpStreamParameters::from_fbs(stream_parameters.as_ref()) }), label: data.label.to_string(), protocol: data.protocol.to_string(), paused: data.paused, }) } } #[derive(Debug)] pub(crate) struct TransportConsumeDataRequest { pub(crate) data_consumer_id: DataConsumerId, pub(crate) data_producer_id: DataProducerId, pub(crate) r#type: DataConsumerType, pub(crate) sctp_stream_parameters: Option, pub(crate) label: String, pub(crate) protocol: String, pub(crate) paused: bool, pub(crate) subchannels: Option>, } #[derive(Debug)] pub(crate) struct TransportConsumeDataResponse { pub(crate) r#type: DataConsumerType, pub(crate) sctp_stream_parameters: Option, pub(crate) label: String, pub(crate) protocol: String, pub(crate) paused: bool, pub(crate) data_producer_paused: bool, pub(crate) subchannels: Vec, } impl Request for TransportConsumeDataRequest { const METHOD: request::Method = request::Method::TransportConsumeData; type HandlerId = TransportId; type Response = TransportConsumeDataResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::ConsumeDataRequest::create( &mut builder, self.data_consumer_id.to_string(), self.data_producer_id.to_string(), match self.r#type { DataConsumerType::Sctp => data_producer::Type::Sctp, DataConsumerType::Direct => data_producer::Type::Direct, }, ToFbs::to_fbs(&self.sctp_stream_parameters), if self.label.is_empty() { None } else { Some(self.label) }, if self.protocol.is_empty() { None } else { Some(self.protocol) }, self.paused, self.subchannels, ); let request_body = request::Body::create_transport_consume_data_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::DataConsumerDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = data_consumer::DumpResponse::try_from(data)?; Ok(TransportConsumeDataResponse { r#type: match data.type_ { data_producer::Type::Sctp => DataConsumerType::Sctp, data_producer::Type::Direct => DataConsumerType::Direct, }, sctp_stream_parameters: data.sctp_stream_parameters.map(|stream_parameters| { SctpStreamParameters::from_fbs(stream_parameters.as_ref()) }), label: data.label.to_string(), protocol: data.protocol.to_string(), paused: data.paused, data_producer_paused: data.data_producer_paused, subchannels: data.subchannels, }) } } #[derive(Debug)] pub(crate) struct TransportEnableTraceEventRequest { pub(crate) types: Vec, } impl Request for TransportEnableTraceEventRequest { const METHOD: request::Method = request::Method::TransportEnableTraceEvent; type HandlerId = TransportId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::EnableTraceEventRequest { events: ToFbs::to_fbs(&self.types), }; let request_body = request::Body::TransportEnableTraceEventRequest(Box::new(data)); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } fn default_for_soft_error() -> Option { None } } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct TransportSendRtcpNotification { pub(crate) rtcp_packet: Vec, } impl Notification for TransportSendRtcpNotification { const EVENT: notification::Event = notification::Event::TransportSendRtcp; type HandlerId = TransportId; fn into_bytes(self, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::SendRtcpNotification::create(&mut builder, self.rtcp_packet); let notification_body = notification::Body::create_transport_send_rtcp_notification(&mut builder, data); let notification = notification::Notification::create( &mut builder, handler_id.to_string(), Self::EVENT, Some(notification_body), ); let message_body = message::Body::create_notification(&mut builder, notification); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } } #[derive(Debug)] pub(crate) struct ProducerCloseRequest { pub(crate) producer_id: ProducerId, } impl Request for ProducerCloseRequest { const METHOD: request::Method = request::Method::TransportCloseProducer; type HandlerId = TransportId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::CloseProducerRequest::create(&mut builder, self.producer_id.to_string()); let request_body = request::Body::create_transport_close_producer_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct ProducerDumpRequest {} impl Request for ProducerDumpRequest { const METHOD: request::Method = request::Method::ProducerDump; type HandlerId = ProducerId; type Response = ProducerDump; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::ProducerDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; ProducerDump::try_from_fbs(data) } } #[derive(Debug)] pub(crate) struct ProducerGetStatsRequest {} impl Request for ProducerGetStatsRequest { const METHOD: request::Method = request::Method::ProducerGetStats; type HandlerId = ProducerId; type Response = response::Body; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { match response { Some(data) => Ok(data.try_into().unwrap()), _ => { panic!("Wrong message from worker: {response:?}"); } } } } #[derive(Debug, Serialize)] pub(crate) struct ProducerPauseRequest {} impl Request for ProducerPauseRequest { const METHOD: request::Method = request::Method::ProducerPause; type HandlerId = ProducerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] pub(crate) struct ProducerResumeRequest {} impl Request for ProducerResumeRequest { const METHOD: request::Method = request::Method::ProducerResume; type HandlerId = ProducerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct ProducerEnableTraceEventRequest { pub(crate) types: Vec, } impl Request for ProducerEnableTraceEventRequest { const METHOD: request::Method = request::Method::ProducerEnableTraceEvent; type HandlerId = ProducerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = producer::EnableTraceEventRequest { events: ToFbs::to_fbs(&self.types), }; let request_body = request::Body::ProducerEnableTraceEventRequest(Box::new(data)); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } fn default_for_soft_error() -> Option { None } } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct ProducerSendNotification { pub(crate) rtp_packet: Vec, } impl Notification for ProducerSendNotification { const EVENT: notification::Event = notification::Event::ProducerSend; type HandlerId = ProducerId; fn into_bytes(self, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = producer::SendNotification::create(&mut builder, self.rtp_packet); let notification_body = notification::Body::create_producer_send_notification(&mut builder, data); let notification = notification::Notification::create( &mut builder, handler_id.to_string(), Self::EVENT, Some(notification_body), ); let message_body = message::Body::create_notification(&mut builder, notification); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } } #[derive(Debug)] pub(crate) struct ConsumerCloseRequest { pub(crate) consumer_id: ConsumerId, } impl Request for ConsumerCloseRequest { const METHOD: request::Method = request::Method::TransportCloseConsumer; type HandlerId = TransportId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::CloseConsumerRequest::create(&mut builder, self.consumer_id.to_string()); let request_body = request::Body::create_transport_close_consumer_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct ConsumerDumpRequest {} impl Request for ConsumerDumpRequest { const METHOD: request::Method = request::Method::ConsumerDump; type HandlerId = ConsumerId; type Response = ConsumerDump; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::ConsumerDumpResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; ConsumerDump::try_from_fbs(data) } } #[derive(Debug)] pub(crate) struct ConsumerGetStatsRequest {} impl Request for ConsumerGetStatsRequest { const METHOD: request::Method = request::Method::ConsumerGetStats; type HandlerId = ConsumerId; type Response = response::Body; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { match response { Some(data) => Ok(data.try_into().unwrap()), _ => { panic!("Wrong message from worker: {response:?}"); } } } } #[derive(Debug)] pub(crate) struct ConsumerPauseRequest {} impl Request for ConsumerPauseRequest { const METHOD: request::Method = request::Method::ConsumerPause; type HandlerId = ConsumerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] pub(crate) struct ConsumerResumeRequest {} impl Request for ConsumerResumeRequest { const METHOD: request::Method = request::Method::ConsumerResume; type HandlerId = ConsumerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] pub(crate) struct ConsumerSetPreferredLayersRequest { pub(crate) data: ConsumerLayers, } impl Request for ConsumerSetPreferredLayersRequest { const METHOD: request::Method = request::Method::ConsumerSetPreferredLayers; type HandlerId = ConsumerId; type Response = Option; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = consumer::SetPreferredLayersRequest::create( &mut builder, ConsumerLayers::to_fbs(&self.data), ); let request_body = request::Body::create_consumer_set_preferred_layers_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::ConsumerSetPreferredLayersResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = consumer::SetPreferredLayersResponse::try_from(data)?; match data.preferred_layers { Some(preferred_layers) => Ok(Some(ConsumerLayers::from_fbs(preferred_layers.as_ref()))), None => Ok(None), } } } #[derive(Debug, Serialize)] pub(crate) struct ConsumerSetPriorityRequest { pub(crate) priority: u8, } #[derive(Debug, Serialize)] pub(crate) struct ConsumerSetPriorityResponse { pub(crate) priority: u8, } impl Request for ConsumerSetPriorityRequest { const METHOD: request::Method = request::Method::ConsumerSetPriority; type HandlerId = ConsumerId; type Response = ConsumerSetPriorityResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = consumer::SetPriorityRequest::create(&mut builder, self.priority); let request_body = request::Body::create_consumer_set_priority_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::ConsumerSetPriorityResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = consumer::SetPriorityResponse::try_from(data)?; Ok(ConsumerSetPriorityResponse { priority: data.priority, }) } } #[derive(Debug)] pub(crate) struct ConsumerRequestKeyFrameRequest {} impl Request for ConsumerRequestKeyFrameRequest { const METHOD: request::Method = request::Method::ConsumerRequestKeyFrame; type HandlerId = ConsumerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct ConsumerEnableTraceEventRequest { pub(crate) types: Vec, } impl Request for ConsumerEnableTraceEventRequest { const METHOD: request::Method = request::Method::ConsumerEnableTraceEvent; type HandlerId = ConsumerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = consumer::EnableTraceEventRequest { events: ToFbs::to_fbs(&self.types), }; let request_body = request::Body::ConsumerEnableTraceEventRequest(Box::new(data)); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } fn default_for_soft_error() -> Option { None } } #[derive(Debug)] pub(crate) struct DataProducerCloseRequest { pub(crate) data_producer_id: DataProducerId, } impl Request for DataProducerCloseRequest { const METHOD: request::Method = request::Method::TransportCloseDataproducer; type HandlerId = TransportId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::CloseDataProducerRequest::create( &mut builder, self.data_producer_id.to_string(), ); let request_body = request::Body::create_transport_close_data_producer_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct DataProducerDumpRequest {} impl Request for DataProducerDumpRequest { const METHOD: request::Method = request::Method::DataproducerDump; type HandlerId = DataProducerId; type Response = response::Body; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { match response { Some(data) => Ok(data.try_into().unwrap()), _ => { panic!("Wrong message from worker: {response:?}"); } } } } #[derive(Debug)] pub(crate) struct DataProducerGetStatsRequest {} impl Request for DataProducerGetStatsRequest { const METHOD: request::Method = request::Method::DataproducerGetStats; type HandlerId = DataProducerId; type Response = response::Body; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { match response { Some(data) => Ok(data.try_into().unwrap()), _ => { panic!("Wrong message from worker: {response:?}"); } } } } #[derive(Debug, Serialize)] pub(crate) struct DataProducerPauseRequest {} impl Request for DataProducerPauseRequest { const METHOD: request::Method = request::Method::DataproducerPause; type HandlerId = DataProducerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] pub(crate) struct DataProducerResumeRequest {} impl Request for DataProducerResumeRequest { const METHOD: request::Method = request::Method::DataproducerResume; type HandlerId = DataProducerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct DataProducerSendNotification { pub(crate) ppid: u32, pub(crate) payload: Vec, pub(crate) subchannels: Option>, pub(crate) required_subchannel: Option, } impl Notification for DataProducerSendNotification { const EVENT: notification::Event = notification::Event::DataproducerSend; type HandlerId = DataProducerId; fn into_bytes(self, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = data_producer::SendNotification::create( &mut builder, self.ppid, self.payload, self.subchannels, self.required_subchannel, ); let notification_body = notification::Body::create_data_producer_send_notification(&mut builder, data); let notification = notification::Notification::create( &mut builder, handler_id.to_string(), Self::EVENT, Some(notification_body), ); let message_body = message::Body::create_notification(&mut builder, notification); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } } #[derive(Debug)] pub(crate) struct DataConsumerCloseRequest { pub(crate) data_consumer_id: DataConsumerId, } impl Request for DataConsumerCloseRequest { const METHOD: request::Method = request::Method::TransportCloseDataconsumer; type HandlerId = TransportId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = transport::CloseDataConsumerRequest::create( &mut builder, self.data_consumer_id.to_string(), ); let request_body = request::Body::create_transport_close_data_consumer_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct DataConsumerDumpRequest {} impl Request for DataConsumerDumpRequest { const METHOD: request::Method = request::Method::DataconsumerDump; type HandlerId = DataConsumerId; type Response = response::Body; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { match response { Some(data) => Ok(data.try_into().unwrap()), _ => { panic!("Wrong message from worker: {response:?}"); } } } } #[derive(Debug)] pub(crate) struct DataConsumerGetStatsRequest {} impl Request for DataConsumerGetStatsRequest { const METHOD: request::Method = request::Method::DataconsumerGetStats; type HandlerId = DataConsumerId; type Response = response::Body; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { match response { Some(data) => Ok(data.try_into().unwrap()), _ => { panic!("Wrong message from worker: {response:?}"); } } } } #[derive(Debug, Serialize)] pub(crate) struct DataConsumerPauseRequest {} impl Request for DataConsumerPauseRequest { const METHOD: request::Method = request::Method::DataconsumerPause; type HandlerId = DataConsumerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] pub(crate) struct DataConsumerResumeRequest {} impl Request for DataConsumerResumeRequest { const METHOD: request::Method = request::Method::DataconsumerResume; type HandlerId = DataConsumerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] pub(crate) struct DataConsumerGetBufferedAmountRequest {} #[derive(Debug, Serialize)] pub(crate) struct DataConsumerGetBufferedAmountResponse { pub(crate) buffered_amount: u32, } impl Request for DataConsumerGetBufferedAmountRequest { const METHOD: request::Method = request::Method::DataconsumerGetBufferedAmount; type HandlerId = DataConsumerId; type Response = DataConsumerGetBufferedAmountResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::DataConsumerGetBufferedAmountResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = data_consumer::GetBufferedAmountResponse::try_from(data)?; Ok(DataConsumerGetBufferedAmountResponse { buffered_amount: data.buffered_amount, }) } } #[derive(Debug, Serialize)] pub(crate) struct DataConsumerSetBufferedAmountLowThresholdRequest { pub(crate) threshold: u32, } impl Request for DataConsumerSetBufferedAmountLowThresholdRequest { const METHOD: request::Method = request::Method::DataconsumerSetBufferedAmountLowThreshold; type HandlerId = DataConsumerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = data_consumer::SetBufferedAmountLowThresholdRequest::create( &mut builder, self.threshold, ); let request_body = request::Body::create_data_consumer_set_buffered_amount_low_threshold_request( &mut builder, data, ); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Clone, Serialize)] #[serde(into = "u32")] pub(crate) struct DataConsumerSendRequest { pub(crate) ppid: u32, pub(crate) payload: Vec, } impl Request for DataConsumerSendRequest { const METHOD: request::Method = request::Method::DataconsumerSend; type HandlerId = DataConsumerId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = data_consumer::SendRequest::create(&mut builder, self.ppid, self.payload); let request_body = request::Body::create_data_consumer_send_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } impl From for u32 { fn from(request: DataConsumerSendRequest) -> Self { request.ppid } } #[derive(Debug, Clone, Serialize)] pub(crate) struct DataConsumerSetSubchannelsRequest { pub(crate) subchannels: Vec, } #[derive(Debug, Clone, Serialize)] pub(crate) struct DataConsumerSetSubchannelsResponse { pub(crate) subchannels: Vec, } impl Request for DataConsumerSetSubchannelsRequest { const METHOD: request::Method = request::Method::DataconsumerSetSubchannels; type HandlerId = DataConsumerId; type Response = DataConsumerSetSubchannelsResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = data_consumer::SetSubchannelsRequest::create(&mut builder, self.subchannels); let request_body = request::Body::create_data_consumer_set_subchannels_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::DataConsumerSetSubchannelsResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = data_consumer::SetSubchannelsResponse::try_from(data)?; Ok(DataConsumerSetSubchannelsResponse { subchannels: data.subchannels, }) } } #[derive(Debug, Clone, Serialize)] pub(crate) struct DataConsumerAddSubchannelRequest { pub(crate) subchannel: u16, } #[derive(Debug, Clone, Serialize)] pub(crate) struct DataConsumerAddSubchannelResponse { pub(crate) subchannels: Vec, } impl Request for DataConsumerAddSubchannelRequest { const METHOD: request::Method = request::Method::DataconsumerAddSubchannel; type HandlerId = DataConsumerId; type Response = DataConsumerAddSubchannelResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = data_consumer::AddSubchannelRequest::create(&mut builder, self.subchannel); let request_body = request::Body::create_data_consumer_add_subchannel_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::DataConsumerAddSubchannelResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = data_consumer::AddSubchannelResponse::try_from(data)?; Ok(DataConsumerAddSubchannelResponse { subchannels: data.subchannels, }) } } #[derive(Debug, Clone, Serialize)] pub(crate) struct DataConsumerRemoveSubchannelRequest { pub(crate) subchannel: u16, } #[derive(Debug, Clone, Serialize)] pub(crate) struct DataConsumerRemoveSubchannelResponse { pub(crate) subchannels: Vec, } impl Request for DataConsumerRemoveSubchannelRequest { const METHOD: request::Method = request::Method::DataconsumerRemoveSubchannel; type HandlerId = DataConsumerId; type Response = DataConsumerRemoveSubchannelResponse; fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = data_consumer::RemoveSubchannelRequest::create(&mut builder, self.subchannel); let request_body = request::Body::create_data_consumer_remove_subchannel_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( response: Option>, ) -> Result> { let Some(response::BodyRef::DataConsumerRemoveSubchannelResponse(data)) = response else { panic!("Wrong message from worker: {response:?}"); }; let data = data_consumer::RemoveSubchannelResponse::try_from(data)?; Ok(DataConsumerRemoveSubchannelResponse { subchannels: data.subchannels, }) } } #[derive(Debug)] pub(crate) struct RtpObserverCloseRequest { pub(crate) rtp_observer_id: RtpObserverId, } impl Request for RtpObserverCloseRequest { const METHOD: request::Method = request::Method::RouterCloseRtpobserver; type HandlerId = RouterId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = router::CloseRtpObserverRequest::create(&mut builder, self.rtp_observer_id.to_string()); let request_body = request::Body::create_router_close_rtp_observer_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct RtpObserverPauseRequest {} impl Request for RtpObserverPauseRequest { const METHOD: request::Method = request::Method::RtpobserverPause; type HandlerId = RtpObserverId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug)] pub(crate) struct RtpObserverResumeRequest {} impl Request for RtpObserverResumeRequest { const METHOD: request::Method = request::Method::RtpobserverResume; type HandlerId = RtpObserverId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), None::, ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RtpObserverAddProducerRequest { pub(crate) producer_id: ProducerId, } impl Request for RtpObserverAddProducerRequest { const METHOD: request::Method = request::Method::RtpobserverAddProducer; type HandlerId = RtpObserverId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = rtp_observer::AddProducerRequest::create(&mut builder, self.producer_id.to_string()); let request_body = request::Body::create_rtp_observer_add_producer_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub(crate) struct RtpObserverRemoveProducerRequest { pub(crate) producer_id: ProducerId, } impl Request for RtpObserverRemoveProducerRequest { const METHOD: request::Method = request::Method::RtpobserverRemoveProducer; type HandlerId = RtpObserverId; type Response = (); fn into_bytes(self, id: u32, handler_id: Self::HandlerId) -> Vec { let mut builder = Builder::new(); let data = rtp_observer::RemoveProducerRequest::create(&mut builder, self.producer_id.to_string()); let request_body = request::Body::create_rtp_observer_remove_producer_request(&mut builder, data); let request = request::Request::create( &mut builder, id, Self::METHOD, handler_id.to_string(), Some(request_body), ); let message_body = message::Body::create_request(&mut builder, request); let message = message::Message::create(&mut builder, message_body); builder.finish(message, None).to_vec() } fn convert_response( _response: Option>, ) -> Result> { Ok(()) } } ================================================ FILE: rust/src/ortc/tests.rs ================================================ use super::*; use mediasoup_types::rtp_parameters::{MimeTypeAudio, RtpHeaderExtension}; use std::iter; #[test] fn generate_router_rtp_capabilities_succeeds() { let media_codecs = vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: Some(125), // Let's force it. clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("level-asymmetry-allowed", 1_u32.into()), ("profile-level-id", "42e01f".into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], // Will be ignored. }, ]; let rtp_capabilities = generate_router_rtp_capabilities(media_codecs) .expect("Failed to generate router RTP capabilities"); assert_eq!( rtp_capabilities.codecs, vec![ RtpCodecCapabilityFinalized::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: 100, // 100 is the first available dynamic PT. clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc,], }, RtpCodecCapabilityFinalized::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: 125, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc ], }, RtpCodecCapabilityFinalized::Video { mime_type: MimeTypeVideo::Rtx, preferred_payload_type: 101, // 101 is the second available dynamic PT. clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 125u32.into())]), rtcp_feedback: vec![], }, RtpCodecCapabilityFinalized::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: 102, // 102 is the third available dynamic PT. clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ // Since packetization-mode param was not included in the // H264 codec and it's default value is 0, it's not added // by ortc file. // ("packetization-mode", 0_u32.into()), ("level-asymmetry-allowed", 1_u32.into()), ("profile-level-id", "42e01f".into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, RtpCodecCapabilityFinalized::Video { mime_type: MimeTypeVideo::Rtx, preferred_payload_type: 103, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 102u32.into())]), rtcp_feedback: vec![], }, ] ); } #[test] fn generate_router_rtp_capabilities_unsupported() { assert!(matches!( generate_router_rtp_capabilities(vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }]), Err(RtpCapabilitiesError::UnsupportedCodec { .. }) )); } #[test] fn generate_router_rtp_capabilities_too_many_codecs() { assert!(matches!( generate_router_rtp_capabilities( iter::repeat_n( RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, 100 ) .collect::>() ), Err(RtpCapabilitiesError::CannotAllocate) )); } #[test] fn get_producer_rtp_parameters_mapping_get_consumable_rtp_parameters_get_consumer_rtp_parameters_get_pipe_consumer_rtp_parameters_succeeds( ) { let media_codecs = vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("level-asymmetry-allowed", 1_u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ("foo", "lalala".into()), ]), rtcp_feedback: vec![], }, ]; let router_rtp_capabilities = generate_router_rtp_capabilities(media_codecs) .expect("Failed to generate router RTP capabilities"); let rtp_parameters = RtpParameters { mid: None, codecs: vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 111, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("foo", 1234u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::GoogRemb, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 111_u32.into())]), rtcp_feedback: vec![], }, ], header_extensions: vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 1, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 2, encrypt: false, }, ], encodings: vec![ RtpEncodingParameters { ssrc: Some(11111111), rtx: Some(RtpEncodingParametersRtx { ssrc: 11111112 }), scalability_mode: ScalabilityMode::L1T3, max_bitrate: Some(111111), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(21111111), rtx: Some(RtpEncodingParametersRtx { ssrc: 21111112 }), scalability_mode: ScalabilityMode::L1T3, max_bitrate: Some(222222), ..RtpEncodingParameters::default() }, RtpEncodingParameters { rid: Some("high".to_string()), scalability_mode: ScalabilityMode::L1T3, max_bitrate: Some(333333), ..RtpEncodingParameters::default() }, ], rtcp: RtcpParameters { cname: Some("qwerty1234".to_string()), ..RtcpParameters::default() }, msid: None, }; let rtp_mapping = get_producer_rtp_parameters_mapping(&rtp_parameters, &router_rtp_capabilities) .expect("Failed to get producer RTP parameters mapping"); assert_eq!( rtp_mapping.codecs, vec![ RtpMappingCodec { payload_type: 111, mapped_payload_type: 101 }, RtpMappingCodec { payload_type: 112, mapped_payload_type: 102 }, ] ); assert_eq!(rtp_mapping.encodings.first().unwrap().ssrc, Some(11111111)); assert_eq!(rtp_mapping.encodings.first().unwrap().rid, None); assert_eq!(rtp_mapping.encodings.get(1).unwrap().ssrc, Some(21111111)); assert_eq!(rtp_mapping.encodings.get(1).unwrap().rid, None); assert_eq!(rtp_mapping.encodings.get(2).unwrap().ssrc, None); assert_eq!( rtp_mapping.encodings.get(2).unwrap().rid, Some("high".to_string()) ); let consumable_rtp_parameters = get_consumable_rtp_parameters( MediaKind::Video, &rtp_parameters, &router_rtp_capabilities, &rtp_mapping, ); assert_eq!( consumable_rtp_parameters.codecs, vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 101, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("foo", 1234u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 102, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 101_u32.into())]), rtcp_feedback: vec![], }, ] ); assert_eq!( consumable_rtp_parameters.encodings.first().unwrap().ssrc, Some(rtp_mapping.encodings.first().unwrap().mapped_ssrc), ); assert_eq!( consumable_rtp_parameters .encodings .first() .unwrap() .max_bitrate, Some(111111), ); assert_eq!( consumable_rtp_parameters .encodings .first() .unwrap() .scalability_mode, ScalabilityMode::L1T3, ); assert_eq!( consumable_rtp_parameters.encodings.get(1).unwrap().ssrc, Some(rtp_mapping.encodings.get(1).unwrap().mapped_ssrc), ); assert_eq!( consumable_rtp_parameters .encodings .get(1) .unwrap() .max_bitrate, Some(222222), ); assert_eq!( consumable_rtp_parameters .encodings .get(1) .unwrap() .scalability_mode, ScalabilityMode::L1T3, ); assert_eq!( consumable_rtp_parameters.encodings.get(2).unwrap().ssrc, Some(rtp_mapping.encodings.get(2).unwrap().mapped_ssrc), ); assert_eq!( consumable_rtp_parameters .encodings .get(2) .unwrap() .max_bitrate, Some(333333), ); assert_eq!( consumable_rtp_parameters .encodings .get(2) .unwrap() .scalability_mode, ScalabilityMode::L1T3, ); assert_eq!( consumable_rtp_parameters.rtcp, RtcpParameters { cname: rtp_parameters.rtcp.cname.clone(), reduced_size: true, } ); let remote_rtp_capabilities = RtpCapabilities { codecs: vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: Some(100), clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: Some(101), clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ("baz", "LOLOLO".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::Unsupported, ], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Rtx, preferred_payload_type: Some(102), clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 101_u32.into())]), rtcp_feedback: vec![], }, ], header_extensions: vec![ RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::Mid, preferred_id: 1, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::Mid, preferred_id: 1, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::RtpStreamId, preferred_id: 2, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::SsrcAudioLevel, preferred_id: 6, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::VideoOrientation, preferred_id: 8, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::TimeOffset, preferred_id: 9, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, ], }; let consumer_rtp_parameters = get_consumer_rtp_parameters( &consumable_rtp_parameters, &remote_rtp_capabilities, false, true, ) .expect("Failed to get consumer RTP parameters"); assert_eq!( consumer_rtp_parameters.codecs, vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 101, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("foo", 1234u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::Unsupported, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 102, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 101_u32.into())]), rtcp_feedback: vec![], }, ] ); assert_eq!(consumer_rtp_parameters.encodings.len(), 1); assert!(consumer_rtp_parameters .encodings .first() .unwrap() .ssrc .is_some()); assert!(consumer_rtp_parameters .encodings .first() .unwrap() .rtx .is_some()); assert_eq!( consumer_rtp_parameters .encodings .first() .unwrap() .scalability_mode, ScalabilityMode::L3T3, ); assert_eq!( consumer_rtp_parameters .encodings .first() .unwrap() .max_bitrate, Some(333333), ); assert_eq!( consumer_rtp_parameters.header_extensions, vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 1, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 8, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::TimeOffset, id: 9, encrypt: false, }, ], ); assert_eq!( consumer_rtp_parameters.rtcp, RtcpParameters { cname: rtp_parameters.rtcp.cname.clone(), reduced_size: true, }, ); let pipe_consumer_rtp_parameters = get_pipe_consumer_rtp_parameters(&consumable_rtp_parameters, false); assert_eq!( pipe_consumer_rtp_parameters.codecs, vec![RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 101, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("foo", 1234u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![RtcpFeedback::NackPli, RtcpFeedback::CcmFir], }], ); assert_eq!(pipe_consumer_rtp_parameters.encodings.len(), 3); assert!(pipe_consumer_rtp_parameters .encodings .first() .unwrap() .ssrc .is_some()); assert!(pipe_consumer_rtp_parameters .encodings .first() .unwrap() .rtx .is_none()); assert!(pipe_consumer_rtp_parameters .encodings .first() .unwrap() .max_bitrate .is_some()); assert_eq!( pipe_consumer_rtp_parameters .encodings .first() .unwrap() .scalability_mode, ScalabilityMode::L1T3, ); assert!(pipe_consumer_rtp_parameters .encodings .get(1) .unwrap() .ssrc .is_some()); assert!(pipe_consumer_rtp_parameters .encodings .get(1) .unwrap() .rtx .is_none()); assert!(pipe_consumer_rtp_parameters .encodings .get(1) .unwrap() .max_bitrate .is_some()); assert_eq!( pipe_consumer_rtp_parameters .encodings .get(1) .unwrap() .scalability_mode, ScalabilityMode::L1T3, ); assert!(pipe_consumer_rtp_parameters .encodings .get(2) .unwrap() .ssrc .is_some()); assert!(pipe_consumer_rtp_parameters .encodings .get(2) .unwrap() .rtx .is_none()); assert!(pipe_consumer_rtp_parameters .encodings .get(2) .unwrap() .max_bitrate .is_some()); assert_eq!( pipe_consumer_rtp_parameters .encodings .get(2) .unwrap() .scalability_mode, ScalabilityMode::L1T3, ); assert_eq!( pipe_consumer_rtp_parameters.rtcp, RtcpParameters { cname: rtp_parameters.rtcp.cname, reduced_size: true, }, ); } #[test] fn get_producer_rtp_parameters_mapping_unsupported() { let media_codecs = vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "640032".into()), ]), rtcp_feedback: vec![], }, ]; let router_rtp_capabilities = generate_router_rtp_capabilities(media_codecs) .expect("Failed to generate router RTP capabilities"); let rtp_parameters = RtpParameters { mid: None, codecs: vec![RtpCodecParameters::Video { mime_type: MimeTypeVideo::Vp8, payload_type: 120, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::Unsupported], }], header_extensions: vec![], encodings: vec![RtpEncodingParameters { ssrc: Some(11111111), ..RtpEncodingParameters::default() }], rtcp: RtcpParameters { cname: Some("qwerty1234".to_string()), ..RtcpParameters::default() }, msid: None, }; assert!(matches!( get_producer_rtp_parameters_mapping(&rtp_parameters, &router_rtp_capabilities), Err(RtpParametersMappingError::UnsupportedCodec { .. }), )); } ================================================ FILE: rust/src/ortc.rs ================================================ use crate::fbs::{ToFbs, TryFromFbs}; use crate::supported_rtp_capabilities; use mediasoup_sys::fbs::rtp_parameters; use mediasoup_types::rtp_parameters::{ MediaKind, MimeType, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters, RtpCapabilities, RtpCapabilitiesFinalized, RtpCodecCapability, RtpCodecCapabilityFinalized, RtpCodecParameters, RtpCodecParametersParameters, RtpCodecParametersParametersValue, RtpEncodingParameters, RtpEncodingParametersRtx, RtpHeaderExtensionDirection, RtpHeaderExtensionParameters, RtpHeaderExtensionUri, RtpParameters, }; use mediasoup_types::scalability_modes::ScalabilityMode; use once_cell::sync::Lazy; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::collections::BTreeMap; use std::convert::TryFrom; use std::error::Error; use std::mem; use std::num::{NonZeroU32, NonZeroU8}; use std::ops::Deref; use thiserror::Error; #[cfg(test)] mod tests; const DYNAMIC_PAYLOAD_TYPES: &[u8] = &[ 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 96, 97, 98, 99, ]; // TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header // extension. // // This is an object where we store some objects we may later need. struct Cache { pub dependency_descriptor_header_extension_parameters_for_pipe_consumer: Option, } static CACHE: Lazy> = Lazy::new(|| { Mutex::new(Cache { dependency_descriptor_header_extension_parameters_for_pipe_consumer: None, }) }); #[doc(hidden)] #[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RtpMappingCodec { pub payload_type: u8, pub mapped_payload_type: u8, } #[doc(hidden)] #[derive(Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RtpMappingEncoding { #[serde(skip_serializing_if = "Option::is_none")] pub ssrc: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rid: Option, #[serde(default, skip_serializing_if = "ScalabilityMode::is_none")] pub scalability_mode: ScalabilityMode, pub mapped_ssrc: u32, } #[doc(hidden)] #[derive(Debug, Default, Clone, Ord, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] pub struct RtpMapping { pub codecs: Vec, pub encodings: Vec, } impl<'a> TryFromFbs<'a> for RtpMapping { type FbsType = rtp_parameters::RtpMappingRef<'a>; type Error = Box; fn try_from_fbs(mapping: Self::FbsType) -> Result { Ok(Self { codecs: mapping .codecs()? .iter() .map(|mapping| { Ok(RtpMappingCodec { payload_type: mapping?.payload_type()?, mapped_payload_type: mapping?.mapped_payload_type()?, }) }) .collect::, Box>>()?, encodings: mapping .encodings()? .iter() .map(|mapping| { Ok(RtpMappingEncoding { rid: mapping?.rid()?.map(|rid| rid.to_string()), ssrc: mapping?.ssrc()?, scalability_mode: mapping? .scalability_mode()? .map(|maybe_scalability_mode| maybe_scalability_mode.parse()) .transpose()? .unwrap_or_default(), mapped_ssrc: mapping?.mapped_ssrc()?, }) }) .collect::, Box>>()?, }) } } impl ToFbs for RtpMapping { type FbsType = rtp_parameters::RtpMapping; fn to_fbs(&self) -> Self::FbsType { rtp_parameters::RtpMapping { codecs: self .codecs .iter() .map(|mapping| rtp_parameters::CodecMapping { payload_type: mapping.payload_type, mapped_payload_type: mapping.mapped_payload_type, }) .collect(), encodings: self .encodings .iter() .map(|mapping| rtp_parameters::EncodingMapping { rid: mapping.rid.clone().map(|rid| rid.to_string()), ssrc: mapping.ssrc, scalability_mode: Some(mapping.scalability_mode.to_string()), mapped_ssrc: mapping.mapped_ssrc, }) .collect(), } } } /// Error caused by invalid RTP parameters. #[derive(Debug, Error, Eq, PartialEq)] pub enum RtpParametersError { /// Invalid codec apt parameter. #[error("Invalid codec apt parameter {0}")] InvalidAptParameter(Cow<'static, str>), } /// Error caused by invalid RTP capabilities. #[derive(Debug, Error, Eq, PartialEq)] pub enum RtpCapabilitiesError { /// Media codec not supported. #[error("Media codec not supported [mime_type:{mime_type:?}")] UnsupportedCodec { /// Mime type mime_type: MimeType, }, /// Cannot allocate more dynamic codec payload types. #[error("Cannot allocate more dynamic codec payload types")] CannotAllocate, /// Invalid codec apt parameter. #[error("Invalid codec apt parameter {0}")] InvalidAptParameter(Cow<'static, str>), /// Duplicated preferred payload type #[error("Duplicated preferred payload type {0}")] DuplicatedPreferredPayloadType(u8), } /// Error caused by invalid or unsupported RTP parameters given. #[derive(Debug, Error, Eq, PartialEq)] pub enum RtpParametersMappingError { /// Unsupported codec. #[error("Unsupported codec [mime_type:{mime_type:?}, payloadType:{payload_type}]")] UnsupportedCodec { /// Mime type. mime_type: MimeType, /// Payload type. payload_type: u8, }, /// No RTX codec for capability codec PT. #[error("No RTX codec for capability codec PT {preferred_payload_type}")] UnsupportedRtxCodec { /// Preferred payload type. preferred_payload_type: u8, }, /// Missing media codec found for RTX PT. #[error("Missing media codec found for RTX PT {payload_type}")] MissingMediaCodecForRtx { /// Payload type. payload_type: u8, }, } /// Error caused by bad consumer RTP parameters. #[derive(Debug, Error, Eq, PartialEq)] pub enum ConsumerRtpParametersError { /// Invalid capabilities #[error("Invalid capabilities: {0}")] InvalidCapabilities(RtpCapabilitiesError), /// No compatible media codecs #[error("No compatible media codecs")] NoCompatibleMediaCodecs, } fn generate_ssrc() -> u32 { fastrand::u32(100_000_000..999_999_999) } /// Validates [`RtpParameters`]. pub(crate) fn validate_rtp_parameters( rtp_parameters: &RtpParameters, ) -> Result<(), RtpParametersError> { for codec in &rtp_parameters.codecs { validate_rtp_codec_parameters(codec)?; } Ok(()) } /// Validates [`RtpCodecParameters`]. fn validate_rtp_codec_parameters(codec: &RtpCodecParameters) -> Result<(), RtpParametersError> { for (key, value) in codec.parameters().iter() { // Specific parameters validation. if key.as_ref() == "apt" { match value { RtpCodecParametersParametersValue::Number(_) => { // Good } RtpCodecParametersParametersValue::String(string) => { return Err(RtpParametersError::InvalidAptParameter(string.clone())); } } } } Ok(()) } // Validates [`RtpCodecCapability`]. fn validate_rtp_codec_capability(codec: &RtpCodecCapability) -> Result<(), RtpCapabilitiesError> { for (key, value) in codec.parameters().iter() { // Specific parameters validation. if key.as_ref() == "apt" { match value { RtpCodecParametersParametersValue::Number(_) => { // Good } RtpCodecParametersParametersValue::String(string) => { return Err(RtpCapabilitiesError::InvalidAptParameter(string.clone())); } } } } Ok(()) } /// Validates [`RtpCapabilities`]. pub(crate) fn validate_rtp_capabilities( caps: &RtpCapabilities, ) -> Result<(), RtpCapabilitiesError> { for codec in &caps.codecs { validate_rtp_codec_capability(codec)?; } Ok(()) } /// Generate RTP capabilities for the Router based on the given media codecs and mediasoup supported /// RTP capabilities. pub(crate) fn generate_router_rtp_capabilities( mut media_codecs: Vec, ) -> Result { let supported_rtp_capabilities = supported_rtp_capabilities::get_supported_rtp_capabilities(); validate_rtp_capabilities(&supported_rtp_capabilities)?; let mut dynamic_payload_types = Vec::from(DYNAMIC_PAYLOAD_TYPES); let mut caps = RtpCapabilitiesFinalized { codecs: vec![], header_extensions: supported_rtp_capabilities.header_extensions, }; for media_codec in &mut media_codecs { validate_rtp_codec_capability(media_codec)?; let codec = match supported_rtp_capabilities .codecs .iter() .find(|supported_codec| { match_codecs(media_codec.deref().into(), (*supported_codec).into(), false).is_ok() }) { Some(codec) => codec, None => { return Err(RtpCapabilitiesError::UnsupportedCodec { mime_type: media_codec.mime_type(), }); } }; let preferred_payload_type = match media_codec.preferred_payload_type() { Some(preferred_payload_type) => { // If the given media codec has preferred_payload_type, keep it. // Also remove the payload_type from the list of available dynamic values. dynamic_payload_types.retain(|&pt| pt != preferred_payload_type); preferred_payload_type } None => { if let Some(preferred_payload_type) = codec.preferred_payload_type() { // Otherwise if the supported codec has preferredPayloadType, use it. // No need to remove it from the list since it's not a dynamic value. preferred_payload_type } else { // Otherwise choose a dynamic one. if dynamic_payload_types.is_empty() { return Err(RtpCapabilitiesError::CannotAllocate); } // Take the first available payload type and remove it from the list. dynamic_payload_types.remove(0) } } }; // Ensure there is not duplicated preferredPayloadType values. for codec in &caps.codecs { if codec.preferred_payload_type() == preferred_payload_type { return Err(RtpCapabilitiesError::DuplicatedPreferredPayloadType( preferred_payload_type, )); } } let codec_finalized = match codec { RtpCodecCapability::Audio { mime_type, preferred_payload_type: _, clock_rate, channels, parameters, rtcp_feedback, } => RtpCodecCapabilityFinalized::Audio { mime_type: *mime_type, preferred_payload_type, clock_rate: *clock_rate, channels: *channels, parameters: { // Merge the media codec parameters. let mut parameters = parameters.clone(); parameters.extend(mem::take(media_codec.parameters_mut())); parameters }, rtcp_feedback: rtcp_feedback.clone(), }, RtpCodecCapability::Video { mime_type, preferred_payload_type: _, clock_rate, parameters, rtcp_feedback, } => RtpCodecCapabilityFinalized::Video { mime_type: *mime_type, preferred_payload_type, clock_rate: *clock_rate, parameters: { // Merge the media codec parameters. let mut parameters = parameters.clone(); parameters.extend(mem::take(media_codec.parameters_mut())); parameters }, rtcp_feedback: rtcp_feedback.clone(), }, }; // Add a RTX video codec if video. if matches!(codec_finalized, RtpCodecCapabilityFinalized::Video { .. }) { if dynamic_payload_types.is_empty() { return Err(RtpCapabilitiesError::CannotAllocate); } // Take the first available payload_type and remove it from the list. let payload_type = dynamic_payload_types.remove(0); let rtx_codec = RtpCodecCapabilityFinalized::Video { mime_type: MimeTypeVideo::Rtx, preferred_payload_type: payload_type, clock_rate: codec_finalized.clock_rate(), parameters: RtpCodecParametersParameters::from([( "apt", codec_finalized.preferred_payload_type().into(), )]), rtcp_feedback: vec![], }; // Append to the codec list. caps.codecs.push(codec_finalized); caps.codecs.push(rtx_codec); } else { // Append to the codec list. caps.codecs.push(codec_finalized); } } // TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header // extension. // // We need to create and store this Dependency-Descriptor header extension to // leter be used by `getPipeConsumerRtpParameters()` function. let dependency_descriptor_header_extension_for_pipe_consumer = caps.header_extensions.iter().find(|ext| { ext.uri == RtpHeaderExtensionUri::DependencyDescriptor && ext.direction != RtpHeaderExtensionDirection::SendRecv }); if let Some(dependency_descriptor) = dependency_descriptor_header_extension_for_pipe_consumer { if dependency_descriptor.direction != RtpHeaderExtensionDirection::SendRecv { let mut cache = CACHE.lock(); cache.dependency_descriptor_header_extension_parameters_for_pipe_consumer = Some(RtpHeaderExtensionParameters { uri: dependency_descriptor.uri, id: dependency_descriptor.preferred_id, encrypt: dependency_descriptor.preferred_encrypt, }); } } Ok(caps) } /// Get a mapping of codec payloads and encodings of the given Producer RTP parameters as values /// expected by the Router. pub(crate) fn get_producer_rtp_parameters_mapping( rtp_parameters: &RtpParameters, rtp_capabilities: &RtpCapabilitiesFinalized, ) -> Result { let mut rtp_mapping = RtpMapping::default(); // Match parameters media codecs to capabilities media codecs. let mut codec_to_cap_codec = BTreeMap::<&RtpCodecParameters, Cow<'_, RtpCodecCapabilityFinalized>>::new(); for codec in &rtp_parameters.codecs { if codec.is_rtx() { continue; } // Search for the same media codec in capabilities. match rtp_capabilities.codecs.iter().find_map(|cap_codec| { match_codecs(codec.into(), cap_codec.into(), true) .ok() .map(|profile_level_id| { // This is rather ugly, but we need to fix `profile-level-id` and this was the // quickest way to do it profile_level_id.map_or(Cow::Borrowed(cap_codec), |profile_level_id| { let mut cap_codec = cap_codec.clone(); cap_codec .parameters_mut() .insert("profile-level-id", profile_level_id); Cow::Owned(cap_codec) }) }) }) { Some(matched_codec_capability) => { codec_to_cap_codec.insert(codec, matched_codec_capability); } None => { return Err(RtpParametersMappingError::UnsupportedCodec { mime_type: codec.mime_type(), payload_type: codec.payload_type(), }); } } } // Match parameters RTX codecs to capabilities RTX codecs. for codec in &rtp_parameters.codecs { if !codec.is_rtx() { continue; } // Search for the associated media codec. let associated_media_codec = rtp_parameters.codecs.iter().find(|media_codec| { let media_codec_payload_type = media_codec.payload_type(); let codec_parameters_apt = codec.parameters().get("apt"); match codec_parameters_apt { Some(RtpCodecParametersParametersValue::Number(apt)) => { u32::from(media_codec_payload_type) == *apt } _ => false, } }); match associated_media_codec { Some(associated_media_codec) => { let cap_media_codec = codec_to_cap_codec.get(associated_media_codec).unwrap(); // Ensure that the capabilities media codec has a RTX codec. let associated_cap_rtx_codec = rtp_capabilities.codecs.iter().find(|cap_codec| { if !cap_codec.is_rtx() { return false; } let cap_codec_parameters_apt = cap_codec.parameters().get("apt"); match cap_codec_parameters_apt { Some(RtpCodecParametersParametersValue::Number(apt)) => { u32::from(cap_media_codec.preferred_payload_type()) == *apt } _ => false, } }); match associated_cap_rtx_codec { Some(associated_cap_rtx_codec) => { codec_to_cap_codec.insert(codec, Cow::Borrowed(associated_cap_rtx_codec)); } None => { return Err(RtpParametersMappingError::UnsupportedRtxCodec { preferred_payload_type: cap_media_codec.preferred_payload_type(), }); } } } None => { return Err(RtpParametersMappingError::MissingMediaCodecForRtx { payload_type: codec.payload_type(), }); } } } // Generate codecs mapping. for (codec, cap_codec) in codec_to_cap_codec { rtp_mapping.codecs.push(RtpMappingCodec { payload_type: codec.payload_type(), mapped_payload_type: cap_codec.preferred_payload_type(), }); } // Generate encodings mapping. let mut mapped_ssrc: u32 = generate_ssrc(); for encoding in &rtp_parameters.encodings { rtp_mapping.encodings.push(RtpMappingEncoding { ssrc: encoding.ssrc, rid: encoding.rid.clone(), scalability_mode: encoding.scalability_mode.clone(), mapped_ssrc, }); mapped_ssrc += 1; } Ok(rtp_mapping) } // Generate RTP parameters to be internally used by Consumers given the RTP parameters of a Producer // and the RTP capabilities of the Router. pub(crate) fn get_consumable_rtp_parameters( kind: MediaKind, params: &RtpParameters, caps: &RtpCapabilitiesFinalized, rtp_mapping: &RtpMapping, ) -> RtpParameters { let mut consumable_params = RtpParameters::default(); for codec in ¶ms.codecs { if codec.is_rtx() { continue; } let consumable_codec_pt = rtp_mapping .codecs .iter() .find(|entry| entry.payload_type == codec.payload_type()) .unwrap() .mapped_payload_type; let consumable_codec = match caps .codecs .iter() .find(|cap_codec| cap_codec.preferred_payload_type() == consumable_codec_pt) .unwrap() { RtpCodecCapabilityFinalized::Audio { mime_type, preferred_payload_type, clock_rate, channels, parameters: _, rtcp_feedback, } => { RtpCodecParameters::Audio { mime_type: *mime_type, payload_type: *preferred_payload_type, clock_rate: *clock_rate, channels: *channels, // Keep the Producer codec parameters. parameters: codec.parameters().clone(), rtcp_feedback: rtcp_feedback.clone(), } } RtpCodecCapabilityFinalized::Video { mime_type, preferred_payload_type, clock_rate, parameters: _, rtcp_feedback, } => { RtpCodecParameters::Video { mime_type: *mime_type, payload_type: *preferred_payload_type, clock_rate: *clock_rate, // Keep the Producer codec parameters. parameters: codec.parameters().clone(), rtcp_feedback: rtcp_feedback.clone(), } } }; let consumable_cap_rtx_codec = caps.codecs.iter().find(|cap_rtx_codec| { if !cap_rtx_codec.is_rtx() { return false; } let cap_rtx_codec_parameters_apt = cap_rtx_codec.parameters().get("apt"); match cap_rtx_codec_parameters_apt { Some(RtpCodecParametersParametersValue::Number(apt)) => { u8::try_from(*apt).is_ok_and(|apt| apt == consumable_codec.payload_type()) } _ => false, } }); consumable_params.codecs.push(consumable_codec); if let Some(consumable_cap_rtx_codec) = consumable_cap_rtx_codec { let consumable_rtx_codec = match consumable_cap_rtx_codec { RtpCodecCapabilityFinalized::Audio { mime_type, preferred_payload_type, clock_rate, channels, parameters, rtcp_feedback, } => RtpCodecParameters::Audio { mime_type: *mime_type, payload_type: *preferred_payload_type, clock_rate: *clock_rate, channels: *channels, parameters: parameters.clone(), rtcp_feedback: rtcp_feedback.clone(), }, RtpCodecCapabilityFinalized::Video { mime_type, preferred_payload_type, clock_rate, parameters, rtcp_feedback, } => RtpCodecParameters::Video { mime_type: *mime_type, payload_type: *preferred_payload_type, clock_rate: *clock_rate, parameters: parameters.clone(), rtcp_feedback: rtcp_feedback.clone(), }, }; consumable_params.codecs.push(consumable_rtx_codec); } } for cap_ext in &caps.header_extensions { // Just take RTP header extension that can be used in Consumers. if cap_ext.kind != kind { continue; } if !matches!( cap_ext.direction, RtpHeaderExtensionDirection::SendRecv | RtpHeaderExtensionDirection::SendOnly ) { continue; } let consumable_ext = RtpHeaderExtensionParameters { uri: cap_ext.uri, id: cap_ext.preferred_id, encrypt: cap_ext.preferred_encrypt, }; consumable_params.header_extensions.push(consumable_ext); } for (consumable_encoding, mapped_ssrc) in params.encodings.iter().zip( rtp_mapping .encodings .iter() .map(|encoding| encoding.mapped_ssrc), ) { let mut consumable_encoding = consumable_encoding.clone(); // Remove useless fields. consumable_encoding.rid.take(); consumable_encoding.rtx.take(); consumable_encoding.codec_payload_type.take(); // Set the mapped ssrc. consumable_encoding.ssrc = Some(mapped_ssrc); consumable_params.encodings.push(consumable_encoding); } consumable_params.rtcp = RtcpParameters { cname: params.rtcp.cname.clone(), reduced_size: true, }; consumable_params.msid.clone_from(¶ms.msid); consumable_params } /// Check whether the given RTP capabilities can consume the given Producer. pub(crate) fn can_consume( consumable_params: &RtpParameters, caps: &RtpCapabilities, ) -> Result { validate_rtp_capabilities(caps)?; let mut matching_codecs = Vec::<&RtpCodecParameters>::new(); for codec in &consumable_params.codecs { if caps .codecs .iter() .any(|cap_codec| match_codecs(cap_codec.into(), codec.into(), true).is_ok()) { matching_codecs.push(codec); } } // Ensure there is at least one media codec. Ok(matching_codecs .first() .map(|codec| !codec.is_rtx()) .unwrap_or_default()) } /// Generate RTP parameters for a specific Consumer. /// /// It reduces encodings to just one and takes into account given RTP capabilities to reduce codecs, /// codecs' RTCP feedback and header extensions, and also enables or disabled RTX. #[allow(clippy::suspicious_operation_groupings)] pub(crate) fn get_consumer_rtp_parameters( consumable_rtp_parameters: &RtpParameters, remote_rtp_capabilities: &RtpCapabilities, pipe: bool, enable_rtx: bool, ) -> Result { let mut consumer_params = RtpParameters { rtcp: consumable_rtp_parameters.rtcp.clone(), msid: consumable_rtp_parameters.msid.clone(), ..RtpParameters::default() }; for cap_codec in &remote_rtp_capabilities.codecs { validate_rtp_codec_capability(cap_codec) .map_err(ConsumerRtpParametersError::InvalidCapabilities)?; } let mut rtx_supported = false; for mut codec in consumable_rtp_parameters.codecs.clone() { if !enable_rtx && codec.is_rtx() { continue; } if let Some(matched_cap_codec) = remote_rtp_capabilities .codecs .iter() .find(|cap_codec| match_codecs((*cap_codec).into(), (&codec).into(), true).is_ok()) { *codec.rtcp_feedback_mut() = matched_cap_codec .rtcp_feedback() .iter() .filter(|&&fb| enable_rtx || fb != RtcpFeedback::Nack) .copied() .collect(); consumer_params.codecs.push(codec); } } // Must sanitize the list of matched codecs by removing useless RTX codecs. let mut remove_codecs = Vec::new(); for (idx, codec) in consumer_params.codecs.iter().enumerate() { if codec.is_rtx() { // Search for the associated media codec. let associated_media_codec = consumer_params.codecs.iter().find(|media_codec| { match codec.parameters().get("apt") { Some(RtpCodecParametersParametersValue::Number(apt)) => { u8::try_from(*apt).is_ok_and(|apt| media_codec.payload_type() == apt) } _ => false, } }); if associated_media_codec.is_some() { rtx_supported = true; } else { remove_codecs.push(idx); } } } for idx in remove_codecs.into_iter().rev() { consumer_params.codecs.remove(idx); } // Ensure there is at least one media codec. if consumer_params.codecs.is_empty() || consumer_params.codecs[0].is_rtx() { return Err(ConsumerRtpParametersError::NoCompatibleMediaCodecs); } consumer_params.header_extensions = consumable_rtp_parameters .header_extensions .iter() .filter(|ext| { remote_rtp_capabilities .header_extensions .iter() .any(|cap_ext| cap_ext.preferred_id == ext.id && cap_ext.uri == ext.uri) }) .cloned() .collect(); // Reduce codecs' RTCP feedback. Use Transport-CC if available, REMB otherwise. if consumer_params .header_extensions .iter() .any(|ext| ext.uri == RtpHeaderExtensionUri::TransportWideCcDraft01) { for codec in &mut consumer_params.codecs { codec .rtcp_feedback_mut() .retain(|fb| fb != &RtcpFeedback::GoogRemb); } } else if consumer_params .header_extensions .iter() .any(|ext| ext.uri == RtpHeaderExtensionUri::AbsSendTime) { for codec in &mut consumer_params.codecs { codec .rtcp_feedback_mut() .retain(|fb| fb != &RtcpFeedback::TransportCc); } } else { for codec in &mut consumer_params.codecs { codec .rtcp_feedback_mut() .retain(|fb| !matches!(fb, RtcpFeedback::GoogRemb | RtcpFeedback::TransportCc)); } } if pipe { for ((encoding, ssrc), rtx_ssrc) in consumable_rtp_parameters .encodings .iter() .zip(generate_ssrc()..) .zip(generate_ssrc()..) { consumer_params.encodings.push(RtpEncodingParameters { ssrc: Some(ssrc), rtx: if rtx_supported { Some(RtpEncodingParametersRtx { ssrc: rtx_ssrc }) } else { None }, ..encoding.clone() }); } } else { let mut consumer_encoding = RtpEncodingParameters { ssrc: Some(generate_ssrc()), ..RtpEncodingParameters::default() }; if rtx_supported { consumer_encoding.rtx = Some(RtpEncodingParametersRtx { ssrc: consumer_encoding.ssrc.unwrap() + 1, }); } // If any of the consumable_rtp_parameters.encodings has scalability_mode, process it // (assume all encodings have the same value). let mut scalability_mode = consumable_rtp_parameters .encodings .first() .map(|encoding| encoding.scalability_mode.clone()) .unwrap_or_default(); // If there is simulcast, mangle spatial layers in scalabilityMode. if consumable_rtp_parameters.encodings.len() > 1 { scalability_mode = format!( "L{}T{}", consumable_rtp_parameters.encodings.len(), scalability_mode.temporal_layers() ) .parse() .unwrap(); } consumer_encoding.scalability_mode = scalability_mode; // Use the maximum max_bitrate in any encoding and honor it in the Consumer's encoding. consumer_encoding.max_bitrate = consumable_rtp_parameters .encodings .iter() .map(|encoding| encoding.max_bitrate) .max() .flatten(); // Set a single encoding for the Consumer. consumer_params.encodings.push(consumer_encoding); } Ok(consumer_params) } /// Generate RTP parameters for a pipe Consumer. /// /// It keeps all original consumable encodings and removes support for BWE. If /// enableRtx is false, it also removes RTX and NACK support. pub(crate) fn get_pipe_consumer_rtp_parameters( consumable_rtp_parameters: &RtpParameters, enable_rtx: bool, ) -> RtpParameters { let mut consumer_params = RtpParameters { mid: None, codecs: vec![], header_extensions: vec![], encodings: vec![], rtcp: consumable_rtp_parameters.rtcp.clone(), msid: consumable_rtp_parameters.msid.clone(), }; for codec in &consumable_rtp_parameters.codecs { if !enable_rtx && codec.is_rtx() { continue; } let mut codec = codec.clone(); codec.rtcp_feedback_mut().retain(|fb| { matches!(fb, RtcpFeedback::NackPli | RtcpFeedback::CcmFir) || (enable_rtx && fb == &RtcpFeedback::Nack) }); consumer_params.codecs.push(codec); } // Reduce RTP extensions by disabling transport MID and BWE related ones. consumer_params.header_extensions = consumable_rtp_parameters .header_extensions .iter() .filter(|ext| { !matches!( ext.uri, RtpHeaderExtensionUri::Mid | RtpHeaderExtensionUri::AbsSendTime | RtpHeaderExtensionUri::TransportWideCcDraft01 ) }) .cloned() .collect(); // TODO: Remove this if we switch to 'sendrecv' in Dependency-Descriptor header // extension. // // We need to add Dependency-Descriptor header extension manually since it's // 'recvonly' so it's not present in received `consumableRtpParameters`. let cache = CACHE.lock(); if let Some(dependency_descriptor) = &cache.dependency_descriptor_header_extension_parameters_for_pipe_consumer { consumer_params .header_extensions .push(dependency_descriptor.clone()); // Sort header extensions by ID. consumer_params.header_extensions.sort_by_key(|ext| ext.id); } for ((encoding, ssrc), rtx_ssrc) in consumable_rtp_parameters .encodings .iter() .zip(generate_ssrc()..) .zip(generate_ssrc()..) { consumer_params.encodings.push(RtpEncodingParameters { ssrc: Some(ssrc), rtx: if enable_rtx { Some(RtpEncodingParametersRtx { ssrc: rtx_ssrc }) } else { None }, ..encoding.clone() }); } consumer_params } struct CodecToMatch<'a> { channels: Option, clock_rate: NonZeroU32, mime_type: MimeType, parameters: &'a RtpCodecParametersParameters, } impl<'a> From<&'a RtpCodecCapability> for CodecToMatch<'a> { fn from(rtp_codec_capability: &'a RtpCodecCapability) -> Self { match rtp_codec_capability { RtpCodecCapability::Audio { mime_type, channels, clock_rate, parameters, .. } => Self { channels: Some(*channels), clock_rate: *clock_rate, mime_type: MimeType::Audio(*mime_type), parameters, }, RtpCodecCapability::Video { mime_type, clock_rate, parameters, .. } => Self { channels: None, clock_rate: *clock_rate, mime_type: MimeType::Video(*mime_type), parameters, }, } } } impl<'a> From<&'a RtpCodecCapabilityFinalized> for CodecToMatch<'a> { fn from(rtp_codec_capability: &'a RtpCodecCapabilityFinalized) -> Self { match rtp_codec_capability { RtpCodecCapabilityFinalized::Audio { mime_type, channels, clock_rate, parameters, .. } => Self { channels: Some(*channels), clock_rate: *clock_rate, mime_type: MimeType::Audio(*mime_type), parameters, }, RtpCodecCapabilityFinalized::Video { mime_type, clock_rate, parameters, .. } => Self { channels: None, clock_rate: *clock_rate, mime_type: MimeType::Video(*mime_type), parameters, }, } } } impl<'a> From<&'a RtpCodecParameters> for CodecToMatch<'a> { fn from(rtp_codec_parameters: &'a RtpCodecParameters) -> Self { match rtp_codec_parameters { RtpCodecParameters::Audio { mime_type, channels, clock_rate, parameters, .. } => Self { channels: Some(*channels), clock_rate: *clock_rate, mime_type: MimeType::Audio(*mime_type), parameters, }, RtpCodecParameters::Video { mime_type, clock_rate, parameters, .. } => Self { channels: None, clock_rate: *clock_rate, mime_type: MimeType::Video(*mime_type), parameters, }, } } } /// Returns selected `Ok(Some(profile-level-id))` for H264 codec and `Ok(None)` for others fn match_codecs( codec_a: CodecToMatch<'_>, codec_b: CodecToMatch<'_>, strict: bool, ) -> Result, ()> { if codec_a.mime_type != codec_b.mime_type { return Err(()); } if codec_a.channels != codec_b.channels { return Err(()); } if codec_a.clock_rate != codec_b.clock_rate { return Err(()); } // Per codec special checks. match codec_a.mime_type { MimeType::Audio(MimeTypeAudio::MultiChannelOpus) => { let num_streams_a = codec_a.parameters.get("num_streams"); let num_streams_b = codec_b.parameters.get("num_streams"); if num_streams_a != num_streams_b { return Err(()); } let coupled_streams_a = codec_a.parameters.get("coupled_streams"); let coupled_streams_b = codec_b.parameters.get("coupled_streams"); if coupled_streams_a != coupled_streams_b { return Err(()); } } MimeType::Video(MimeTypeVideo::H264) => { if strict { let packetization_mode_a = codec_a .parameters .get("packetization-mode") .unwrap_or(&RtpCodecParametersParametersValue::Number(0)); let packetization_mode_b = codec_b .parameters .get("packetization-mode") .unwrap_or(&RtpCodecParametersParametersValue::Number(0)); if packetization_mode_a != packetization_mode_b { return Err(()); } let profile_level_id_a = codec_a .parameters .get("profile-level-id") .and_then(|p| match p { RtpCodecParametersParametersValue::String(s) => Some(s.as_ref()), RtpCodecParametersParametersValue::Number(_) => None, }); let profile_level_id_b = codec_b .parameters .get("profile-level-id") .and_then(|p| match p { RtpCodecParametersParametersValue::String(s) => Some(s.as_ref()), RtpCodecParametersParametersValue::Number(_) => None, }); let (profile_level_id_a, profile_level_id_b) = match h264_profile_level_id::is_same_profile( profile_level_id_a, profile_level_id_b, ) { Some((profile_level_id_a, profile_level_id_b)) => { (profile_level_id_a, profile_level_id_b) } None => { return Err(()); } }; let selected_profile_level_id = h264_profile_level_id::generate_profile_level_id_for_answer( Some(profile_level_id_a), codec_a .parameters .get("level-asymmetry-allowed") .map(|p| p == &RtpCodecParametersParametersValue::Number(1)) .unwrap_or_default(), Some(profile_level_id_b), codec_b .parameters .get("level-asymmetry-allowed") .map(|p| p == &RtpCodecParametersParametersValue::Number(1)) .unwrap_or_default(), ); return match selected_profile_level_id { Ok(selected_profile_level_id) => { Ok(Some(selected_profile_level_id.to_string())) } Err(_) => Err(()), }; } } MimeType::Video(MimeTypeVideo::Vp9) => { // If strict matching check profile-id. if strict { let profile_id_a = codec_a .parameters .get("profile-id") .unwrap_or(&RtpCodecParametersParametersValue::Number(0)); let profile_id_b = codec_b .parameters .get("profile-id") .unwrap_or(&RtpCodecParametersParametersValue::Number(0)); if profile_id_a != profile_id_b { return Err(()); } } } _ => {} } Ok(None) } ================================================ FILE: rust/src/prelude.rs ================================================ //! mediasoup prelude. //! //! Re-exports commonly used traits and structs from this crate. //! //! # Examples //! //! Import the prelude with: //! //! ``` //! # #[allow(unused_imports)] //! use mediasoup::prelude::*; //! ``` pub use crate::worker_manager::WorkerManager; pub use crate::worker::{Worker, WorkerSettings}; pub use crate::router::{ PipeDataProducerToRouterError, PipeDataProducerToRouterPair, PipeProducerToRouterError, PipeProducerToRouterPair, PipeToRouterOptions, Router, RouterOptions, }; pub use crate::webrtc_server::{ WebRtcServer, WebRtcServerId, WebRtcServerListenInfos, WebRtcServerOptions, }; pub use crate::direct_transport::{DirectTransport, DirectTransportOptions, WeakDirectTransport}; pub use crate::pipe_transport::{ PipeTransport, PipeTransportOptions, PipeTransportRemoteParameters, WeakPipeTransport, }; pub use crate::plain_transport::{ PlainTransport, PlainTransportOptions, PlainTransportRemoteParameters, WeakPlainTransport, }; pub use crate::transport::{ ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, Transport, TransportGeneric, TransportId, }; pub use crate::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, WebRtcTransportRemoteParameters, }; pub use crate::active_speaker_observer::{ ActiveSpeakerObserver, ActiveSpeakerObserverDominantSpeaker, ActiveSpeakerObserverOptions, WeakActiveSpeakerObserver, }; pub use crate::audio_level_observer::{ AudioLevelObserver, AudioLevelObserverOptions, AudioLevelObserverVolume, WeakAudioLevelObserver, }; pub use crate::rtp_observer::{RtpObserver, RtpObserverAddProducerOptions, RtpObserverId}; pub use crate::consumer::{Consumer, ConsumerId, ConsumerLayers, ConsumerOptions, WeakConsumer}; pub use crate::data_consumer::{ DataConsumer, DataConsumerId, DataConsumerOptions, DirectDataConsumer, RegularDataConsumer, WeakDataConsumer, }; pub use crate::data_producer::{ DataProducer, DataProducerId, DataProducerOptions, DirectDataProducer, NonClosingDataProducer, RegularDataProducer, WeakDataProducer, }; pub use crate::producer::{Producer, ProducerId, ProducerOptions, WeakProducer}; pub use mediasoup_types::data_structures::{ AppData, DtlsParameters, IceCandidate, IceParameters, ListenInfo, Protocol, WebRtcMessage, }; pub use mediasoup_types::rtp_parameters::{ MediaKind, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters, RtpCapabilities, RtpCapabilitiesFinalized, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters, RtpEncodingParameters, RtpEncodingParametersRtx, RtpHeaderExtensionParameters, RtpHeaderExtensionUri, RtpParameters, }; pub use mediasoup_types::sctp_parameters::SctpStreamParameters; pub use mediasoup_types::srtp_parameters::SrtpCryptoSuite; ================================================ FILE: rust/src/router/active_speaker_observer/tests.rs ================================================ use crate::active_speaker_observer::ActiveSpeakerObserverOptions; use crate::router::RouterOptions; use crate::rtp_observer::RtpObserver; use crate::worker::{Worker, WorkerSettings}; use crate::worker_manager::WorkerManager; use futures_lite::future; use std::env; async fn init() -> Worker { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker") } #[test] fn router_close_event() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); let active_speaker_observer = router .create_active_speaker_observer(ActiveSpeakerObserverOptions::default()) .await .expect("Failed to create ActiveSpeakerObserver"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = active_speaker_observer.on_close(Box::new(move || { let _ = close_tx.send(()); })); let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>(); let _handler = active_speaker_observer.on_router_close(Box::new(move || { let _ = router_close_tx.send(()); })); router.close(); router_close_rx .await .expect("Failed to receive router_close event"); close_rx.await.expect("Failed to receive close event"); assert!(active_speaker_observer.closed()); }); } ================================================ FILE: rust/src/router/active_speaker_observer.rs ================================================ #[cfg(test)] mod tests; use crate::fbs::TryFromFbs; use crate::messages::{ RtpObserverAddProducerRequest, RtpObserverCloseRequest, RtpObserverPauseRequest, RtpObserverRemoveProducerRequest, RtpObserverResumeRequest, }; use crate::producer::{Producer, ProducerId}; use crate::router::Router; use crate::rtp_observer::{RtpObserver, RtpObserverAddProducerOptions, RtpObserverId}; use crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler}; use async_executor::Executor; use async_trait::async_trait; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::notification; use mediasoup_types::data_structures::AppData; use parking_lot::Mutex; use serde::Deserialize; use std::fmt; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; /// [`ActiveSpeakerObserver`] options #[derive(Debug, Clone)] #[non_exhaustive] pub struct ActiveSpeakerObserverOptions { /// Interval in ms for checking audio volumes. /// Default 300. pub interval: u16, /// Custom application data. pub app_data: AppData, } impl Default for ActiveSpeakerObserverOptions { fn default() -> Self { Self { interval: 300, app_data: AppData::default(), } } } /// Represents dominant speaker. #[derive(Debug, Clone)] pub struct ActiveSpeakerObserverDominantSpeaker { /// The audio producer instance. pub producer: Producer, } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { dominant_speaker: Bag< Arc, ActiveSpeakerObserverDominantSpeaker, >, pause: Bag>, resume: Bag>, add_producer: Bag, Producer>, remove_producer: Bag, Producer>, router_close: BagOnce>, close: BagOnce>, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct DominantSpeakerNotification { producer_id: ProducerId, } #[derive(Debug, Deserialize)] #[serde(tag = "event", rename_all = "lowercase", content = "data")] enum Notification { DominantSpeaker(DominantSpeakerNotification), } impl<'a> TryFromFbs<'a> for Notification { type FbsType = notification::NotificationRef<'a>; type Error = NotificationParseError; fn try_from_fbs(notification: Self::FbsType) -> Result { match notification.event().unwrap() { notification::Event::ActivespeakerobserverDominantSpeaker => { let Ok(Some( notification::BodyRef::ActiveSpeakerObserverDominantSpeakerNotification(body), )) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let dominant_speaker_notification = DominantSpeakerNotification { producer_id: body.producer_id().unwrap().parse().unwrap(), }; Ok(Notification::DominantSpeaker(dominant_speaker_notification)) } _ => Err(NotificationParseError::InvalidEvent), } } } struct Inner { id: RtpObserverId, executor: Arc>, channel: Channel, handlers: Arc, paused: AtomicBool, app_data: AppData, // Make sure router is not dropped until this active speaker observer is not dropped router: Router, closed: AtomicBool, // Drop subscription to audio speaker observer-specific notifications when observer itself is // dropped _subscription_handler: Mutex>, _on_router_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let router_id = self.router.id(); let request = RtpObserverCloseRequest { rtp_observer_id: self.id, }; self.executor .spawn(async move { match channel.request(router_id, request).await { Err(RequestError::ChannelClosed) => { debug!("active speaker observer closing failed on drop: Channel already closed"); } Err(error) => { error!("active speaker observer closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// An active speaker observer monitors the volume of the selected audio producers. /// /// It just handles audio producers (if [`ActiveSpeakerObserver::add_producer()`] is called with a /// video producer it will fail). /// /// Audio levels are read from an RTP header extension. No decoding of audio data is done. See /// [RFC6464](https://tools.ietf.org/html/rfc6464) for more information. #[derive(Clone)] #[must_use = "Active speaker observer will be closed on drop, make sure to keep it around for as long as needed"] pub struct ActiveSpeakerObserver { inner: Arc, } impl fmt::Debug for ActiveSpeakerObserver { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ActiveSpeakerObserver") .field("id", &self.inner.id) .field("paused", &self.inner.paused) .field("router", &self.inner.router) .field("closed", &self.inner.closed) .finish() } } #[async_trait] impl RtpObserver for ActiveSpeakerObserver { fn id(&self) -> RtpObserverId { self.inner.id } fn router(&self) -> &Router { &self.inner.router } fn paused(&self) -> bool { self.inner.paused.load(Ordering::SeqCst) } fn app_data(&self) -> &AppData { &self.inner.app_data } fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } async fn pause(&self) -> Result<(), RequestError> { debug!("pause()"); self.inner .channel .request(self.id(), RtpObserverPauseRequest {}) .await?; let was_paused = self.inner.paused.swap(true, Ordering::SeqCst); if !was_paused { self.inner.handlers.pause.call_simple(); } Ok(()) } async fn resume(&self) -> Result<(), RequestError> { debug!("resume()"); self.inner .channel .request(self.id(), RtpObserverResumeRequest {}) .await?; let was_paused = self.inner.paused.swap(false, Ordering::SeqCst); if !was_paused { self.inner.handlers.resume.call_simple(); } Ok(()) } async fn add_producer( &self, RtpObserverAddProducerOptions { producer_id }: RtpObserverAddProducerOptions, ) -> Result<(), RequestError> { let producer = match self.inner.router.get_producer(&producer_id) { Some(producer) => producer, None => { return Ok(()); } }; self.inner .channel .request(self.id(), RtpObserverAddProducerRequest { producer_id }) .await?; self.inner.handlers.add_producer.call_simple(&producer); Ok(()) } async fn remove_producer(&self, producer_id: ProducerId) -> Result<(), RequestError> { let producer = match self.inner.router.get_producer(&producer_id) { Some(producer) => producer, None => { return Ok(()); } }; self.inner .channel .request(self.id(), RtpObserverRemoveProducerRequest { producer_id }) .await?; self.inner.handlers.remove_producer.call_simple(&producer); Ok(()) } fn on_pause(&self, callback: Box) -> HandlerId { self.inner.handlers.pause.add(Arc::new(callback)) } fn on_resume(&self, callback: Box) -> HandlerId { self.inner.handlers.resume.add(Arc::new(callback)) } fn on_add_producer( &self, callback: Box, ) -> HandlerId { self.inner.handlers.add_producer.add(Arc::new(callback)) } fn on_remove_producer( &self, callback: Box, ) -> HandlerId { self.inner.handlers.remove_producer.add(Arc::new(callback)) } fn on_router_close(&self, callback: Box) -> HandlerId { self.inner.handlers.router_close.add(Box::new(callback)) } fn on_close(&self, callback: Box) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } } impl ActiveSpeakerObserver { pub(super) fn new( id: RtpObserverId, executor: Arc>, channel: Channel, app_data: AppData, router: Router, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let paused = AtomicBool::new(false); let subscription_handler = { let router = router.clone(); let handlers = Arc::clone(&handlers); channel.subscribe_to_notifications(id.into(), move |notification| { match Notification::try_from_fbs(notification) { Ok(notification) => match notification { Notification::DominantSpeaker(dominant_speaker) => { let DominantSpeakerNotification { producer_id } = dominant_speaker; match router.get_producer(&producer_id) { Some(producer) => { let dominant_speaker = ActiveSpeakerObserverDominantSpeaker { producer }; handlers.dominant_speaker.call_simple(&dominant_speaker); } None => { error!( "Producer for dominant speaker event not found: {}", producer_id ); } }; } }, Err(error) => { error!("Failed to parse notification: {}", error); } } }) }; let inner_weak = Arc::>>>::default(); let on_router_close_handler = router.on_close({ let inner_weak = Arc::clone(&inner_weak); move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.router_close.call_simple(); inner.close(false); } } }); let inner = Arc::new(Inner { id, executor, channel, handlers, paused, app_data, router, closed: AtomicBool::new(false), _subscription_handler: Mutex::new(subscription_handler), _on_router_close_handler: Mutex::new(on_router_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); Self { inner } } /// Callback is called at most every interval (see [`ActiveSpeakerObserverOptions`]). pub fn on_dominant_speaker< F: Fn(&ActiveSpeakerObserverDominantSpeaker) + Send + Sync + 'static, >( &self, callback: F, ) -> HandlerId { self.inner.handlers.dominant_speaker.add(Arc::new(callback)) } /// Downgrade `ActiveSpeakerObserver` to [`WeakActiveSpeakerObserver`] instance. #[must_use] pub fn downgrade(&self) -> WeakActiveSpeakerObserver { WeakActiveSpeakerObserver { inner: Arc::downgrade(&self.inner), } } } /// [`WeakActiveSpeakerObserver`] doesn't own active speaker observer instance on mediasoup-worker /// and will not prevent one from being destroyed once last instance of regular /// [`ActiveSpeakerObserver`] is dropped. /// /// [`WeakActiveSpeakerObserver`] vs [`ActiveSpeakerObserver`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakActiveSpeakerObserver { inner: Weak, } impl fmt::Debug for WeakActiveSpeakerObserver { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakActiveSpeakerObserver").finish() } } impl WeakActiveSpeakerObserver { /// Attempts to upgrade `WeakActiveSpeakerObserver` to [`ActiveSpeakerObserver`] if last instance of one wasn't /// dropped yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; Some(ActiveSpeakerObserver { inner }) } } ================================================ FILE: rust/src/router/audio_level_observer/tests.rs ================================================ use crate::audio_level_observer::AudioLevelObserverOptions; use crate::router::RouterOptions; use crate::rtp_observer::RtpObserver; use crate::worker::{Worker, WorkerSettings}; use crate::worker_manager::WorkerManager; use futures_lite::future; use std::env; async fn init() -> Worker { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker") } #[test] fn router_close_event() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); let audio_level_observer = router .create_audio_level_observer(AudioLevelObserverOptions::default()) .await .expect("Failed to create AudioLevelObserver"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = audio_level_observer.on_close(Box::new(move || { let _ = close_tx.send(()); })); let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>(); let _handler = audio_level_observer.on_router_close(Box::new(move || { let _ = router_close_tx.send(()); })); router.close(); router_close_rx .await .expect("Failed to receive router_close event"); close_rx.await.expect("Failed to receive close event"); assert!(audio_level_observer.closed()); }); } ================================================ FILE: rust/src/router/audio_level_observer.rs ================================================ #[cfg(test)] mod tests; use crate::fbs::TryFromFbs; use crate::messages::{ RtpObserverAddProducerRequest, RtpObserverCloseRequest, RtpObserverPauseRequest, RtpObserverRemoveProducerRequest, RtpObserverResumeRequest, }; use crate::producer::{Producer, ProducerId}; use crate::router::Router; use crate::rtp_observer::{RtpObserver, RtpObserverAddProducerOptions, RtpObserverId}; use crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler}; use async_executor::Executor; use async_trait::async_trait; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::{audio_level_observer, notification}; use mediasoup_types::data_structures::AppData; use parking_lot::Mutex; use serde::Deserialize; use std::fmt; use std::num::NonZeroU16; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; /// [`AudioLevelObserver`] options #[derive(Debug, Clone)] #[non_exhaustive] pub struct AudioLevelObserverOptions { /// Maximum number of entries in the 'volumes' event. /// Default 1. pub max_entries: NonZeroU16, /// Minimum average volume (in dBvo from -127 to 0) for entries in the 'volumes' event. /// Default -80. pub threshold: i8, /// Interval in ms for checking audio volumes. /// Default 1000. pub interval: u16, /// Custom application data. pub app_data: AppData, } impl Default for AudioLevelObserverOptions { fn default() -> Self { Self { max_entries: NonZeroU16::new(1).unwrap(), threshold: -80, interval: 1000, app_data: AppData::default(), } } } /// Represents volume of one audio producer. #[derive(Debug, Clone)] pub struct AudioLevelObserverVolume { /// The audio producer instance. pub producer: Producer, /// The average volume (in dBvo from -127 to 0) of the audio producer in the last interval. pub volume: i8, } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { volumes: Bag>, silence: Bag>, pause: Bag>, resume: Bag>, add_producer: Bag, Producer>, remove_producer: Bag, Producer>, router_close: BagOnce>, close: BagOnce>, } #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct VolumeNotification { producer_id: ProducerId, volume: i8, } #[derive(Debug, Deserialize)] #[serde(tag = "event", rename_all = "lowercase", content = "data")] enum Notification { Volumes(Vec), Silence, } impl<'a> TryFromFbs<'a> for Notification { type FbsType = notification::NotificationRef<'a>; type Error = NotificationParseError; fn try_from_fbs(notification: Self::FbsType) -> Result { match notification.event().unwrap() { notification::Event::AudiolevelobserverVolumes => { let Ok(Some(notification::BodyRef::AudioLevelObserverVolumesNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let volumes_fbs: Vec<_> = body .volumes() .unwrap() .iter() .map(|volume| audio_level_observer::Volume::try_from(volume.unwrap()).unwrap()) .collect(); let volumes = volumes_fbs .iter() .map(|volume| VolumeNotification { producer_id: volume.producer_id.parse().unwrap(), volume: volume.volume, }) .collect(); Ok(Notification::Volumes(volumes)) } notification::Event::AudiolevelobserverSilence => Ok(Notification::Silence), _ => Err(NotificationParseError::InvalidEvent), } } } struct Inner { id: RtpObserverId, executor: Arc>, channel: Channel, handlers: Arc, paused: AtomicBool, app_data: AppData, // Make sure router is not dropped until this audio level observer is not dropped router: Router, closed: AtomicBool, // Drop subscription to audio level observer-specific notifications when observer itself is // dropped _subscription_handler: Mutex>, _on_router_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let router_id = self.router.id(); let request = RtpObserverCloseRequest { rtp_observer_id: self.id, }; self.executor .spawn(async move { match channel.request(router_id, request).await { Err(RequestError::ChannelClosed) => { debug!("audio level observer closing failed on drop: Channel already closed"); } Err(error) => { error!("audio level observer closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// An audio level observer monitors the volume of the selected audio producers. /// /// It just handles audio producers (if [`AudioLevelObserver::add_producer()`] is called with a /// video producer it will fail). /// /// Audio levels are read from an RTP header extension. No decoding of audio data is done. See /// [RFC6464](https://tools.ietf.org/html/rfc6464) for more information. #[derive(Clone)] #[must_use = "Audio level observer will be closed on drop, make sure to keep it around for as long as needed"] pub struct AudioLevelObserver { inner: Arc, } impl fmt::Debug for AudioLevelObserver { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("AudioLevelObserver") .field("id", &self.inner.id) .field("paused", &self.inner.paused) .field("router", &self.inner.router) .field("closed", &self.inner.closed) .finish() } } #[async_trait] impl RtpObserver for AudioLevelObserver { fn id(&self) -> RtpObserverId { self.inner.id } fn router(&self) -> &Router { &self.inner.router } fn paused(&self) -> bool { self.inner.paused.load(Ordering::SeqCst) } fn app_data(&self) -> &AppData { &self.inner.app_data } fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } async fn pause(&self) -> Result<(), RequestError> { debug!("pause()"); self.inner .channel .request(self.id(), RtpObserverPauseRequest {}) .await?; let was_paused = self.inner.paused.swap(true, Ordering::SeqCst); if !was_paused { self.inner.handlers.pause.call_simple(); } Ok(()) } async fn resume(&self) -> Result<(), RequestError> { debug!("resume()"); self.inner .channel .request(self.id(), RtpObserverResumeRequest {}) .await?; let was_paused = self.inner.paused.swap(false, Ordering::SeqCst); if !was_paused { self.inner.handlers.resume.call_simple(); } Ok(()) } async fn add_producer( &self, RtpObserverAddProducerOptions { producer_id }: RtpObserverAddProducerOptions, ) -> Result<(), RequestError> { let producer = match self.inner.router.get_producer(&producer_id) { Some(producer) => producer, None => { return Ok(()); } }; self.inner .channel .request(self.id(), RtpObserverAddProducerRequest { producer_id }) .await?; self.inner.handlers.add_producer.call_simple(&producer); Ok(()) } async fn remove_producer(&self, producer_id: ProducerId) -> Result<(), RequestError> { let producer = match self.inner.router.get_producer(&producer_id) { Some(producer) => producer, None => { return Ok(()); } }; self.inner .channel .request(self.id(), RtpObserverRemoveProducerRequest { producer_id }) .await?; self.inner.handlers.remove_producer.call_simple(&producer); Ok(()) } fn on_pause(&self, callback: Box) -> HandlerId { self.inner.handlers.pause.add(Arc::new(callback)) } fn on_resume(&self, callback: Box) -> HandlerId { self.inner.handlers.resume.add(Arc::new(callback)) } fn on_add_producer( &self, callback: Box, ) -> HandlerId { self.inner.handlers.add_producer.add(Arc::new(callback)) } fn on_remove_producer( &self, callback: Box, ) -> HandlerId { self.inner.handlers.remove_producer.add(Arc::new(callback)) } fn on_router_close(&self, callback: Box) -> HandlerId { self.inner.handlers.router_close.add(Box::new(callback)) } fn on_close(&self, callback: Box) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } } impl AudioLevelObserver { pub(super) fn new( id: RtpObserverId, executor: Arc>, channel: Channel, app_data: AppData, router: Router, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let paused = AtomicBool::new(false); let subscription_handler = { let router = router.clone(); let handlers = Arc::clone(&handlers); channel.subscribe_to_notifications(id.into(), move |notification| { match Notification::try_from_fbs(notification) { Ok(notification) => match notification { Notification::Volumes(volumes) => { let volumes = volumes .iter() .filter_map(|notification| { let VolumeNotification { producer_id, volume, } = notification; router.get_producer(producer_id).map(|producer| { AudioLevelObserverVolume { producer, volume: *volume, } }) }) .collect::>(); handlers.volumes.call(|callback| { callback(&volumes); }); } Notification::Silence => { handlers.silence.call_simple(); } }, Err(error) => { error!("Failed to parse notification: {}", error); } } }) }; let inner_weak = Arc::>>>::default(); let on_router_close_handler = router.on_close({ let inner_weak = Arc::clone(&inner_weak); move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.router_close.call_simple(); inner.close(false); } } }); let inner = Arc::new(Inner { id, executor, channel, handlers, paused, app_data, router, closed: AtomicBool::new(false), _subscription_handler: Mutex::new(subscription_handler), _on_router_close_handler: Mutex::new(on_router_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); Self { inner } } /// Callback is called at most every interval (see [`AudioLevelObserverOptions`]). /// /// Audio volumes entries ordered by volume (louder ones go first). pub fn on_volumes( &self, callback: F, ) -> HandlerId { self.inner.handlers.volumes.add(Arc::new(callback)) } /// Callback is called when no one of the producers in this RTP observer is generating audio with a volume /// beyond the given threshold. pub fn on_silence(&self, callback: F) -> HandlerId { self.inner.handlers.silence.add(Arc::new(callback)) } /// Downgrade `AudioLevelObserver` to [`WeakAudioLevelObserver`] instance. #[must_use] pub fn downgrade(&self) -> WeakAudioLevelObserver { WeakAudioLevelObserver { inner: Arc::downgrade(&self.inner), } } } /// [`WeakAudioLevelObserver`] doesn't own audio level observer instance on mediasoup-worker and /// will not prevent one from being destroyed once last instance of regular [`AudioLevelObserver`] /// is dropped. /// /// [`WeakAudioLevelObserver`] vs [`AudioLevelObserver`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakAudioLevelObserver { inner: Weak, } impl fmt::Debug for WeakAudioLevelObserver { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakAudioLevelObserver").finish() } } impl WeakAudioLevelObserver { /// Attempts to upgrade `WeakAudioLevelObserver` to [`AudioLevelObserver`] if last instance of one wasn't /// dropped yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; Some(AudioLevelObserver { inner }) } } ================================================ FILE: rust/src/router/consumer/tests.rs ================================================ use crate::consumer::ConsumerOptions; use crate::producer::ProducerOptions; use crate::router::{Router, RouterOptions}; use crate::transport::Transport; use crate::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use crate::worker::WorkerSettings; use crate::worker_manager::WorkerManager; use futures_lite::future; use mediasoup_types::data_structures::{ListenInfo, Protocol}; use mediasoup_types::rtp_parameters::{ MediaKind, MimeTypeAudio, RtpCapabilities, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters, RtpParameters, }; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; fn media_codecs() -> Vec { vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }] } fn audio_producer_options() -> ProducerOptions { ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 111, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }], ..RtpParameters::default() }, ) } fn consumer_device_capabilities() -> RtpCapabilities { RtpCapabilities { codecs: vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: Some(100), clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }], ..RtpCapabilities::default() } } async fn init() -> (Router, WebRtcTransport, WebRtcTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); let transport_1 = router .create_webrtc_transport(transport_options.clone()) .await .expect("Failed to create transport1"); let transport_2 = router .create_webrtc_transport(transport_options) .await .expect("Failed to create transport2"); (router, transport_1, transport_2) } #[test] fn producer_close_event() { future::block_on(async move { let (_router, transport_1, transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let audio_consumer = transport_2 .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume audio"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = audio_consumer.on_close(move || { let _ = close_tx.send(()); }); let (mut producer_close_tx, producer_close_rx) = async_oneshot::oneshot::<()>(); let _handler = audio_consumer.on_producer_close(move || { let _ = producer_close_tx.send(()); }); drop(audio_producer); producer_close_rx .await .expect("Failed to receive producer_close event"); close_rx.await.expect("Failed to receive close event"); assert!(audio_consumer.closed()); }); } #[test] fn transport_close_event() { future::block_on(async move { let (router, transport_1, transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let audio_consumer = transport_2 .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume audio"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = audio_consumer.on_close(move || { let _ = close_tx.send(()); }); let (mut transport_close_tx, transport_close_rx) = async_oneshot::oneshot::<()>(); let _handler = audio_consumer.on_transport_close(move || { let _ = transport_close_tx.send(()); }); router.close(); transport_close_rx .await .expect("Failed to receive transport_close event"); close_rx.await.expect("Failed to receive close event"); assert!(audio_consumer.closed()); }); } ================================================ FILE: rust/src/router/consumer.rs ================================================ #[cfg(test)] mod tests; use crate::fbs::{FromFbs, ToFbs, TryFromFbs}; use crate::messages::{ ConsumerCloseRequest, ConsumerDumpRequest, ConsumerEnableTraceEventRequest, ConsumerGetStatsRequest, ConsumerPauseRequest, ConsumerRequestKeyFrameRequest, ConsumerResumeRequest, ConsumerSetPreferredLayersRequest, ConsumerSetPriorityRequest, }; use crate::producer::{Producer, ProducerId, ProducerStat, ProducerType, WeakProducer}; use crate::transport::Transport; use crate::uuid_based_wrapper_type; use crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler}; use async_executor::Executor; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::{ consumer, notification, response, rtp_parameters, rtp_stream, rtx_stream, }; use mediasoup_types::data_structures::{ AppData, RtpPacketTraceInfo, SsrcTraceInfo, TraceEventDirection, }; use mediasoup_types::rtp_parameters::{ MediaKind, MimeType, RtpCapabilities, RtpEncodingParameters, RtpParameters, }; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt; use std::fmt::Debug; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; uuid_based_wrapper_type!( /// [`Consumer`] identifier. ConsumerId ); /// Spatial/temporal layers of the consumer. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConsumerLayers { /// The spatial layer index (from 0 to N). pub spatial_layer: u8, /// The temporal layer index (from 0 to N). pub temporal_layer: Option, } impl ToFbs for ConsumerLayers { type FbsType = consumer::ConsumerLayers; fn to_fbs(&self) -> Self::FbsType { consumer::ConsumerLayers { spatial_layer: self.spatial_layer, temporal_layer: self.temporal_layer, } } } impl FromFbs for ConsumerLayers { type FbsType = consumer::ConsumerLayers; fn from_fbs(consumer_layers: &Self::FbsType) -> Self { Self { spatial_layer: consumer_layers.spatial_layer, temporal_layer: consumer_layers.temporal_layer, } } } /// Score of consumer and corresponding producer. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConsumerScore { /// Score of the RTP stream in the consumer (from 0 to 10) representing its transmission /// quality. pub score: u8, /// Score of the currently selected RTP stream in the associated producer (from 0 to 10) /// representing its transmission quality. pub producer_score: u8, /// The scores of all RTP streams in the producer ordered by encoding (just useful when the /// producer uses simulcast). pub producer_scores: Vec, } impl FromFbs for ConsumerScore { type FbsType = consumer::ConsumerScore; fn from_fbs(consumer_score: &Self::FbsType) -> Self { Self { score: consumer_score.score, producer_score: consumer_score.producer_score, producer_scores: consumer_score.producer_scores.clone().into_iter().collect(), } } } /// [`Consumer`] options. #[derive(Debug, Clone)] #[non_exhaustive] pub struct ConsumerOptions { /// The id of the Producer to consume. pub producer_id: ProducerId, /// RTP capabilities of the consuming endpoint. pub rtp_capabilities: RtpCapabilities, /// Whether the Consumer must start in paused mode. Default false. /// /// When creating a video Consumer, it's recommended to set paused to true, then transmit the /// Consumer parameters to the consuming endpoint and, once the consuming endpoint has created /// its local side Consumer, unpause the server side Consumer using the resume() method. This is /// an optimization to make it possible for the consuming endpoint to render the video as far as /// possible. If the server side Consumer was created with paused: false, mediasoup will /// immediately request a key frame to the remote Producer and such a key frame may reach the /// consuming endpoint even before it's ready to consume it, generating “black” video until the /// device requests a keyframe by itself. pub paused: bool, /// The MID for the Consumer. If not specified, a sequentially growing number will be assigned. pub mid: Option, /// Preferred spatial and temporal layer for simulcast or SVC media sources. /// If `None`, the highest ones are selected. pub preferred_layers: Option, /// Whether this Consumer should enable RTP retransmissions, storing sent RTP and processing the /// incoming RTCP NACK from the remote Consumer. If not set it's true by default for video codecs /// and false for audio codecs. If set to true, NACK will be enabled if both endpoints (mediasoup /// and the remote Consumer) support NACK for this codec. When it comes to audio codecs, just /// OPUS supports NACK. pub enable_rtx: Option, /// Whether this Consumer should ignore DTX packets (only valid for Opus codec). /// If set, DTX packets are not forwarded to the remote Consumer. pub ignore_dtx: bool, /// Whether this Consumer should consume all RTP streams generated by the Producer. pub pipe: bool, /// Custom application data. pub app_data: AppData, } impl ConsumerOptions { /// Create consumer options with given producer ID and RTP capabilities. #[must_use] pub fn new(producer_id: ProducerId, rtp_capabilities: RtpCapabilities) -> Self { Self { producer_id, rtp_capabilities, paused: false, preferred_layers: None, ignore_dtx: false, enable_rtx: None, pipe: false, mid: None, app_data: AppData::default(), } } } #[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct RtpStreamParams { pub clock_rate: u32, pub cname: String, pub encoding_idx: u32, pub mime_type: MimeType, pub payload_type: u8, pub spatial_layers: u8, pub ssrc: u32, pub temporal_layers: u8, pub use_dtx: bool, pub use_in_band_fec: bool, pub use_nack: bool, pub use_pli: bool, pub rid: Option, pub rtx_ssrc: Option, pub rtx_payload_type: Option, } impl<'a> TryFromFbs<'a> for RtpStreamParams { type FbsType = rtp_stream::ParamsRef<'a>; type Error = Box; fn try_from_fbs(params: Self::FbsType) -> Result { Ok(Self { clock_rate: params.clock_rate()?, cname: params.cname()?.to_string(), encoding_idx: params.encoding_idx()?, mime_type: params.mime_type()?.parse()?, payload_type: params.payload_type()?, spatial_layers: params.spatial_layers()?, ssrc: params.ssrc()?, temporal_layers: params.temporal_layers()?, use_dtx: params.use_dtx()?, use_in_band_fec: params.use_in_band_fec()?, use_nack: params.use_nack()?, use_pli: params.use_pli()?, rid: params.rid()?.map(|rid| rid.to_string()), rtx_ssrc: params.rtx_ssrc()?, rtx_payload_type: params.rtx_payload_type()?, }) } } #[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct RtxStreamParams { pub clock_rate: u32, pub cname: String, pub mime_type: MimeType, pub payload_type: u8, pub ssrc: u32, pub rrid: Option, } impl<'a> TryFromFbs<'a> for RtxStreamParams { type FbsType = rtx_stream::ParamsRef<'a>; type Error = Box; fn try_from_fbs(params: Self::FbsType) -> Result { Ok(Self { clock_rate: params.clock_rate()?, cname: params.cname()?.to_string(), mime_type: params.mime_type()?.parse()?, payload_type: params.payload_type()?, ssrc: params.ssrc()?, rrid: params.rrid()?.map(|rrid| rrid.to_string()), }) } } #[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct RtpStream { pub params: RtpStreamParams, pub score: u8, } impl<'a> TryFromFbs<'a> for RtpStream { type FbsType = rtp_stream::DumpRef<'a>; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { Ok(Self { params: RtpStreamParams::try_from_fbs(dump.params()?)?, score: dump.score()?, }) } } #[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct RtpRtxParameters { pub ssrc: Option, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct ConsumerDump { pub id: ConsumerId, pub kind: MediaKind, pub paused: bool, pub priority: u8, pub producer_id: ProducerId, pub producer_paused: bool, pub rtp_parameters: RtpParameters, pub supported_codec_payload_types: Vec, pub trace_event_types: Vec, pub r#type: ConsumerType, pub consumable_rtp_encodings: Vec, pub rtp_streams: Vec, /// Essentially `Option` or `Option<-1>` pub preferred_spatial_layer: Option, /// Essentially `Option` or `Option<-1>` pub target_spatial_layer: Option, /// Essentially `Option` or `Option<-1>` pub current_spatial_layer: Option, /// Essentially `Option` or `Option<-1>` pub preferred_temporal_layer: Option, /// Essentially `Option` or `Option<-1>` pub target_temporal_layer: Option, /// Essentially `Option` or `Option<-1>` pub current_temporal_layer: Option, } impl<'a> TryFromFbs<'a> for ConsumerDump { type FbsType = consumer::DumpResponseRef<'a>; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { let dump = dump.data(); Ok(Self { id: dump?.base()?.id()?.parse()?, kind: MediaKind::from_fbs(&dump?.base()?.kind()?), paused: dump?.base()?.paused()?, priority: dump?.base()?.priority()?, producer_id: dump?.base()?.producer_id()?.parse()?, producer_paused: dump?.base()?.producer_paused()?, rtp_parameters: RtpParameters::try_from_fbs(dump?.base()?.rtp_parameters()?)?, supported_codec_payload_types: Vec::from( dump?.base()?.supported_codec_payload_types()?, ), trace_event_types: dump? .base()? .trace_event_types()? .iter() .map(|trace_event_type| Ok(ConsumerTraceEventType::from_fbs(&trace_event_type?))) .collect::>>()?, r#type: ConsumerType::from_fbs(&dump?.base()?.type_()?), consumable_rtp_encodings: dump? .base()? .consumable_rtp_encodings()? .iter() .map(|encoding_parameters| { RtpEncodingParameters::try_from_fbs(encoding_parameters?) }) .collect::>>()?, rtp_streams: dump? .rtp_streams()? .iter() .map(|stream| RtpStream::try_from_fbs(stream?)) .collect::>>()?, preferred_spatial_layer: dump?.preferred_spatial_layer()?, target_spatial_layer: dump?.target_spatial_layer()?, current_spatial_layer: dump?.current_spatial_layer()?, preferred_temporal_layer: dump?.preferred_temporal_layer()?, target_temporal_layer: dump?.target_temporal_layer()?, current_temporal_layer: dump?.current_temporal_layer()?, }) } } /// Consumer type. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum ConsumerType { /// A single RTP stream is sent with no spatial/temporal layers. Simple, /// Two or more RTP streams are sent, each of them with one or more temporal layers. Simulcast, /// A single RTP stream is sent with spatial/temporal layers. Svc, /// Special type for consumers created on a /// [`PipeTransport`](crate::pipe_transport::PipeTransport). Pipe, } impl From for ConsumerType { fn from(producer_type: ProducerType) -> Self { match producer_type { ProducerType::Simple => ConsumerType::Simple, ProducerType::Simulcast => ConsumerType::Simulcast, ProducerType::Svc => ConsumerType::Svc, } } } impl ToFbs for ConsumerType { type FbsType = rtp_parameters::Type; fn to_fbs(&self) -> Self::FbsType { match self { ConsumerType::Simple => rtp_parameters::Type::Simple, ConsumerType::Simulcast => rtp_parameters::Type::Simulcast, ConsumerType::Svc => rtp_parameters::Type::Svc, ConsumerType::Pipe => rtp_parameters::Type::Pipe, } } } impl FromFbs for ConsumerType { type FbsType = rtp_parameters::Type; fn from_fbs(r#type: &Self::FbsType) -> Self { match r#type { rtp_parameters::Type::Simple => ConsumerType::Simple, rtp_parameters::Type::Simulcast => ConsumerType::Simulcast, rtp_parameters::Type::Svc => ConsumerType::Svc, rtp_parameters::Type::Pipe => ConsumerType::Pipe, } } } /// RTC statistics of the consumer alone. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[allow(missing_docs)] #[non_exhaustive] pub struct ConsumerStat { // Common to all RtpStreams. // `type` field is present in worker, but ignored here pub timestamp: u64, pub ssrc: u32, pub rtx_ssrc: Option, pub kind: MediaKind, pub mime_type: MimeType, pub packets_lost: i32, pub fraction_lost: u8, pub jitter: u32, pub packets_discarded: u64, pub packets_retransmitted: u64, pub packets_repaired: u64, pub nack_count: u64, pub nack_packet_count: u64, pub pli_count: u64, pub fir_count: u64, pub packet_count: u64, pub byte_count: u64, pub bitrate: u32, pub round_trip_time: Option, pub rtx_packets_discarded: Option, pub score: u8, } impl FromFbs for ConsumerStat { type FbsType = rtp_stream::Stats; fn from_fbs(stats: &Self::FbsType) -> Self { let rtp_stream::StatsData::SendStats(ref stats) = stats.data else { panic!("Wrong message from worker: {stats:?}"); }; let rtp_stream::StatsData::BaseStats(ref base) = stats.base.data else { panic!("Wrong message from worker: {stats:?}"); }; Self { timestamp: base.timestamp, ssrc: base.ssrc, rtx_ssrc: base.rtx_ssrc, kind: MediaKind::from_fbs(&base.kind), mime_type: base.mime_type.to_string().parse().unwrap(), packets_lost: base.packets_lost, fraction_lost: base.fraction_lost, jitter: base.jitter, packets_discarded: base.packets_discarded, packets_retransmitted: base.packets_retransmitted, packets_repaired: base.packets_repaired, nack_count: base.nack_count, nack_packet_count: base.nack_packet_count, pli_count: base.pli_count, fir_count: base.fir_count, packet_count: stats.packet_count, byte_count: stats.byte_count, bitrate: stats.bitrate, round_trip_time: Some(base.round_trip_time), rtx_packets_discarded: Some(base.rtx_packets_discarded), score: base.score, } } } /// RTC statistics of the consumer, may or may not include producer statistics. #[allow(clippy::large_enum_variant)] #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum ConsumerStats { /// RTC statistics without producer JustConsumer((ConsumerStat,)), /// RTC statistics with producer WithProducer((ConsumerStat, ProducerStat)), /// RTC statistics with multiple consumers (pipe). MultipleConsumers(Vec), } impl ConsumerStats { /// RTC statistics of the consumer pub fn consumer_stats(&self) -> &ConsumerStat { match self { ConsumerStats::JustConsumer((consumer_stat,)) => consumer_stat, ConsumerStats::WithProducer((consumer_stat, _)) => consumer_stat, ConsumerStats::MultipleConsumers(consumer_stats) => &consumer_stats[0], } } } /// 'trace' event data. #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum ConsumerTraceEventData { /// RTP packet. Rtp { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// RTP packet info. info: RtpPacketTraceInfo, }, /// RTP video keyframe packet. KeyFrame { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// RTP packet info. info: RtpPacketTraceInfo, }, /// RTCP NACK packet. Nack { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, }, /// RTCP PLI packet. Pli { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// SSRC info. info: SsrcTraceInfo, }, /// RTCP FIR packet. Fir { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// SSRC info. info: SsrcTraceInfo, }, } impl FromFbs for ConsumerTraceEventData { type FbsType = consumer::TraceNotification; fn from_fbs(data: &Self::FbsType) -> Self { match data.type_ { consumer::TraceEventType::Rtp => ConsumerTraceEventData::Rtp { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(consumer::TraceInfo::RtpTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; RtpPacketTraceInfo { is_rtx: info.is_rtx, ..RtpPacketTraceInfo::from_fbs(info.rtp_packet.as_ref()) } }, }, consumer::TraceEventType::Keyframe => ConsumerTraceEventData::KeyFrame { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(consumer::TraceInfo::KeyFrameTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; RtpPacketTraceInfo { is_rtx: info.is_rtx, ..RtpPacketTraceInfo::from_fbs(info.rtp_packet.as_ref()) } }, }, consumer::TraceEventType::Nack => ConsumerTraceEventData::Nack { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), }, consumer::TraceEventType::Pli => ConsumerTraceEventData::Pli { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(consumer::TraceInfo::PliTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; SsrcTraceInfo { ssrc: info.ssrc } }, }, consumer::TraceEventType::Fir => ConsumerTraceEventData::Fir { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(consumer::TraceInfo::FirTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; SsrcTraceInfo { ssrc: info.ssrc } }, }, } } } /// Types of consumer trace events. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum ConsumerTraceEventType { /// RTP packet. Rtp, /// RTP video keyframe packet. KeyFrame, /// RTCP NACK packet. Nack, /// RTCP PLI packet. Pli, /// RTCP FIR packet. Fir, } impl ToFbs for ConsumerTraceEventType { type FbsType = consumer::TraceEventType; fn to_fbs(&self) -> Self::FbsType { match self { ConsumerTraceEventType::Rtp => consumer::TraceEventType::Rtp, ConsumerTraceEventType::KeyFrame => consumer::TraceEventType::Keyframe, ConsumerTraceEventType::Nack => consumer::TraceEventType::Nack, ConsumerTraceEventType::Pli => consumer::TraceEventType::Pli, ConsumerTraceEventType::Fir => consumer::TraceEventType::Fir, } } } impl FromFbs for ConsumerTraceEventType { type FbsType = consumer::TraceEventType; fn from_fbs(event_type: &Self::FbsType) -> Self { match event_type { consumer::TraceEventType::Rtp => ConsumerTraceEventType::Rtp, consumer::TraceEventType::Keyframe => ConsumerTraceEventType::KeyFrame, consumer::TraceEventType::Nack => ConsumerTraceEventType::Nack, consumer::TraceEventType::Pli => ConsumerTraceEventType::Pli, consumer::TraceEventType::Fir => ConsumerTraceEventType::Fir, } } } #[derive(Debug, Deserialize)] #[serde(tag = "event", rename_all = "lowercase", content = "data")] enum Notification { ProducerClose, ProducerPause, ProducerResume, Rtp(Vec), Score(ConsumerScore), LayersChange(Option), Trace(ConsumerTraceEventData), } impl<'a> TryFromFbs<'a> for Notification { type FbsType = notification::NotificationRef<'a>; type Error = NotificationParseError; fn try_from_fbs(notification: Self::FbsType) -> Result { match notification.event().unwrap() { notification::Event::ConsumerProducerClose => Ok(Notification::ProducerClose), notification::Event::ConsumerProducerPause => Ok(Notification::ProducerPause), notification::Event::ConsumerProducerResume => Ok(Notification::ProducerResume), notification::Event::ConsumerRtp => { let Ok(Some(notification::BodyRef::ConsumerRtpNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let rtp_notification_fbs = consumer::RtpNotification::try_from(body).unwrap(); Ok(Notification::Rtp(rtp_notification_fbs.data)) } notification::Event::ConsumerScore => { let Ok(Some(notification::BodyRef::ConsumerScoreNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let score_fbs = consumer::ConsumerScore::try_from(body.score().unwrap()).unwrap(); let score = ConsumerScore::from_fbs(&score_fbs); Ok(Notification::Score(score)) } notification::Event::ConsumerLayersChange => { let Ok(Some(notification::BodyRef::ConsumerLayersChangeNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; match body.layers().unwrap() { Some(layers) => { let layers_fbs = consumer::ConsumerLayers::try_from(layers).unwrap(); let layers = ConsumerLayers::from_fbs(&layers_fbs); Ok(Notification::LayersChange(Some(layers))) } None => Ok(Notification::LayersChange(None)), } } notification::Event::ConsumerTrace => { let Ok(Some(notification::BodyRef::ConsumerTraceNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let trace_notification_fbs = consumer::TraceNotification::try_from(body).unwrap(); let trace_notification = ConsumerTraceEventData::from_fbs(&trace_notification_fbs); Ok(Notification::Trace(trace_notification)) } _ => Err(NotificationParseError::InvalidEvent), } } } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { rtp: Bag>, pause: Bag>, resume: Bag>, producer_pause: Bag>, producer_resume: Bag>, score: Bag, ConsumerScore>, #[allow(clippy::type_complexity)] layers_change: Bag) + Send + Sync>, Option>, trace: Bag, ConsumerTraceEventData>, producer_close: BagOnce>, transport_close: BagOnce>, close: BagOnce>, } struct Inner { id: ConsumerId, producer_id: ProducerId, kind: MediaKind, r#type: ConsumerType, rtp_parameters: RtpParameters, paused: Arc>, executor: Arc>, channel: Channel, producer_paused: Arc>, priority: Mutex, score: Arc>, preferred_layers: Mutex>, current_layers: Arc>>, handlers: Arc, app_data: AppData, transport: Arc, weak_producer: WeakProducer, closed: Arc, // Drop subscription to consumer-specific notifications when consumer itself is dropped _subscription_handlers: Mutex>>, _on_transport_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let transport_id = self.transport.id(); let request = ConsumerCloseRequest { consumer_id: self.id, }; let weak_producer = self.weak_producer.clone(); self.executor .spawn(async move { if weak_producer.upgrade().is_some() { match channel.request(transport_id, request).await { Err(RequestError::ChannelClosed) => { debug!( "consumer closing failed on drop: Channel already closed" ); } Err(error) => { error!("consumer closing failed on drop: {}", error); } Ok(_) => {} } } }) .detach(); } } } } /// A consumer represents an audio or video source being forwarded from a mediasoup router to an /// endpoint. It's created on top of a transport that defines how the media packets are carried. #[derive(Clone)] #[must_use = "Consumer will be closed on drop, make sure to keep it around for as long as needed"] pub struct Consumer { inner: Arc, } impl fmt::Debug for Consumer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Consumer") .field("id", &self.inner.id) .field("producer_id", &self.inner.producer_id) .field("kind", &self.inner.kind) .field("type", &self.inner.r#type) .field("rtp_parameters", &self.inner.rtp_parameters) .field("paused", &self.inner.paused) .field("producer_paused", &self.inner.producer_paused) .field("priority", &self.inner.priority) .field("score", &self.inner.score) .field("preferred_layers", &self.inner.preferred_layers) .field("current_layers", &self.inner.current_layers) .field("transport", &self.inner.transport) .field("closed", &self.inner.closed) .finish() } } impl Consumer { #[allow(clippy::too_many_arguments)] pub(super) fn new( id: ConsumerId, producer: Producer, r#type: ConsumerType, rtp_parameters: RtpParameters, paused: bool, executor: Arc>, channel: Channel, producer_paused: bool, score: ConsumerScore, preferred_layers: Option, app_data: AppData, transport: Arc, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let score = Arc::new(Mutex::new(score)); let closed = Arc::new(AtomicBool::new(false)); #[allow(clippy::mutex_atomic)] let paused = Arc::new(Mutex::new(paused)); #[allow(clippy::mutex_atomic)] let producer_paused = Arc::new(Mutex::new(producer_paused)); let current_layers = Arc::>>::default(); let inner_weak = Arc::>>>::default(); let subscription_handler = { let handlers = Arc::clone(&handlers); let closed = Arc::clone(&closed); let paused = Arc::clone(&paused); let producer_paused = Arc::clone(&producer_paused); let score = Arc::clone(&score); let current_layers = Arc::clone(¤t_layers); let inner_weak = Arc::clone(&inner_weak); channel.subscribe_to_notifications(id.into(), move |notification| { match Notification::try_from_fbs(notification) { Ok(notification) => match notification { Notification::ProducerClose => { if !closed.load(Ordering::SeqCst) { handlers.producer_close.call_simple(); let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner .executor .clone() .spawn(async move { // Potential drop needs to happen from a different // thread to prevent potential deadlock inner.close(false); }) .detach(); } } } Notification::ProducerPause => { let paused = { let paused = paused.lock(); let mut producer_paused = producer_paused.lock(); *producer_paused = true; *paused }; handlers.producer_pause.call_simple(); if !paused { handlers.pause.call_simple(); } } Notification::ProducerResume => { let paused = { let paused = paused.lock(); let mut producer_paused = producer_paused.lock(); *producer_paused = false; *paused }; handlers.producer_resume.call_simple(); if !paused { handlers.resume.call_simple(); } } Notification::Rtp(data) => { handlers.rtp.call(|callback| { callback(&data); }); } Notification::Score(consumer_score) => { *score.lock() = consumer_score.clone(); handlers.score.call_simple(&consumer_score); } Notification::LayersChange(consumer_layers) => { *current_layers.lock() = consumer_layers; handlers.layers_change.call_simple(&consumer_layers); } Notification::Trace(trace_event_data) => { handlers.trace.call_simple(&trace_event_data); } }, Err(error) => { error!("Failed to parse notification: {}", error); } } }) }; let on_transport_close_handler = transport.on_close({ let inner_weak = Arc::clone(&inner_weak); Box::new(move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.transport_close.call_simple(); inner.close(false); } }) }); let inner = Arc::new(Inner { id, producer_id: producer.id(), kind: producer.kind(), r#type, rtp_parameters, paused, producer_paused, priority: Mutex::new(1_u8), score, preferred_layers: Mutex::new(preferred_layers), current_layers, executor, channel, handlers, app_data, transport, weak_producer: producer.downgrade(), closed, _subscription_handlers: Mutex::new(vec![subscription_handler]), _on_transport_close_handler: Mutex::new(on_transport_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); Self { inner } } /// Consumer id. #[must_use] pub fn id(&self) -> ConsumerId { self.inner.id } /// Associated Producer id. #[must_use] pub fn producer_id(&self) -> ProducerId { self.inner.producer_id } /// Transport to which consumer belongs. pub fn transport(&self) -> &Arc { &self.inner.transport } /// Media kind. #[must_use] pub fn kind(&self) -> MediaKind { self.inner.kind } /// Consumer RTP parameters. /// # Notes on usage /// Check the /// [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/) /// section for more details (TypeScript-oriented, but concepts apply here as well). #[must_use] pub fn rtp_parameters(&self) -> &RtpParameters { &self.inner.rtp_parameters } /// Consumer type. #[must_use] pub fn r#type(&self) -> ConsumerType { self.inner.r#type } /// Whether the consumer is paused. It does not take into account whether the associated /// producer is paused. #[must_use] pub fn paused(&self) -> bool { *self.inner.paused.lock() } /// Whether the associate Producer is paused. #[must_use] pub fn producer_paused(&self) -> bool { *self.inner.producer_paused.lock() } /// Consumer priority (see [`Consumer::set_priority`] method). #[must_use] pub fn priority(&self) -> u8 { *self.inner.priority.lock() } /// The score of the RTP stream being sent, representing its transmission quality. #[must_use] pub fn score(&self) -> ConsumerScore { self.inner.score.lock().clone() } /// Preferred spatial and temporal layers (see [`Consumer::set_preferred_layers`] method). For /// simulcast and SVC consumers, `None` otherwise. #[must_use] pub fn preferred_layers(&self) -> Option { *self.inner.preferred_layers.lock() } /// Currently active spatial and temporal layers (for `Simulcast` and `SVC` consumers only). /// It's `None` if no layers are being sent to the consuming endpoint at this time (or if the /// consumer is consuming from a `Simulcast` or `SVC` producer). #[must_use] pub fn current_layers(&self) -> Option { *self.inner.current_layers.lock() } /// Custom application data. #[must_use] pub fn app_data(&self) -> &AppData { &self.inner.app_data } /// Whether the consumer is closed. #[must_use] pub fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } /// Dump Consumer. #[doc(hidden)] pub async fn dump(&self) -> Result { debug!("dump()"); self.inner .channel .request(self.id(), ConsumerDumpRequest {}) .await } /// Returns current RTC statistics of the consumer. /// /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/) /// section for more details (TypeScript-oriented, but concepts apply here as well). pub async fn get_stats(&self) -> Result { debug!("get_stats()"); let response = self .inner .channel .request(self.id(), ConsumerGetStatsRequest {}) .await?; if let response::Body::ConsumerGetStatsResponse(data) = response { match self.r#type() { ConsumerType::Simple | ConsumerType::Simulcast | ConsumerType::Svc => { match data.stats.len() { 0 => panic!("Empty stats response from worker"), 1 => { let consumer_stat = ConsumerStat::from_fbs(&data.stats[0]); Ok(ConsumerStats::JustConsumer((consumer_stat,))) } 2 => { let consumer_stat = ConsumerStat::from_fbs(&data.stats[0]); let producer_stat = ProducerStat::from_fbs(&data.stats[1]); Ok(ConsumerStats::WithProducer((consumer_stat, producer_stat))) } _ => panic!("More than two stats response from worker"), } } ConsumerType::Pipe => { let mut stats = Vec::::with_capacity(data.stats.len()); for stat in data.stats { stats.push(ConsumerStat::from_fbs(&stat)); } Ok(ConsumerStats::MultipleConsumers(stats)) } } } else { panic!("Wrong message from worker: {response:?}"); } } /// Pauses the consumer (no RTP is sent to the consuming endpoint). pub async fn pause(&self) -> Result<(), RequestError> { debug!("pause()"); self.inner .channel .request(self.id(), ConsumerPauseRequest {}) .await?; let was_paused = { let mut paused = self.inner.paused.lock(); let was_paused = *paused || *self.inner.producer_paused.lock(); *paused = true; was_paused }; if !was_paused { self.inner.handlers.pause.call_simple(); } Ok(()) } /// Resumes the consumer (RTP is sent again to the consuming endpoint). pub async fn resume(&self) -> Result<(), RequestError> { debug!("resume()"); self.inner .channel .request(self.id(), ConsumerResumeRequest {}) .await?; let was_paused = { let mut paused = self.inner.paused.lock(); let was_paused = *paused || *self.inner.producer_paused.lock(); *paused = false; was_paused }; if was_paused { self.inner.handlers.resume.call_simple(); } Ok(()) } /// Sets the preferred (highest) spatial and temporal layers to be sent to the consuming /// endpoint. Just valid for `Simulcast` and `SVC` consumers. pub async fn set_preferred_layers( &self, consumer_layers: ConsumerLayers, ) -> Result<(), RequestError> { debug!("set_preferred_layers()"); let consumer_layers = self .inner .channel .request( self.id(), ConsumerSetPreferredLayersRequest { data: consumer_layers, }, ) .await?; *self.inner.preferred_layers.lock() = consumer_layers; Ok(()) } /// Sets the priority for this consumer. It affects how the estimated outgoing bitrate in the /// transport (obtained via transport-cc or REMB) is distributed among all video consumers, by /// prioritizing those with higher priority. pub async fn set_priority(&self, priority: u8) -> Result<(), RequestError> { debug!("set_preferred_layers()"); let result = self .inner .channel .request(self.id(), ConsumerSetPriorityRequest { priority }) .await?; *self.inner.priority.lock() = result.priority; Ok(()) } /// Unsets the priority for this consumer (it sets it to its default value `1`). pub async fn unset_priority(&self) -> Result<(), RequestError> { debug!("unset_priority()"); let priority = 1; let result = self .inner .channel .request(self.id(), ConsumerSetPriorityRequest { priority }) .await?; *self.inner.priority.lock() = result.priority; Ok(()) } /// Request a key frame from associated producer. Just valid for video consumers. pub async fn request_key_frame(&self) -> Result<(), RequestError> { debug!("request_key_frame()"); self.inner .channel .request(self.id(), ConsumerRequestKeyFrameRequest {}) .await } /// Instructs the consumer to emit "trace" events. For monitoring purposes. Use with caution. pub async fn enable_trace_event( &self, types: Vec, ) -> Result<(), RequestError> { debug!("enable_trace_event()"); self.inner .channel .request(self.id(), ConsumerEnableTraceEventRequest { types }) .await } /// Callback is called when the consumer receives through its router a RTP packet from the /// associated producer. /// /// # Notes on usage /// Just available in direct transports, this is, those created via /// [`Router::create_direct_transport`](crate::router::Router::create_direct_transport). pub fn on_rtp(&self, callback: F) -> HandlerId { self.inner.handlers.rtp.add(Arc::new(callback)) } /// Callback is called when the consumer or its associated producer is paused and, as result, /// the consumer becomes paused. pub fn on_pause(&self, callback: F) -> HandlerId { self.inner.handlers.pause.add(Arc::new(callback)) } /// Callback is called when the consumer or its associated producer is resumed and, as result, /// the consumer is no longer paused. pub fn on_resume(&self, callback: F) -> HandlerId { self.inner.handlers.resume.add(Arc::new(callback)) } /// Callback is called when the associated producer is paused. pub fn on_producer_pause(&self, callback: F) -> HandlerId { self.inner.handlers.producer_pause.add(Arc::new(callback)) } /// Callback is called when the associated producer is resumed. pub fn on_producer_resume(&self, callback: F) -> HandlerId { self.inner.handlers.producer_resume.add(Arc::new(callback)) } /// Callback is called when the consumer score changes. pub fn on_score( &self, callback: F, ) -> HandlerId { self.inner.handlers.score.add(Arc::new(callback)) } /// Callback is called when the spatial/temporal layers being sent to the endpoint change. Just /// for `Simulcast` or `SVC` consumers. /// /// # Notes on usage /// This callback is called under various circumstances in `SVC` or `Simulcast` consumers /// (assuming the consumer endpoints supports BWE via REMB or Transport-CC): /// * When the consumer (or its associated producer) is paused. /// * When all the RTP streams of the associated producer become inactive (no RTP received for a /// while). /// * When the available bitrate of the BWE makes the consumer upgrade or downgrade the spatial /// and/or temporal layers. /// * When there is no available bitrate for this consumer (even for the lowest layers) so the /// callback is called with `None` as argument. /// /// The Rust application can detect the latter (consumer deactivated due to not enough /// bandwidth) by checking if both `consumer.paused()` and `consumer.producer_paused()` are /// falsy after the consumer has called this callback with `None` as argument. pub fn on_layers_change) + Send + Sync + 'static>( &self, callback: F, ) -> HandlerId { self.inner.handlers.layers_change.add(Arc::new(callback)) } /// See [`Consumer::enable_trace_event`] method. pub fn on_trace( &self, callback: F, ) -> HandlerId { self.inner.handlers.trace.add(Arc::new(callback)) } /// Callback is called when the associated producer is closed for whatever reason. The consumer /// itself is also closed. pub fn on_producer_close(&self, callback: F) -> HandlerId { self.inner.handlers.producer_close.add(Box::new(callback)) } /// Callback is called when the transport this consumer belongs to is closed for whatever /// reason. The consumer itself is also closed. pub fn on_transport_close(&self, callback: F) -> HandlerId { self.inner.handlers.transport_close.add(Box::new(callback)) } /// Callback is called when the consumer is closed for whatever reason. /// /// NOTE: Callback will be called in place if consumer is already closed. pub fn on_close(&self, callback: F) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } /// Downgrade `Consumer` to [`WeakConsumer`] instance. #[must_use] pub fn downgrade(&self) -> WeakConsumer { WeakConsumer { inner: Arc::downgrade(&self.inner), } } } /// [`WeakConsumer`] doesn't own consumer instance on mediasoup-worker and will not prevent one from /// being destroyed once last instance of regular [`Consumer`] is dropped. /// /// [`WeakConsumer`] vs [`Consumer`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakConsumer { inner: Weak, } impl fmt::Debug for WeakConsumer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakConsumer").finish() } } impl WeakConsumer { /// Attempts to upgrade `WeakConsumer` to [`Consumer`] if last instance of one wasn't dropped /// yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; Some(Consumer { inner }) } } ================================================ FILE: rust/src/router/data_consumer/tests.rs ================================================ use crate::data_consumer::DataConsumerOptions; use crate::data_producer::{DataProducer, DataProducerOptions}; use crate::plain_transport::PlainTransportOptions; use crate::router::{Router, RouterOptions}; use crate::transport::Transport; use crate::webrtc_transport::{WebRtcTransportListenInfos, WebRtcTransportOptions}; use crate::worker::WorkerSettings; use crate::worker_manager::WorkerManager; use futures_lite::future; use mediasoup_types::data_structures::{ListenInfo, Protocol}; use mediasoup_types::sctp_parameters::SctpStreamParameters; use std::env; use std::net::{IpAddr, Ipv4Addr}; async fn init() -> (Router, DataProducer) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); let transport = router .create_webrtc_transport({ let mut transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); let data_producer = transport .produce_data(DataProducerOptions::new_sctp( SctpStreamParameters::new_unordered_with_life_time(12345, 5000), )) .await .expect("Failed to create data producer"); (router, data_producer) } #[test] fn data_producer_close_event() { future::block_on(async move { let (router, data_producer) = init().await; let transport2 = router .create_plain_transport({ let mut transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); let data_consumer = transport2 .consume_data(DataConsumerOptions::new_sctp_unordered_with_life_time( data_producer.id(), 4000, )) .await .expect("Failed to consume data"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = data_consumer.on_close(move || { let _ = close_tx.send(()); }); let (mut data_producer_close_tx, data_producer_close_rx) = async_oneshot::oneshot::<()>(); let _handler = data_consumer.on_data_producer_close(move || { let _ = data_producer_close_tx.send(()); }); drop(data_producer); data_producer_close_rx .await .expect("Failed to receive data_producer_close event"); close_rx.await.expect("Failed to receive close event"); assert!(data_consumer.closed()); }); } #[test] fn transport_close_event() { future::block_on(async move { let (router, data_producer) = init().await; let transport2 = router .create_plain_transport({ let mut transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); let data_consumer = transport2 .consume_data(DataConsumerOptions::new_sctp_unordered_with_life_time( data_producer.id(), 4000, )) .await .expect("Failed to consume data"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = data_consumer.on_close(move || { let _ = close_tx.send(()); }); let (mut transport_close_tx, transport_close_rx) = async_oneshot::oneshot::<()>(); let _handler = data_consumer.on_transport_close(move || { let _ = transport_close_tx.send(()); }); router.close(); transport_close_rx .await .expect("Failed to receive transport_close event"); close_rx.await.expect("Failed to receive close event"); assert!(data_consumer.closed()); }); } ================================================ FILE: rust/src/router/data_consumer.rs ================================================ #[cfg(test)] mod tests; use crate::data_producer::{DataProducer, DataProducerId, WeakDataProducer}; use crate::fbs::{FromFbs, TryFromFbs}; use crate::messages::{ DataConsumerAddSubchannelRequest, DataConsumerCloseRequest, DataConsumerDumpRequest, DataConsumerGetBufferedAmountRequest, DataConsumerGetStatsRequest, DataConsumerPauseRequest, DataConsumerRemoveSubchannelRequest, DataConsumerResumeRequest, DataConsumerSendRequest, DataConsumerSetBufferedAmountLowThresholdRequest, DataConsumerSetSubchannelsRequest, }; use crate::transport::Transport; use crate::uuid_based_wrapper_type; use crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler}; use async_executor::Executor; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::{data_consumer, data_producer, notification, response}; use mediasoup_types::data_structures::{AppData, WebRtcMessage}; use mediasoup_types::sctp_parameters::SctpStreamParameters; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::borrow::Cow; use std::error::Error; // TODO. // use std::borrow::Cow; use std::fmt; use std::fmt::Debug; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; uuid_based_wrapper_type!( /// [`DataConsumer`] identifier. DataConsumerId ); /// [`DataConsumer`] options. #[derive(Debug, Clone)] #[non_exhaustive] pub struct DataConsumerOptions { // The id of the [`DataProducer`](crate::data_producer::DataProducer) to consume. pub(super) data_producer_id: DataProducerId, /// Just if consuming over SCTP. /// Whether data messages must be received in order. If true the messages will be sent reliably. /// Defaults to the value in the DataProducer if it has type `Sctp` or to true if it has type /// `Direct`. pub(super) ordered: Option, /// Just if consuming over SCTP. /// When ordered is false indicates the time (in milliseconds) after which a SCTP packet will /// stop being retransmitted. /// Defaults to the value in the DataProducer if it has type `Sctp` or unset if it has type /// `Direct`. pub(super) max_packet_life_time: Option, /// Just if consuming over SCTP. /// When ordered is false indicates the maximum number of times a packet will be retransmitted. /// Defaults to the value in the [`DataProducer`](crate::data_producer::DataProducer) if it /// has type `Sctp` or unset if it has type `Direct`. pub(super) max_retransmits: Option, /// Whether the DataConsumer must start in paused mode. Default false. pub paused: bool, /// Subchannels this DataConsumer initially subscribes to. /// Only used in case this DataConsumer receives messages from a local DataProducer /// that specifies subchannel(s) when calling send(). pub subchannels: Option>, /// Custom application data. pub app_data: AppData, } impl DataConsumerOptions { /// Inherits parameters of corresponding /// [`DataProducer`](crate::data_producer::DataProducer). #[must_use] pub fn new_sctp(data_producer_id: DataProducerId) -> Self { Self { data_producer_id, ordered: None, max_packet_life_time: None, max_retransmits: None, subchannels: None, paused: false, app_data: AppData::default(), } } /// For [`DirectTransport`](crate::direct_transport::DirectTransport). #[must_use] pub fn new_direct(data_producer_id: DataProducerId, subchannels: Option>) -> Self { Self { data_producer_id, ordered: Some(true), max_packet_life_time: None, max_retransmits: None, paused: false, subchannels, app_data: AppData::default(), } } /// Messages will be sent reliably in order. #[must_use] pub fn new_sctp_ordered(data_producer_id: DataProducerId) -> Self { Self { data_producer_id, ordered: Some(true), max_packet_life_time: None, max_retransmits: None, paused: false, subchannels: None, app_data: AppData::default(), } } /// Messages will be sent unreliably with time (in milliseconds) after which a SCTP packet will /// stop being retransmitted. #[must_use] pub fn new_sctp_unordered_with_life_time( data_producer_id: DataProducerId, max_packet_life_time: u16, ) -> Self { Self { data_producer_id, ordered: Some(false), max_packet_life_time: Some(max_packet_life_time), max_retransmits: None, paused: false, subchannels: None, app_data: AppData::default(), } } /// Messages will be sent unreliably with a limited number of retransmission attempts. #[must_use] pub fn new_sctp_unordered_with_retransmits( data_producer_id: DataProducerId, max_retransmits: u16, ) -> Self { Self { data_producer_id, ordered: Some(false), max_packet_life_time: None, max_retransmits: Some(max_retransmits), paused: false, subchannels: None, app_data: AppData::default(), } } } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct DataConsumerDump { pub id: DataConsumerId, pub data_producer_id: DataProducerId, pub r#type: DataConsumerType, pub label: String, pub protocol: String, pub sctp_stream_parameters: Option, pub buffered_amount_low_threshold: u32, pub paused: bool, pub subchannels: Vec, pub data_producer_paused: bool, } impl<'a> TryFromFbs<'a> for DataConsumerDump { type FbsType = data_consumer::DumpResponse; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { Ok(Self { id: dump.id.parse()?, data_producer_id: dump.data_producer_id.parse()?, r#type: if dump.type_ == data_producer::Type::Sctp { DataConsumerType::Sctp } else { DataConsumerType::Direct }, label: dump.label.clone(), protocol: dump.protocol.clone(), sctp_stream_parameters: dump .sctp_stream_parameters .as_ref() .map(|parameters| SctpStreamParameters::from_fbs(parameters.as_ref())), buffered_amount_low_threshold: dump.buffered_amount_low_threshold, paused: dump.paused, subchannels: dump.subchannels.clone(), data_producer_paused: dump.data_producer_paused, }) } } /// RTC statistics of the data consumer. #[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] #[allow(missing_docs)] pub struct DataConsumerStat { // `type` field is present in worker, but ignored here pub timestamp: u64, pub label: String, pub protocol: String, pub messages_sent: u64, pub bytes_sent: u64, pub buffered_amount: u32, } impl FromFbs for DataConsumerStat { type FbsType = data_consumer::GetStatsResponse; fn from_fbs(stats: &Self::FbsType) -> Self { Self { timestamp: stats.timestamp, label: stats.label.to_string(), protocol: stats.protocol.to_string(), messages_sent: stats.messages_sent, bytes_sent: stats.bytes_sent, buffered_amount: stats.buffered_amount, } } } /// Data consumer type. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum DataConsumerType { /// The endpoint receives messages using the SCTP protocol. Sctp, /// Messages are received directly by the Rust process over a direct transport. Direct, } #[derive(Debug, Deserialize)] #[serde(tag = "event", rename_all = "lowercase", content = "data")] enum Notification { DataProducerClose, DataProducerPause, DataProducerResume, SctpSendBufferFull, Message { ppid: u32, data: Vec, }, #[serde(rename_all = "camelCase")] BufferedAmountLow { buffered_amount: u32, }, } impl<'a> TryFromFbs<'a> for Notification { type FbsType = notification::NotificationRef<'a>; type Error = NotificationParseError; fn try_from_fbs(notification: Self::FbsType) -> Result { match notification.event().unwrap() { notification::Event::DataconsumerDataproducerClose => { Ok(Notification::DataProducerClose) } notification::Event::DataconsumerDataproducerPause => { Ok(Notification::DataProducerPause) } notification::Event::DataconsumerDataproducerResume => { Ok(Notification::DataProducerResume) } notification::Event::DataconsumerSctpSendbufferFull => { Ok(Notification::SctpSendBufferFull) } notification::Event::DataconsumerMessage => { let Ok(Some(notification::BodyRef::DataConsumerMessageNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; Ok(Notification::Message { ppid: body.ppid().unwrap(), data: body.data().unwrap().into(), }) } notification::Event::DataconsumerBufferedAmountLow => { let Ok(Some(notification::BodyRef::DataConsumerBufferedAmountLowNotification( body, ))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; Ok(Notification::BufferedAmountLow { buffered_amount: body.buffered_amount().unwrap(), }) } _ => Err(NotificationParseError::InvalidEvent), } } } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { message: Bag) + Send + Sync>>, sctp_send_buffer_full: Bag>, buffered_amount_low: Bag>, data_producer_close: BagOnce>, pause: Bag>, resume: Bag>, data_producer_pause: Bag>, data_producer_resume: Bag>, transport_close: BagOnce>, close: BagOnce>, } struct Inner { id: DataConsumerId, r#type: DataConsumerType, sctp_stream_parameters: Option, label: String, protocol: String, data_producer_id: DataProducerId, direct: bool, paused: Arc>, subchannels: Arc>>, data_producer_paused: Arc>, executor: Arc>, channel: Channel, handlers: Arc, app_data: AppData, transport: Arc, weak_data_producer: WeakDataProducer, closed: Arc, // Drop subscription to data consumer-specific notifications when data consumer itself is // dropped _subscription_handlers: Mutex>>, _on_transport_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let transport_id = self.transport.id(); let request = DataConsumerCloseRequest { data_consumer_id: self.id, }; let weak_data_producer = self.weak_data_producer.clone(); self.executor .spawn(async move { if weak_data_producer.upgrade().is_some() { match channel.request(transport_id, request).await { Err(RequestError::ChannelClosed) => { debug!("data consumer closing failed on drop: Channel already closed"); } Err(error) => { error!("data consumer closing failed on drop: {}", error); } Ok(_) => {} } } }) .detach(); } } } } /// Data consumer created on transport other than /// [`DirectTransport`](crate::direct_transport::DirectTransport). #[derive(Clone)] #[must_use = "Data consumer will be closed on drop, make sure to keep it around for as long as needed"] pub struct RegularDataConsumer { inner: Arc, } impl fmt::Debug for RegularDataConsumer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RegularDataConsumer") .field("id", &self.inner.id) .field("type", &self.inner.r#type) .field("sctp_stream_parameters", &self.inner.sctp_stream_parameters) .field("label", &self.inner.label) .field("protocol", &self.inner.protocol) .field("data_producer_id", &self.inner.data_producer_id) .field("paused", &self.inner.paused) .field("data_producer_paused", &self.inner.data_producer_paused) .field("subchannels", &self.inner.subchannels) .field("transport", &self.inner.transport) .field("closed", &self.inner.closed) .finish() } } impl From for DataConsumer { fn from(producer: RegularDataConsumer) -> Self { DataConsumer::Regular(producer) } } /// Data consumer created on [`DirectTransport`](crate::direct_transport::DirectTransport). #[derive(Clone)] #[must_use = "Data consumer will be closed on drop, make sure to keep it around for as long as needed"] pub struct DirectDataConsumer { inner: Arc, } impl fmt::Debug for DirectDataConsumer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DirectDataConsumer") .field("id", &self.inner.id) .field("type", &self.inner.r#type) .field("sctp_stream_parameters", &self.inner.sctp_stream_parameters) .field("label", &self.inner.label) .field("protocol", &self.inner.protocol) .field("data_producer_id", &self.inner.data_producer_id) .field("paused", &self.inner.paused) .field("data_producer_paused", &self.inner.data_producer_paused) .field("subchannels", &self.inner.subchannels) .field("transport", &self.inner.transport) .field("closed", &self.inner.closed) .finish() } } impl From for DataConsumer { fn from(producer: DirectDataConsumer) -> Self { DataConsumer::Direct(producer) } } /// A data consumer represents an endpoint capable of receiving data messages from a mediasoup /// [`Router`](crate::router::Router). /// /// A data consumer can use [SCTP](https://tools.ietf.org/html/rfc4960) (AKA /// DataChannel) to receive those messages, or can directly receive them in the Rust application if /// the data consumer was created on top of a /// [`DirectTransport`](crate::direct_transport::DirectTransport). #[derive(Clone)] #[non_exhaustive] #[must_use = "Data consumer will be closed on drop, make sure to keep it around for as long as needed"] pub enum DataConsumer { /// Data consumer created on transport other than /// [`DirectTransport`](crate::direct_transport::DirectTransport). Regular(RegularDataConsumer), /// Data consumer created on [`DirectTransport`](crate::direct_transport::DirectTransport). Direct(DirectDataConsumer), } impl fmt::Debug for DataConsumer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self { DataConsumer::Regular(producer) => f.debug_tuple("Regular").field(&producer).finish(), DataConsumer::Direct(producer) => f.debug_tuple("Direct").field(&producer).finish(), } } } impl DataConsumer { #[allow(clippy::too_many_arguments)] pub(super) fn new( id: DataConsumerId, r#type: DataConsumerType, sctp_stream_parameters: Option, label: String, protocol: String, paused: bool, data_producer: DataProducer, executor: Arc>, channel: Channel, data_producer_paused: bool, subchannels: Vec, app_data: AppData, transport: Arc, direct: bool, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let closed = Arc::new(AtomicBool::new(false)); let paused = Arc::new(Mutex::new(paused)); #[allow(clippy::mutex_atomic)] let data_producer_paused = Arc::new(Mutex::new(data_producer_paused)); let subchannels = Arc::new(Mutex::new(subchannels)); let inner_weak = Arc::>>>::default(); let subscription_handler = { let handlers = Arc::clone(&handlers); let closed = Arc::clone(&closed); let paused = Arc::clone(&paused); let data_producer_paused = Arc::clone(&data_producer_paused); let inner_weak = Arc::clone(&inner_weak); channel.subscribe_to_notifications(id.into(), move |notification| { match Notification::try_from_fbs(notification) { Ok(notification) => match notification { Notification::DataProducerClose => { if !closed.load(Ordering::SeqCst) { handlers.data_producer_close.call_simple(); let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner .executor .clone() .spawn(async move { // Potential drop needs to happen from a different // thread to prevent potential deadlock inner.close(false); }) .detach(); } } } Notification::DataProducerPause => { let mut data_producer_paused = data_producer_paused.lock(); let paused = *paused.lock(); *data_producer_paused = true; handlers.data_producer_pause.call_simple(); if !paused { handlers.pause.call_simple(); } } Notification::DataProducerResume => { let mut data_producer_paused = data_producer_paused.lock(); let paused = *paused.lock(); *data_producer_paused = false; handlers.data_producer_resume.call_simple(); if !paused { handlers.resume.call_simple(); } } Notification::SctpSendBufferFull => { handlers.sctp_send_buffer_full.call_simple(); } Notification::Message { ppid, data } => { match WebRtcMessage::new(ppid, Cow::from(data)) { Ok(message) => { handlers.message.call(|callback| { callback(&message); }); } Err(ppid) => { error!("Bad ppid {}", ppid); } } } Notification::BufferedAmountLow { buffered_amount } => { handlers.buffered_amount_low.call(|callback| { callback(buffered_amount); }); } }, Err(error) => { error!("Failed to parse notification: {}", error); } } }) }; let on_transport_close_handler = transport.on_close({ let inner_weak = Arc::clone(&inner_weak); Box::new(move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.transport_close.call_simple(); inner.close(false); } }) }); let inner = Arc::new(Inner { id, r#type, sctp_stream_parameters, label, protocol, data_producer_id: data_producer.id(), paused, data_producer_paused, direct, executor, channel, handlers, subchannels, app_data, transport, weak_data_producer: data_producer.downgrade(), closed, _subscription_handlers: Mutex::new(vec![subscription_handler]), _on_transport_close_handler: Mutex::new(on_transport_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); if direct { Self::Direct(DirectDataConsumer { inner }) } else { Self::Regular(RegularDataConsumer { inner }) } } /// Data consumer identifier. #[must_use] pub fn id(&self) -> DataConsumerId { self.inner().id } /// The associated data producer identifier. #[must_use] pub fn data_producer_id(&self) -> DataProducerId { self.inner().data_producer_id } /// Transport to which data consumer belongs. pub fn transport(&self) -> &Arc { &self.inner().transport } /// The type of the data consumer. #[must_use] pub fn r#type(&self) -> DataConsumerType { self.inner().r#type } /// Whether the data consumer is paused. It does not take into account whether the /// associated data producer is paused. #[must_use] pub fn paused(&self) -> bool { *self.inner().paused.lock() } /// Whether the associate data producer is paused. #[must_use] pub fn producer_paused(&self) -> bool { *self.inner().data_producer_paused.lock() } /// The SCTP stream parameters (just if the data consumer type is `Sctp`). #[must_use] pub fn sctp_stream_parameters(&self) -> Option { self.inner().sctp_stream_parameters } /// The data consumer label. #[must_use] pub fn label(&self) -> &String { &self.inner().label } /// The data consumer sub-protocol. #[must_use] pub fn protocol(&self) -> &String { &self.inner().protocol } /// The data consumer subchannels. #[must_use] pub fn subchannels(&self) -> Vec { self.inner().subchannels.lock().clone() } /// Custom application data. #[must_use] pub fn app_data(&self) -> &AppData { &self.inner().app_data } /// Whether the data consumer is closed. #[must_use] pub fn closed(&self) -> bool { self.inner().closed.load(Ordering::SeqCst) } /// Dump DataConsumer. #[doc(hidden)] pub async fn dump(&self) -> Result { debug!("dump()"); let response = self .inner() .channel .request(self.id(), DataConsumerDumpRequest {}) .await?; if let response::Body::DataConsumerDumpResponse(data) = response { Ok(DataConsumerDump::try_from_fbs(*data).expect("Error parsing dump response")) } else { panic!("Wrong message from worker: {response:?}"); } } /// Returns current statistics of the data consumer. /// /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/) /// section for more details (TypeScript-oriented, but concepts apply here as well). pub async fn get_stats(&self) -> Result, RequestError> { debug!("get_stats()"); let response = self .inner() .channel .request(self.id(), DataConsumerGetStatsRequest {}) .await?; if let response::Body::DataConsumerGetStatsResponse(data) = response { Ok(vec![DataConsumerStat::from_fbs(&data)]) } else { panic!("Wrong message from worker: {response:?}"); } } /// Pauses the data consumer (no mossage is sent to the consuming endpoint). pub async fn pause(&self) -> Result<(), RequestError> { debug!("pause()"); self.inner() .channel .request(self.id(), DataConsumerPauseRequest {}) .await?; let mut paused = self.inner().paused.lock(); let was_paused = *paused || *self.inner().data_producer_paused.lock(); *paused = true; if !was_paused { self.inner().handlers.pause.call_simple(); } Ok(()) } /// Resumes the data consumer (messages are sent again to the consuming endpoint). pub async fn resume(&self) -> Result<(), RequestError> { debug!("resume()"); self.inner() .channel .request(self.id(), DataConsumerResumeRequest {}) .await?; let mut paused = self.inner().paused.lock(); let was_paused = *paused || *self.inner().data_producer_paused.lock(); *paused = false; if was_paused { self.inner().handlers.resume.call_simple(); } Ok(()) } /// Returns the number of bytes of data currently buffered to be sent over the underlying SCTP /// association. /// /// # Notes on usage /// The underlying SCTP association uses a common send buffer for all data consumers, hence the /// value given by this method indicates the data buffered for all data consumers in the /// transport. pub async fn get_buffered_amount(&self) -> Result { debug!("get_buffered_amount()"); let response = self .inner() .channel .request(self.id(), DataConsumerGetBufferedAmountRequest {}) .await?; Ok(response.buffered_amount) } /// Whenever the underlying SCTP association buffered bytes drop to this value, /// `on_buffered_amount_low` callback is called. pub async fn set_buffered_amount_low_threshold( &self, threshold: u32, ) -> Result<(), RequestError> { debug!( "set_buffered_amount_low_threshold() [threshold:{}]", threshold ); self.inner() .channel .request( self.id(), DataConsumerSetBufferedAmountLowThresholdRequest { threshold }, ) .await } /// Sets subchannels to the worker DataConsumer. pub async fn set_subchannels(&self, subchannels: Vec) -> Result<(), RequestError> { let response = self .inner() .channel .request(self.id(), DataConsumerSetSubchannelsRequest { subchannels }) .await?; *self.inner().subchannels.lock() = response.subchannels; Ok(()) } /// Adds a subchannel to the worker DataConsumer. pub async fn add_subchannel(&self, subchannel: u16) -> Result<(), RequestError> { let response = self .inner() .channel .request(self.id(), DataConsumerAddSubchannelRequest { subchannel }) .await?; *self.inner().subchannels.lock() = response.subchannels; Ok(()) } /// Removes a subchannel to the worker DataConsumer. pub async fn remove_subchannel(&self, subchannel: u16) -> Result<(), RequestError> { let response = self .inner() .channel .request( self.id(), DataConsumerRemoveSubchannelRequest { subchannel }, ) .await?; *self.inner().subchannels.lock() = response.subchannels; Ok(()) } /// Callback is called when a message has been received from the corresponding data producer. /// /// # Notes on usage /// Just available in direct transports, this is, those created via /// [`Router::create_direct_transport`](crate::router::Router::create_direct_transport). pub fn on_message) + Send + Sync + 'static>( &self, callback: F, ) -> HandlerId { self.inner().handlers.message.add(Arc::new(callback)) } /// Callback is called when a message could not be sent because the SCTP send buffer was full. pub fn on_sctp_send_buffer_full( &self, callback: F, ) -> HandlerId { self.inner() .handlers .sctp_send_buffer_full .add(Arc::new(callback)) } /// Emitted when the underlying SCTP association buffered bytes drop down to the value set with /// [`DataConsumer::set_buffered_amount_low_threshold`]. /// /// # Notes on usage /// Only applicable for consumers of type `Sctp`. pub fn on_buffered_amount_low( &self, callback: F, ) -> HandlerId { self.inner() .handlers .buffered_amount_low .add(Arc::new(callback)) } /// Callback is called when the associated data producer is closed for whatever reason. The data /// consumer itself is also closed. pub fn on_data_producer_close(&self, callback: F) -> HandlerId { self.inner() .handlers .data_producer_close .add(Box::new(callback)) } /// Callback is called when the data consumer or its associated data producer is /// paused and, as result, the data consumer becomes paused. pub fn on_pause(&self, callback: F) -> HandlerId { self.inner().handlers.pause.add(Arc::new(callback)) } /// Callback is called when the data consumer or its associated data producer is /// resumed and, as result, the data consumer is no longer paused. pub fn on_resume(&self, callback: F) -> HandlerId { self.inner().handlers.resume.add(Arc::new(callback)) } /// Callback is called when the associated data producer is paused. pub fn on_data_producer_pause( &self, callback: F, ) -> HandlerId { self.inner() .handlers .data_producer_pause .add(Arc::new(callback)) } /// Callback is called when the associated data producer is resumed. pub fn on_data_producer_resume( &self, callback: F, ) -> HandlerId { self.inner() .handlers .data_producer_resume .add(Arc::new(callback)) } /// Callback is called when the transport this data consumer belongs to is closed for whatever /// reason. The data consumer itself is also closed. pub fn on_transport_close(&self, callback: F) -> HandlerId { self.inner() .handlers .transport_close .add(Box::new(callback)) } /// Callback is called when the data consumer is closed for whatever reason. /// /// NOTE: Callback will be called in place if data consumer is already closed. pub fn on_close(&self, callback: F) -> HandlerId { let handler_id = self.inner().handlers.close.add(Box::new(callback)); if self.inner().closed.load(Ordering::Relaxed) { self.inner().handlers.close.call_simple(); } handler_id } /// Downgrade `DataConsumer` to [`WeakDataConsumer`] instance. #[must_use] pub fn downgrade(&self) -> WeakDataConsumer { WeakDataConsumer { inner: Arc::downgrade(self.inner()), } } fn inner(&self) -> &Arc { match self { DataConsumer::Regular(data_consumer) => &data_consumer.inner, DataConsumer::Direct(data_consumer) => &data_consumer.inner, } } } impl DirectDataConsumer { /// Sends direct messages from the Rust process. pub async fn send(&self, message: WebRtcMessage<'_>) -> Result<(), RequestError> { let (ppid, payload) = message.into_ppid_and_payload(); self.inner .channel .request( self.inner.id, DataConsumerSendRequest { ppid, payload: payload.into_owned(), }, ) .await } } /// [`WeakDataConsumer`] doesn't own data consumer instance on mediasoup-worker and will not prevent /// one from being destroyed once last instance of regular [`DataConsumer`] is dropped. /// /// [`WeakDataConsumer`] vs [`DataConsumer`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakDataConsumer { inner: Weak, } impl fmt::Debug for WeakDataConsumer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakDataConsumer").finish() } } impl WeakDataConsumer { /// Attempts to upgrade `WeakDataConsumer` to [`DataConsumer`] if last instance of one wasn't /// dropped yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; let data_consumer = if inner.direct { DataConsumer::Direct(DirectDataConsumer { inner }) } else { DataConsumer::Regular(RegularDataConsumer { inner }) }; Some(data_consumer) } } ================================================ FILE: rust/src/router/data_producer/tests.rs ================================================ use crate::data_producer::DataProducerOptions; use crate::router::{Router, RouterOptions}; use crate::transport::Transport; use crate::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use crate::worker::WorkerSettings; use crate::worker_manager::WorkerManager; use futures_lite::future; use mediasoup_types::data_structures::{ListenInfo, Protocol}; use mediasoup_types::sctp_parameters::SctpStreamParameters; use std::env; use std::net::{IpAddr, Ipv4Addr}; async fn init() -> (Router, WebRtcTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); let transport1 = router .create_webrtc_transport({ let mut transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); (router, transport1) } #[test] fn transport_close_event() { future::block_on(async move { let (router, transport1) = init().await; let data_producer = transport1 .produce_data(DataProducerOptions::new_sctp( SctpStreamParameters::new_ordered(666), )) .await .expect("Failed to produce data"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = data_producer.on_close(move || { let _ = close_tx.send(()); }); let (mut transport_close_tx, transport_close_rx) = async_oneshot::oneshot::<()>(); let _handler = data_producer.on_transport_close(move || { let _ = transport_close_tx.send(()); }); router.close(); transport_close_rx .await .expect("Failed to receive transport_close event"); close_rx.await.expect("Failed to receive close event"); assert!(data_producer.closed()); }); } ================================================ FILE: rust/src/router/data_producer.rs ================================================ #[cfg(test)] mod tests; use crate::fbs::{FromFbs, TryFromFbs}; use crate::messages::{ DataProducerCloseRequest, DataProducerDumpRequest, DataProducerGetStatsRequest, DataProducerPauseRequest, DataProducerResumeRequest, DataProducerSendNotification, }; use crate::transport::Transport; use crate::uuid_based_wrapper_type; use crate::worker::{Channel, NotificationError, RequestError}; use async_executor::Executor; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::{data_producer, response}; use mediasoup_types::data_structures::{AppData, WebRtcMessage}; use mediasoup_types::sctp_parameters::SctpStreamParameters; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt; use std::fmt::Debug; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; uuid_based_wrapper_type!( /// [`DataProducer`] identifier. DataProducerId ); /// [`DataProducer`] options. #[derive(Debug, Clone)] #[non_exhaustive] pub struct DataProducerOptions { /// DataProducer id (just for /// [`Router::pipe_data_producer_to_router`](crate::router::Router::pipe_producer_to_router) /// method). pub(super) id: Option, /// SCTP parameters defining how the endpoint is sending the data. /// Required if SCTP/DataChannel is used. /// Must not be given if the data producer is created on a DirectTransport. pub(super) sctp_stream_parameters: Option, /// A label which can be used to distinguish this DataChannel from others. pub label: String, /// Name of the sub-protocol used by this DataChannel. pub protocol: String, /// Whether the data producer must start in paused mode. Default false. pub paused: bool, /// Custom application data. pub app_data: AppData, } impl DataProducerOptions { #[must_use] pub(super) fn new_pipe_transport( data_producer_id: DataProducerId, sctp_stream_parameters: SctpStreamParameters, ) -> Self { Self { id: Some(data_producer_id), sctp_stream_parameters: Some(sctp_stream_parameters), label: "".to_string(), protocol: "".to_string(), paused: false, app_data: AppData::default(), } } /// Data producer options for non-Direct transport. #[must_use] pub fn new_sctp(sctp_stream_parameters: SctpStreamParameters) -> Self { Self { id: None, sctp_stream_parameters: Some(sctp_stream_parameters), label: "".to_string(), protocol: "".to_string(), paused: false, app_data: AppData::default(), } } /// Data producer options for Direct transport. #[must_use] pub fn new_direct() -> Self { Self { id: None, sctp_stream_parameters: None, label: "".to_string(), protocol: "".to_string(), paused: false, app_data: AppData::default(), } } } /// Data consumer type. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum DataProducerType { /// The endpoint sends messages using the SCTP protocol. Sctp, /// Messages are sent directly from the Rust process over a direct transport. Direct, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct DataProducerDump { pub id: DataProducerId, pub r#type: DataProducerType, pub label: String, pub protocol: String, pub sctp_stream_parameters: Option, pub paused: bool, } impl<'a> TryFromFbs<'a> for DataProducerDump { type FbsType = data_producer::DumpResponse; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { Ok(Self { id: dump.id.parse()?, r#type: if dump.type_ == data_producer::Type::Sctp { DataProducerType::Sctp } else { DataProducerType::Direct }, label: dump.label, protocol: dump.protocol, sctp_stream_parameters: dump .sctp_stream_parameters .map(|parameters| SctpStreamParameters::from_fbs(parameters.as_ref())), paused: dump.paused, }) } } /// RTC statistics of the data producer. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] #[allow(missing_docs)] pub struct DataProducerStat { // `type` field is present in worker, but ignored here pub timestamp: u64, pub label: String, pub protocol: String, pub messages_received: u64, pub bytes_received: u64, } impl FromFbs for DataProducerStat { type FbsType = data_producer::GetStatsResponse; fn from_fbs(stats: &Self::FbsType) -> Self { Self { timestamp: stats.timestamp, label: stats.label.to_string(), protocol: stats.protocol.to_string(), messages_received: stats.messages_received, bytes_received: stats.bytes_received, } } } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { pause: Bag>, resume: Bag>, transport_close: BagOnce>, close: BagOnce>, } struct Inner { id: DataProducerId, r#type: DataProducerType, sctp_stream_parameters: Option, label: String, protocol: String, paused: AtomicBool, direct: bool, executor: Arc>, channel: Channel, handlers: Arc, app_data: AppData, transport: Arc, closed: AtomicBool, _on_transport_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let transport_id = self.transport.id(); let request = DataProducerCloseRequest { data_producer_id: self.id, }; self.executor .spawn(async move { match channel.request(transport_id, request).await { Err(RequestError::ChannelClosed) => { debug!( "data producer closing failed on drop: Channel already closed" ); } Err(error) => { error!("data producer closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// Data producer created on transport other than /// [`DirectTransport`](crate::direct_transport::DirectTransport). #[derive(Clone)] #[must_use = "Data producer will be closed on drop, make sure to keep it around for as long as needed"] pub struct RegularDataProducer { inner: Arc, } impl fmt::Debug for RegularDataProducer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RegularDataProducer") .field("id", &self.inner.id) .field("type", &self.inner.r#type) .field("sctp_stream_parameters", &self.inner.sctp_stream_parameters) .field("label", &self.inner.label) .field("protocol", &self.inner.protocol) .field("paused", &self.inner.paused) .field("transport", &self.inner.transport) .field("closed", &self.inner.closed) .finish() } } impl From for DataProducer { fn from(producer: RegularDataProducer) -> Self { DataProducer::Regular(producer) } } /// Data producer created on [`DirectTransport`](crate::direct_transport::DirectTransport). #[derive(Clone)] #[must_use = "Data producer will be closed on drop, make sure to keep it around for as long as needed"] pub struct DirectDataProducer { inner: Arc, } impl fmt::Debug for DirectDataProducer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DirectDataProducer") .field("id", &self.inner.id) .field("type", &self.inner.r#type) .field("sctp_stream_parameters", &self.inner.sctp_stream_parameters) .field("label", &self.inner.label) .field("protocol", &self.inner.protocol) .field("paused", &self.inner.paused) .field("transport", &self.inner.transport) .field("closed", &self.inner.closed) .finish() } } impl From for DataProducer { fn from(producer: DirectDataProducer) -> Self { DataProducer::Direct(producer) } } /// A data producer represents an endpoint capable of injecting data messages into a mediasoup /// [`Router`](crate::router::Router). /// /// A data producer can use [SCTP](https://tools.ietf.org/html/rfc4960) (AKA DataChannel) to deliver /// those messages, or can directly send them from the Rust application if the data producer was /// created on top of a [`DirectTransport`](crate::direct_transport::DirectTransport). #[derive(Clone)] #[non_exhaustive] #[must_use = "Data producer will be closed on drop, make sure to keep it around for as long as needed"] pub enum DataProducer { /// Data producer created on transport other than /// [`DirectTransport`](crate::direct_transport::DirectTransport). Regular(RegularDataProducer), /// Data producer created on [`DirectTransport`](crate::direct_transport::DirectTransport). Direct(DirectDataProducer), } impl fmt::Debug for DataProducer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self { DataProducer::Regular(producer) => f.debug_tuple("Regular").field(&producer).finish(), DataProducer::Direct(producer) => f.debug_tuple("Direct").field(&producer).finish(), } } } impl DataProducer { #[allow(clippy::too_many_arguments)] pub(super) fn new( id: DataProducerId, r#type: DataProducerType, sctp_stream_parameters: Option, label: String, protocol: String, paused: bool, executor: Arc>, channel: Channel, app_data: AppData, transport: Arc, direct: bool, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let inner_weak = Arc::>>>::default(); let on_transport_close_handler = transport.on_close({ let inner_weak = Arc::clone(&inner_weak); Box::new(move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.transport_close.call_simple(); inner.close(false); } }) }); let inner = Arc::new(Inner { id, r#type, sctp_stream_parameters, label, protocol, paused: AtomicBool::new(paused), direct, executor, channel, handlers, app_data, transport, closed: AtomicBool::new(false), _on_transport_close_handler: Mutex::new(on_transport_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); if direct { Self::Direct(DirectDataProducer { inner }) } else { Self::Regular(RegularDataProducer { inner }) } } /// Data producer identifier. #[must_use] pub fn id(&self) -> DataProducerId { self.inner().id } /// Transport to which data producer belongs. pub fn transport(&self) -> &Arc { &self.inner().transport } /// The type of the data producer. #[must_use] pub fn r#type(&self) -> DataProducerType { self.inner().r#type } /// The SCTP stream parameters (just if the data producer type is `Sctp`). #[must_use] pub fn sctp_stream_parameters(&self) -> Option { self.inner().sctp_stream_parameters } /// Whether the DataProducer is paused. #[must_use] pub fn paused(&self) -> bool { self.inner().paused.load(Ordering::SeqCst) } /// The data producer label. #[must_use] pub fn label(&self) -> &String { &self.inner().label } /// The data producer sub-protocol. #[must_use] pub fn protocol(&self) -> &String { &self.inner().protocol } /// Custom application data. #[must_use] pub fn app_data(&self) -> &AppData { &self.inner().app_data } /// Whether the data producer is closed. #[must_use] pub fn closed(&self) -> bool { self.inner().closed.load(Ordering::SeqCst) } /// Dump DataProducer. #[doc(hidden)] pub async fn dump(&self) -> Result { debug!("dump()"); let response = self .inner() .channel .request(self.id(), DataProducerDumpRequest {}) .await?; if let response::Body::DataProducerDumpResponse(data) = response { Ok(DataProducerDump::try_from_fbs(*data).expect("Error parsing dump response")) } else { panic!("Wrong message from worker: {response:?}"); } } /// Returns current statistics of the data producer. /// /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/) /// section for more details (TypeScript-oriented, but concepts apply here as well). pub async fn get_stats(&self) -> Result, RequestError> { debug!("get_stats()"); let response = self .inner() .channel .request(self.id(), DataProducerGetStatsRequest {}) .await?; if let response::Body::DataProducerGetStatsResponse(data) = response { Ok(vec![DataProducerStat::from_fbs(&data)]) } else { panic!("Wrong message from worker: {response:?}"); } } /// Pauses the data producer (no message is sent to its associated data consumers). /// Calls [`DataConsumer::on_data_producer_pause`](crate::data_consumer::DataConsumer::on_data_producer_pause) /// callback on all its associated data consumers. pub async fn pause(&self) -> Result<(), RequestError> { debug!("pause()"); self.inner() .channel .request(self.id(), DataProducerPauseRequest {}) .await?; let was_paused = self.inner().paused.swap(true, Ordering::SeqCst); if !was_paused { self.inner().handlers.pause.call_simple(); } Ok(()) } /// Resumes the data producer (messages are sent to its associated data consumers). /// Calls [`DataConsumer::on_data_producer_resume`](crate::data_consumer::DataConsumer::on_data_producer_resume) /// callback on all its associated data consumers. pub async fn resume(&self) -> Result<(), RequestError> { debug!("resume()"); self.inner() .channel .request(self.id(), DataProducerResumeRequest {}) .await?; let was_paused = self.inner().paused.swap(false, Ordering::SeqCst); if was_paused { self.inner().handlers.resume.call_simple(); } Ok(()) } /// Callback is called when the transport this data producer belongs to is closed for whatever /// reason. The producer itself is also closed. A `on_data_producer_close` callback is called on /// all its associated consumers. pub fn on_transport_close(&self, callback: F) -> HandlerId { self.inner() .handlers .transport_close .add(Box::new(callback)) } /// Callback is called when the data producer is paused. pub fn on_pause(&self, callback: F) -> HandlerId { self.inner().handlers.pause.add(Arc::new(callback)) } /// Callback is called when the data producer is resumed. pub fn on_resume(&self, callback: F) -> HandlerId { self.inner().handlers.resume.add(Arc::new(callback)) } /// Callback is called when the producer is closed for whatever reason. /// /// NOTE: Callback will be called in place if data producer is already closed. pub fn on_close(&self, callback: F) -> HandlerId { let handler_id = self.inner().handlers.close.add(Box::new(callback)); if self.inner().closed.load(Ordering::Relaxed) { self.inner().handlers.close.call_simple(); } handler_id } pub(super) fn close(&self) { self.inner().close(true); } /// Downgrade `DataProducer` to [`WeakDataProducer`] instance. #[must_use] pub fn downgrade(&self) -> WeakDataProducer { WeakDataProducer { inner: Arc::downgrade(self.inner()), } } fn inner(&self) -> &Arc { match self { DataProducer::Regular(data_producer) => &data_producer.inner, DataProducer::Direct(data_producer) => &data_producer.inner, } } } impl DirectDataProducer { /// Sends direct messages from the Rust to the worker. pub fn send( &self, message: WebRtcMessage<'_>, subchannels: Option>, required_subchannel: Option, ) -> Result<(), NotificationError> { let (ppid, payload) = message.into_ppid_and_payload(); self.inner.channel.notify( self.inner.id, DataProducerSendNotification { ppid, payload: payload.into_owned(), subchannels, required_subchannel, }, ) } } /// Same as [`DataProducer`], but will not be closed when dropped. /// /// Use [`NonClosingDataProducer::into_inner()`] method to get regular [`DataProducer`] instead and /// restore regular behavior of [`Drop`] implementation. pub struct NonClosingDataProducer { data_producer: DataProducer, on_drop: Option>, } impl fmt::Debug for NonClosingDataProducer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("NonClosingDataProducer") .field("data_producer", &self.data_producer) .finish() } } impl Drop for NonClosingDataProducer { fn drop(&mut self) { if let Some(on_drop) = self.on_drop.take() { on_drop(self.data_producer.clone()) } } } impl NonClosingDataProducer { /// * `on_drop` - Callback that takes last `Producer` instance and must do something with it to /// prevent dropping and thus closing pub(crate) fn new( data_producer: DataProducer, on_drop: F, ) -> Self { Self { data_producer, on_drop: Some(Box::new(on_drop)), } } /// Get inner [`DataProducer`] (which will close on drop in contrast to /// `NonClosingDataProducer`). pub fn into_inner(mut self) -> DataProducer { self.on_drop.take(); self.data_producer.clone() } } /// [`WeakDataProducer`] doesn't own data producer instance on mediasoup-worker and will not prevent /// one from being destroyed once last instance of regular [`DataProducer`] is dropped. /// /// [`WeakDataProducer`] vs [`DataProducer`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakDataProducer { inner: Weak, } impl fmt::Debug for WeakDataProducer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakDataProducer").finish() } } impl WeakDataProducer { /// Attempts to upgrade `WeakDataProducer` to [`DataProducer`] if last instance of one wasn't /// dropped yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; let data_producer = if inner.direct { DataProducer::Direct(DirectDataProducer { inner }) } else { DataProducer::Regular(RegularDataProducer { inner }) }; Some(data_producer) } } ================================================ FILE: rust/src/router/direct_transport/tests.rs ================================================ use crate::direct_transport::{DirectTransport, DirectTransportOptions}; use crate::router::{Router, RouterOptions}; use crate::transport::Transport; use crate::worker::WorkerSettings; use crate::worker_manager::WorkerManager; use futures_lite::future; use std::env; async fn init() -> (Router, DirectTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); let transport = router .create_direct_transport(DirectTransportOptions::default()) .await .expect("Failed to create transport1"); (router, transport) } #[test] fn router_close_event() { future::block_on(async move { let (router, transport) = init().await; let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_close(Box::new(move || { let _ = close_tx.send(()); })); let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_router_close(Box::new(move || { let _ = router_close_tx.send(()); })); router.close(); router_close_rx .await .expect("Failed to receive router_close event"); close_rx.await.expect("Failed to receive close event"); assert!(transport.closed()); }); } ================================================ FILE: rust/src/router/direct_transport.rs ================================================ #[cfg(test)] mod tests; use crate::consumer::{Consumer, ConsumerId, ConsumerOptions}; use crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType}; use crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType}; use crate::fbs::{FromFbs, TryFromFbs}; use crate::messages::{TransportCloseRequest, TransportSendRtcpNotification}; use crate::producer::{Producer, ProducerId, ProducerOptions}; use crate::router::transport::{TransportImpl, TransportType}; use crate::router::Router; use crate::transport::{ ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, RecvRtpHeaderExtensions, RtpListener, SctpListener, Transport, TransportGeneric, TransportId, TransportTraceEventData, TransportTraceEventType, }; use crate::worker::{ Channel, NotificationError, NotificationParseError, RequestError, SubscriptionHandler, }; use async_executor::Executor; use async_trait::async_trait; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::{direct_transport, notification, response, transport}; use mediasoup_types::data_structures::{AppData, SctpState}; use mediasoup_types::sctp_parameters::SctpParameters; use nohash_hasher::IntMap; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Weak}; /// [`DirectTransport`] options. #[derive(Debug, Clone)] #[non_exhaustive] pub struct DirectTransportOptions { /// Maximum allowed size for direct messages sent from DataProducers. /// Default 262_144. pub max_message_size: u32, /// Custom application data. pub app_data: AppData, } impl Default for DirectTransportOptions { fn default() -> Self { Self { max_message_size: 262_144, app_data: AppData::default(), } } } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct DirectTransportDump { // Common to all Transports. pub id: TransportId, pub direct: bool, pub producer_ids: Vec, pub consumer_ids: Vec, pub map_ssrc_consumer_id: Vec<(u32, ConsumerId)>, pub map_rtx_ssrc_consumer_id: Vec<(u32, ConsumerId)>, pub data_producer_ids: Vec, pub data_consumer_ids: Vec, pub recv_rtp_header_extensions: RecvRtpHeaderExtensions, pub rtp_listener: RtpListener, pub max_message_size: u32, pub sctp_parameters: Option, pub sctp_state: Option, pub sctp_listener: Option, pub trace_event_types: Vec, } impl<'a> TryFromFbs<'a> for DirectTransportDump { type FbsType = direct_transport::DumpResponse; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { Ok(Self { id: dump.base.id.parse()?, direct: true, producer_ids: dump .base .producer_ids .iter() .map(|producer_id| Ok(producer_id.parse()?)) .collect::>>()?, consumer_ids: dump .base .consumer_ids .iter() .map(|consumer_id| Ok(consumer_id.parse()?)) .collect::>>()?, map_ssrc_consumer_id: dump .base .map_ssrc_consumer_id .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, map_rtx_ssrc_consumer_id: dump .base .map_rtx_ssrc_consumer_id .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, data_producer_ids: dump .base .data_producer_ids .iter() .map(|data_producer_id| Ok(data_producer_id.parse()?)) .collect::>>()?, data_consumer_ids: dump .base .data_consumer_ids .iter() .map(|data_consumer_id| Ok(data_consumer_id.parse()?)) .collect::>>()?, recv_rtp_header_extensions: RecvRtpHeaderExtensions::from_fbs( dump.base.recv_rtp_header_extensions.as_ref(), ), rtp_listener: RtpListener::try_from_fbs(*dump.base.rtp_listener)?, max_message_size: dump.base.max_message_size, sctp_parameters: dump .base .sctp_parameters .as_ref() .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())), sctp_state: FromFbs::from_fbs(&dump.base.sctp_state), sctp_listener: dump.base.sctp_listener.as_ref().map(|listener| { SctpListener::try_from_fbs(listener.as_ref().clone()) .expect("Error parsing SctpListner") }), trace_event_types: FromFbs::from_fbs(&dump.base.trace_event_types), }) } } /// RTC statistics of the direct transport. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] #[allow(missing_docs)] pub struct DirectTransportStat { // Common to all Transports. pub transport_id: TransportId, pub timestamp: u64, pub sctp_state: Option, pub bytes_received: u64, pub recv_bitrate: u32, pub bytes_sent: u64, pub send_bitrate: u32, pub rtp_bytes_received: u64, pub rtp_recv_bitrate: u32, pub rtp_bytes_sent: u64, pub rtp_send_bitrate: u32, pub rtx_bytes_received: u64, pub rtx_recv_bitrate: u32, pub rtx_bytes_sent: u64, pub rtx_send_bitrate: u32, pub probation_bytes_sent: u64, pub probation_send_bitrate: u32, #[serde(skip_serializing_if = "Option::is_none")] pub available_outgoing_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub available_incoming_bitrate: Option, pub max_incoming_bitrate: Option, pub max_outgoing_bitrate: Option, pub min_outgoing_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rtp_packet_loss_received: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rtp_packet_loss_sent: Option, } impl<'a> TryFromFbs<'a> for DirectTransportStat { type FbsType = direct_transport::GetStatsResponse; type Error = Box; fn try_from_fbs(stats: Self::FbsType) -> Result { Ok(Self { transport_id: stats.base.transport_id.parse()?, timestamp: stats.base.timestamp, sctp_state: FromFbs::from_fbs(&stats.base.sctp_state), bytes_received: stats.base.bytes_received, recv_bitrate: stats.base.recv_bitrate, bytes_sent: stats.base.bytes_sent, send_bitrate: stats.base.send_bitrate, rtp_bytes_received: stats.base.rtp_bytes_received, rtp_recv_bitrate: stats.base.rtp_recv_bitrate, rtp_bytes_sent: stats.base.rtp_bytes_sent, rtp_send_bitrate: stats.base.rtp_send_bitrate, rtx_bytes_received: stats.base.rtx_bytes_received, rtx_recv_bitrate: stats.base.rtx_recv_bitrate, rtx_bytes_sent: stats.base.rtx_bytes_sent, rtx_send_bitrate: stats.base.rtx_send_bitrate, probation_bytes_sent: stats.base.probation_bytes_sent, probation_send_bitrate: stats.base.probation_send_bitrate, available_outgoing_bitrate: stats.base.available_outgoing_bitrate, available_incoming_bitrate: stats.base.available_incoming_bitrate, max_incoming_bitrate: stats.base.max_incoming_bitrate, max_outgoing_bitrate: stats.base.max_outgoing_bitrate, min_outgoing_bitrate: stats.base.min_outgoing_bitrate, rtp_packet_loss_received: stats.base.rtp_packet_loss_received, rtp_packet_loss_sent: stats.base.rtp_packet_loss_sent, }) } } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { rtcp: Bag>, new_producer: Bag, Producer>, new_consumer: Bag, Consumer>, new_data_producer: Bag, DataProducer>, new_data_consumer: Bag, DataConsumer>, trace: Bag, TransportTraceEventData>, router_close: BagOnce>, close: BagOnce>, } #[derive(Debug, Deserialize)] #[serde(tag = "event", rename_all = "lowercase", content = "data")] enum Notification { Trace(TransportTraceEventData), Rtcp(Vec), } impl<'a> TryFromFbs<'a> for Notification { type FbsType = notification::NotificationRef<'a>; type Error = NotificationParseError; fn try_from_fbs(notification: Self::FbsType) -> Result { match notification.event().unwrap() { notification::Event::TransportTrace => { let Ok(Some(notification::BodyRef::TransportTraceNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let trace_notification_fbs = transport::TraceNotification::try_from(body).unwrap(); let trace_notification = TransportTraceEventData::from_fbs(&trace_notification_fbs); Ok(Notification::Trace(trace_notification)) } notification::Event::DirecttransportRtcp => { let Ok(Some(notification::BodyRef::DirectTransportRtcpNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let rtcp_notification_fbs = direct_transport::RtcpNotification::try_from(body).unwrap(); Ok(Notification::Rtcp(rtcp_notification_fbs.data)) } _ => Err(NotificationParseError::InvalidEvent), } } } struct Inner { id: TransportId, next_mid_for_consumers: AtomicUsize, used_sctp_stream_ids: Mutex>, cname_for_producers: Mutex>, executor: Arc>, channel: Channel, handlers: Arc, app_data: AppData, // Make sure router is not dropped until this transport is not dropped router: Router, closed: AtomicBool, // Drop subscription to transport-specific notifications when transport itself is dropped _subscription_handlers: Mutex>>, _on_router_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let router_id = self.router.id(); let request = TransportCloseRequest { transport_id: self.id, }; self.executor .spawn(async move { match channel.request(router_id, request).await { Err(RequestError::ChannelClosed) => { debug!("transport closing failed on drop: Channel already closed"); } Err(error) => { error!("transport closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// A direct transport represents a direct connection between the mediasoup Rust process and a /// [`Router`] instance in a mediasoup-worker thread. /// /// A direct transport can be used to directly send and receive data messages from/to Rust by means /// of [`DataProducer`]s and [`DataConsumer`]s of type `Direct` created on a direct transport. /// Direct messages sent by a [`DataProducer`] in a direct transport can be consumed by endpoints /// connected through a SCTP capable transport ([`WebRtcTransport`](crate::webrtc_transport::WebRtcTransport), /// [`PlainTransport`](crate::plain_transport::PlainTransport), [`PipeTransport`](crate::pipe_transport::PipeTransport) /// and also by the Rust application by means of a [`DataConsumer`] created on a [`DirectTransport`] /// (and vice-versa: messages sent over SCTP/DataChannel can be consumed by the Rust application by /// means of a [`DataConsumer`] created on a [`DirectTransport`]). /// /// A direct transport can also be used to inject and directly consume RTP and RTCP packets in Rust /// by using the [`DirectProducer::send`](crate::producer::DirectProducer::send) and /// [`Consumer::on_rtp`] API (plus [`DirectTransport::send_rtcp`] and [`DirectTransport::on_rtcp`] /// API). #[derive(Clone)] #[must_use = "Transport will be closed on drop, make sure to keep it around for as long as needed"] pub struct DirectTransport { inner: Arc, } impl fmt::Debug for DirectTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DirectTransport") .field("id", &self.inner.id) .field("next_mid_for_consumers", &self.inner.next_mid_for_consumers) .field("used_sctp_stream_ids", &self.inner.used_sctp_stream_ids) .field("cname_for_producers", &self.inner.cname_for_producers) .field("router", &self.inner.router) .field("closed", &self.inner.closed) .finish() } } #[async_trait] impl Transport for DirectTransport { fn id(&self) -> TransportId { self.inner.id } fn router(&self) -> &Router { &self.inner.router } fn app_data(&self) -> &AppData { &self.inner.app_data } fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } async fn produce(&self, producer_options: ProducerOptions) -> Result { debug!("produce()"); let producer = self .produce_impl(producer_options, TransportType::Direct) .await?; self.inner.handlers.new_producer.call_simple(&producer); Ok(producer) } async fn consume(&self, consumer_options: ConsumerOptions) -> Result { debug!("consume()"); let consumer = self .consume_impl(consumer_options, TransportType::Direct, false) .await?; self.inner.handlers.new_consumer.call_simple(&consumer); Ok(consumer) } async fn produce_data( &self, data_producer_options: DataProducerOptions, ) -> Result { debug!("produce_data()"); let data_producer = self .produce_data_impl( DataProducerType::Direct, data_producer_options, TransportType::Direct, ) .await?; self.inner .handlers .new_data_producer .call_simple(&data_producer); Ok(data_producer) } async fn consume_data( &self, data_consumer_options: DataConsumerOptions, ) -> Result { debug!("consume_data()"); let data_consumer = self .consume_data_impl( DataConsumerType::Direct, data_consumer_options, TransportType::Direct, ) .await?; self.inner .handlers .new_data_consumer .call_simple(&data_consumer); Ok(data_consumer) } async fn enable_trace_event( &self, types: Vec, ) -> Result<(), RequestError> { debug!("enable_trace_event()"); self.enable_trace_event_impl(types).await } fn on_new_producer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_producer.add(callback) } fn on_new_consumer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_consumer.add(callback) } fn on_new_data_producer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_data_producer.add(callback) } fn on_new_data_consumer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_data_consumer.add(callback) } fn on_trace( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.trace.add(callback) } fn on_router_close(&self, callback: Box) -> HandlerId { self.inner.handlers.router_close.add(callback) } fn on_close(&self, callback: Box) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } } #[async_trait] impl TransportGeneric for DirectTransport { type Dump = DirectTransportDump; type Stat = DirectTransportStat; /// Dump Transport. #[doc(hidden)] async fn dump(&self) -> Result { debug!("dump()"); let response = self.dump_impl().await?; if let response::Body::DirectTransportDumpResponse(data) = response { Ok(DirectTransportDump::try_from_fbs(*data) .expect("Error parsing dump response: {response:?}")) } else { panic!("Wrong message from worker: {response:?}"); } } /// Returns current RTC statistics of the transport. /// /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/) /// section for more details (TypeScript-oriented, but concepts apply here as well). async fn get_stats(&self) -> Result, RequestError> { debug!("get_stats()"); let response = self.get_stats_impl().await?; if let response::Body::DirectTransportGetStatsResponse(data) = response { Ok(vec![DirectTransportStat::try_from_fbs(*data) .expect("Error parsing dump response: {response:?}")]) } else { panic!("Wrong message from worker: {response:?}"); } } } impl TransportImpl for DirectTransport { fn channel(&self) -> &Channel { &self.inner.channel } fn executor(&self) -> &Arc> { &self.inner.executor } fn next_mid_for_consumers(&self) -> &AtomicUsize { &self.inner.next_mid_for_consumers } fn used_sctp_stream_ids(&self) -> &Mutex> { &self.inner.used_sctp_stream_ids } fn cname_for_producers(&self) -> &Mutex> { &self.inner.cname_for_producers } } impl DirectTransport { pub(super) fn new( id: TransportId, executor: Arc>, channel: Channel, app_data: AppData, router: Router, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let subscription_handler = { let handlers = Arc::clone(&handlers); channel.subscribe_to_notifications(id.into(), move |notification| { match Notification::try_from_fbs(notification) { Ok(notification) => match notification { Notification::Trace(trace_event_data) => { handlers.trace.call_simple(&trace_event_data); } Notification::Rtcp(data) => { handlers.rtcp.call(|callback| { callback(&data); }); } }, Err(error) => { error!("Failed to parse notification: {}", error); } } }) }; let next_mid_for_consumers = AtomicUsize::default(); let used_sctp_stream_ids = Mutex::new(IntMap::default()); let cname_for_producers = Mutex::new(None); let inner_weak = Arc::>>>::default(); let on_router_close_handler = router.on_close({ let inner_weak = Arc::clone(&inner_weak); move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.router_close.call_simple(); inner.close(false); } } }); let inner = Arc::new(Inner { id, next_mid_for_consumers, used_sctp_stream_ids, cname_for_producers, executor, channel, handlers, app_data, router, closed: AtomicBool::new(false), _subscription_handlers: Mutex::new(vec![subscription_handler]), _on_router_close_handler: Mutex::new(on_router_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); Self { inner } } /// Send a RTCP packet from the Rust process. /// /// * `rtcp_packet` - Bytes containing a valid RTCP packet (can be a compound packet). pub fn send_rtcp(&self, rtcp_packet: Vec) -> Result<(), NotificationError> { self.inner .channel .notify(self.id(), TransportSendRtcpNotification { rtcp_packet }) } /// Callback is called when the direct transport receives a RTCP packet from its router. pub fn on_rtcp(&self, callback: F) -> HandlerId { self.inner.handlers.rtcp.add(Arc::new(callback)) } /// Downgrade `DirectTransport` to [`WeakDirectTransport`] instance. #[must_use] pub fn downgrade(&self) -> WeakDirectTransport { WeakDirectTransport { inner: Arc::downgrade(&self.inner), } } } /// [`WeakDirectTransport`] doesn't own direct transport instance on mediasoup-worker and will not /// prevent one from being destroyed once last instance of regular [`DirectTransport`] is dropped. /// /// [`WeakDirectTransport`] vs [`DirectTransport`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakDirectTransport { inner: Weak, } impl fmt::Debug for WeakDirectTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakDirectTransport").finish() } } impl WeakDirectTransport { /// Attempts to upgrade `WeakDirectTransport` to [`DirectTransport`] if last instance of one /// wasn't dropped yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; Some(DirectTransport { inner }) } } ================================================ FILE: rust/src/router/pipe_transport/tests.rs ================================================ use crate::consumer::ConsumerOptions; use crate::data_consumer::DataConsumerOptions; use crate::data_producer::DataProducerOptions; use crate::producer::ProducerOptions; use crate::router::{ PipeDataProducerToRouterPair, PipeProducerToRouterPair, PipeToRouterOptions, Router, RouterOptions, }; use crate::transport::Transport; use crate::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use crate::worker::WorkerSettings; use crate::worker_manager::WorkerManager; use futures_lite::future; use mediasoup_types::data_structures::{ListenInfo, Protocol}; use mediasoup_types::rtp_parameters::{ MediaKind, MimeTypeVideo, RtpCapabilities, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters, RtpParameters, }; use mediasoup_types::sctp_parameters::SctpStreamParameters; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::num::NonZeroU32; fn media_codecs() -> Vec { vec![RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }] } fn video_producer_options() -> ProducerOptions { ProducerOptions::new( MediaKind::Video, RtpParameters { mid: Some("VIDEO".to_string()), codecs: vec![RtpCodecParameters::Video { mime_type: MimeTypeVideo::Vp8, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }], ..RtpParameters::default() }, ) } fn data_producer_options() -> DataProducerOptions { DataProducerOptions::new_sctp(SctpStreamParameters::new_unordered_with_life_time( 666, 5000, )) } fn consumer_device_capabilities() -> RtpCapabilities { RtpCapabilities { codecs: vec![RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: Some(101), clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }], header_extensions: vec![], } } async fn init() -> (Router, Router, WebRtcTransport, WebRtcTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker1 = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let worker2 = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router1 = worker1 .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let router2 = worker2 .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let mut transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); transport_options.enable_sctp = true; let transport_1 = router1 .create_webrtc_transport(transport_options.clone()) .await .expect("Failed to create transport1"); let transport_2 = router2 .create_webrtc_transport(transport_options) .await .expect("Failed to create transport2"); (router1, router2, transport_1, transport_2) } #[test] fn producer_close_is_transmitted_to_pipe_consumer() { future::block_on(async move { let (router1, router2, transport1, transport2) = init().await; let video_producer = transport1 .produce(video_producer_options()) .await .expect("Failed to produce video"); let PipeProducerToRouterPair { pipe_producer, pipe_consumer: _, } = router1 .pipe_producer_to_router( video_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .expect("Failed to pipe video producer to router"); let pipe_producer = pipe_producer.into_inner(); let video_consumer = transport2 .consume(ConsumerOptions::new( pipe_producer.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume video"); let (mut producer_close_tx, producer_close_rx) = async_oneshot::oneshot::<()>(); let _handler = video_consumer.on_producer_close(move || { let _ = producer_close_tx.send(()); }); drop(video_producer); producer_close_rx .await .expect("Failed to receive producer close event"); }); } #[test] fn data_producer_close_is_transmitted_to_pipe_data_consumer() { future::block_on(async move { let (router1, router2, transport1, transport2) = init().await; let data_producer = transport1 .produce_data(data_producer_options()) .await .expect("Failed to produce data"); let PipeDataProducerToRouterPair { pipe_data_producer, pipe_data_consumer: _, } = router1 .pipe_data_producer_to_router( data_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .expect("Failed to pipe data producer to router"); let pipe_data_producer = pipe_data_producer.into_inner(); let data_consumer = transport2 .consume_data(DataConsumerOptions::new_sctp(pipe_data_producer.id())) .await .expect("Failed to create data consumer"); let (mut data_producer_close_tx, data_producer_close_rx) = async_oneshot::oneshot::<()>(); let _handler = data_consumer.on_data_producer_close(move || { let _ = data_producer_close_tx.send(()); }); data_producer.close(); data_producer_close_rx .await .expect("Failed to receive data producer close event"); }); } ================================================ FILE: rust/src/router/pipe_transport.rs ================================================ #[cfg(test)] mod tests; use crate::consumer::{Consumer, ConsumerId, ConsumerOptions}; use crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType}; use crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType}; use crate::fbs::{FromFbs, TryFromFbs}; use crate::messages::{PipeTransportConnectRequest, PipeTransportData, TransportCloseRequest}; use crate::producer::{Producer, ProducerId, ProducerOptions}; use crate::router::transport::{TransportImpl, TransportType}; use crate::router::Router; use crate::transport::{ ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, RecvRtpHeaderExtensions, RtpListener, SctpListener, Transport, TransportGeneric, TransportId, TransportTraceEventData, TransportTraceEventType, }; use crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler}; use async_executor::Executor; use async_trait::async_trait; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::{notification, pipe_transport, response, transport}; use mediasoup_types::data_structures::{AppData, ListenInfo, SctpState, TransportTuple}; use mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters}; use mediasoup_types::srtp_parameters::SrtpParameters; use nohash_hasher::IntMap; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt; use std::net::IpAddr; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Weak}; /// [`PipeTransport`] options. #[derive(Debug, Clone)] #[non_exhaustive] pub struct PipeTransportOptions { /// Listening info. pub listen_info: ListenInfo, /// Create a SCTP association. /// Default false. pub enable_sctp: bool, /// SCTP streams number. pub num_sctp_streams: NumSctpStreams, /// Maximum allowed size for SCTP messages sent by DataProducers. /// Default 268_435_456. pub max_sctp_message_size: u32, /// Maximum SCTP send buffer used by DataConsumers. /// Default 268_435_456. pub sctp_send_buffer_size: u32, /// Enable RTX and NACK for RTP retransmission. Useful if both Routers are located in different /// hosts and there is packet lost in the link. For this to work, both PipeTransports must /// enable this setting. /// Default false. pub enable_rtx: bool, /// Enable SRTP. Useful to protect the RTP and RTCP traffic if both Routers are located in /// different hosts. For this to work, connect() must be called with remote SRTP parameters. /// Default false. pub enable_srtp: bool, /// Custom application data. pub app_data: AppData, } impl PipeTransportOptions { /// Create Pipe transport options with given listen IP. #[must_use] pub fn new(listen_info: ListenInfo) -> Self { Self { listen_info, enable_sctp: false, num_sctp_streams: NumSctpStreams::default(), max_sctp_message_size: 268_435_456, sctp_send_buffer_size: 268_435_456, enable_rtx: false, enable_srtp: false, app_data: AppData::default(), } } } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct PipeTransportDump { // Common to all Transports. pub id: TransportId, pub direct: bool, pub producer_ids: Vec, pub consumer_ids: Vec, pub map_ssrc_consumer_id: IntMap, pub map_rtx_ssrc_consumer_id: IntMap, pub data_producer_ids: Vec, pub data_consumer_ids: Vec, pub recv_rtp_header_extensions: RecvRtpHeaderExtensions, pub rtp_listener: RtpListener, pub max_message_size: u32, pub sctp_parameters: Option, pub sctp_state: Option, pub sctp_listener: Option, pub trace_event_types: Vec, // PipeTransport specific. pub tuple: TransportTuple, pub rtx: bool, pub srtp_parameters: Option, } impl<'a> TryFromFbs<'a> for PipeTransportDump { type FbsType = pipe_transport::DumpResponse; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { Ok(Self { // Common to all Transports. id: dump.base.id.parse()?, direct: false, producer_ids: dump .base .producer_ids .iter() .map(|producer_id| Ok(producer_id.parse()?)) .collect::>>()?, consumer_ids: dump .base .consumer_ids .iter() .map(|consumer_id| Ok(consumer_id.parse()?)) .collect::>>()?, map_ssrc_consumer_id: dump .base .map_ssrc_consumer_id .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, map_rtx_ssrc_consumer_id: dump .base .map_rtx_ssrc_consumer_id .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, data_producer_ids: dump .base .data_producer_ids .iter() .map(|data_producer_id| Ok(data_producer_id.parse()?)) .collect::>>()?, data_consumer_ids: dump .base .data_consumer_ids .iter() .map(|data_consumer_id| Ok(data_consumer_id.parse()?)) .collect::>>()?, recv_rtp_header_extensions: RecvRtpHeaderExtensions::from_fbs( dump.base.recv_rtp_header_extensions.as_ref(), ), rtp_listener: RtpListener::try_from_fbs(*dump.base.rtp_listener)?, max_message_size: dump.base.max_message_size, sctp_parameters: dump .base .sctp_parameters .as_ref() .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())), sctp_state: FromFbs::from_fbs(&dump.base.sctp_state), sctp_listener: dump.base.sctp_listener.as_ref().map(|listener| { SctpListener::try_from_fbs(listener.as_ref().clone()) .expect("Error parsing SctpListner") }), trace_event_types: FromFbs::from_fbs(&dump.base.trace_event_types), // PipeTransport specific. tuple: TransportTuple::from_fbs(&dump.tuple), rtx: dump.rtx, srtp_parameters: dump .srtp_parameters .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())), }) } } /// RTC statistics of the pipe transport. #[derive(Debug, Clone, PartialOrd, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] #[allow(missing_docs)] pub struct PipeTransportStat { // Common to all Transports. // `type` field is present in worker, but ignored here pub transport_id: TransportId, pub timestamp: u64, pub sctp_state: Option, pub bytes_received: u64, pub recv_bitrate: u32, pub bytes_sent: u64, pub send_bitrate: u32, pub rtp_bytes_received: u64, pub rtp_recv_bitrate: u32, pub rtp_bytes_sent: u64, pub rtp_send_bitrate: u32, pub rtx_bytes_received: u64, pub rtx_recv_bitrate: u32, pub rtx_bytes_sent: u64, pub rtx_send_bitrate: u32, pub probation_bytes_sent: u64, pub probation_send_bitrate: u32, #[serde(skip_serializing_if = "Option::is_none")] pub available_outgoing_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub available_incoming_bitrate: Option, pub max_incoming_bitrate: Option, pub max_outgoing_bitrate: Option, pub min_outgoing_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rtp_packet_loss_received: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rtp_packet_loss_sent: Option, // PipeTransport specific. pub tuple: TransportTuple, } impl<'a> TryFromFbs<'a> for PipeTransportStat { type FbsType = pipe_transport::GetStatsResponse; type Error = Box; fn try_from_fbs(stats: Self::FbsType) -> Result { Ok(Self { transport_id: stats.base.transport_id.parse()?, timestamp: stats.base.timestamp, sctp_state: FromFbs::from_fbs(&stats.base.sctp_state), bytes_received: stats.base.bytes_received, recv_bitrate: stats.base.recv_bitrate, bytes_sent: stats.base.bytes_sent, send_bitrate: stats.base.send_bitrate, rtp_bytes_received: stats.base.rtp_bytes_received, rtp_recv_bitrate: stats.base.rtp_recv_bitrate, rtp_bytes_sent: stats.base.rtp_bytes_sent, rtp_send_bitrate: stats.base.rtp_send_bitrate, rtx_bytes_received: stats.base.rtx_bytes_received, rtx_recv_bitrate: stats.base.rtx_recv_bitrate, rtx_bytes_sent: stats.base.rtx_bytes_sent, rtx_send_bitrate: stats.base.rtx_send_bitrate, probation_bytes_sent: stats.base.probation_bytes_sent, probation_send_bitrate: stats.base.probation_send_bitrate, available_outgoing_bitrate: stats.base.available_outgoing_bitrate, available_incoming_bitrate: stats.base.available_incoming_bitrate, max_incoming_bitrate: stats.base.max_incoming_bitrate, max_outgoing_bitrate: stats.base.max_outgoing_bitrate, min_outgoing_bitrate: stats.base.min_outgoing_bitrate, rtp_packet_loss_received: stats.base.rtp_packet_loss_received, rtp_packet_loss_sent: stats.base.rtp_packet_loss_sent, // PlainTransport specific. tuple: TransportTuple::from_fbs(stats.tuple.as_ref()), }) } } /// Remote parameters for pipe transport. #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PipeTransportRemoteParameters { /// Remote IPv4 or IPv6. pub ip: IpAddr, /// Remote port. pub port: u16, /// SRTP parameters used by the paired `PipeTransport` to encrypt its RTP and RTCP. pub srtp_parameters: Option, } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { new_producer: Bag, Producer>, new_consumer: Bag, Consumer>, new_data_producer: Bag, DataProducer>, new_data_consumer: Bag, DataConsumer>, tuple: Bag, TransportTuple>, sctp_state_change: Bag>, trace: Bag, TransportTraceEventData>, router_close: BagOnce>, close: BagOnce>, } #[derive(Debug, Deserialize)] #[serde(tag = "event", rename_all = "lowercase", content = "data")] enum Notification { #[serde(rename_all = "camelCase")] SctpStateChange { sctp_state: SctpState, }, Trace(TransportTraceEventData), } impl<'a> TryFromFbs<'a> for Notification { type FbsType = notification::NotificationRef<'a>; type Error = NotificationParseError; fn try_from_fbs(notification: Self::FbsType) -> Result { match notification.event().unwrap() { notification::Event::TransportSctpStateChange => { let Ok(Some(notification::BodyRef::TransportSctpStateChangeNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let sctp_state = SctpState::from_fbs(&body.sctp_state().unwrap()); Ok(Notification::SctpStateChange { sctp_state }) } notification::Event::TransportTrace => { let Ok(Some(notification::BodyRef::TransportTraceNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let trace_notification_fbs = transport::TraceNotification::try_from(body).unwrap(); let trace_notification = TransportTraceEventData::from_fbs(&trace_notification_fbs); Ok(Notification::Trace(trace_notification)) } _ => Err(NotificationParseError::InvalidEvent), } } } struct Inner { id: TransportId, next_mid_for_consumers: AtomicUsize, used_sctp_stream_ids: Mutex>, cname_for_producers: Mutex>, executor: Arc>, channel: Channel, handlers: Arc, data: Arc, app_data: AppData, // Make sure router is not dropped until this transport is not dropped router: Router, closed: AtomicBool, // Drop subscription to transport-specific notifications when transport itself is dropped _subscription_handler: Mutex>, _on_router_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let router_id = self.router.id(); let request = TransportCloseRequest { transport_id: self.id, }; self.executor .spawn(async move { match channel.request(router_id, request).await { Err(RequestError::ChannelClosed) => { debug!("transport closing failed on drop: Channel already closed"); } Err(error) => { error!("transport closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// A pipe transport represents a network path through which RTP, RTCP (optionally secured with /// SRTP) and SCTP (DataChannel) is transmitted. Pipe transports are intended to intercommunicate /// two [`Router`] instances collocated on the same host or on separate hosts. /// /// # Notes on usage /// When calling [`PipeTransport::consume`], all RTP streams of the [`Producer`] are transmitted /// verbatim (in contrast to what happens in [`WebRtcTransport`](crate::webrtc_transport::WebRtcTransport) /// and [`PlainTransport`](crate::plain_transport::PlainTransport) in which a single and continuous /// RTP stream is sent to the consuming endpoint). #[derive(Clone)] #[must_use = "Transport will be closed on drop, make sure to keep it around for as long as needed"] pub struct PipeTransport { inner: Arc, } impl fmt::Debug for PipeTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PipeTransport") .field("id", &self.inner.id) .field("next_mid_for_consumers", &self.inner.next_mid_for_consumers) .field("used_sctp_stream_ids", &self.inner.used_sctp_stream_ids) .field("cname_for_producers", &self.inner.cname_for_producers) .field("router", &self.inner.router) .field("closed", &self.inner.closed) .finish() } } #[async_trait] impl Transport for PipeTransport { fn id(&self) -> TransportId { self.inner.id } fn router(&self) -> &Router { &self.inner.router } fn app_data(&self) -> &AppData { &self.inner.app_data } fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } async fn produce(&self, producer_options: ProducerOptions) -> Result { debug!("produce()"); let producer = self .produce_impl(producer_options, TransportType::Pipe) .await?; self.inner.handlers.new_producer.call_simple(&producer); Ok(producer) } async fn consume(&self, consumer_options: ConsumerOptions) -> Result { debug!("consume()"); let consumer = self .consume_impl(consumer_options, TransportType::Pipe, self.inner.data.rtx) .await?; self.inner.handlers.new_consumer.call_simple(&consumer); Ok(consumer) } async fn produce_data( &self, data_producer_options: DataProducerOptions, ) -> Result { debug!("produce_data()"); let data_producer = self .produce_data_impl( DataProducerType::Sctp, data_producer_options, TransportType::Pipe, ) .await?; self.inner .handlers .new_data_producer .call_simple(&data_producer); Ok(data_producer) } async fn consume_data( &self, data_consumer_options: DataConsumerOptions, ) -> Result { debug!("consume_data()"); let data_consumer = self .consume_data_impl( DataConsumerType::Sctp, data_consumer_options, TransportType::Pipe, ) .await?; self.inner .handlers .new_data_consumer .call_simple(&data_consumer); Ok(data_consumer) } async fn enable_trace_event( &self, types: Vec, ) -> Result<(), RequestError> { debug!("enable_trace_event()"); self.enable_trace_event_impl(types).await } fn on_new_producer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_producer.add(callback) } fn on_new_consumer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_consumer.add(callback) } fn on_new_data_producer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_data_producer.add(callback) } fn on_new_data_consumer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_data_consumer.add(callback) } fn on_trace( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.trace.add(callback) } fn on_router_close(&self, callback: Box) -> HandlerId { self.inner.handlers.router_close.add(callback) } fn on_close(&self, callback: Box) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } } #[async_trait] impl TransportGeneric for PipeTransport { type Dump = PipeTransportDump; type Stat = PipeTransportStat; #[doc(hidden)] async fn dump(&self) -> Result { debug!("dump()"); let response = self.dump_impl().await?; if let response::Body::PipeTransportDumpResponse(data) = response { Ok(PipeTransportDump::try_from_fbs(*data) .expect("Error parsing dump response: {response:?}")) } else { panic!("Wrong message from worker: {response:?}"); } } async fn get_stats(&self) -> Result, RequestError> { debug!("get_stats()"); let response = self.get_stats_impl().await?; if let response::Body::PipeTransportGetStatsResponse(data) = response { Ok(vec![PipeTransportStat::try_from_fbs(*data) .expect("Error parsing dump response: {response:?}")]) } else { panic!("Wrong message from worker: {response:?}"); } } } impl TransportImpl for PipeTransport { fn channel(&self) -> &Channel { &self.inner.channel } fn executor(&self) -> &Arc> { &self.inner.executor } fn next_mid_for_consumers(&self) -> &AtomicUsize { &self.inner.next_mid_for_consumers } fn used_sctp_stream_ids(&self) -> &Mutex> { &self.inner.used_sctp_stream_ids } fn cname_for_producers(&self) -> &Mutex> { &self.inner.cname_for_producers } } impl PipeTransport { pub(super) fn new( id: TransportId, executor: Arc>, channel: Channel, data: PipeTransportData, app_data: AppData, router: Router, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let data = Arc::new(data); let subscription_handler = { let handlers = Arc::clone(&handlers); let data = Arc::clone(&data); channel.subscribe_to_notifications(id.into(), move |notification| { match Notification::try_from_fbs(notification) { Ok(notification) => match notification { Notification::SctpStateChange { sctp_state } => { data.sctp_state.lock().replace(sctp_state); handlers.sctp_state_change.call(|callback| { callback(sctp_state); }); } Notification::Trace(trace_event_data) => { handlers.trace.call_simple(&trace_event_data); } }, Err(error) => { error!("Failed to parse notification: {}", error); } } }) }; let next_mid_for_consumers = AtomicUsize::default(); let used_sctp_stream_ids = Mutex::new({ let mut used_used_sctp_stream_ids = IntMap::default(); if let Some(sctp_parameters) = &data.sctp_parameters { for i in 0..sctp_parameters.mis { used_used_sctp_stream_ids.insert(i, false); } } used_used_sctp_stream_ids }); let cname_for_producers = Mutex::new(None); let inner_weak = Arc::>>>::default(); let on_router_close_handler = router.on_close({ let inner_weak = Arc::clone(&inner_weak); move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.router_close.call_simple(); inner.close(false); } } }); let inner = Arc::new(Inner { id, next_mid_for_consumers, used_sctp_stream_ids, cname_for_producers, executor, channel, handlers, data, app_data, router, closed: AtomicBool::new(false), _subscription_handler: Mutex::new(subscription_handler), _on_router_close_handler: Mutex::new(on_router_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); Self { inner } } /// Provide the [`PipeTransport`] with remote parameters. pub async fn connect( &self, remote_parameters: PipeTransportRemoteParameters, ) -> Result<(), RequestError> { debug!("connect()"); let response = self .inner .channel .request( self.id(), PipeTransportConnectRequest { ip: remote_parameters.ip, port: remote_parameters.port, srtp_parameters: remote_parameters.srtp_parameters, }, ) .await?; *self.inner.data.tuple.lock() = response.tuple; Ok(()) } /// Set maximum incoming bitrate for media streams sent by the remote endpoint over this /// transport. pub async fn set_max_incoming_bitrate(&self, bitrate: u32) -> Result<(), RequestError> { debug!("set_max_incoming_bitrate() [bitrate:{}]", bitrate); self.set_max_incoming_bitrate_impl(bitrate).await } /// The transport tuple. It refers to both RTP and RTCP since pipe transports use RTCP-mux by /// design. /// /// # Notes on usage /// * Once the pipe transport is created, `transport.tuple()` will contain information about /// its `local_address`, `local_port` and `protocol`. /// * Information about `remote_ip` and `remote_port` will be set after calling `connect()` /// method. #[must_use] pub fn tuple(&self) -> TransportTuple { self.inner.data.tuple.lock().clone() } /// Local SCTP parameters. Or `None` if SCTP is not enabled. #[must_use] pub fn sctp_parameters(&self) -> Option { self.inner.data.sctp_parameters } /// Current SCTP state. Or `None` if SCTP is not enabled. #[must_use] pub fn sctp_state(&self) -> Option { *self.inner.data.sctp_state.lock() } /// Local SRTP parameters representing the crypto suite and key material used to encrypt sending /// RTP and SRTP. Those parameters must be given to the paired `PipeTransport` in the /// `connect()` method. #[must_use] pub fn srtp_parameters(&self) -> Option { self.inner.data.srtp_parameters.lock().clone() } /// Callback is called after the remote RTP origin has been discovered. Only if `comedia` mode /// was set. pub fn on_tuple( &self, callback: F, ) -> HandlerId { self.inner.handlers.tuple.add(Arc::new(callback)) } /// Callback is called when the transport SCTP state changes. pub fn on_sctp_state_change( &self, callback: F, ) -> HandlerId { self.inner .handlers .sctp_state_change .add(Arc::new(callback)) } /// Downgrade `PipeTransport` to [`WeakPipeTransport`] instance. #[must_use] pub fn downgrade(&self) -> WeakPipeTransport { WeakPipeTransport { inner: Arc::downgrade(&self.inner), } } } /// [`WeakPipeTransport`] doesn't own pipe transport instance on mediasoup-worker and will not /// prevent one from being destroyed once last instance of regular [`PipeTransport`] is dropped. /// /// [`WeakPipeTransport`] vs [`PipeTransport`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakPipeTransport { inner: Weak, } impl fmt::Debug for WeakPipeTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakPipeTransport").finish() } } impl WeakPipeTransport { /// Attempts to upgrade `WeakPipeTransport` to [`PipeTransport`] if last instance of one /// wasn't dropped yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; Some(PipeTransport { inner }) } } ================================================ FILE: rust/src/router/plain_transport/tests.rs ================================================ use crate::plain_transport::PlainTransportOptions; use crate::router::{Router, RouterOptions}; use crate::transport::Transport; use crate::worker::WorkerSettings; use crate::worker_manager::WorkerManager; use futures_lite::future; use mediasoup_types::data_structures::{ListenInfo, Protocol}; use std::env; use std::net::{IpAddr, Ipv4Addr}; async fn init() -> Router { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); worker .create_router(RouterOptions::default()) .await .expect("Failed to create router") } #[test] fn router_close_event() { future::block_on(async move { let router = init().await; let transport = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("4.4.4.4".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.rtcp_mux = false; plain_transport_options }) .await .expect("Failed to create Plain transport"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_close(Box::new(move || { let _ = close_tx.send(()); })); let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_router_close(Box::new(move || { let _ = router_close_tx.send(()); })); router.close(); router_close_rx .await .expect("Failed to receive router_close event"); close_rx.await.expect("Failed to receive close event"); assert!(transport.closed()); }); } ================================================ FILE: rust/src/router/plain_transport.rs ================================================ #[cfg(test)] mod tests; use crate::consumer::{Consumer, ConsumerId, ConsumerOptions}; use crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType}; use crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType}; use crate::fbs::{FromFbs, TryFromFbs}; use crate::messages::{PlainTransportData, TransportCloseRequest, TransportConnectPlainRequest}; use crate::producer::{Producer, ProducerId, ProducerOptions}; use crate::router::transport::{TransportImpl, TransportType}; use crate::router::Router; use crate::transport::{ ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, RecvRtpHeaderExtensions, RtpListener, SctpListener, Transport, TransportGeneric, TransportId, TransportTraceEventData, TransportTraceEventType, }; use crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler}; use async_executor::Executor; use async_trait::async_trait; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::{notification, plain_transport, response, transport}; use mediasoup_types::data_structures::{AppData, ListenInfo, SctpState, TransportTuple}; use mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters}; use mediasoup_types::srtp_parameters::{SrtpCryptoSuite, SrtpParameters}; use nohash_hasher::IntMap; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt; use std::net::IpAddr; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Weak}; /// [`PlainTransport`] options. /// /// # Notes on usage /// * Note that `comedia` mode just makes sense when the remote endpoint is gonna produce RTP on /// this plain transport. Otherwise, if the remote endpoint does not send any RTP (or SCTP) packet /// to mediasoup, there is no way to detect its remote RTP IP and port, so the endpoint won't /// receive any packet from mediasoup. /// * In other words, do not use `comedia` mode if the remote endpoint is not going to produce RTP /// but just consume it. In those cases, do not set `comedia` flag and call /// [`PlainTransport::connect()`] with the IP and port(s) of the remote endpoint. #[derive(Debug, Clone)] #[non_exhaustive] pub struct PlainTransportOptions { /// Listening info. pub listen_info: ListenInfo, /// Optional listening info for RTCP. pub rtcp_listen_info: Option, /// Use RTCP-mux (RTP and RTCP in the same port). /// Default true. pub rtcp_mux: bool, /// Whether remote IP:port should be auto-detected based on first RTP/RTCP /// packet received. If enabled, connect() method must not be called unless /// SRTP is enabled. If so, it must be called with just remote SRTP parameters. /// Default false. pub comedia: bool, /// Create a SCTP association. /// Default false. pub enable_sctp: bool, /// SCTP streams number. pub num_sctp_streams: NumSctpStreams, /// Maximum allowed size for SCTP messages sent by DataProducers. /// Default 262144. pub max_sctp_message_size: u32, /// Maximum SCTP send buffer used by DataConsumers. /// Default 262144. pub sctp_send_buffer_size: u32, /// Enable SRTP. For this to work, connect() must be called with remote SRTP parameters. /// Default false. pub enable_srtp: bool, /// The SRTP crypto suite to be used if enableSrtp is set. /// Default 'AesCm128HmacSha180'. pub srtp_crypto_suite: SrtpCryptoSuite, /// Custom application data. pub app_data: AppData, } impl PlainTransportOptions { /// Create Plain transport options with given listen IP. #[must_use] pub fn new(listen_info: ListenInfo) -> Self { Self { listen_info, rtcp_listen_info: None, rtcp_mux: true, comedia: false, enable_sctp: false, num_sctp_streams: NumSctpStreams::default(), max_sctp_message_size: 262_144, sctp_send_buffer_size: 262_144, enable_srtp: false, srtp_crypto_suite: SrtpCryptoSuite::default(), app_data: AppData::default(), } } } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct PlainTransportDump { // Common to all Transports. pub id: TransportId, pub direct: bool, pub producer_ids: Vec, pub consumer_ids: Vec, pub map_ssrc_consumer_id: IntMap, pub map_rtx_ssrc_consumer_id: IntMap, pub data_producer_ids: Vec, pub data_consumer_ids: Vec, pub recv_rtp_header_extensions: RecvRtpHeaderExtensions, pub rtp_listener: RtpListener, pub max_message_size: u32, pub sctp_parameters: Option, pub sctp_state: Option, pub sctp_listener: Option, pub trace_event_types: Vec, // PlainTransport specific. pub rtcp_mux: bool, pub comedia: bool, pub tuple: TransportTuple, pub rtcp_tuple: Option, pub srtp_parameters: Option, } impl<'a> TryFromFbs<'a> for PlainTransportDump { type FbsType = plain_transport::DumpResponse; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { Ok(Self { // Common to all Transports. id: dump.base.id.parse()?, direct: false, producer_ids: dump .base .producer_ids .iter() .map(|producer_id| Ok(producer_id.parse()?)) .collect::>>()?, consumer_ids: dump .base .consumer_ids .iter() .map(|consumer_id| Ok(consumer_id.parse()?)) .collect::>>()?, map_ssrc_consumer_id: dump .base .map_ssrc_consumer_id .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, map_rtx_ssrc_consumer_id: dump .base .map_rtx_ssrc_consumer_id .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, data_producer_ids: dump .base .data_producer_ids .iter() .map(|data_producer_id| Ok(data_producer_id.parse()?)) .collect::>>()?, data_consumer_ids: dump .base .data_consumer_ids .iter() .map(|data_consumer_id| Ok(data_consumer_id.parse()?)) .collect::>>()?, recv_rtp_header_extensions: RecvRtpHeaderExtensions::from_fbs( dump.base.recv_rtp_header_extensions.as_ref(), ), rtp_listener: RtpListener::try_from_fbs(*dump.base.rtp_listener)?, max_message_size: dump.base.max_message_size, sctp_parameters: dump .base .sctp_parameters .as_ref() .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())), sctp_state: FromFbs::from_fbs(&dump.base.sctp_state), sctp_listener: dump.base.sctp_listener.as_ref().map(|listener| { SctpListener::try_from_fbs(listener.as_ref().clone()) .expect("Error parsing SctpListner") }), trace_event_types: dump .base .trace_event_types .iter() .map(TransportTraceEventType::from_fbs) .collect(), // PlainTransport specific. rtcp_mux: dump.rtcp_mux, comedia: dump.comedia, tuple: TransportTuple::from_fbs(dump.tuple.as_ref()), rtcp_tuple: dump .rtcp_tuple .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())), srtp_parameters: dump .srtp_parameters .map(|parameters| SrtpParameters::from_fbs(parameters.as_ref())), }) } } /// RTC statistics of the plain transport. #[derive(Debug, Clone, PartialOrd, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] #[allow(missing_docs)] pub struct PlainTransportStat { // Common to all Transports. // `type` field is present in worker, but ignored here pub transport_id: TransportId, pub timestamp: u64, pub sctp_state: Option, pub bytes_received: u64, pub recv_bitrate: u32, pub bytes_sent: u64, pub send_bitrate: u32, pub rtp_bytes_received: u64, pub rtp_recv_bitrate: u32, pub rtp_bytes_sent: u64, pub rtp_send_bitrate: u32, pub rtx_bytes_received: u64, pub rtx_recv_bitrate: u32, pub rtx_bytes_sent: u64, pub rtx_send_bitrate: u32, pub probation_bytes_sent: u64, pub probation_send_bitrate: u32, #[serde(skip_serializing_if = "Option::is_none")] pub available_outgoing_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub available_incoming_bitrate: Option, pub max_incoming_bitrate: Option, pub max_outgoing_bitrate: Option, pub min_outgoing_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rtp_packet_loss_received: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rtp_packet_loss_sent: Option, // PlainTransport specific. pub rtcp_mux: bool, pub comedia: bool, pub tuple: TransportTuple, pub rtcp_tuple: Option, } impl<'a> TryFromFbs<'a> for PlainTransportStat { type FbsType = plain_transport::GetStatsResponse; type Error = Box; fn try_from_fbs(stats: Self::FbsType) -> Result { Ok(Self { transport_id: stats.base.transport_id.parse()?, timestamp: stats.base.timestamp, sctp_state: FromFbs::from_fbs(&stats.base.sctp_state), bytes_received: stats.base.bytes_received, recv_bitrate: stats.base.recv_bitrate, bytes_sent: stats.base.bytes_sent, send_bitrate: stats.base.send_bitrate, rtp_bytes_received: stats.base.rtp_bytes_received, rtp_recv_bitrate: stats.base.rtp_recv_bitrate, rtp_bytes_sent: stats.base.rtp_bytes_sent, rtp_send_bitrate: stats.base.rtp_send_bitrate, rtx_bytes_received: stats.base.rtx_bytes_received, rtx_recv_bitrate: stats.base.rtx_recv_bitrate, rtx_bytes_sent: stats.base.rtx_bytes_sent, rtx_send_bitrate: stats.base.rtx_send_bitrate, probation_bytes_sent: stats.base.probation_bytes_sent, probation_send_bitrate: stats.base.probation_send_bitrate, available_outgoing_bitrate: stats.base.available_outgoing_bitrate, available_incoming_bitrate: stats.base.available_incoming_bitrate, max_incoming_bitrate: stats.base.max_incoming_bitrate, max_outgoing_bitrate: stats.base.max_outgoing_bitrate, min_outgoing_bitrate: stats.base.min_outgoing_bitrate, rtp_packet_loss_received: stats.base.rtp_packet_loss_received, rtp_packet_loss_sent: stats.base.rtp_packet_loss_sent, // PlainTransport specific. rtcp_mux: stats.rtcp_mux, comedia: stats.comedia, tuple: TransportTuple::from_fbs(stats.tuple.as_ref()), rtcp_tuple: stats .rtcp_tuple .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())), }) } } /// Remote parameters for plain transport. #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PlainTransportRemoteParameters { /// Remote IPv4 or IPv6. /// Required if `comedia` is not set. pub ip: Option, /// Remote port. /// Required if `comedia` is not set. pub port: Option, /// Remote RTCP port. /// Required if `comedia` is not set and RTCP-mux is not enabled. pub rtcp_port: Option, /// SRTP parameters used by the remote endpoint to encrypt its RTP and RTCP. /// The SRTP crypto suite of the local `srtpParameters` gets also updated after connect() /// resolves. /// Required if enable_srtp was set. pub srtp_parameters: Option, } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { new_producer: Bag, Producer>, new_consumer: Bag, Consumer>, new_data_producer: Bag, DataProducer>, new_data_consumer: Bag, DataConsumer>, tuple: Bag, TransportTuple>, rtcp_tuple: Bag, TransportTuple>, sctp_state_change: Bag>, trace: Bag, TransportTraceEventData>, router_close: BagOnce>, close: BagOnce>, } #[derive(Debug, Deserialize)] #[serde(tag = "event", rename_all = "lowercase", content = "data")] enum Notification { Tuple { tuple: TransportTuple, }, #[serde(rename_all = "camelCase")] RtcpTuple { rtcp_tuple: TransportTuple, }, #[serde(rename_all = "camelCase")] SctpStateChange { sctp_state: SctpState, }, Trace(TransportTraceEventData), } impl<'a> TryFromFbs<'a> for Notification { type FbsType = notification::NotificationRef<'a>; type Error = NotificationParseError; fn try_from_fbs(notification: Self::FbsType) -> Result { match notification.event().unwrap() { notification::Event::PlaintransportTuple => { let Ok(Some(notification::BodyRef::PlainTransportTupleNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let tuple_fbs = transport::Tuple::try_from(body.tuple().unwrap()).unwrap(); let tuple = TransportTuple::from_fbs(&tuple_fbs); Ok(Notification::Tuple { tuple }) } notification::Event::PlaintransportRtcpTuple => { let Ok(Some(notification::BodyRef::PlainTransportRtcpTupleNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let rtcp_tuple_fbs = transport::Tuple::try_from(body.tuple().unwrap()).unwrap(); let rtcp_tuple = TransportTuple::from_fbs(&rtcp_tuple_fbs); Ok(Notification::RtcpTuple { rtcp_tuple }) } notification::Event::TransportSctpStateChange => { let Ok(Some(notification::BodyRef::TransportSctpStateChangeNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let sctp_state = SctpState::from_fbs(&body.sctp_state().unwrap()); Ok(Notification::SctpStateChange { sctp_state }) } notification::Event::TransportTrace => { let Ok(Some(notification::BodyRef::TransportTraceNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let trace_notification_fbs = transport::TraceNotification::try_from(body).unwrap(); let trace_notification = TransportTraceEventData::from_fbs(&trace_notification_fbs); Ok(Notification::Trace(trace_notification)) } _ => Err(NotificationParseError::InvalidEvent), } } } struct Inner { id: TransportId, next_mid_for_consumers: AtomicUsize, used_sctp_stream_ids: Mutex>, cname_for_producers: Mutex>, executor: Arc>, channel: Channel, handlers: Arc, data: Arc, app_data: AppData, // Make sure router is not dropped until this transport is not dropped router: Router, closed: AtomicBool, // Drop subscription to transport-specific notifications when transport itself is dropped _subscription_handler: Mutex>, _on_router_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let router_id = self.router.id(); let request = TransportCloseRequest { transport_id: self.id, }; self.executor .spawn(async move { match channel.request(router_id, request).await { Err(RequestError::ChannelClosed) => { debug!("transport closing failed on drop: Channel already closed"); } Err(error) => { error!("transport closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// A plain transport represents a network path through which RTP, RTCP (optionally secured with /// SRTP) and SCTP (DataChannel) is transmitted. #[derive(Clone)] #[must_use = "Transport will be closed on drop, make sure to keep it around for as long as needed"] pub struct PlainTransport { inner: Arc, } impl fmt::Debug for PlainTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PlainTransport") .field("id", &self.inner.id) .field("next_mid_for_consumers", &self.inner.next_mid_for_consumers) .field("used_sctp_stream_ids", &self.inner.used_sctp_stream_ids) .field("cname_for_producers", &self.inner.cname_for_producers) .field("router", &self.inner.router) .field("closed", &self.inner.closed) .finish() } } #[async_trait] impl Transport for PlainTransport { fn id(&self) -> TransportId { self.inner.id } fn router(&self) -> &Router { &self.inner.router } fn app_data(&self) -> &AppData { &self.inner.app_data } fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } async fn produce(&self, producer_options: ProducerOptions) -> Result { debug!("produce()"); let producer = self .produce_impl(producer_options, TransportType::Plain) .await?; self.inner.handlers.new_producer.call_simple(&producer); Ok(producer) } async fn consume(&self, consumer_options: ConsumerOptions) -> Result { debug!("consume()"); let consumer = self .consume_impl(consumer_options, TransportType::Plain, false) .await?; self.inner.handlers.new_consumer.call_simple(&consumer); Ok(consumer) } async fn produce_data( &self, data_producer_options: DataProducerOptions, ) -> Result { debug!("produce_data()"); let data_producer = self .produce_data_impl( DataProducerType::Sctp, data_producer_options, TransportType::Plain, ) .await?; self.inner .handlers .new_data_producer .call_simple(&data_producer); Ok(data_producer) } async fn consume_data( &self, data_consumer_options: DataConsumerOptions, ) -> Result { debug!("consume_data()"); let data_consumer = self .consume_data_impl( DataConsumerType::Sctp, data_consumer_options, TransportType::Plain, ) .await?; self.inner .handlers .new_data_consumer .call_simple(&data_consumer); Ok(data_consumer) } async fn enable_trace_event( &self, types: Vec, ) -> Result<(), RequestError> { debug!("enable_trace_event()"); self.enable_trace_event_impl(types).await } fn on_new_producer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_producer.add(callback) } fn on_new_consumer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_consumer.add(callback) } fn on_new_data_producer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_data_producer.add(callback) } fn on_new_data_consumer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_data_consumer.add(callback) } fn on_trace( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.trace.add(callback) } fn on_router_close(&self, callback: Box) -> HandlerId { self.inner.handlers.router_close.add(callback) } fn on_close(&self, callback: Box) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } } #[async_trait] impl TransportGeneric for PlainTransport { type Dump = PlainTransportDump; type Stat = PlainTransportStat; #[doc(hidden)] async fn dump(&self) -> Result { debug!("dump()"); let response = self.dump_impl().await?; if let response::Body::PlainTransportDumpResponse(data) = response { Ok(PlainTransportDump::try_from_fbs(*data) .expect("Error parsing dump response: {response:?}")) } else { panic!("Wrong message from worker: {response:?}"); } } async fn get_stats(&self) -> Result, RequestError> { debug!("get_stats()"); let response = self.get_stats_impl().await?; if let response::Body::PlainTransportGetStatsResponse(data) = response { Ok(vec![PlainTransportStat::try_from_fbs(*data) .expect("Error parsing dump response: {response:?}")]) } else { panic!("Wrong message from worker: {response:?}"); } } } impl TransportImpl for PlainTransport { fn channel(&self) -> &Channel { &self.inner.channel } fn executor(&self) -> &Arc> { &self.inner.executor } fn next_mid_for_consumers(&self) -> &AtomicUsize { &self.inner.next_mid_for_consumers } fn used_sctp_stream_ids(&self) -> &Mutex> { &self.inner.used_sctp_stream_ids } fn cname_for_producers(&self) -> &Mutex> { &self.inner.cname_for_producers } } impl PlainTransport { pub(super) fn new( id: TransportId, executor: Arc>, channel: Channel, data: PlainTransportData, app_data: AppData, router: Router, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let data = Arc::new(data); let subscription_handler = { let handlers = Arc::clone(&handlers); let data = Arc::clone(&data); channel.subscribe_to_notifications(id.into(), move |notification| { match Notification::try_from_fbs(notification) { Ok(notification) => match notification { Notification::Tuple { tuple } => { *data.tuple.lock() = tuple.clone(); handlers.tuple.call_simple(&tuple); } Notification::RtcpTuple { rtcp_tuple } => { data.rtcp_tuple.lock().replace(rtcp_tuple.clone()); handlers.rtcp_tuple.call_simple(&rtcp_tuple); } Notification::SctpStateChange { sctp_state } => { data.sctp_state.lock().replace(sctp_state); handlers.sctp_state_change.call(|callback| { callback(sctp_state); }); } Notification::Trace(trace_event_data) => { handlers.trace.call_simple(&trace_event_data); } }, Err(error) => { error!("Failed to parse notification: {}", error); } } }) }; let next_mid_for_consumers = AtomicUsize::default(); let used_sctp_stream_ids = Mutex::new({ let mut used_used_sctp_stream_ids = IntMap::default(); if let Some(sctp_parameters) = &data.sctp_parameters { for i in 0..sctp_parameters.mis { used_used_sctp_stream_ids.insert(i, false); } } used_used_sctp_stream_ids }); let cname_for_producers = Mutex::new(None); let inner_weak = Arc::>>>::default(); let on_router_close_handler = router.on_close({ let inner_weak = Arc::clone(&inner_weak); move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.router_close.call_simple(); inner.close(false); } } }); let inner = Arc::new(Inner { id, next_mid_for_consumers, used_sctp_stream_ids, cname_for_producers, executor, channel, handlers, data, app_data, router, closed: AtomicBool::new(false), _subscription_handler: Mutex::new(subscription_handler), _on_router_close_handler: Mutex::new(on_router_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); Self { inner } } /// Provide the [`PlainTransport`] with remote parameters. /// /// # Notes on usage /// * If `comedia` is enabled in this plain transport and SRTP is not, `connect()` must not be /// called. /// * If `comedia` is enabled and SRTP is also enabled (`enable_srtp` was set in the /// [`Router::create_plain_transport`] options) then `connect()` must be called with just the /// remote `srtp_parameters`. /// * If `comedia` is disabled, `connect()` must be eventually called with remote `ip`, `port`, /// optional `rtcp_port` (if RTCP-mux is not enabled) and optional `srtp_parameters` (if SRTP /// is enabled). /// /// # Examples /// ```rust /// use mediasoup::plain_transport::PlainTransportRemoteParameters; /// /// # async fn f( /// # plain_transport: mediasoup::plain_transport::PlainTransport, /// # ) -> Result<(), Box> { /// // Calling connect() on a PlainTransport created with comedia and rtcp_mux set. /// plain_transport /// .connect(PlainTransportRemoteParameters { /// ip: Some("1.2.3.4".parse().unwrap()), /// port: Some(9998), /// rtcp_port: None, /// srtp_parameters: None, /// }) /// .await?; /// # Ok(()) /// # } /// ``` /// ```rust /// use mediasoup::plain_transport::PlainTransportRemoteParameters; /// /// # async fn f( /// # plain_transport: mediasoup::plain_transport::PlainTransport, /// # ) -> Result<(), Box> { /// // Calling connect() on a PlainTransport created with comedia unset and rtcp_mux /// // also unset. /// plain_transport /// .connect(PlainTransportRemoteParameters { /// ip: Some("1.2.3.4".parse().unwrap()), /// port: Some(9998), /// rtcp_port: Some(9999), /// srtp_parameters: None, /// }) /// .await?; /// # Ok(()) /// # } /// ``` /// ```rust /// use mediasoup::plain_transport::PlainTransportRemoteParameters; /// use mediasoup_types::srtp_parameters::{SrtpParameters, SrtpCryptoSuite}; /// /// # async fn f( /// # plain_transport: mediasoup::plain_transport::PlainTransport, /// # ) -> Result<(), Box> { /// // Calling connect() on a PlainTransport created with comedia set and /// // enable_srtp enabled. /// plain_transport /// .connect(PlainTransportRemoteParameters { /// ip: None, /// port: None, /// rtcp_port: None, /// srtp_parameters: Some(SrtpParameters { /// crypto_suite: SrtpCryptoSuite::AesCm128HmacSha180, /// key_base64: "ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv".to_string(), /// }), /// }) /// .await?; /// # Ok(()) /// # } /// ``` /// ```rust /// use mediasoup::plain_transport::PlainTransportRemoteParameters; /// use mediasoup_types::srtp_parameters::{SrtpParameters, SrtpCryptoSuite}; /// /// # async fn f( /// # plain_transport: mediasoup::plain_transport::PlainTransport, /// # ) -> Result<(), Box> { /// // Calling connect() on a PlainTransport created with comedia unset, /// // rtcp_mux set and enableSrtp enabled. /// plain_transport /// .connect(PlainTransportRemoteParameters { /// ip: Some("1.2.3.4".parse().unwrap()), /// port: Some(9998), /// rtcp_port: None, /// srtp_parameters: Some(SrtpParameters { /// crypto_suite: SrtpCryptoSuite::AesCm128HmacSha180, /// key_base64: "ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv".to_string(), /// }), /// }) /// .await?; /// # Ok(()) /// # } /// ``` pub async fn connect( &self, remote_parameters: PlainTransportRemoteParameters, ) -> Result<(), RequestError> { debug!("connect()"); let response = self .inner .channel .request( self.inner.id, TransportConnectPlainRequest { ip: remote_parameters.ip, port: remote_parameters.port, rtcp_port: remote_parameters.rtcp_port, srtp_parameters: remote_parameters.srtp_parameters, }, ) .await?; *self.inner.data.tuple.lock() = response.tuple; if let Some(rtcp_tuple) = response.rtcp_tuple { self.inner.data.rtcp_tuple.lock().replace(rtcp_tuple); } if let Some(srtp_parameters) = response.srtp_parameters { self.inner .data .srtp_parameters .lock() .replace(srtp_parameters); } Ok(()) } /// Set maximum incoming bitrate for media streams sent by the remote endpoint over this /// transport. pub async fn set_max_incoming_bitrate(&self, bitrate: u32) -> Result<(), RequestError> { debug!("set_max_incoming_bitrate() [bitrate:{}]", bitrate); self.set_max_incoming_bitrate_impl(bitrate).await } /// The transport tuple. If RTCP-mux is enabled (`rtcp_mux` is set), this tuple refers to both /// RTP and RTCP. /// /// # Notes on usage /// * Once the plain transport is created, `transport.tuple()` will contain information about /// its `local_address`, `local_port` and `protocol`. /// * Information about `remote_ip` and `remote_port` will be set: /// * after calling `connect()` method, or /// * via dynamic remote address detection when using `comedia` mode. #[must_use] pub fn tuple(&self) -> TransportTuple { self.inner.data.tuple.lock().clone() } /// The transport tuple for RTCP. If RTCP-mux is enabled (`rtcp_mux` is set), its value is /// `None`. /// /// # Notes on usage /// * Once the plain transport is created (with RTCP-mux disabled), `transport.rtcp_tuple()` /// will contain information about its `local_address`, `local_port` and `protocol`. /// * Information about `remote_ip` and `remote_port` will be set: /// * after calling `connect()` method, or /// * via dynamic remote address detection when using `comedia` mode. #[must_use] pub fn rtcp_tuple(&self) -> Option { self.inner.data.rtcp_tuple.lock().clone() } /// Current SCTP state. Or `None` if SCTP is not enabled. #[must_use] pub fn sctp_parameters(&self) -> Option { self.inner.data.sctp_parameters } /// Current SCTP state. Or `None` if SCTP is not enabled. #[must_use] pub fn sctp_state(&self) -> Option { *self.inner.data.sctp_state.lock() } /// Local SRTP parameters representing the crypto suite and key material used to encrypt sending /// RTP and SRTP. Note that, if `comedia` mode is set, these local SRTP parameters may change /// after calling `connect()` with the remote SRTP parameters (to override the local SRTP crypto /// suite with the one given in `connect()`). #[must_use] pub fn srtp_parameters(&self) -> Option { self.inner.data.srtp_parameters.lock().clone() } /// Callback is called after the remote RTP origin has been discovered. Only if `comedia` mode /// was set. pub fn on_tuple( &self, callback: F, ) -> HandlerId { self.inner.handlers.tuple.add(Arc::new(callback)) } /// Callback is called after the remote RTCP origin has been discovered. Only if `comedia` mode /// was set and `rtcp_mux` was not. pub fn on_rtcp_tuple( &self, callback: F, ) -> HandlerId { self.inner.handlers.rtcp_tuple.add(Arc::new(callback)) } /// Callback is called when the transport SCTP state changes. pub fn on_sctp_state_change( &self, callback: F, ) -> HandlerId { self.inner .handlers .sctp_state_change .add(Arc::new(callback)) } /// Downgrade `PlainTransport` to [`WeakPlainTransport`] instance. #[must_use] pub fn downgrade(&self) -> WeakPlainTransport { WeakPlainTransport { inner: Arc::downgrade(&self.inner), } } } /// [`WeakPlainTransport`] doesn't own pipe transport instance on mediasoup-worker and will not /// prevent one from being destroyed once last instance of regular [`PlainTransport`] is dropped. /// /// [`WeakPlainTransport`] vs [`PlainTransport`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakPlainTransport { inner: Weak, } impl fmt::Debug for WeakPlainTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakPlainTransport").finish() } } impl WeakPlainTransport { /// Attempts to upgrade `WeakPlainTransport` to [`PlainTransport`] if last instance of one /// wasn't dropped yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; Some(PlainTransport { inner }) } } ================================================ FILE: rust/src/router/producer/tests.rs ================================================ use crate::producer::ProducerOptions; use crate::router::{Router, RouterOptions}; use crate::transport::Transport; use crate::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use crate::worker::WorkerSettings; use crate::worker_manager::WorkerManager; use futures_lite::future; use mediasoup_types::data_structures::{ListenInfo, Protocol}; use mediasoup_types::rtp_parameters::{ MediaKind, MimeTypeAudio, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters, RtpParameters, }; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; fn media_codecs() -> Vec { vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }] } fn audio_producer_options() -> ProducerOptions { ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 0, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }], ..RtpParameters::default() }, ) } async fn init() -> (Router, WebRtcTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); let transport_1 = router .create_webrtc_transport(transport_options.clone()) .await .expect("Failed to create transport1"); (router, transport_1) } #[test] fn transport_close_event() { future::block_on(async move { let (router, transport_1) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = audio_producer.on_close(move || { let _ = close_tx.send(()); }); let (mut transport_close_tx, transport_close_rx) = async_oneshot::oneshot::<()>(); let _handler = audio_producer.on_transport_close(move || { let _ = transport_close_tx.send(()); }); router.close(); transport_close_rx .await .expect("Failed to receive transport_close event"); close_rx.await.expect("Failed to receive close event"); assert!(audio_producer.closed()); }); } ================================================ FILE: rust/src/router/producer.rs ================================================ #[cfg(test)] mod tests; use crate::consumer::{RtpStreamParams, RtxStreamParams}; use crate::fbs::{FromFbs, ToFbs, TryFromFbs}; use crate::messages::{ ProducerCloseRequest, ProducerDumpRequest, ProducerEnableTraceEventRequest, ProducerGetStatsRequest, ProducerPauseRequest, ProducerResumeRequest, ProducerSendNotification, }; pub use crate::ortc::RtpMapping; use crate::transport::Transport; use crate::uuid_based_wrapper_type; use crate::worker::{ Channel, NotificationError, NotificationParseError, RequestError, SubscriptionHandler, }; use async_executor::Executor; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::{notification, producer, response, rtp_parameters, rtp_stream}; use mediasoup_types::data_structures::{ AppData, RtpPacketTraceInfo, SrTraceInfo, SsrcTraceInfo, TraceEventDirection, }; use mediasoup_types::rtp_parameters::{MediaKind, MimeType, RtpParameters}; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::error::Error; use std::fmt; use std::fmt::Debug; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; uuid_based_wrapper_type!( /// [`Producer`] identifier. ProducerId ); /// [`Producer`] options. /// /// # Notes on usage /// Check the /// [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/) /// section for more details (TypeScript-oriented, but concepts apply here as well). #[derive(Debug, Clone)] #[non_exhaustive] pub struct ProducerOptions { /// Producer id (just for /// [`Router::pipe_producer_to_router`](crate::router::Router::pipe_producer_to_router) method). pub(super) id: Option, /// Media kind. pub kind: MediaKind, /// RTP parameters defining what the endpoint is sending. pub rtp_parameters: RtpParameters, /// Whether the producer must start in paused mode. Default false. pub paused: bool, /// Just for video. Time (in ms) before asking the sender for a new key frame after having asked /// a previous one. If 0 there is no delay. pub key_frame_request_delay: u32, /// Add mediasoup custom 'urn:mediasoup:params:rtp-hdrext:packet-id' header extension /// to RTP packets received from the sender endpoint. pub enable_mediasoup_packet_id_header_extension: bool, /// Custom application data. pub app_data: AppData, } impl ProducerOptions { /// Create producer options that will be used with Pipe transport #[must_use] pub fn new_pipe_transport( producer_id: ProducerId, kind: MediaKind, rtp_parameters: RtpParameters, ) -> Self { Self { id: Some(producer_id), kind, rtp_parameters, paused: false, key_frame_request_delay: 0, enable_mediasoup_packet_id_header_extension: false, app_data: AppData::default(), } } /// Create producer options that will be used with non-Pipe transport #[must_use] pub fn new(kind: MediaKind, rtp_parameters: RtpParameters) -> Self { Self { id: None, kind, rtp_parameters, paused: false, key_frame_request_delay: 0, enable_mediasoup_packet_id_header_extension: false, app_data: AppData::default(), } } } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct RtxStream { pub params: RtxStreamParams, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct RtpStreamRecv { pub params: RtpStreamParams, pub score: u8, pub rtx_stream: Option, } impl<'a> TryFromFbs<'a> for RtpStreamRecv { type FbsType = rtp_stream::DumpRef<'a>; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { Ok(Self { params: RtpStreamParams::try_from_fbs(dump.params()?)?, score: dump.score()?, rtx_stream: if let Some(rtx_stream) = dump.rtx_stream()? { Some(RtxStream { params: RtxStreamParams::try_from_fbs(rtx_stream.params()?)?, }) } else { None }, }) } } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct ProducerDump { pub id: ProducerId, pub kind: MediaKind, pub paused: bool, pub rtp_mapping: RtpMapping, pub rtp_parameters: RtpParameters, pub rtp_streams: Vec, pub trace_event_types: Vec, pub r#type: ProducerType, } impl<'a> TryFromFbs<'a> for ProducerDump { type FbsType = producer::DumpResponseRef<'a>; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { Ok(Self { id: dump.id()?.parse()?, kind: MediaKind::from_fbs(&dump.kind()?), paused: dump.paused()?, rtp_mapping: RtpMapping::try_from_fbs(dump.rtp_mapping()?)?, rtp_parameters: RtpParameters::try_from_fbs(dump.rtp_parameters()?)?, rtp_streams: dump .rtp_streams()? .iter() .map(|rtp_stream| RtpStreamRecv::try_from_fbs(rtp_stream?)) .collect::>>()?, trace_event_types: dump .trace_event_types()? .iter() .map(|trace_event_type| { ProducerTraceEventType::from_fbs(&trace_event_type.unwrap()) }) .collect(), r#type: ProducerType::from_fbs(&dump.type_()?), }) } } /// Producer type. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum ProducerType { /// A single RTP stream is received with no spatial/temporal layers. Simple, /// Two or more RTP streams are received, each of them with one or more temporal layers. Simulcast, /// A single RTP stream is received with spatial/temporal layers. Svc, } impl FromFbs for ProducerType { type FbsType = rtp_parameters::Type; fn from_fbs(producer_type: &Self::FbsType) -> Self { match producer_type { rtp_parameters::Type::Simple => ProducerType::Simple, rtp_parameters::Type::Simulcast => ProducerType::Simulcast, rtp_parameters::Type::Svc => ProducerType::Svc, // TODO: Create a new FBS type ProducerType with just Simple, // Simulcast and Svc. rtp_parameters::Type::Pipe => unimplemented!(), } } } /// Score of the RTP stream in the producer representing its transmission quality. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ProducerScore { /// Index of the RTP stream in the [`RtpParameters::encodings`] array of the producer. pub encoding_idx: u32, /// RTP stream SSRC. pub ssrc: u32, /// RTP stream RID value. pub rid: Option, /// RTP stream score (from 0 to 10) representing the transmission quality. pub score: u8, } impl FromFbs for ProducerScore { type FbsType = producer::Score; fn from_fbs(producer_score: &producer::Score) -> Self { Self { encoding_idx: producer_score.encoding_idx, ssrc: producer_score.ssrc, rid: producer_score.rid.clone(), score: producer_score.score, } } } /// Rotation angle #[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize_repr, Serialize_repr)] #[repr(u16)] pub enum Rotation { /// 0 None = 0, /// 90 (clockwise) Clockwise = 90, /// 180 Rotate180 = 180, /// 270 (90 counter-clockwise) CounterClockwise = 270, } /// As documented in /// [WebRTC Video Processing and Codec Requirements](https://tools.ietf.org/html/rfc7742#section-4). #[derive(Debug, Copy, Clone, Deserialize, Serialize)] pub struct ProducerVideoOrientation { /// Whether the source is a video camera. pub camera: bool, /// Whether the video source is flipped. pub flip: bool, /// Rotation degrees. pub rotation: Rotation, } impl FromFbs for ProducerVideoOrientation { type FbsType = producer::VideoOrientationChangeNotification; fn from_fbs(video_orientation: &Self::FbsType) -> Self { Self { camera: video_orientation.camera, flip: video_orientation.flip, rotation: match video_orientation.rotation { 0 => Rotation::None, 90 => Rotation::Clockwise, 180 => Rotation::Rotate180, 270 => Rotation::CounterClockwise, _ => Rotation::None, }, } } } /// Bitrate by layer. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] #[allow(missing_docs)] pub struct BitrateByLayer { layer: String, bitrate: u32, } /// RTC statistics of the producer. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] #[allow(missing_docs)] pub struct ProducerStat { // Common to all RtpStreams. // `type` field is present in worker, but ignored here pub timestamp: u64, pub ssrc: u32, pub rtx_ssrc: Option, pub rid: Option, pub kind: MediaKind, pub mime_type: MimeType, pub packets_lost: i32, pub fraction_lost: u8, pub jitter: u32, pub packets_discarded: u64, pub packets_retransmitted: u64, pub packets_repaired: u64, pub nack_count: u64, pub nack_packet_count: u64, pub pli_count: u64, pub fir_count: u64, pub packet_count: u64, pub byte_count: u64, pub bitrate: u32, pub round_trip_time: Option, pub rtx_packets_discarded: Option, pub score: u8, // RtpStreamRecv specific. pub bitrate_by_layer: Vec, } impl FromFbs for ProducerStat { type FbsType = rtp_stream::Stats; fn from_fbs(stats: &rtp_stream::Stats) -> Self { let rtp_stream::StatsData::RecvStats(ref stats) = stats.data else { panic!("Wrong message from worker: {stats:?}"); }; let rtp_stream::StatsData::BaseStats(ref base) = stats.base.data else { panic!("Wrong message from worker: {stats:?}"); }; Self { timestamp: base.timestamp, ssrc: base.ssrc, rtx_ssrc: base.rtx_ssrc, rid: base.rid.clone(), kind: MediaKind::from_fbs(&base.kind), mime_type: base.mime_type.to_string().parse().unwrap(), packets_lost: base.packets_lost, fraction_lost: base.fraction_lost, jitter: base.jitter, packets_discarded: base.packets_discarded, packets_retransmitted: base.packets_retransmitted, packets_repaired: base.packets_repaired, nack_count: base.nack_count, nack_packet_count: base.nack_packet_count, pli_count: base.pli_count, fir_count: base.fir_count, packet_count: stats.packet_count, byte_count: stats.byte_count, bitrate: stats.bitrate, round_trip_time: Some(base.round_trip_time), rtx_packets_discarded: Some(base.rtx_packets_discarded), score: base.score, bitrate_by_layer: stats .bitrate_by_layer .iter() .map(|bitrate_by_layer| BitrateByLayer { layer: bitrate_by_layer.layer.to_string(), bitrate: bitrate_by_layer.bitrate, }) .collect(), } } } /// 'trace' event data. #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum ProducerTraceEventData { /// RTP packet. Rtp { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// RTP packet info. info: RtpPacketTraceInfo, }, /// RTP video keyframe packet. KeyFrame { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// RTP packet info. info: RtpPacketTraceInfo, }, /// RTCP NACK packet. Nack { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, }, /// RTCP PLI packet. Pli { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// SSRC info. info: SsrcTraceInfo, }, /// RTCP FIR packet. Fir { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// SSRC info. info: SsrcTraceInfo, }, /// RTCP Sender Report. Sr { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// SSRC info. info: SrTraceInfo, }, } impl FromFbs for ProducerTraceEventData { type FbsType = producer::TraceNotification; fn from_fbs(data: &Self::FbsType) -> Self { match data.type_ { producer::TraceEventType::Rtp => ProducerTraceEventData::Rtp { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(producer::TraceInfo::RtpTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; RtpPacketTraceInfo { is_rtx: info.is_rtx, ..RtpPacketTraceInfo::from_fbs(info.rtp_packet.as_ref()) } }, }, producer::TraceEventType::Keyframe => ProducerTraceEventData::KeyFrame { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(producer::TraceInfo::KeyFrameTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; RtpPacketTraceInfo { is_rtx: info.is_rtx, ..RtpPacketTraceInfo::from_fbs(info.rtp_packet.as_ref()) } }, }, producer::TraceEventType::Nack => ProducerTraceEventData::Nack { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), }, producer::TraceEventType::Pli => ProducerTraceEventData::Pli { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(producer::TraceInfo::PliTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; SsrcTraceInfo { ssrc: info.ssrc } }, }, producer::TraceEventType::Fir => ProducerTraceEventData::Fir { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(producer::TraceInfo::FirTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; SsrcTraceInfo { ssrc: info.ssrc } }, }, producer::TraceEventType::Sr => ProducerTraceEventData::Sr { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(producer::TraceInfo::SrTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; SrTraceInfo::from_fbs(info.as_ref()) }, }, } } } /// Types of consumer trace events. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum ProducerTraceEventType { /// RTP packet. Rtp, /// RTP video keyframe packet. KeyFrame, /// RTCP NACK packet. Nack, /// RTCP PLI packet. Pli, /// RTCP FIR packet. Fir, /// RTCP Sender Report. SR, } impl ToFbs for ProducerTraceEventType { type FbsType = producer::TraceEventType; fn to_fbs(&self) -> Self::FbsType { match self { ProducerTraceEventType::Rtp => producer::TraceEventType::Rtp, ProducerTraceEventType::KeyFrame => producer::TraceEventType::Keyframe, ProducerTraceEventType::Nack => producer::TraceEventType::Nack, ProducerTraceEventType::Pli => producer::TraceEventType::Pli, ProducerTraceEventType::Fir => producer::TraceEventType::Fir, ProducerTraceEventType::SR => producer::TraceEventType::Sr, } } } impl FromFbs for ProducerTraceEventType { type FbsType = producer::TraceEventType; fn from_fbs(event_type: &Self::FbsType) -> Self { match event_type { producer::TraceEventType::Rtp => ProducerTraceEventType::Rtp, producer::TraceEventType::Keyframe => ProducerTraceEventType::KeyFrame, producer::TraceEventType::Nack => ProducerTraceEventType::Nack, producer::TraceEventType::Pli => ProducerTraceEventType::Pli, producer::TraceEventType::Fir => ProducerTraceEventType::Fir, producer::TraceEventType::Sr => ProducerTraceEventType::SR, } } } #[derive(Debug, Deserialize)] #[serde(tag = "event", rename_all = "lowercase", content = "data")] enum Notification { Score(Vec), VideoOrientationChange(ProducerVideoOrientation), Trace(ProducerTraceEventData), } impl<'a> TryFromFbs<'a> for Notification { type FbsType = notification::NotificationRef<'a>; type Error = NotificationParseError; fn try_from_fbs(notification: Self::FbsType) -> Result { match notification.event().unwrap() { notification::Event::ProducerScore => { let Ok(Some(notification::BodyRef::ProducerScoreNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let scores_fbs: Vec<_> = body .scores() .unwrap() .iter() .map(|score| producer::Score::try_from(score.unwrap()).unwrap()) .collect(); let scores = FromFbs::from_fbs(&scores_fbs); Ok(Notification::Score(scores)) } notification::Event::ProducerVideoOrientationChange => { let Ok(Some(notification::BodyRef::ProducerVideoOrientationChangeNotification( body, ))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let video_orientation_fbs = producer::VideoOrientationChangeNotification::try_from(body).unwrap(); let video_orientation = ProducerVideoOrientation::from_fbs(&video_orientation_fbs); Ok(Notification::VideoOrientationChange(video_orientation)) } notification::Event::ProducerTrace => { let Ok(Some(notification::BodyRef::ProducerTraceNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let trace_notification_fbs = producer::TraceNotification::try_from(body).unwrap(); let trace_notification = ProducerTraceEventData::from_fbs(&trace_notification_fbs); Ok(Notification::Trace(trace_notification)) } _ => Err(NotificationParseError::InvalidEvent), } } } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { score: Bag>, video_orientation_change: Bag>, pause: Bag>, resume: Bag>, trace: Bag, ProducerTraceEventData>, transport_close: BagOnce>, close: BagOnce>, } struct Inner { id: ProducerId, kind: MediaKind, r#type: ProducerType, rtp_parameters: RtpParameters, consumable_rtp_parameters: RtpParameters, direct: bool, paused: AtomicBool, score: Arc>>, executor: Arc>, channel: Channel, handlers: Arc, app_data: AppData, transport: Arc, closed: AtomicBool, // Drop subscription to producer-specific notifications when producer itself is dropped _subscription_handler: Mutex>, _on_transport_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let transport_id = self.transport.id(); let request = ProducerCloseRequest { producer_id: self.id, }; self.executor .spawn(async move { match channel.request(transport_id, request).await { Err(RequestError::ChannelClosed) => { debug!("producer closing failed on drop: Channel already closed"); } Err(error) => { error!("producer closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// Producer created on transport other than /// [`DirectTransport`](crate::direct_transport::DirectTransport). #[derive(Clone)] #[must_use = "Producer will be closed on drop, make sure to keep it around for as long as needed"] pub struct RegularProducer { inner: Arc, } impl fmt::Debug for RegularProducer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("RegularProducer") .field("id", &self.inner.id) .field("kind", &self.inner.kind) .field("type", &self.inner.r#type) .field("rtp_parameters", &self.inner.rtp_parameters) .field( "consumable_rtp_parameters", &self.inner.consumable_rtp_parameters, ) .field("paused", &self.inner.paused) .field("score", &self.inner.score) .field("transport", &self.inner.transport) .field("closed", &self.inner.closed) .finish() } } impl From for Producer { fn from(producer: RegularProducer) -> Self { Producer::Regular(producer) } } /// Producer created on [`DirectTransport`](crate::direct_transport::DirectTransport). #[derive(Clone)] #[must_use = "Producer will be closed on drop, make sure to keep it around for as long as needed"] pub struct DirectProducer { inner: Arc, } impl fmt::Debug for DirectProducer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("DirectProducer") .field("id", &self.inner.id) .field("kind", &self.inner.kind) .field("type", &self.inner.r#type) .field("rtp_parameters", &self.inner.rtp_parameters) .field( "consumable_rtp_parameters", &self.inner.consumable_rtp_parameters, ) .field("paused", &self.inner.paused) .field("score", &self.inner.score) .field("transport", &self.inner.transport) .field("closed", &self.inner.closed) .finish() } } impl From for Producer { fn from(producer: DirectProducer) -> Self { Producer::Direct(producer) } } /// A producer represents an audio or video source being injected into a mediasoup router. It's /// created on top of a transport that defines how the media packets are carried. #[derive(Clone)] #[non_exhaustive] #[must_use = "Producer will be closed on drop, make sure to keep it around for as long as needed"] pub enum Producer { /// Producer created on transport other than /// [`DirectTransport`](crate::direct_transport::DirectTransport). Regular(RegularProducer), /// Producer created on [`DirectTransport`](crate::direct_transport::DirectTransport). Direct(DirectProducer), } impl fmt::Debug for Producer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self { Producer::Regular(producer) => f.debug_tuple("Regular").field(&producer).finish(), Producer::Direct(producer) => f.debug_tuple("Direct").field(&producer).finish(), } } } impl Producer { #[allow(clippy::too_many_arguments)] pub(super) async fn new( id: ProducerId, kind: MediaKind, r#type: ProducerType, rtp_parameters: RtpParameters, consumable_rtp_parameters: RtpParameters, paused: bool, executor: Arc>, channel: Channel, app_data: AppData, transport: Arc, direct: bool, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let score = Arc::>>::default(); let subscription_handler = { let handlers = Arc::clone(&handlers); let score = Arc::clone(&score); channel.subscribe_to_notifications(id.into(), move |notification| { match Notification::try_from_fbs(notification) { Ok(notification) => match notification { Notification::Score(scores) => { score.lock().clone_from(&scores); handlers.score.call(|callback| { callback(&scores); }); } Notification::VideoOrientationChange(video_orientation) => { handlers.video_orientation_change.call(|callback| { callback(video_orientation); }); } Notification::Trace(trace_event_data) => { handlers.trace.call_simple(&trace_event_data); } }, Err(error) => { error!("Failed to parse notification: {}", error); } } }) }; let inner_weak = Arc::>>>::default(); let on_transport_close_handler = transport.on_close({ let inner_weak = Arc::clone(&inner_weak); Box::new(move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.transport_close.call_simple(); inner.close(false); } }) }); let inner = Arc::new(Inner { id, kind, r#type, rtp_parameters, consumable_rtp_parameters, direct, paused: AtomicBool::new(paused), score, executor, channel, handlers, app_data, transport, closed: AtomicBool::new(false), _subscription_handler: Mutex::new(subscription_handler), _on_transport_close_handler: Mutex::new(on_transport_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); if direct { Self::Direct(DirectProducer { inner }) } else { Self::Regular(RegularProducer { inner }) } } /// Producer identifier. #[must_use] pub fn id(&self) -> ProducerId { self.inner().id } /// Transport to which producer belongs. pub fn transport(&self) -> &Arc { &self.inner().transport } /// Media kind. #[must_use] pub fn kind(&self) -> MediaKind { self.inner().kind } /// Producer RTP parameters. /// # Notes on usage /// Check the /// [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/) /// section for more details (TypeScript-oriented, but concepts apply here as well). #[must_use] pub fn rtp_parameters(&self) -> &RtpParameters { &self.inner().rtp_parameters } /// Producer type. #[must_use] pub fn r#type(&self) -> ProducerType { self.inner().r#type } /// Whether the Producer is paused. #[must_use] pub fn paused(&self) -> bool { self.inner().paused.load(Ordering::SeqCst) } /// The score of each RTP stream being received, representing their transmission quality. #[must_use] pub fn score(&self) -> Vec { self.inner().score.lock().clone() } /// Custom application data. #[must_use] pub fn app_data(&self) -> &AppData { &self.inner().app_data } /// Whether the producer is closed. #[must_use] pub fn closed(&self) -> bool { self.inner().closed.load(Ordering::SeqCst) } /// Dump Producer. #[doc(hidden)] pub async fn dump(&self) -> Result { debug!("dump()"); self.inner() .channel .request(self.id(), ProducerDumpRequest {}) .await } /// Returns current RTC statistics of the producer. /// /// Check the [RTC Statistics](https://mediasoup.org/documentation/v3/mediasoup/rtc-statistics/) /// section for more details (TypeScript-oriented, but concepts apply here as well). pub async fn get_stats(&self) -> Result, RequestError> { debug!("get_stats()"); let response = self .inner() .channel .request(self.id(), ProducerGetStatsRequest {}) .await?; if let response::Body::ProducerGetStatsResponse(data) = response { Ok(FromFbs::from_fbs(&data.stats)) } else { panic!("Wrong message from worker: {response:?}"); } } /// Pauses the producer (no RTP is sent to its associated consumers). Calls /// [`Consumer::on_producer_pause`](crate::consumer::Consumer::on_producer_pause) callback on /// all its associated consumers. pub async fn pause(&self) -> Result<(), RequestError> { debug!("pause()"); self.inner() .channel .request(self.id(), ProducerPauseRequest {}) .await?; let was_paused = self.inner().paused.swap(true, Ordering::SeqCst); if !was_paused { self.inner().handlers.pause.call_simple(); } Ok(()) } /// Resumes the producer (RTP is sent to its associated consumers). Calls /// [`Consumer::on_producer_resume`](crate::consumer::Consumer::on_producer_resume) callback on /// all its associated consumers. pub async fn resume(&self) -> Result<(), RequestError> { debug!("resume()"); self.inner() .channel .request(self.id(), ProducerResumeRequest {}) .await?; let was_paused = self.inner().paused.swap(false, Ordering::SeqCst); if was_paused { self.inner().handlers.resume.call_simple(); } Ok(()) } /// Instructs the procuer to emit "trace" events. For monitoring purposes. Use with caution. pub async fn enable_trace_event( &self, types: Vec, ) -> Result<(), RequestError> { debug!("enable_trace_event()"); self.inner() .channel .request(self.id(), ProducerEnableTraceEventRequest { types }) .await } /// Callback is called when the producer score changes. pub fn on_score( &self, callback: F, ) -> HandlerId { self.inner().handlers.score.add(Arc::new(callback)) } /// Callback is called when the video orientation changes. This is just possible if the /// `urn:3gpp:video-orientation` RTP extension has been negotiated in the producer RTP /// parameters. pub fn on_video_orientation_change( &self, callback: F, ) -> HandlerId { self.inner() .handlers .video_orientation_change .add(Arc::new(callback)) } /// Callback is called when the producer is paused. pub fn on_pause(&self, callback: F) -> HandlerId { self.inner().handlers.pause.add(Arc::new(callback)) } /// Callback is called when the producer is resumed. pub fn on_resume(&self, callback: F) -> HandlerId { self.inner().handlers.resume.add(Arc::new(callback)) } /// See [`Producer::enable_trace_event`] method. pub fn on_trace( &self, callback: F, ) -> HandlerId { self.inner().handlers.trace.add(Arc::new(callback)) } /// Callback is called when the transport this producer belongs to is closed for whatever /// reason. The producer itself is also closed. A `on_producer_close` callback is called on all /// its associated consumers. pub fn on_transport_close(&self, callback: F) -> HandlerId { self.inner() .handlers .transport_close .add(Box::new(callback)) } /// Callback is called when the producer is closed for whatever reason. /// /// NOTE: Callback will be called in place if producer is already closed. pub fn on_close(&self, callback: F) -> HandlerId { let handler_id = self.inner().handlers.close.add(Box::new(callback)); if self.inner().closed.load(Ordering::Relaxed) { self.inner().handlers.close.call_simple(); } handler_id } /// Consumable RTP parameters. // This is used in tests, otherwise would have been `pub(super)` #[doc(hidden)] #[must_use] pub fn consumable_rtp_parameters(&self) -> &RtpParameters { &self.inner().consumable_rtp_parameters } pub(super) fn close(&self) { self.inner().close(true); } /// Downgrade `Producer` to [`WeakProducer`] instance. #[must_use] pub fn downgrade(&self) -> WeakProducer { WeakProducer { inner: Arc::downgrade(self.inner()), } } fn inner(&self) -> &Arc { match self { Producer::Regular(producer) => &producer.inner, Producer::Direct(producer) => &producer.inner, } } } impl DirectProducer { /// Sends a RTP packet from the Rust process. pub fn send(&self, rtp_packet: Vec) -> Result<(), NotificationError> { self.inner .channel .notify(self.inner.id, ProducerSendNotification { rtp_packet }) } } /// Same as [`Producer`], but will not be closed when dropped. /// /// The idea here is that [`ProducerId`] of both original [`Producer`] and `PipedProducer` is the /// same and lifetime of piped producer is also tied to original producer, so you may not need to /// store `PipedProducer` at all. /// /// Use [`PipedProducer::into_inner()`] method to get regular [`Producer`] instead and restore /// regular behavior of [`Drop`] implementation. pub struct PipedProducer { producer: Producer, on_drop: Option>, } impl fmt::Debug for PipedProducer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("PipedProducer") .field("producer", &self.producer) .finish() } } impl Drop for PipedProducer { fn drop(&mut self) { if let Some(on_drop) = self.on_drop.take() { on_drop(self.producer.clone()) } } } impl PipedProducer { /// * `on_drop` - Callback that takes last `Producer` instance and must do something with it to /// prevent dropping and thus closing pub(crate) fn new( producer: Producer, on_drop: F, ) -> Self { Self { producer, on_drop: Some(Box::new(on_drop)), } } /// Get inner [`Producer`] (which will close on drop in contrast to `PipedProducer`). pub fn into_inner(mut self) -> Producer { self.on_drop.take(); self.producer.clone() } } /// [`WeakProducer`] doesn't own producer instance on mediasoup-worker and will not prevent one from /// being destroyed once last instance of regular [`Producer`] is dropped. /// /// [`WeakProducer`] vs [`Producer`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakProducer { inner: Weak, } impl fmt::Debug for WeakProducer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakProducer").finish() } } impl WeakProducer { /// Attempts to upgrade `WeakProducer` to [`Producer`] if last instance of one wasn't dropped /// yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; let producer = if inner.direct { Producer::Direct(DirectProducer { inner }) } else { Producer::Regular(RegularProducer { inner }) }; Some(producer) } } ================================================ FILE: rust/src/router/rtp_observer.rs ================================================ use crate::producer::{Producer, ProducerId}; use crate::router::Router; use crate::uuid_based_wrapper_type; use crate::worker::RequestError; use async_trait::async_trait; use event_listener_primitives::HandlerId; use mediasoup_types::data_structures::AppData; uuid_based_wrapper_type!( /// [`RtpObserver`] identifier. RtpObserverId ); /// Options for adding producer to `[RtpObserver`] #[derive(Debug, Clone)] #[non_exhaustive] pub struct RtpObserverAddProducerOptions { /// The id of the Producer to be added. pub producer_id: ProducerId, } impl RtpObserverAddProducerOptions { /// * `producer_id` - The id of the [`Producer`] to be added. #[must_use] pub fn new(producer_id: ProducerId) -> Self { Self { producer_id } } } /// An RTP observer inspects the media received by a set of selected producers. /// /// mediasoup implements the following RTP observers: /// * [`AudioLevelObserver`](crate::audio_level_observer::AudioLevelObserver) /// * [`ActiveSpeakerObserver`](crate::active_speaker_observer::ActiveSpeakerObserver) #[async_trait] pub trait RtpObserver { /// RtpObserver id. #[must_use] fn id(&self) -> RtpObserverId; /// Router to which RTP observer belongs. fn router(&self) -> &Router; /// Whether the RtpObserver is paused. #[must_use] fn paused(&self) -> bool; /// Custom application data. #[must_use] fn app_data(&self) -> &AppData; /// Whether the RTP observer is closed. #[must_use] fn closed(&self) -> bool; /// Pauses the RTP observer. No RTP is inspected until resume() is called. async fn pause(&self) -> Result<(), RequestError>; /// Resumes the RTP observer. RTP is inspected again. async fn resume(&self) -> Result<(), RequestError>; /// Provides the RTP observer with a new producer to monitor. async fn add_producer( &self, rtp_observer_add_producer_options: RtpObserverAddProducerOptions, ) -> Result<(), RequestError>; /// Removes the given producer from the RTP observer. async fn remove_producer(&self, producer_id: ProducerId) -> Result<(), RequestError>; /// Callback is called when the RTP observer is paused. fn on_pause(&self, callback: Box) -> HandlerId; /// Callback is called when the RTP observer is resumed. fn on_resume(&self, callback: Box) -> HandlerId; /// Callback is called when a new producer is added into the RTP observer. fn on_add_producer( &self, callback: Box, ) -> HandlerId; /// Callback is called when a producer is removed from the RTP observer. fn on_remove_producer( &self, callback: Box, ) -> HandlerId; /// Callback is called when the router this RTP observer belongs to is closed for whatever reason. The RTP /// observer itself is also closed. fn on_router_close(&self, callback: Box) -> HandlerId; /// Callback is called when the RTP observer is closed for whatever reason. /// /// NOTE: Callback will be called in place if observer is already closed. fn on_close(&self, callback: Box) -> HandlerId; } ================================================ FILE: rust/src/router/tests.rs ================================================ use crate::router::RouterOptions; use crate::worker::{Worker, WorkerSettings}; use crate::worker_manager::WorkerManager; use futures_lite::future; use std::env; async fn init() -> Worker { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); worker_manager .create_worker(WorkerSettings { enable_liburing: false, ..WorkerSettings::default() }) .await .expect("Failed to create worker") } #[test] fn worker_close_event() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = router.on_close(move || { let _ = close_tx.send(()); }); let (mut worker_close_tx, worker_close_rx) = async_oneshot::oneshot::<()>(); let _handler = router.on_worker_close(move || { let _ = worker_close_tx.send(()); }); worker.close(); worker_close_rx .await .expect("Failed to receive worker_close event"); close_rx.await.expect("Failed to receive close event"); assert!(router.closed()); }); } ================================================ FILE: rust/src/router/transport.rs ================================================ use crate::consumer::{Consumer, ConsumerId, ConsumerOptions, ConsumerType}; use crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType}; use crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType}; use crate::fbs::{FromFbs, ToFbs, TryFromFbs}; use crate::messages::{ TransportConsumeDataRequest, TransportConsumeRequest, TransportDumpRequest, TransportEnableTraceEventRequest, TransportGetStatsRequest, TransportProduceDataRequest, TransportProduceRequest, TransportSetMaxIncomingBitrateRequest, TransportSetMaxOutgoingBitrateRequest, TransportSetMinOutgoingBitrateRequest, }; pub use crate::ortc::{ ConsumerRtpParametersError, RtpCapabilitiesError, RtpParametersError, RtpParametersMappingError, }; use crate::producer::{Producer, ProducerId, ProducerOptions}; use crate::router::Router; use crate::worker::{Channel, RequestError}; use crate::{ortc, uuid_based_wrapper_type}; use async_executor::Executor; use async_trait::async_trait; use event_listener_primitives::HandlerId; use log::warn; use mediasoup_sys::fbs::{response, transport}; use mediasoup_types::data_structures::{ AppData, BweTraceInfo, RtpPacketTraceInfo, TraceEventDirection, }; use mediasoup_types::rtp_parameters::{MediaKind, RtpEncodingParameters}; use mediasoup_types::sctp_parameters::SctpStreamParameters; use nohash_hasher::IntMap; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::error::Error; use std::fmt::Debug; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use thiserror::Error; use uuid::Uuid; uuid_based_wrapper_type!( /// Transport identifier. TransportId ); /// Data contained in transport trace events. /// /// See also "trace" event in the [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging#trace-Event) /// section (TypeScript-oriented, but concepts apply here as well). #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(tag = "type", rename_all = "lowercase")] pub enum TransportTraceEventData { /// RTP probation packet. Probation { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// RTP packet info. info: RtpPacketTraceInfo, }, /// Transport bandwidth estimation changed. Bwe { /// Event timestamp. timestamp: u64, /// Event direction. direction: TraceEventDirection, /// BWE info. info: BweTraceInfo, }, } impl FromFbs for TransportTraceEventData { type FbsType = transport::TraceNotification; fn from_fbs(data: &Self::FbsType) -> Self { match data.type_ { transport::TraceEventType::Probation => unimplemented!(), transport::TraceEventType::Bwe => TransportTraceEventData::Bwe { timestamp: data.timestamp, direction: TraceEventDirection::from_fbs(&data.direction), info: { let Some(transport::TraceInfo::BweTraceInfo(info)) = &data.info else { panic!("Wrong message from worker: {data:?}"); }; BweTraceInfo::from_fbs(info.as_ref()) }, }, } } } /// Valid types for "trace" event. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum TransportTraceEventType { /// RTP probation packet. Probation, /// Transport bandwidth estimation changed. Bwe, } impl ToFbs for TransportTraceEventType { type FbsType = transport::TraceEventType; fn to_fbs(&self) -> Self::FbsType { match self { TransportTraceEventType::Probation => transport::TraceEventType::Probation, TransportTraceEventType::Bwe => transport::TraceEventType::Bwe, } } } impl FromFbs for TransportTraceEventType { type FbsType = transport::TraceEventType; fn from_fbs(event_type: &transport::TraceEventType) -> Self { match event_type { transport::TraceEventType::Probation => TransportTraceEventType::Probation, transport::TraceEventType::Bwe => TransportTraceEventType::Bwe, } } } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct RtpListener { /// Vector of mid and producer ID pub mid_table: Vec<(String, ProducerId)>, /// Vector of rid and producer ID pub rid_table: Vec<(String, ProducerId)>, /// Vector of Ssrc and producer ID pub ssrc_table: Vec<(u32, ProducerId)>, } impl<'a> TryFromFbs<'a> for RtpListener { type FbsType = transport::RtpListener; type Error = Box; fn try_from_fbs(rtp_listener: Self::FbsType) -> Result { Ok(Self { mid_table: rtp_listener .mid_table .iter() .map(|key_value| Ok((key_value.key.to_string(), key_value.value.parse()?))) .collect::>>()?, rid_table: rtp_listener .rid_table .iter() .map(|key_value| Ok((key_value.key.to_string(), key_value.value.parse()?))) .collect::>>()?, ssrc_table: rtp_listener .ssrc_table .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, }) } } #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct RecvRtpHeaderExtensions { mid: Option, rid: Option, rrid: Option, abs_send_time: Option, transport_wide_cc01: Option, } impl FromFbs for RecvRtpHeaderExtensions { type FbsType = transport::RecvRtpHeaderExtensions; fn from_fbs(extensions: &Self::FbsType) -> Self { Self { mid: extensions.mid, rid: extensions.rid, rrid: extensions.rrid, abs_send_time: extensions.abs_send_time, transport_wide_cc01: extensions.transport_wide_cc01, } } } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct SctpListener { /// Vector of stream ID (as string) to data producer ID stream_id_table: Vec<(u16, DataProducerId)>, } impl<'a> TryFromFbs<'a> for SctpListener { type FbsType = transport::SctpListener; type Error = Box; fn try_from_fbs(listener: Self::FbsType) -> Result { Ok(Self { stream_id_table: listener .stream_id_table .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, }) } } #[derive(Debug, Copy, Clone, PartialEq)] pub(super) enum TransportType { Direct, Pipe, Plain, WebRtc, } /// A transport connects an endpoint with a mediasoup router and enables transmission of media in /// both directions by means of [`Producer`], [`Consumer`], [`DataProducer`] and [`DataConsumer`] /// instances created on it. /// /// mediasoup implements the following transports: /// * [`WebRtcTransport`](crate::webrtc_transport::WebRtcTransport) /// * [`PlainTransport`](crate::plain_transport::PlainTransport) /// * [`PipeTransport`](crate::pipe_transport::PipeTransport) /// * [`DirectTransport`](crate::direct_transport::DirectTransport) /// /// For additional methods see [`TransportGeneric`]. #[async_trait] pub trait Transport: Debug + Send + Sync { /// Transport id. #[must_use] fn id(&self) -> TransportId; /// Router to which transport belongs. fn router(&self) -> &Router; /// Custom application data. #[must_use] fn app_data(&self) -> &AppData; /// Whether the transport is closed. #[must_use] fn closed(&self) -> bool; /// Instructs the router to receive audio or video RTP (or SRTP depending on the transport). /// This is the way to inject media into mediasoup. /// /// Transport will be kept alive as long as at least one producer instance is alive. /// /// # Notes on usage /// Check the [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/) /// section for more details (TypeScript-oriented, but concepts apply here as well). async fn produce(&self, producer_options: ProducerOptions) -> Result; /// Instructs the router to send audio or video RTP (or SRTP depending on the transport). /// This is the way to extract media from mediasoup. /// /// Transport will be kept alive as long as at least one consumer instance is alive. /// /// # Notes on usage /// Check the [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/) /// section for more details (TypeScript-oriented, but concepts apply here as well). /// /// When creating a consumer it's recommended to set [`ConsumerOptions::paused`] to `true`, then /// transmit the consumer parameters to the consuming endpoint and, once the consuming endpoint /// has created its local side consumer, unpause the server side consumer using the /// [`Consumer::resume()`] method. /// /// Reasons for create the server side consumer in `paused` mode: /// * If the remote endpoint is a WebRTC browser or application and it receives a RTP packet of /// the new consumer before the remote [`RTCPeerConnection`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) /// is ready to process it (this is, before the remote consumer is created in the remote /// endpoint) it may happen that the `RTCPeerConnection` will wrongly associate the SSRC of /// the received packet to an already existing SDP `m=` section, so the imminent creation of /// the new consumer and its associated `m=` section will fail. /// * Related [issue](https://github.com/versatica/libmediasoupclient/issues/57). /// * Also, when creating a video consumer, this is an optimization to make it possible for the /// consuming endpoint to render the video as far as possible. If the server side consumer was /// created with `paused: false`, mediasoup will immediately request a key frame to the /// producer and that key frame may reach the consuming endpoint even before it's ready to /// consume it, generating "black" video until the device requests a keyframe by itself. async fn consume(&self, consumer_options: ConsumerOptions) -> Result; /// Instructs the router to receive data messages. Those messages can be delivered by an /// endpoint via SCTP protocol (AKA [`DataChannel`](https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel) /// in WebRTC) or can be directly sent from the Rust application if the transport is a /// [`DirectTransport`](crate::direct_transport::DirectTransport). /// /// Transport will be kept alive as long as at least one data producer instance is alive. async fn produce_data( &self, data_producer_options: DataProducerOptions, ) -> Result; /// Instructs the router to send data messages to the endpoint via SCTP protocol (AKA /// [`DataChannel`](https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel) in WebRTC) /// or directly to the Rust process if the transport is a /// [`DirectTransport`](crate::direct_transport::DirectTransport). /// /// Transport will be kept alive as long as at least one data consumer instance is alive. async fn consume_data( &self, data_consumer_options: DataConsumerOptions, ) -> Result; /// Instructs the transport to emit "trace" events. For monitoring purposes. Use with caution. async fn enable_trace_event( &self, types: Vec, ) -> Result<(), RequestError>; /// Callback is called when a new producer is created. fn on_new_producer( &self, callback: Arc, ) -> HandlerId; /// Callback is called when a new consumer is created. fn on_new_consumer( &self, callback: Arc, ) -> HandlerId; /// Callback is called when a new data producer is created. fn on_new_data_producer( &self, callback: Arc, ) -> HandlerId; /// Callback is called when a new data consumer is created. fn on_new_data_consumer( &self, callback: Arc, ) -> HandlerId; /// See [`Transport::enable_trace_event()`] fn on_trace( &self, callback: Arc, ) -> HandlerId; /// Callback is called when the router this transport belongs to is closed for whatever reason. /// The transport itself is also closed. `on_transport_close` callbacks are also called on all /// its producers and consumers. fn on_router_close(&self, callback: Box) -> HandlerId; /// Callback is called when the router is closed for whatever reason. /// /// NOTE: Callback will be called in place if transport is already closed. fn on_close(&self, callback: Box) -> HandlerId; } /// Generic transport trait with methods available on all transports in addition to [`Transport`]. #[async_trait] pub trait TransportGeneric: Transport + Clone + 'static { /// Dump data structure specific to each transport. #[doc(hidden)] type Dump: Debug + 'static; /// Stats data structure specific to each transport. type Stat: Debug + 'static; /// Dump Transport. async fn dump(&self) -> Result; /// Returns current RTC statistics of the transport. Each transport class produces a different /// set of statistics. async fn get_stats(&self) -> Result, RequestError>; } /// Error that caused [`Transport::produce`] to fail. #[derive(Debug, Error)] pub enum ProduceError { /// Producer with the same id already exists. #[error("Producer with the same id \"{0}\" already exists")] AlreadyExists(ProducerId), /// Incorrect RTP parameters. #[error("Incorrect RTP parameters: {0}")] IncorrectRtpParameters(RtpParametersError), /// RTP mapping error. #[error("RTP mapping error: {0}")] FailedRtpParametersMapping(RtpParametersMappingError), /// Request to worker failed. #[error("Request to worker failed: {0}")] Request(RequestError), } /// Error that caused [`Transport::consume`] to fail. #[derive(Debug, Error)] pub enum ConsumeError { /// Producer with specified id not found. #[error("Producer with id \"{0}\" not found")] ProducerNotFound(ProducerId), /// RTP capabilities error. #[error("RTP capabilities error: {0}")] FailedRtpCapabilitiesValidation(RtpCapabilitiesError), /// Bad consumer RTP parameters. #[error("Bad consumer RTP parameters: {0}")] BadConsumerRtpParameters(ConsumerRtpParametersError), /// Request to worker failed. #[error("Request to worker failed: {0}")] Request(RequestError), } /// Error that caused [`Transport::produce_data`] to fail. #[derive(Debug, Error)] pub enum ProduceDataError { /// Data producer with the same id already exists. #[error("Data producer with the same id \"{0}\" already exists")] AlreadyExists(DataProducerId), /// SCTP stream parameters are required for this transport. #[error("SCTP stream parameters are required for this transport")] SctpStreamParametersRequired, /// Request to worker failed. #[error("Request to worker failed: {0}")] Request(RequestError), } /// Error that caused [`Transport::consume_data`] to fail. #[derive(Debug, Error)] pub enum ConsumeDataError { /// Data producer with specified id not found #[error("Data producer with id \"{0}\" not found")] DataProducerNotFound(DataProducerId), /// No free `sctp_stream_id` available in transport. #[error("No free sctp_stream_id available in transport")] NoSctpStreamId, /// Request to worker failed. #[error("Request to worker failed: {0}")] Request(RequestError), } #[async_trait] pub(super) trait TransportImpl: TransportGeneric { fn channel(&self) -> &Channel; fn executor(&self) -> &Arc>; fn next_mid_for_consumers(&self) -> &AtomicUsize; fn used_sctp_stream_ids(&self) -> &Mutex>; fn cname_for_producers(&self) -> &Mutex>; fn allocate_sctp_stream_id(&self) -> Option { let mut used_sctp_stream_ids = self.used_sctp_stream_ids().lock(); // This is simple, but not the fastest implementation, maybe worth improving for (index, used) in used_sctp_stream_ids.iter_mut() { if !*used { *used = true; return Some(*index); } } None } fn deallocate_sctp_stream_id(&self, sctp_stream_id: u16) { let used_sctp_stream_ids = self.used_sctp_stream_ids(); if let Some(used) = used_sctp_stream_ids.lock().get_mut(&sctp_stream_id) { *used = false; } } async fn dump_impl(&self) -> Result { self.channel() .request(self.id(), TransportDumpRequest {}) .await } async fn get_stats_impl(&self) -> Result { self.channel() .request(self.id(), TransportGetStatsRequest {}) .await } async fn set_max_incoming_bitrate_impl(&self, bitrate: u32) -> Result<(), RequestError> { self.channel() .request(self.id(), TransportSetMaxIncomingBitrateRequest { bitrate }) .await } async fn set_max_outgoing_bitrate_impl(&self, bitrate: u32) -> Result<(), RequestError> { self.channel() .request(self.id(), TransportSetMaxOutgoingBitrateRequest { bitrate }) .await } async fn enable_trace_event_impl( &self, types: Vec, ) -> Result<(), RequestError> { self.channel() .request(self.id(), TransportEnableTraceEventRequest { types }) .await } async fn set_min_outgoing_bitrate_impl(&self, bitrate: u32) -> Result<(), RequestError> { self.channel() .request(self.id(), TransportSetMinOutgoingBitrateRequest { bitrate }) .await } async fn produce_impl( &self, producer_options: ProducerOptions, transport_type: TransportType, ) -> Result { if let Some(id) = &producer_options.id { if self.router().has_producer(id) { return Err(ProduceError::AlreadyExists(*id)); } } let ProducerOptions { id, kind, mut rtp_parameters, paused, key_frame_request_delay, enable_mediasoup_packet_id_header_extension, app_data, } = producer_options; ortc::validate_rtp_parameters(&rtp_parameters) .map_err(ProduceError::IncorrectRtpParameters)?; if rtp_parameters.encodings.is_empty() { rtp_parameters .encodings .push(RtpEncodingParameters::default()); } // Don't do this in PipeTransports since there we must keep CNAME value in each Producer. if transport_type != TransportType::Pipe { let mut cname_for_producers = self.cname_for_producers().lock(); if let Some(cname_for_producers) = cname_for_producers.as_ref() { rtp_parameters.rtcp.cname = Some(cname_for_producers.clone()); } else if let Some(cname) = rtp_parameters.rtcp.cname.as_ref() { // If CNAME is given and we don't have yet a CNAME for Producers in this // Transport, take it. cname_for_producers.replace(cname.clone()); } else { // Otherwise if we don't have yet a CNAME for Producers and the RTP parameters // do not include CNAME, create a random one. let cname = Uuid::new_v4().to_string(); cname_for_producers.replace(cname.clone()); // Override Producer's CNAME. rtp_parameters.rtcp.cname = Some(cname); } } let router_rtp_capabilities = self.router().rtp_capabilities(); let rtp_mapping = ortc::get_producer_rtp_parameters_mapping(&rtp_parameters, &router_rtp_capabilities) .map_err(ProduceError::FailedRtpParametersMapping)?; let consumable_rtp_parameters = ortc::get_consumable_rtp_parameters( kind, &rtp_parameters, &router_rtp_capabilities, &rtp_mapping, ); let producer_id = id.unwrap_or_else(ProducerId::new); let _buffer_guard = self.channel().buffer_messages_for(producer_id.into()); let response = self .channel() .request( self.id(), TransportProduceRequest { producer_id, kind, rtp_parameters: rtp_parameters.clone(), rtp_mapping, key_frame_request_delay, enable_mediasoup_packet_id_header_extension, paused, }, ) .await .map_err(ProduceError::Request)?; let producer_fut = Producer::new( producer_id, kind, response.r#type, rtp_parameters, consumable_rtp_parameters, paused, Arc::clone(self.executor()), self.channel().clone(), app_data, Arc::new(self.clone()), transport_type == TransportType::Direct, ); Ok(producer_fut.await) } async fn consume_impl( &self, consumer_options: ConsumerOptions, transport_type: TransportType, rtx: bool, ) -> Result { let ConsumerOptions { producer_id, rtp_capabilities, paused, mid, preferred_layers, enable_rtx, ignore_dtx, pipe, app_data, } = consumer_options; ortc::validate_rtp_capabilities(&rtp_capabilities) .map_err(ConsumeError::FailedRtpCapabilitiesValidation)?; let producer = match self.router().get_producer(&producer_id) { Some(producer) => producer, None => { return Err(ConsumeError::ProducerNotFound(producer_id)); } }; let enable_rtx = enable_rtx.unwrap_or(producer.kind() == MediaKind::Video); let rtp_parameters = if transport_type == TransportType::Pipe { ortc::get_pipe_consumer_rtp_parameters(producer.consumable_rtp_parameters(), rtx) } else { let mut rtp_parameters = ortc::get_consumer_rtp_parameters( producer.consumable_rtp_parameters(), &rtp_capabilities, pipe, enable_rtx, ) .map_err(ConsumeError::BadConsumerRtpParameters)?; if !pipe { // Set MID. rtp_parameters.mid = mid.or_else(|| { // We use up to 8 bytes for MID (string). let next_mid_for_consumers = self .next_mid_for_consumers() .fetch_add(1, Ordering::Relaxed); let mid = next_mid_for_consumers % 100_000_000; Some(format!("{mid}")) }) } rtp_parameters }; let consumer_id = ConsumerId::new(); let r#type = if transport_type == TransportType::Pipe || pipe { ConsumerType::Pipe } else { producer.r#type().into() }; let _buffer_guard = self.channel().buffer_messages_for(consumer_id.into()); let response = self .channel() .request( self.id(), TransportConsumeRequest { consumer_id, producer_id: producer.id(), kind: producer.kind(), rtp_parameters: rtp_parameters.clone(), r#type, consumable_rtp_encodings: producer .consumable_rtp_parameters() .encodings .clone(), paused, preferred_layers, ignore_dtx, }, ) .await .map_err(ConsumeError::Request)?; Ok(Consumer::new( consumer_id, producer, r#type, rtp_parameters, response.paused, Arc::clone(self.executor()), self.channel().clone(), response.producer_paused, response.score, response.preferred_layers, app_data, Arc::new(self.clone()), )) } async fn produce_data_impl( &self, r#type: DataProducerType, data_producer_options: DataProducerOptions, transport_type: TransportType, ) -> Result { if let Some(id) = &data_producer_options.id { if self.router().has_data_producer(id) { return Err(ProduceDataError::AlreadyExists(*id)); } } match r#type { DataProducerType::Sctp => { if data_producer_options.sctp_stream_parameters.is_none() { return Err(ProduceDataError::SctpStreamParametersRequired); } } DataProducerType::Direct => { if data_producer_options.sctp_stream_parameters.is_some() { warn!( "sctp_stream_parameters are ignored when producing data on a DirectTransport", ); } } } let DataProducerOptions { id, sctp_stream_parameters, label, protocol, paused, app_data, } = data_producer_options; let data_producer_id = id.unwrap_or_else(DataProducerId::new); let _buffer_guard = self.channel().buffer_messages_for(data_producer_id.into()); let response = self .channel() .request( self.id(), TransportProduceDataRequest { data_producer_id, r#type, sctp_stream_parameters, label, protocol, paused, }, ) .await .map_err(ProduceDataError::Request)?; Ok(DataProducer::new( data_producer_id, response.r#type, response.sctp_stream_parameters, response.label, response.protocol, response.paused, Arc::clone(self.executor()), self.channel().clone(), app_data, Arc::new(self.clone()), transport_type == TransportType::Direct, )) } async fn consume_data_impl( &self, r#type: DataConsumerType, data_consumer_options: DataConsumerOptions, transport_type: TransportType, ) -> Result { let DataConsumerOptions { data_producer_id, ordered, max_packet_life_time, max_retransmits, paused, subchannels, app_data, } = data_consumer_options; let data_producer = match self.router().get_data_producer(&data_producer_id) { Some(data_producer) => data_producer, None => { return Err(ConsumeDataError::DataProducerNotFound(data_producer_id)); } }; let sctp_stream_parameters = match r#type { DataConsumerType::Sctp => { let stream_id = self .allocate_sctp_stream_id() .ok_or(ConsumeDataError::NoSctpStreamId)?; let mut sctp_stream_parameters = data_producer.sctp_stream_parameters().map_or( SctpStreamParameters { stream_id, ordered: true, max_packet_life_time: None, max_retransmits: None, }, |mut sctp_parameters| { sctp_parameters.stream_id = stream_id; sctp_parameters }, ); if let Some(ordered) = ordered { sctp_stream_parameters.ordered = ordered; if ordered { sctp_stream_parameters.max_packet_life_time = None; sctp_stream_parameters.max_retransmits = None; } } if ordered != Some(true) { if let Some(max_packet_life_time) = max_packet_life_time { sctp_stream_parameters.ordered = false; sctp_stream_parameters.max_packet_life_time = Some(max_packet_life_time); } if let Some(max_retransmits) = max_retransmits { sctp_stream_parameters.ordered = false; sctp_stream_parameters.max_retransmits = Some(max_retransmits); } } Some(sctp_stream_parameters) } DataConsumerType::Direct => { if ordered.is_some() || max_packet_life_time.is_some() || max_retransmits.is_some() { warn!("ordered, max_packet_life_time and max_retransmits are ignored when consuming data on a DirectTransport"); } None } }; let data_consumer_id = DataConsumerId::new(); let _buffer_guard = self.channel().buffer_messages_for(data_consumer_id.into()); let response = self .channel() .request( self.id(), TransportConsumeDataRequest { data_consumer_id, data_producer_id: data_producer.id(), r#type, sctp_stream_parameters, label: data_producer.label().clone(), protocol: data_producer.protocol().clone(), subchannels, paused, }, ) .await .map_err(ConsumeDataError::Request)?; let data_consumer = DataConsumer::new( data_consumer_id, response.r#type, response.sctp_stream_parameters, response.label, response.protocol, response.paused, data_producer, Arc::clone(self.executor()), self.channel().clone(), response.data_producer_paused, response.subchannels, app_data, Arc::new(self.clone()), transport_type == TransportType::Direct, ); if let Some(sctp_stream_parameters) = data_consumer.sctp_stream_parameters() { let stream_id = sctp_stream_parameters.stream_id; let transport = self.clone(); data_consumer .on_close(move || { transport.deallocate_sctp_stream_id(stream_id); }) .detach(); } Ok(data_consumer) } } ================================================ FILE: rust/src/router/webrtc_transport/tests.rs ================================================ use crate::prelude::WebRtcTransport; use crate::router::{NewTransport, Router, RouterOptions}; use crate::transport::Transport; use crate::webrtc_server::{WebRtcServerIpPort, WebRtcServerListenInfos, WebRtcServerOptions}; use crate::webrtc_transport::{WebRtcTransportListenInfos, WebRtcTransportOptions}; use crate::worker::{Worker, WorkerSettings}; use crate::worker_manager::WorkerManager; use async_io::Timer; use futures_lite::future; use hash_hasher::HashedSet; use mediasoup_types::data_structures::{ IceCandidateTcpType, IceCandidateType, IceState, ListenInfo, Protocol, }; use parking_lot::Mutex; use portpicker::pick_unused_port; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::sync::Arc; use std::time::Duration; async fn init() -> (Worker, Router) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); (worker, router) } #[test] fn create_with_webrtc_server_succeeds() { future::block_on(async move { let (worker, router) = init().await; let port1 = pick_unused_port().unwrap(); let port2 = pick_unused_port().unwrap(); let webrtc_server = worker .create_webrtc_server({ let listen_infos = WebRtcServerListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port1), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); let listen_infos = listen_infos.insert(ListenInfo { protocol: Protocol::Tcp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port2), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); WebRtcServerOptions::new(listen_infos) }) .await .expect("Failed to create WebRTC server"); let (new_server_transport_tx, new_server_transport_rx) = async_oneshot::oneshot::(); let _handler = webrtc_server.on_new_webrtc_transport({ let new_server_transport_tx = Arc::new(Mutex::new(Some(new_server_transport_tx))); move |transport| { let _ = new_server_transport_tx .lock() .take() .unwrap() .send(transport.clone()); } }); let (new_router_transport_tx, new_router_transport_rx) = async_oneshot::oneshot::(); let _handler = router.on_new_transport({ let new_router_transport_tx = Arc::new(Mutex::new(Some(new_router_transport_tx))); move |transport| { if let NewTransport::WebRtc(transport) = transport { let _ = new_router_transport_tx .lock() .take() .unwrap() .send(transport.clone()); } } }); let transport = router .create_webrtc_transport({ let mut webrtc_transport_options = WebRtcTransportOptions::new_with_server(webrtc_server.clone()); // Let's disable UDP so resulting ICE candidates should only contain TCP. webrtc_transport_options.enable_udp = false; webrtc_transport_options }) .await .expect("Failed to create WebRTC transport"); let router_dump = router.dump().await.expect("Failed to dump router"); assert_eq!(router_dump.transport_ids, { let mut set = HashedSet::default(); set.insert(transport.id()); set }); assert_eq!(new_server_transport_rx.await.unwrap().id(), transport.id()); assert_eq!(new_router_transport_rx.await.unwrap().id(), transport.id()); assert!(!transport.closed()); { let ice_candidates = transport.ice_candidates(); assert_eq!(ice_candidates.len(), 1); assert_eq!(ice_candidates[0].address, "127.0.0.1"); assert_eq!(ice_candidates[0].protocol, Protocol::Tcp); assert_eq!(ice_candidates[0].port, port2); assert_eq!(ice_candidates[0].r#type, IceCandidateType::Host); assert_eq!( ice_candidates[0].tcp_type, Some(IceCandidateTcpType::Passive) ); } assert_eq!(transport.ice_state(), IceState::New); assert_eq!(transport.ice_selected_tuple(), None); { let webrtc_server_dump = webrtc_server .dump() .await .expect("Failed to dump WebRTC server"); assert_eq!(webrtc_server_dump.id, webrtc_server.id()); assert_eq!( webrtc_server_dump.udp_sockets, vec![WebRtcServerIpPort { ip: IpAddr::V4(Ipv4Addr::LOCALHOST), port: port1 }] ); assert_eq!( webrtc_server_dump.tcp_servers, vec![WebRtcServerIpPort { ip: IpAddr::V4(Ipv4Addr::LOCALHOST), port: port2 }] ); assert_eq!(webrtc_server_dump.webrtc_transport_ids, { let mut set = HashedSet::default(); set.insert(transport.id()); set }); assert_eq!(webrtc_server_dump.local_ice_username_fragments.len(), 1); assert_eq!( webrtc_server_dump.local_ice_username_fragments[0].webrtc_transport_id, transport.id() ); assert_eq!(webrtc_server_dump.tuple_hashes, vec![]); } { let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_close(Box::new(move || { let _ = tx.send(()); })); drop(transport); rx.await.expect("Failed to receive close event"); } // Drop is async, give consumer a bit of time to finish Timer::after(Duration::from_millis(200)).await; { let webrtc_server_dump = webrtc_server .dump() .await .expect("Failed to dump WebRTC server"); assert_eq!(webrtc_server_dump.id, webrtc_server.id()); assert_eq!( webrtc_server_dump.udp_sockets, vec![WebRtcServerIpPort { ip: IpAddr::V4(Ipv4Addr::LOCALHOST), port: port1 }] ); assert_eq!( webrtc_server_dump.tcp_servers, vec![WebRtcServerIpPort { ip: IpAddr::V4(Ipv4Addr::LOCALHOST), port: port2 }] ); assert_eq!( webrtc_server_dump.webrtc_transport_ids, HashedSet::default() ); assert_eq!(webrtc_server_dump.local_ice_username_fragments, vec![]); assert_eq!(webrtc_server_dump.tuple_hashes, vec![]); } }); } #[test] fn router_close_event() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_close(Box::new(move || { let _ = close_tx.send(()); })); let (mut router_close_tx, router_close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_router_close(Box::new(move || { let _ = router_close_tx.send(()); })); router.close(); router_close_rx .await .expect("Failed to receive router_close event"); close_rx.await.expect("Failed to receive close event"); }); } #[test] fn webrtc_server_close_event() { future::block_on(async move { let (worker, router) = init().await; let port1 = pick_unused_port().unwrap(); let port2 = pick_unused_port().unwrap(); let webrtc_server = worker .create_webrtc_server({ let listen_infos = WebRtcServerListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port1), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); let listen_infos = listen_infos.insert(ListenInfo { protocol: Protocol::Tcp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port2), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); WebRtcServerOptions::new(listen_infos) }) .await .expect("Failed to create WebRTC server"); let transport = router .create_webrtc_transport(WebRtcTransportOptions::new_with_server( webrtc_server.clone(), )) .await .expect("Failed to create WebRTC transport"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_close(Box::new(move || { let _ = close_tx.send(()); })); let (mut webrtc_server_close_tx, webrtc_server_close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_webrtc_server_close(Box::new(move || { let _ = webrtc_server_close_tx.send(()); })); webrtc_server.close(); webrtc_server_close_rx .await .expect("Failed to receive webrtc_server_close event"); close_rx.await.expect("Failed to receive close event"); }); } ================================================ FILE: rust/src/router/webrtc_transport.rs ================================================ #[cfg(test)] mod tests; use crate::consumer::{Consumer, ConsumerId, ConsumerOptions}; use crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions, DataConsumerType}; use crate::data_producer::{DataProducer, DataProducerId, DataProducerOptions, DataProducerType}; use crate::fbs::{FromFbs, TryFromFbs}; use crate::messages::{ TransportCloseRequest, TransportRestartIceRequest, WebRtcTransportConnectRequest, WebRtcTransportData, }; use crate::producer::{Producer, ProducerId, ProducerOptions}; use crate::router::transport::{TransportImpl, TransportType}; use crate::router::Router; use crate::transport::{ ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, RecvRtpHeaderExtensions, RtpListener, SctpListener, Transport, TransportGeneric, TransportId, TransportTraceEventData, TransportTraceEventType, }; use crate::webrtc_server::WebRtcServer; use crate::worker::{Channel, NotificationParseError, RequestError, SubscriptionHandler}; use async_executor::Executor; use async_trait::async_trait; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use log::{debug, error}; use mediasoup_sys::fbs::{notification, response, transport, web_rtc_transport}; use mediasoup_types::data_structures::{ AppData, DtlsParameters, DtlsState, IceCandidate, IceParameters, IceRole, IceState, ListenInfo, SctpState, TransportTuple, }; use mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters}; use nohash_hasher::IntMap; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::error::Error; use std::fmt; use std::ops::Deref; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::{Arc, Weak}; use thiserror::Error; /// Struct that protects an invariant of having non-empty list of listen IPs #[derive(Debug, Clone, Eq, PartialEq, Serialize)] pub struct WebRtcTransportListenInfos(Vec); impl WebRtcTransportListenInfos { /// Create transport listen IPs with given IP populated initially. #[must_use] pub fn new(listen_info: ListenInfo) -> Self { Self(vec![listen_info]) } /// Insert another listen IP. #[must_use] pub fn insert(mut self, listen_info: ListenInfo) -> Self { self.0.push(listen_info); self } } impl Deref for WebRtcTransportListenInfos { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } /// Empty list of listen IPs provided, should have at least one element #[derive(Error, Debug, Eq, PartialEq)] #[error("Empty list of listen IPs provided, should have at least one element")] pub struct EmptyListError; impl TryFrom> for WebRtcTransportListenInfos { type Error = EmptyListError; fn try_from(listen_infos: Vec) -> Result { if listen_infos.is_empty() { Err(EmptyListError) } else { Ok(Self(listen_infos)) } } } /// How [`WebRtcTransport`] should listen on interfaces. /// /// # Notes on usage /// * Do not use "0.0.0.0" into `listen_infos`. Values in `listen_infos` must be specific bindable IPs /// on the host. /// * If you use "0.0.0.0" or "::" into `listen_infos`, then you need to also provide /// `announced_address` in the corresponding entry in `listen_infos`. #[derive(Debug, Clone)] pub enum WebRtcTransportListen { /// Listen on individual protocol/IP/port combinations specific to this transport. Individual { /// Listening infos in order of preference (first one is the preferred one). listen_infos: WebRtcTransportListenInfos, }, /// Share [`WebRtcServer`] with other transports withing the same worker. Server { /// [`WebRtcServer`] to use. webrtc_server: WebRtcServer, }, } /// [`WebRtcTransport`] options. /// /// # Notes on usage /// * `initial_available_outgoing_bitrate` is just applied when the consumer endpoint supports REMB /// or Transport-CC. #[derive(Debug, Clone)] #[non_exhaustive] pub struct WebRtcTransportOptions { /// How [`WebRtcTransport`] should listen on interfaces. pub listen: WebRtcTransportListen, /// Initial available outgoing bitrate (in bps). /// Default 600000. pub initial_available_outgoing_bitrate: u32, /// Enable UDP. /// Default true. pub enable_udp: bool, /// Enable TCP. /// Default true if webrtc_server is given, false otherwise. pub enable_tcp: bool, /// Prefer UDP. /// Default false. pub prefer_udp: bool, /// Prefer TCP. /// Default false. pub prefer_tcp: bool, /// ICE consent timeout (in seconds). If 0 it is disabled. /// Default 30. pub ice_consent_timeout: u8, /// Create a SCTP association. /// Default false. pub enable_sctp: bool, /// SCTP streams number. pub num_sctp_streams: NumSctpStreams, /// Maximum allowed size for SCTP messages sent by DataProducers. // Default 262144. pub max_sctp_message_size: u32, /// Maximum SCTP send buffer used by DataConsumers. /// Default 262144. pub sctp_send_buffer_size: u32, /// Custom application data. pub app_data: AppData, } impl WebRtcTransportOptions { /// Create [`WebRtcTransport`] options with given listen infos. #[must_use] pub fn new(listen_infos: WebRtcTransportListenInfos) -> Self { Self { listen: WebRtcTransportListen::Individual { listen_infos }, initial_available_outgoing_bitrate: 600_000, enable_udp: true, enable_tcp: false, prefer_udp: false, prefer_tcp: false, ice_consent_timeout: 30, enable_sctp: false, num_sctp_streams: NumSctpStreams::default(), max_sctp_message_size: 262_144, sctp_send_buffer_size: 262_144, app_data: AppData::default(), } } /// Create [`WebRtcTransport`] options with given [`WebRtcServer`]. #[must_use] pub fn new_with_server(webrtc_server: WebRtcServer) -> Self { Self { listen: WebRtcTransportListen::Server { webrtc_server }, initial_available_outgoing_bitrate: 600_000, enable_udp: true, enable_tcp: true, prefer_udp: false, prefer_tcp: false, ice_consent_timeout: 30, enable_sctp: false, num_sctp_streams: NumSctpStreams::default(), max_sctp_message_size: 262_144, sctp_send_buffer_size: 262_144, app_data: AppData::default(), } } } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct WebRtcTransportDump { // Common to all Transports. pub id: TransportId, pub direct: bool, pub producer_ids: Vec, pub consumer_ids: Vec, pub map_ssrc_consumer_id: IntMap, pub map_rtx_ssrc_consumer_id: IntMap, pub data_producer_ids: Vec, pub data_consumer_ids: Vec, pub recv_rtp_header_extensions: RecvRtpHeaderExtensions, pub rtp_listener: RtpListener, pub max_message_size: u32, pub sctp_parameters: Option, pub sctp_state: Option, pub sctp_listener: Option, pub trace_event_types: Vec, // WebRtcTransport specific. pub dtls_parameters: DtlsParameters, pub dtls_state: DtlsState, pub ice_candidates: Vec, pub ice_parameters: IceParameters, pub ice_role: IceRole, pub ice_state: IceState, pub ice_selected_tuple: Option, } impl<'a> TryFromFbs<'a> for WebRtcTransportDump { type FbsType = web_rtc_transport::DumpResponse; type Error = Box; fn try_from_fbs(dump: Self::FbsType) -> Result { Ok(Self { // Common to all Transports. id: dump.base.id.parse()?, direct: false, producer_ids: dump .base .producer_ids .iter() .map(|producer_id| Ok(producer_id.parse()?)) .collect::>>()?, consumer_ids: dump .base .consumer_ids .iter() .map(|consumer_id| Ok(consumer_id.parse()?)) .collect::>>()?, map_ssrc_consumer_id: dump .base .map_ssrc_consumer_id .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, map_rtx_ssrc_consumer_id: dump .base .map_rtx_ssrc_consumer_id .iter() .map(|key_value| Ok((key_value.key, key_value.value.parse()?))) .collect::>>()?, data_producer_ids: dump .base .data_producer_ids .iter() .map(|data_producer_id| Ok(data_producer_id.parse()?)) .collect::>>()?, data_consumer_ids: dump .base .data_consumer_ids .iter() .map(|data_consumer_id| Ok(data_consumer_id.parse()?)) .collect::>>()?, recv_rtp_header_extensions: RecvRtpHeaderExtensions::from_fbs( dump.base.recv_rtp_header_extensions.as_ref(), ), rtp_listener: RtpListener::try_from_fbs(*dump.base.rtp_listener)?, max_message_size: dump.base.max_message_size, sctp_parameters: dump .base .sctp_parameters .as_ref() .map(|parameters| SctpParameters::from_fbs(parameters.as_ref())), sctp_state: FromFbs::from_fbs(&dump.base.sctp_state), sctp_listener: dump.base.sctp_listener.as_ref().map(|listener| { SctpListener::try_from_fbs(listener.as_ref().clone()) .expect("Error parsing SctpListner") }), trace_event_types: dump .base .trace_event_types .iter() .map(TransportTraceEventType::from_fbs) .collect(), // WebRtcTransport specific. dtls_parameters: DtlsParameters::from_fbs(dump.dtls_parameters.as_ref()), dtls_state: DtlsState::from_fbs(&dump.dtls_state), ice_candidates: dump .ice_candidates .iter() .map(IceCandidate::from_fbs) .collect(), ice_parameters: IceParameters::from_fbs(dump.ice_parameters.as_ref()), ice_role: IceRole::from_fbs(&dump.ice_role), ice_state: IceState::from_fbs(&dump.ice_state), ice_selected_tuple: dump .ice_selected_tuple .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())), }) } } /// RTC statistics of the [`WebRtcTransport`]. #[derive(Debug, Clone, PartialOrd, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] #[allow(missing_docs)] pub struct WebRtcTransportStat { // Common to all Transports. // `type` field is present in worker, but ignored here pub transport_id: TransportId, pub timestamp: u64, pub sctp_state: Option, pub bytes_received: u64, pub recv_bitrate: u32, pub bytes_sent: u64, pub send_bitrate: u32, pub rtp_bytes_received: u64, pub rtp_recv_bitrate: u32, pub rtp_bytes_sent: u64, pub rtp_send_bitrate: u32, pub rtx_bytes_received: u64, pub rtx_recv_bitrate: u32, pub rtx_bytes_sent: u64, pub rtx_send_bitrate: u32, pub probation_bytes_sent: u64, pub probation_send_bitrate: u32, #[serde(skip_serializing_if = "Option::is_none")] pub available_outgoing_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub available_incoming_bitrate: Option, pub max_incoming_bitrate: Option, pub max_outgoing_bitrate: Option, pub min_outgoing_bitrate: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rtp_packet_loss_received: Option, #[serde(skip_serializing_if = "Option::is_none")] pub rtp_packet_loss_sent: Option, // WebRtcTransport specific. pub ice_role: IceRole, pub ice_state: IceState, #[serde(skip_serializing_if = "Option::is_none")] pub ice_selected_tuple: Option, pub dtls_state: DtlsState, } impl<'a> TryFromFbs<'a> for WebRtcTransportStat { type FbsType = web_rtc_transport::GetStatsResponse; type Error = Box; fn try_from_fbs(stats: Self::FbsType) -> Result { Ok(Self { transport_id: stats.base.transport_id.parse()?, timestamp: stats.base.timestamp, sctp_state: FromFbs::from_fbs(&stats.base.sctp_state), bytes_received: stats.base.bytes_received, recv_bitrate: stats.base.recv_bitrate, bytes_sent: stats.base.bytes_sent, send_bitrate: stats.base.send_bitrate, rtp_bytes_received: stats.base.rtp_bytes_received, rtp_recv_bitrate: stats.base.rtp_recv_bitrate, rtp_bytes_sent: stats.base.rtp_bytes_sent, rtp_send_bitrate: stats.base.rtp_send_bitrate, rtx_bytes_received: stats.base.rtx_bytes_received, rtx_recv_bitrate: stats.base.rtx_recv_bitrate, rtx_bytes_sent: stats.base.rtx_bytes_sent, rtx_send_bitrate: stats.base.rtx_send_bitrate, probation_bytes_sent: stats.base.probation_bytes_sent, probation_send_bitrate: stats.base.probation_send_bitrate, available_outgoing_bitrate: stats.base.available_outgoing_bitrate, available_incoming_bitrate: stats.base.available_incoming_bitrate, max_incoming_bitrate: stats.base.max_incoming_bitrate, max_outgoing_bitrate: stats.base.max_outgoing_bitrate, min_outgoing_bitrate: stats.base.min_outgoing_bitrate, rtp_packet_loss_received: stats.base.rtp_packet_loss_received, rtp_packet_loss_sent: stats.base.rtp_packet_loss_sent, // WebRtcTransport specific. ice_role: IceRole::from_fbs(&stats.ice_role), ice_state: IceState::from_fbs(&stats.ice_state), ice_selected_tuple: stats .ice_selected_tuple .map(|tuple| TransportTuple::from_fbs(tuple.as_ref())), dtls_state: DtlsState::from_fbs(&stats.dtls_state), }) } } /// Remote parameters for [`WebRtcTransport`]. #[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct WebRtcTransportRemoteParameters { /// Remote DTLS parameters. pub dtls_parameters: DtlsParameters, } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { new_producer: Bag, Producer>, new_consumer: Bag, Consumer>, new_data_producer: Bag, DataProducer>, new_data_consumer: Bag, DataConsumer>, ice_state_change: Bag>, ice_selected_tuple_change: Bag, TransportTuple>, dtls_state_change: Bag>, sctp_state_change: Bag>, trace: Bag, TransportTraceEventData>, router_close: BagOnce>, webrtc_server_close: BagOnce>, close: BagOnce>, } #[derive(Debug, Deserialize)] #[serde(tag = "event", rename_all = "lowercase", content = "data")] enum Notification { #[serde(rename_all = "camelCase")] IceStateChange { ice_state: IceState, }, #[serde(rename_all = "camelCase")] IceSelectedTupleChange { ice_selected_tuple: TransportTuple, }, #[serde(rename_all = "camelCase")] DtlsStateChange { dtls_state: DtlsState, dtls_remote_cert: Option, }, #[serde(rename_all = "camelCase")] SctpStateChange { sctp_state: SctpState, }, Trace(TransportTraceEventData), } impl<'a> TryFromFbs<'a> for Notification { type FbsType = notification::NotificationRef<'a>; type Error = NotificationParseError; fn try_from_fbs(notification: Self::FbsType) -> Result { match notification.event().unwrap() { notification::Event::WebrtctransportIceStateChange => { let Ok(Some(notification::BodyRef::WebRtcTransportIceStateChangeNotification( body, ))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let ice_state = IceState::from_fbs(&body.ice_state().unwrap()); Ok(Notification::IceStateChange { ice_state }) } notification::Event::WebrtctransportIceSelectedTupleChange => { let Ok(Some( notification::BodyRef::WebRtcTransportIceSelectedTupleChangeNotification(body), )) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let ice_selected_tuple_fbs = transport::Tuple::try_from(body.tuple().unwrap()).unwrap(); let ice_selected_tuple = TransportTuple::from_fbs(&ice_selected_tuple_fbs); Ok(Notification::IceSelectedTupleChange { ice_selected_tuple }) } notification::Event::WebrtctransportDtlsStateChange => { let Ok(Some(notification::BodyRef::WebRtcTransportDtlsStateChangeNotification( body, ))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let dtls_state = DtlsState::from_fbs(&body.dtls_state().unwrap()); Ok(Notification::DtlsStateChange { dtls_state, dtls_remote_cert: None, }) } notification::Event::TransportSctpStateChange => { let Ok(Some(notification::BodyRef::TransportSctpStateChangeNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let sctp_state = SctpState::from_fbs(&body.sctp_state().unwrap()); Ok(Notification::SctpStateChange { sctp_state }) } notification::Event::TransportTrace => { let Ok(Some(notification::BodyRef::TransportTraceNotification(body))) = notification.body() else { panic!("Wrong message from worker: {notification:?}"); }; let trace_notification_fbs = transport::TraceNotification::try_from(body).unwrap(); let trace_notification = TransportTraceEventData::from_fbs(&trace_notification_fbs); Ok(Notification::Trace(trace_notification)) } _ => Err(NotificationParseError::InvalidEvent), } } } struct Inner { id: TransportId, next_mid_for_consumers: AtomicUsize, used_sctp_stream_ids: Mutex>, cname_for_producers: Mutex>, executor: Arc>, channel: Channel, handlers: Arc, data: Arc, app_data: AppData, // Make sure WebRTC server is not dropped until this transport is not dropped webrtc_server: Option, // Make sure router is not dropped until this transport is not dropped router: Router, closed: AtomicBool, // Drop subscription to transport-specific notifications when transport itself is dropped _subscription_handler: Mutex>, _on_webrtc_server_close_handler: Mutex>, _on_router_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(true); } } impl Inner { fn close(&self, close_request: bool) { if !self.closed.swap(true, Ordering::SeqCst) { debug!("close()"); self.handlers.close.call_simple(); if close_request { let channel = self.channel.clone(); let router_id = self.router.id(); let request = TransportCloseRequest { transport_id: self.id, }; self.executor .spawn(async move { match channel.request(router_id, request).await { Err(RequestError::ChannelClosed) => { debug!("transport closing failed on drop: Channel already closed"); } Err(error) => { error!("transport closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// A [`WebRtcTransport`] represents a network path negotiated by both, a WebRTC endpoint and /// mediasoup, via ICE and DTLS procedures. A [`WebRtcTransport`] may be used to receive media, to /// send media or to both receive and send. There is no limitation in mediasoup. However, due to /// their design, mediasoup-client and libmediasoupclient require separate [`WebRtcTransport`]s for /// sending and receiving. /// /// # Notes on usage /// The [`WebRtcTransport`] implementation of mediasoup is /// [ICE Lite](https://tools.ietf.org/html/rfc5245#section-2.7), meaning that it does not initiate /// ICE connections but expects ICE Binding Requests from endpoints. #[derive(Clone)] #[must_use = "Transport will be closed on drop, make sure to keep it around for as long as needed"] pub struct WebRtcTransport { inner: Arc, } impl fmt::Debug for WebRtcTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WebRtcTransport") .field("id", &self.inner.id) .field("next_mid_for_consumers", &self.inner.next_mid_for_consumers) .field("used_sctp_stream_ids", &self.inner.used_sctp_stream_ids) .field("cname_for_producers", &self.inner.cname_for_producers) .field("router", &self.inner.router) .field("closed", &self.inner.closed) .finish() } } #[async_trait] impl Transport for WebRtcTransport { fn id(&self) -> TransportId { self.inner.id } fn router(&self) -> &Router { &self.inner.router } fn app_data(&self) -> &AppData { &self.inner.app_data } fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } async fn produce(&self, producer_options: ProducerOptions) -> Result { debug!("produce()"); let producer = self .produce_impl(producer_options, TransportType::WebRtc) .await?; self.inner.handlers.new_producer.call_simple(&producer); Ok(producer) } async fn consume(&self, consumer_options: ConsumerOptions) -> Result { debug!("consume()"); let consumer = self .consume_impl(consumer_options, TransportType::WebRtc, false) .await?; self.inner.handlers.new_consumer.call_simple(&consumer); Ok(consumer) } async fn produce_data( &self, data_producer_options: DataProducerOptions, ) -> Result { debug!("produce_data()"); let data_producer = self .produce_data_impl( DataProducerType::Sctp, data_producer_options, TransportType::WebRtc, ) .await?; self.inner .handlers .new_data_producer .call_simple(&data_producer); Ok(data_producer) } async fn consume_data( &self, data_consumer_options: DataConsumerOptions, ) -> Result { debug!("consume_data()"); let data_consumer = self .consume_data_impl( DataConsumerType::Sctp, data_consumer_options, TransportType::WebRtc, ) .await?; self.inner .handlers .new_data_consumer .call_simple(&data_consumer); Ok(data_consumer) } async fn enable_trace_event( &self, types: Vec, ) -> Result<(), RequestError> { debug!("enable_trace_event()"); self.enable_trace_event_impl(types).await } fn on_new_producer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_producer.add(callback) } fn on_new_consumer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_consumer.add(callback) } fn on_new_data_producer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_data_producer.add(callback) } fn on_new_data_consumer( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.new_data_consumer.add(callback) } fn on_trace( &self, callback: Arc, ) -> HandlerId { self.inner.handlers.trace.add(callback) } fn on_router_close(&self, callback: Box) -> HandlerId { self.inner.handlers.router_close.add(callback) } fn on_close(&self, callback: Box) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } } #[async_trait] impl TransportGeneric for WebRtcTransport { type Dump = WebRtcTransportDump; type Stat = WebRtcTransportStat; #[doc(hidden)] async fn dump(&self) -> Result { debug!("dump()"); let response = self.dump_impl().await?; if let response::Body::WebRtcTransportDumpResponse(data) = response { Ok(WebRtcTransportDump::try_from_fbs(*data) .expect("Error parsing dump response: {response:?}")) } else { panic!("Wrong message from worker: {response:?}"); } } async fn get_stats(&self) -> Result, RequestError> { debug!("get_stats()"); let response = self.get_stats_impl().await?; if let response::Body::WebRtcTransportGetStatsResponse(data) = response { Ok(vec![WebRtcTransportStat::try_from_fbs( data.as_ref().clone(), ) .expect("Error parsing dump response: {response:?}")]) } else { panic!("Wrong message from worker: {response:?}"); } } } impl TransportImpl for WebRtcTransport { fn channel(&self) -> &Channel { &self.inner.channel } fn executor(&self) -> &Arc> { &self.inner.executor } fn next_mid_for_consumers(&self) -> &AtomicUsize { &self.inner.next_mid_for_consumers } fn used_sctp_stream_ids(&self) -> &Mutex> { &self.inner.used_sctp_stream_ids } fn cname_for_producers(&self) -> &Mutex> { &self.inner.cname_for_producers } } impl WebRtcTransport { #[allow(clippy::too_many_arguments)] pub(super) fn new( id: TransportId, executor: Arc>, channel: Channel, data: WebRtcTransportData, app_data: AppData, router: Router, webrtc_server: Option, ) -> Self { debug!("new()"); let handlers = Arc::::default(); let data = Arc::new(data); let subscription_handler = { let handlers = Arc::clone(&handlers); let data = Arc::clone(&data); channel.subscribe_to_notifications(id.into(), move |notification| { match Notification::try_from_fbs(notification) { Ok(notification) => match notification { Notification::IceStateChange { ice_state } => { *data.ice_state.lock() = ice_state; handlers.ice_state_change.call(|callback| { callback(ice_state); }); } Notification::IceSelectedTupleChange { ice_selected_tuple } => { data.ice_selected_tuple .lock() .replace(ice_selected_tuple.clone()); handlers .ice_selected_tuple_change .call_simple(&ice_selected_tuple); } Notification::DtlsStateChange { dtls_state, dtls_remote_cert, } => { *data.dtls_state.lock() = dtls_state; if let Some(dtls_remote_cert) = dtls_remote_cert { data.dtls_remote_cert.lock().replace(dtls_remote_cert); } handlers.dtls_state_change.call(|callback| { callback(dtls_state); }); } Notification::SctpStateChange { sctp_state } => { data.sctp_state.lock().replace(sctp_state); handlers.sctp_state_change.call(|callback| { callback(sctp_state); }); } Notification::Trace(trace_event_data) => { handlers.trace.call_simple(&trace_event_data); } }, Err(error) => { error!("Failed to parse notification: {}", error); } } }) }; let next_mid_for_consumers = AtomicUsize::default(); let used_sctp_stream_ids = Mutex::new({ let mut used_used_sctp_stream_ids = IntMap::default(); if let Some(sctp_parameters) = &data.sctp_parameters { for i in 0..sctp_parameters.mis { used_used_sctp_stream_ids.insert(i, false); } } used_used_sctp_stream_ids }); let cname_for_producers = Mutex::new(None); let inner_weak = Arc::>>>::default(); let on_webrtc_server_close_handler = webrtc_server.as_ref().map(|webrtc_server| { webrtc_server.on_close({ let inner_weak = Arc::clone(&inner_weak); move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.webrtc_server_close.call_simple(); inner.close(true); } } }) }); let on_router_close_handler = router.on_close({ let inner_weak = Arc::clone(&inner_weak); move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.router_close.call_simple(); inner.close(false); } } }); let inner = Arc::new(Inner { id, next_mid_for_consumers, used_sctp_stream_ids, cname_for_producers, executor, channel, handlers, data, app_data, webrtc_server, router, closed: AtomicBool::new(false), _subscription_handler: Mutex::new(subscription_handler), _on_webrtc_server_close_handler: Mutex::new(on_webrtc_server_close_handler), _on_router_close_handler: Mutex::new(on_router_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); let webrtc_transport = Self { inner }; // Notify WebRTC server that new transport was created. if let Some(webrtc_server) = &webrtc_transport.inner.webrtc_server { webrtc_server.notify_new_webrtc_transport(&webrtc_transport); } webrtc_transport } /// Provide the [`WebRtcTransport`] with remote parameters. /// /// # Example /// ```rust /// use mediasoup_types::data_structures::{DtlsParameters, DtlsRole, DtlsFingerprint}; /// use mediasoup::webrtc_transport::WebRtcTransportRemoteParameters; /// /// # async fn f( /// # webrtc_transport: mediasoup::webrtc_transport::WebRtcTransport, /// # ) -> Result<(), Box> { /// // Calling connect() on a PlainTransport created with comedia and rtcp_mux set. /// webrtc_transport /// .connect(WebRtcTransportRemoteParameters { /// dtls_parameters: DtlsParameters { /// role: DtlsRole::Server, /// fingerprints: vec![ /// DtlsFingerprint::Sha256 { /// value: [ /// 0xE5, 0xF5, 0xCA, 0xA7, 0x2D, 0x93, 0xE6, 0x16, 0xAC, 0x21, 0x09, /// 0x9F, 0x23, 0x51, 0x62, 0x8C, 0xD0, 0x66, 0xE9, 0x0C, 0x22, 0x54, /// 0x2B, 0x82, 0x0C, 0xDF, 0xE0, 0xC5, 0x2C, 0x7E, 0xCD, 0x53, /// ], /// }, /// ], /// }, /// }) /// .await?; /// # Ok(()) /// # } /// ``` pub async fn connect( &self, remote_parameters: WebRtcTransportRemoteParameters, ) -> Result<(), RequestError> { debug!("connect()"); let response = self .inner .channel .request( self.id(), WebRtcTransportConnectRequest { dtls_parameters: remote_parameters.dtls_parameters, }, ) .await?; self.inner.data.dtls_parameters.lock().role = response.dtls_local_role; Ok(()) } /// WebRTC server used during creation of this transport. pub fn webrtc_server(&self) -> &Option { &self.inner.webrtc_server } /// Set maximum incoming bitrate for media streams sent by the remote endpoint over this /// transport. pub async fn set_max_incoming_bitrate(&self, bitrate: u32) -> Result<(), RequestError> { debug!("set_max_incoming_bitrate() [bitrate:{}]", bitrate); self.set_max_incoming_bitrate_impl(bitrate).await } /// Set maximum outgoing bitrate for media streams sent by the remote endpoint over this /// transport. pub async fn set_max_outgoing_bitrate(&self, bitrate: u32) -> Result<(), RequestError> { debug!("set_max_outgoing_bitrate() [bitrate:{}]", bitrate); self.set_max_outgoing_bitrate_impl(bitrate).await } /// Set minimum outgoing bitrate for media streams sent by the remote endpoint over this /// transport. pub async fn set_min_outgoing_bitrate(&self, bitrate: u32) -> Result<(), RequestError> { debug!("set_min_outgoing_bitrate() [bitrate:{}]", bitrate); self.set_min_outgoing_bitrate_impl(bitrate).await } /// Local ICE role. Due to the mediasoup ICE Lite design, this is always `Controlled`. #[must_use] pub fn ice_role(&self) -> IceRole { self.inner.data.ice_role } /// Local ICE parameters. #[must_use] pub fn ice_parameters(&self) -> &IceParameters { &self.inner.data.ice_parameters } /// Local ICE candidates. #[must_use] pub fn ice_candidates(&self) -> &Vec { &self.inner.data.ice_candidates } /// Current ICE state. #[must_use] pub fn ice_state(&self) -> IceState { *self.inner.data.ice_state.lock() } /// The selected transport tuple if ICE is in `Connected` or `Completed` state. It is `None` if /// ICE is not established (no working candidate pair was found). #[must_use] pub fn ice_selected_tuple(&self) -> Option { self.inner.data.ice_selected_tuple.lock().clone() } /// Local DTLS parameters. #[must_use] pub fn dtls_parameters(&self) -> DtlsParameters { self.inner.data.dtls_parameters.lock().clone() } /// Current DTLS state. #[must_use] pub fn dtls_state(&self) -> DtlsState { *self.inner.data.dtls_state.lock() } /// The remote certificate in PEM format. It is `Some` once the DTLS state becomes `Connected`. /// /// # Notes on usage /// The application may want to inspect the remote certificate for authorization purposes by /// using some certificates utility. #[must_use] pub fn dtls_remote_cert(&self) -> Option { self.inner.data.dtls_remote_cert.lock().clone() } /// Local SCTP parameters. Or `None` if SCTP is not enabled. #[must_use] pub fn sctp_parameters(&self) -> Option { self.inner.data.sctp_parameters } /// Current SCTP state. Or `None` if SCTP is not enabled. #[must_use] pub fn sctp_state(&self) -> Option { *self.inner.data.sctp_state.lock() } /// Restarts the ICE layer by generating new local ICE parameters that must be signaled to the /// remote endpoint. pub async fn restart_ice(&self) -> Result { debug!("restart_ice()"); self.inner .channel .request(self.id(), TransportRestartIceRequest {}) .await } /// Callback is called when the WebRTC server used during creation of this transport is closed /// for whatever reason. /// The transport itself is also closed. `on_transport_close` callbacks are also called on all /// its producers and consumers. pub fn on_webrtc_server_close( &self, callback: Box, ) -> HandlerId { self.inner.handlers.webrtc_server_close.add(callback) } /// Callback is called when the transport ICE state changes. pub fn on_ice_state_change( &self, callback: F, ) -> HandlerId { self.inner.handlers.ice_state_change.add(Arc::new(callback)) } /// Callback is called after ICE state becomes `Completed` and when the ICE selected tuple /// changes. pub fn on_ice_selected_tuple_change( &self, callback: F, ) -> HandlerId { self.inner .handlers .ice_selected_tuple_change .add(Arc::new(callback)) } /// Callback is called when the transport DTLS state changes. pub fn on_dtls_state_change( &self, callback: F, ) -> HandlerId { self.inner .handlers .dtls_state_change .add(Arc::new(callback)) } /// Callback is called when the transport SCTP state changes. pub fn on_sctp_state_change( &self, callback: F, ) -> HandlerId { self.inner .handlers .sctp_state_change .add(Arc::new(callback)) } /// Downgrade `WebRtcTransport` to [`WeakWebRtcTransport`] instance. #[must_use] pub fn downgrade(&self) -> WeakWebRtcTransport { WeakWebRtcTransport { inner: Arc::downgrade(&self.inner), } } } /// [`WeakWebRtcTransport`] doesn't own [`WebRtcTransport`] instance on mediasoup-worker and will /// not prevent one from being destroyed once last instance of regular [`WebRtcTransport`] is /// dropped. /// /// [`WeakWebRtcTransport`] vs [`WebRtcTransport`] is similar to [`Weak`] vs [`Arc`]. #[derive(Clone)] pub struct WeakWebRtcTransport { inner: Weak, } impl fmt::Debug for WeakWebRtcTransport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WeakWebRtcTransport").finish() } } impl WeakWebRtcTransport { /// Attempts to upgrade `WeakWebRtcTransport` to [`WebRtcTransport`] if last instance of one /// wasn't dropped yet. #[must_use] pub fn upgrade(&self) -> Option { let inner = self.inner.upgrade()?; Some(WebRtcTransport { inner }) } } ================================================ FILE: rust/src/router.rs ================================================ //! A router enables injection, selection and forwarding of media streams through [`Transport`] //! instances created on it. //! //! Developers may think of a mediasoup router as if it were a "multi-party conference room", //! although mediasoup is much more low level than that and doesn't constrain itself to specific //! high level use cases (for instance, a "multi-party conference room" could involve various //! mediasoup routers, even in different physicals hosts). pub(super) mod active_speaker_observer; pub(super) mod audio_level_observer; pub(super) mod consumer; pub(super) mod data_consumer; pub(super) mod data_producer; pub(super) mod direct_transport; pub(super) mod pipe_transport; pub(super) mod plain_transport; pub(super) mod producer; pub(super) mod rtp_observer; #[cfg(test)] mod tests; pub(super) mod transport; pub(super) mod webrtc_transport; use crate::active_speaker_observer::{ActiveSpeakerObserver, ActiveSpeakerObserverOptions}; use crate::audio_level_observer::{AudioLevelObserver, AudioLevelObserverOptions}; use crate::consumer::{Consumer, ConsumerId, ConsumerOptions}; use crate::data_consumer::{DataConsumer, DataConsumerId, DataConsumerOptions}; use crate::data_producer::{ DataProducer, DataProducerId, DataProducerOptions, NonClosingDataProducer, WeakDataProducer, }; use crate::direct_transport::{DirectTransport, DirectTransportOptions}; use crate::messages::{ RouterCloseRequest, RouterCreateActiveSpeakerObserverData, RouterCreateActiveSpeakerObserverRequest, RouterCreateAudioLevelObserverData, RouterCreateAudioLevelObserverRequest, RouterCreateDirectTransportData, RouterCreateDirectTransportRequest, RouterCreatePipeTransportData, RouterCreatePipeTransportRequest, RouterCreatePlainTransportData, RouterCreatePlainTransportRequest, RouterCreateWebRtcTransportRequest, RouterCreateWebRtcTransportWithServerRequest, RouterCreateWebrtcTransportData, RouterDumpRequest, }; use crate::pipe_transport::{ PipeTransport, PipeTransportOptions, PipeTransportRemoteParameters, WeakPipeTransport, }; use crate::plain_transport::{PlainTransport, PlainTransportOptions}; use crate::producer::{PipedProducer, Producer, ProducerId, ProducerOptions, WeakProducer}; use crate::rtp_observer::{RtpObserver, RtpObserverId}; use crate::transport::{ ConsumeDataError, ConsumeError, ProduceDataError, ProduceError, Transport, TransportGeneric, TransportId, }; use crate::webrtc_transport::{WebRtcTransport, WebRtcTransportListen, WebRtcTransportOptions}; use crate::worker::{Channel, RequestError, Worker}; use crate::{ortc, uuid_based_wrapper_type}; use async_executor::Executor; use async_lock::Mutex as AsyncMutex; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use futures_lite::future; use hash_hasher::{HashedMap, HashedSet}; use log::{debug, error}; use mediasoup_types::data_structures::{AppData, ListenInfo, Protocol}; use mediasoup_types::rtp_parameters::{ RtpCapabilities, RtpCapabilitiesFinalized, RtpCodecCapability, }; use mediasoup_types::sctp_parameters::NumSctpStreams; use parking_lot::{Mutex, RwLock}; use serde::{Deserialize, Serialize}; use std::fmt; use std::net::{IpAddr, Ipv4Addr}; use std::ops::Deref; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Mutex as SyncMutex; use std::sync::{Arc, Weak}; use thiserror::Error; uuid_based_wrapper_type!( /// [`Router`] identifier. RouterId ); /// [`Router`] options. /// /// # Notes on usage /// /// * Feature codecs such as `RTX` MUST NOT be placed into the mediaCodecs list. /// * If `preferred_payload_type` is given in a [`RtpCodecCapability`] (although it's unnecessary) /// it's extremely recommended to use a value in the 96-127 range. #[derive(Debug)] #[non_exhaustive] pub struct RouterOptions { /// Router media codecs. pub media_codecs: Vec, /// Custom application data. pub app_data: AppData, } impl RouterOptions { /// Create router options with given list of declared media codecs. #[must_use] pub fn new(media_codecs: Vec) -> Self { Self { media_codecs, app_data: AppData::default(), } } } impl Default for RouterOptions { fn default() -> Self { Self::new(vec![]) } } /// Options used for piping media or data producer to into another router on the same host. /// /// # Notes on usage /// * SCTP arguments will only apply the first time the underlying transports are created. #[derive(Debug)] pub struct PipeToRouterOptions { /// Target Router instance. pub router: Router, /// Whether the `id` of the returned Producer or DataProducer should be the /// same than the `id` of the original Producer or DataProducer. Default true. /// /// # Note /// If set to true, then the origin router and target router cannot be in the /// same worker. pub keep_id: bool, /// IP used in the PipeTransport pair. /// /// Default `{ protocol: 'udp', ip: '127.0.0.1' }`. listen_info: ListenInfo, /// Create a SCTP association. /// /// Default `true`. pub enable_sctp: bool, /// SCTP streams number. pub num_sctp_streams: NumSctpStreams, /// Enable RTX and NACK for RTP retransmission. /// /// Default `false`. pub enable_rtx: bool, /// Enable SRTP. /// /// Default `false`. pub enable_srtp: bool, } impl PipeToRouterOptions { /// Crate pipe options for piping into given local router. #[must_use] pub fn new(router: Router) -> Self { Self { router, keep_id: true, listen_info: ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }, enable_sctp: true, num_sctp_streams: NumSctpStreams::default(), enable_rtx: false, enable_srtp: false, } } } /// Container for pipe consumer and pipe producer pair. /// /// # Notes on usage /// Pipe consumer and Pipe producer will not be closed on drop, to control this manually get pipe /// producer out of non-closing variant with [`PipedProducer::into_inner()`] call, /// otherwise pipe consumer and pipe producer lifetime will be tied to source producer lifetime. /// /// Pipe consumer is always tied to the lifetime of pipe producer. #[derive(Debug)] pub struct PipeProducerToRouterPair { /// The Consumer created in the current Router. pub pipe_consumer: Consumer, /// The Producer created in the target Router, get regular instance with /// [`PipedProducer::into_inner()`] call. pub pipe_producer: PipedProducer, } /// Error that caused [`Router::pipe_producer_to_router()`] to fail. #[derive(Debug, Error)] pub enum PipeProducerToRouterError { /// Destination router must be different #[error("Destination router must be different")] SameRouter, /// Producer with specified id not found #[error("Producer with id \"{0}\" not found")] ProducerNotFound(ProducerId), /// Failed to create or connect Pipe transport #[error("Failed to create or connect Pipe transport: \"{0}\"")] TransportFailed(RequestError), /// Failed to consume #[error("Failed to consume: \"{0}\"")] ConsumeFailed(ConsumeError), /// Failed to produce #[error("Failed to produce: \"{0}\"")] ProduceFailed(ProduceError), } impl From for PipeProducerToRouterError { fn from(error: RequestError) -> Self { PipeProducerToRouterError::TransportFailed(error) } } impl From for PipeProducerToRouterError { fn from(error: ConsumeError) -> Self { PipeProducerToRouterError::ConsumeFailed(error) } } impl From for PipeProducerToRouterError { fn from(error: ProduceError) -> Self { PipeProducerToRouterError::ProduceFailed(error) } } /// Container for pipe data consumer and pipe data producer pair. /// /// # Notes on usage /// Pipe data consumer and pipe data producer will not be closed on drop, to control this manually /// get pipe data producer out of non-closing variant with [`NonClosingDataProducer::into_inner()`] /// call, otherwise pipe data consumer and pipe data producer lifetime will be tied to source data /// producer lifetime. /// /// Pipe data consumer is always tied to the lifetime of pipe data producer. #[derive(Debug)] pub struct PipeDataProducerToRouterPair { /// The DataConsumer created in the current Router. pub pipe_data_consumer: DataConsumer, /// The DataProducer created in the target Router, get regular instance with /// [`NonClosingDataProducer::into_inner()`] call. pub pipe_data_producer: NonClosingDataProducer, } /// Error that caused [`Router::pipe_data_producer_to_router()`] to fail. #[derive(Debug, Error)] pub enum PipeDataProducerToRouterError { /// Destination router must be different #[error("Destination router must be different")] SameRouter, /// Data producer with specified id not found #[error("Data producer with id \"{0}\" not found")] DataProducerNotFound(DataProducerId), /// Failed to create or connect Pipe transport #[error("Failed to create or connect Pipe transport: \"{0}\"")] TransportFailed(RequestError), /// Failed to consume #[error("Failed to consume: \"{0}\"")] ConsumeFailed(ConsumeDataError), /// Failed to produce #[error("Failed to produce: \"{0}\"")] ProduceFailed(ProduceDataError), } impl From for PipeDataProducerToRouterError { fn from(error: RequestError) -> Self { PipeDataProducerToRouterError::TransportFailed(error) } } impl From for PipeDataProducerToRouterError { fn from(error: ConsumeDataError) -> Self { PipeDataProducerToRouterError::ConsumeFailed(error) } } impl From for PipeDataProducerToRouterError { fn from(error: ProduceDataError) -> Self { PipeDataProducerToRouterError::ProduceFailed(error) } } /// Error that caused [`Router::update_media_codecs`] to fail. #[derive(Debug, Error)] pub enum UpdateMediaCodecsError { /// RTP capabilities generation error #[error("RTP capabilities generation error: {0}")] FailedRtpCapabilitiesGeneration(ortc::RtpCapabilitiesError), } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct RouterDump { pub id: RouterId, pub map_consumer_id_producer_id: HashedMap, pub map_data_consumer_id_data_producer_id: HashedMap, pub map_data_producer_id_data_consumer_ids: HashedMap>, pub map_producer_id_consumer_ids: HashedMap>, pub map_producer_id_observer_ids: HashedMap>, pub rtp_observer_ids: HashedSet, pub transport_ids: HashedSet, } /// New transport that was just created. #[derive(Debug)] pub enum NewTransport<'a> { /// Direct transport Direct(&'a DirectTransport), /// Pipe transport Pipe(&'a PipeTransport), /// Plain transport Plain(&'a PlainTransport), /// WebRtc transport WebRtc(&'a WebRtcTransport), } impl<'a> Deref for NewTransport<'a> { type Target = dyn Transport; fn deref(&self) -> &Self::Target { match self { Self::Direct(transport) => *transport as &Self::Target, Self::Pipe(transport) => *transport as &Self::Target, Self::Plain(transport) => *transport as &Self::Target, Self::WebRtc(transport) => *transport as &Self::Target, } } } /// New RTP observer that was just created. #[derive(Debug)] pub enum NewRtpObserver<'a> { /// Audio level observer AudioLevel(&'a AudioLevelObserver), /// Active speaker observer ActiveSpeaker(&'a ActiveSpeakerObserver), } impl<'a> Deref for NewRtpObserver<'a> { type Target = dyn RtpObserver; fn deref(&self) -> &Self::Target { match self { Self::AudioLevel(observer) => *observer as &Self::Target, Self::ActiveSpeaker(observer) => *observer as &Self::Target, } } } struct PipeTransportPair { local: PipeTransport, remote: PipeTransport, } impl PipeTransportPair { fn downgrade(&self) -> WeakPipeTransportPair { WeakPipeTransportPair { local: self.local.downgrade(), remote: self.remote.downgrade(), } } } #[derive(Debug)] struct WeakPipeTransportPair { local: WeakPipeTransport, remote: WeakPipeTransport, } impl WeakPipeTransportPair { fn upgrade(&self) -> Option { let local = self.local.upgrade()?; let remote = self.remote.upgrade()?; Some(PipeTransportPair { local, remote }) } } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { new_transport: Bag) + Send + Sync>>, new_rtp_observer: Bag) + Send + Sync>>, worker_close: BagOnce>, close: BagOnce>, } struct Inner { id: RouterId, executor: Arc>, rtp_capabilities: SyncMutex, channel: Channel, handlers: Arc, app_data: AppData, producers: Arc>>, data_producers: Arc>>, #[allow(clippy::type_complexity)] mapped_pipe_transports: Arc>>>>>, // Make sure worker is not dropped until this router is not dropped worker: Worker, closed: AtomicBool, _on_worker_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(); } } impl Inner { fn close(&self) { if !self.closed.swap(true, Ordering::SeqCst) { self.handlers.close.call_simple(); { let channel = self.channel.clone(); let request = RouterCloseRequest { router_id: self.id }; self.executor .spawn(async move { match channel.request("", request).await { Err(RequestError::ChannelClosed) => { debug!("router closing failed on drop: Channel already closed"); } Err(error) => { error!("router closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// A router enables injection, selection and forwarding of media streams through [`Transport`] /// instances created on it. /// /// Developers may think of a mediasoup router as if it were a "multi-party conference room", /// although mediasoup is much more low level than that and doesn't constrain itself to specific /// high level use cases (for instance, a "multi-party conference room" could involve various /// mediasoup routers, even in different physicals hosts). #[derive(Clone)] #[must_use = "Router will be closed on drop, make sure to keep it around for as long as needed"] pub struct Router { inner: Arc, } impl fmt::Debug for Router { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Router") .field("id", &self.inner.id) .field("rtp_capabilities", &self.inner.rtp_capabilities) .field("producers", &self.inner.producers) .field("data_producers", &self.inner.data_producers) .field("mapped_pipe_transports", &self.inner.mapped_pipe_transports) .field("worker", &self.inner.worker) .field("closed", &self.inner.closed) .finish() } } impl Router { pub(super) fn new( id: RouterId, executor: Arc>, channel: Channel, rtp_capabilities: RtpCapabilitiesFinalized, app_data: AppData, worker: Worker, ) -> Self { debug!("new()"); let producers = Arc::>>::default(); let data_producers = Arc::>>::default(); let mapped_pipe_transports = Arc::< Mutex>>>>, >::default(); let handlers = Arc::::default(); let inner_weak = Arc::>>>::default(); let on_worker_close_handler = worker.on_close({ let inner_weak = Arc::clone(&inner_weak); move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.worker_close.call_simple(); if !inner.closed.swap(true, Ordering::SeqCst) { inner.handlers.close.call_simple(); } } } }); let inner = Arc::new(Inner { id, executor, rtp_capabilities: SyncMutex::new(rtp_capabilities), channel, handlers, producers, data_producers, mapped_pipe_transports, app_data, worker, closed: AtomicBool::new(false), _on_worker_close_handler: Mutex::new(on_worker_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); Self { inner } } /// Router id. #[must_use] pub fn id(&self) -> RouterId { self.inner.id } /// Worker to which router belongs. pub fn worker(&self) -> &Worker { &self.inner.worker } /// Custom application data. #[must_use] pub fn app_data(&self) -> &AppData { &self.inner.app_data } /// Whether router is closed. #[must_use] pub fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } /// RTP capabilities of the router. These capabilities are typically needed by mediasoup clients /// to compute their sending RTP parameters. /// /// # Notes on usage /// * Check the [RTP Parameters and Capabilities](https://mediasoup.org/documentation/v3/mediasoup/rtp-parameters-and-capabilities/) /// section for more details. /// * See also how to [filter these RTP capabilities](https://mediasoup.org/documentation/v3/tricks/#rtp-capabilities-filtering) /// before using them into a client. #[must_use] pub fn rtp_capabilities(&self) -> RtpCapabilitiesFinalized { self.inner.rtp_capabilities.lock().unwrap().clone() } /// Dump Router. #[doc(hidden)] pub async fn dump(&self) -> Result { debug!("dump()"); self.inner .channel .request(self.inner.id, RouterDumpRequest {}) .await } /// Create a [`DirectTransport`]. /// /// Router will be kept alive as long as at least one transport instance is alive. /// /// # Example /// ```rust /// use mediasoup::prelude::*; /// /// # async fn f(router: Router) -> Result<(), Box> { /// let transport = router.create_direct_transport(DirectTransportOptions::default()).await?; /// # Ok(()) /// # } /// ``` pub async fn create_direct_transport( &self, direct_transport_options: DirectTransportOptions, ) -> Result { debug!("create_direct_transport()"); let transport_id = TransportId::new(); let _buffer_guard = self.inner.channel.buffer_messages_for(transport_id.into()); self.inner .channel .request( self.inner.id, RouterCreateDirectTransportRequest { data: RouterCreateDirectTransportData::from_options( transport_id, &direct_transport_options, ), }, ) .await?; let transport = DirectTransport::new( transport_id, Arc::clone(&self.inner.executor), self.inner.channel.clone(), direct_transport_options.app_data, self.clone(), ); self.inner.handlers.new_transport.call(|callback| { callback(NewTransport::Direct(&transport)); }); self.after_transport_creation(&transport); Ok(transport) } /// Create a [`WebRtcTransport`]. /// /// Router will be kept alive as long as at least one transport instance is alive. /// /// # Example /// ```rust /// use mediasoup::prelude::*; /// use std::net::{IpAddr, Ipv4Addr}; /// /// # async fn f(router: Router) -> Result<(), Box> { /// let transport = router /// .create_webrtc_transport(WebRtcTransportOptions::new(WebRtcTransportListenInfos::new( /// ListenInfo { /// protocol: Protocol::Udp, /// ip: IpAddr::V4(Ipv4Addr::LOCALHOST), /// announced_address: Some("9.9.9.1".to_string()), /// expose_internal_ip: false, /// port: None, /// port_range: None, /// flags: None, /// send_buffer_size: None, /// recv_buffer_size: None, /// }, /// ))) /// .await?; /// # Ok(()) /// # } /// ``` pub async fn create_webrtc_transport( &self, webrtc_transport_options: WebRtcTransportOptions, ) -> Result { debug!("create_webrtc_transport()"); let transport_id = TransportId::new(); let _buffer_guard = self.inner.channel.buffer_messages_for(transport_id.into()); let data = match webrtc_transport_options.listen { WebRtcTransportListen::Individual { listen_infos: _ } => { self.inner .channel .request( self.inner.id, RouterCreateWebRtcTransportRequest { data: RouterCreateWebrtcTransportData::from_options( transport_id, &webrtc_transport_options, ), }, ) .await? } WebRtcTransportListen::Server { webrtc_server: _ } => { self.inner .channel .request( self.inner.id, RouterCreateWebRtcTransportWithServerRequest { data: RouterCreateWebrtcTransportData::from_options( transport_id, &webrtc_transport_options, ), }, ) .await? } }; let transport = WebRtcTransport::new( transport_id, Arc::clone(&self.inner.executor), self.inner.channel.clone(), data, webrtc_transport_options.app_data, self.clone(), match webrtc_transport_options.listen { WebRtcTransportListen::Individual { .. } => None, WebRtcTransportListen::Server { webrtc_server } => Some(webrtc_server), }, ); self.inner.handlers.new_transport.call(|callback| { callback(NewTransport::WebRtc(&transport)); }); self.after_transport_creation(&transport); Ok(transport) } /// Create a [`PipeTransport`]. /// /// Router will be kept alive as long as at least one transport instance is alive. /// /// # Example /// ```rust /// use mediasoup::prelude::*; /// use std::net::{IpAddr, Ipv4Addr}; /// /// # async fn f(router: Router) -> Result<(), Box> { /// let transport = router /// .create_pipe_transport(PipeTransportOptions::new(ListenInfo { /// protocol: Protocol::Udp, /// ip: IpAddr::V4(Ipv4Addr::LOCALHOST), /// announced_address: Some("9.9.9.1".to_string()), /// expose_internal_ip: false, /// port: None, /// port_range: None, /// flags: None, /// send_buffer_size: None, /// recv_buffer_size: None, /// })) /// .await?; /// # Ok(()) /// # } /// ``` pub async fn create_pipe_transport( &self, pipe_transport_options: PipeTransportOptions, ) -> Result { debug!("create_pipe_transport()"); let transport_id = TransportId::new(); let _buffer_guard = self.inner.channel.buffer_messages_for(transport_id.into()); let data = self .inner .channel .request( self.inner.id, RouterCreatePipeTransportRequest { data: RouterCreatePipeTransportData::from_options( transport_id, &pipe_transport_options, ), }, ) .await?; let transport = PipeTransport::new( transport_id, Arc::clone(&self.inner.executor), self.inner.channel.clone(), data, pipe_transport_options.app_data, self.clone(), ); self.inner.handlers.new_transport.call(|callback| { callback(NewTransport::Pipe(&transport)); }); self.after_transport_creation(&transport); Ok(transport) } /// Create a [`PlainTransport`]. /// /// Router will be kept alive as long as at least one transport instance is alive. /// /// # Example /// ```rust /// use mediasoup::prelude::*; /// use std::net::{IpAddr, Ipv4Addr}; /// /// # async fn f(router: Router) -> Result<(), Box> { /// let transport = router /// .create_plain_transport(PlainTransportOptions::new(ListenInfo { /// protocol: Protocol::Udp, /// ip: IpAddr::V4(Ipv4Addr::LOCALHOST), /// announced_address: Some("9.9.9.1".to_string()), /// expose_internal_ip: false, /// port: None, /// port_range: None, /// flags: None, /// send_buffer_size: None, /// recv_buffer_size: None, /// })) /// .await?; /// # Ok(()) /// # } /// ``` pub async fn create_plain_transport( &self, plain_transport_options: PlainTransportOptions, ) -> Result { debug!("create_plain_transport()"); let transport_id = TransportId::new(); let _buffer_guard = self.inner.channel.buffer_messages_for(transport_id.into()); let data = self .inner .channel .request( self.inner.id, RouterCreatePlainTransportRequest { data: RouterCreatePlainTransportData::from_options( transport_id, &plain_transport_options, ), }, ) .await?; let transport = PlainTransport::new( transport_id, Arc::clone(&self.inner.executor), self.inner.channel.clone(), data, plain_transport_options.app_data, self.clone(), ); self.inner.handlers.new_transport.call(|callback| { callback(NewTransport::Plain(&transport)); }); self.after_transport_creation(&transport); Ok(transport) } /// Create an [`AudioLevelObserver`]. /// /// Router will be kept alive as long as at least one observer instance is alive. /// /// # Example /// ```rust /// use mediasoup::prelude::*; /// use std::num::NonZeroU16; /// /// # async fn f(router: Router) -> Result<(), Box> { /// let observer = router /// .create_audio_level_observer({ /// let mut options = AudioLevelObserverOptions::default(); /// options.max_entries = NonZeroU16::new(1).unwrap(); /// options.threshold = -70; /// options.interval = 2000; /// options /// }) /// .await?; /// # Ok(()) /// # } /// ``` pub async fn create_audio_level_observer( &self, audio_level_observer_options: AudioLevelObserverOptions, ) -> Result { debug!("create_audio_level_observer()"); let rtp_observer_id = RtpObserverId::new(); let _buffer_guard = self .inner .channel .buffer_messages_for(rtp_observer_id.into()); self.inner .channel .request( self.inner.id, RouterCreateAudioLevelObserverRequest { data: RouterCreateAudioLevelObserverData::from_options( rtp_observer_id, &audio_level_observer_options, ), }, ) .await?; let audio_level_observer = AudioLevelObserver::new( rtp_observer_id, Arc::clone(&self.inner.executor), self.inner.channel.clone(), audio_level_observer_options.app_data, self.clone(), ); self.inner.handlers.new_rtp_observer.call(|callback| { callback(NewRtpObserver::AudioLevel(&audio_level_observer)); }); Ok(audio_level_observer) } /// Create an [`ActiveSpeakerObserver`]. /// /// Router will be kept alive as long as at least one observer instance is alive. /// /// # Example /// ```rust /// use mediasoup::active_speaker_observer::ActiveSpeakerObserverOptions; /// /// # async fn f(router: mediasoup::router::Router) -> Result<(), Box> { /// let observer = router /// .create_active_speaker_observer({ /// let mut options = ActiveSpeakerObserverOptions::default(); /// options.interval = 300; /// options /// }) /// .await?; /// # Ok(()) /// # } /// ``` pub async fn create_active_speaker_observer( &self, active_speaker_observer_options: ActiveSpeakerObserverOptions, ) -> Result { debug!("create_active_speaker_observer()"); let rtp_observer_id = RtpObserverId::new(); let _buffer_guard = self .inner .channel .buffer_messages_for(rtp_observer_id.into()); self.inner .channel .request( self.inner.id, RouterCreateActiveSpeakerObserverRequest { data: RouterCreateActiveSpeakerObserverData::from_options( rtp_observer_id, &active_speaker_observer_options, ), }, ) .await?; let active_speaker_observer = ActiveSpeakerObserver::new( rtp_observer_id, Arc::clone(&self.inner.executor), self.inner.channel.clone(), active_speaker_observer_options.app_data, self.clone(), ); self.inner.handlers.new_rtp_observer.call(|callback| { callback(NewRtpObserver::ActiveSpeaker(&active_speaker_observer)); }); Ok(active_speaker_observer) } /// Pipes [`Producer`] with the given `producer_id` into another [`Router`] on same host. /// /// # Example /// ```rust /// use mediasoup::prelude::*; /// use mediasoup_types::rtp_parameters::RtpCodecParameters; /// use std::net::{IpAddr, Ipv4Addr}; /// use std::num::{NonZeroU32, NonZeroU8}; /// /// # async fn f(worker_manager: WorkerManager) -> Result<(), Box> { /// // Have two workers. /// let worker1 = worker_manager.create_worker(WorkerSettings::default()).await?; /// let worker2 = worker_manager.create_worker(WorkerSettings::default()).await?; /// /// // Create a router in each worker. /// let media_codecs = vec![ /// RtpCodecCapability::Audio { /// mime_type: MimeTypeAudio::Opus, /// preferred_payload_type: None, /// clock_rate: NonZeroU32::new(48000).unwrap(), /// channels: NonZeroU8::new(2).unwrap(), /// parameters: RtpCodecParametersParameters::from([ /// ("useinbandfec", 1_u32.into()), /// ]), /// rtcp_feedback: vec![], /// }, /// ]; /// let router1 = worker1.create_router(RouterOptions::new(media_codecs.clone())).await?; /// let router2 = worker2.create_router(RouterOptions::new(media_codecs)).await?; /// /// // Produce in router1. /// let transport1 = router1 /// .create_webrtc_transport(WebRtcTransportOptions::new(WebRtcTransportListenInfos::new( /// ListenInfo { /// protocol: Protocol::Udp, /// ip: IpAddr::V4(Ipv4Addr::LOCALHOST), /// announced_address: Some("9.9.9.1".to_string()), /// expose_internal_ip: false, /// port: None, /// port_range: None, /// flags: None, /// send_buffer_size: None, /// recv_buffer_size: None, /// }, /// ))) /// .await?; /// let producer1 = transport1 /// .produce(ProducerOptions::new( /// MediaKind::Audio, /// RtpParameters { /// mid: Some("AUDIO".to_string()), /// codecs: vec![RtpCodecParameters::Audio { /// mime_type: MimeTypeAudio::Opus, /// payload_type: 0, /// clock_rate: NonZeroU32::new(48000).unwrap(), /// channels: NonZeroU8::new(2).unwrap(), /// parameters: RtpCodecParametersParameters::from([ /// ("useinbandfec", 1_u32.into()), /// ("usedtx", 1_u32.into()), /// ]), /// rtcp_feedback: vec![], /// }], /// ..RtpParameters::default() /// }, /// )) /// .await?; /// /// // Pipe producer1 into router2. /// router1 /// .pipe_producer_to_router( /// producer1.id(), /// PipeToRouterOptions::new(router2.clone()) /// ) /// .await?; /// /// // Consume producer1 from router2. /// let transport2 = router2 /// .create_webrtc_transport(WebRtcTransportOptions::new(WebRtcTransportListenInfos::new( /// ListenInfo { /// protocol: Protocol::Udp, /// ip: IpAddr::V4(Ipv4Addr::LOCALHOST), /// announced_address: Some("9.9.9.1".to_string()), /// expose_internal_ip: false, /// port: None, /// port_range: None, /// flags: None, /// send_buffer_size: None, /// recv_buffer_size: None, /// }, /// ))) /// .await?; /// let consumer2 = transport2 /// .consume(ConsumerOptions::new( /// producer1.id(), /// RtpCapabilities { /// codecs: vec![ /// RtpCodecCapability::Audio { /// mime_type: MimeTypeAudio::Opus, /// preferred_payload_type: Some(100), /// clock_rate: NonZeroU32::new(48000).unwrap(), /// channels: NonZeroU8::new(2).unwrap(), /// parameters: RtpCodecParametersParameters::default(), /// rtcp_feedback: vec![], /// }, /// ], /// header_extensions: vec![], /// } /// )) /// .await?; /// /// # Ok(()) /// # } /// ``` pub async fn pipe_producer_to_router( &self, producer_id: ProducerId, pipe_to_router_options: PipeToRouterOptions, ) -> Result { debug!("pipe_producer_to_router()"); let PipeToRouterOptions { ref router, keep_id, .. } = pipe_to_router_options; if keep_id && router.id() == self.id() { return Err(PipeProducerToRouterError::SameRouter); } let producer = match self .inner .producers .read() .get(&producer_id) .and_then(WeakProducer::upgrade) { Some(producer) => producer, None => { return Err(PipeProducerToRouterError::ProducerNotFound(producer_id)); } }; let pipe_transport_pair = self .get_or_create_pipe_transport_pair(pipe_to_router_options) .await?; let pipe_consumer = pipe_transport_pair .local .consume(ConsumerOptions::new( producer_id, RtpCapabilities::default(), )) .await?; let pipe_producer = pipe_transport_pair .remote .produce({ let mut producer_options = ProducerOptions::new_pipe_transport( // Generate a new id for the pipeProducer if requested. if keep_id { producer_id } else { ProducerId::new() }, pipe_consumer.kind(), pipe_consumer.rtp_parameters().clone(), ); producer_options.paused = pipe_consumer.producer_paused(); producer_options.app_data = producer.app_data().clone(); producer_options }) .await?; // Pipe events from the pipe Consumer to the pipe Producer. pipe_consumer .on_close({ let pipe_producer_weak = pipe_producer.downgrade(); move || { if let Some(pipe_producer) = pipe_producer_weak.upgrade() { pipe_producer.close(); } } }) .detach(); pipe_consumer .on_pause({ let executor = Arc::clone(&self.inner.executor); let pipe_producer_weak = pipe_producer.downgrade(); move || { if let Some(pipe_producer) = pipe_producer_weak.upgrade() { executor .spawn(async move { let _ = pipe_producer.pause().await; }) .detach(); } } }) .detach(); pipe_consumer .on_resume({ let executor = Arc::clone(&self.inner.executor); let pipe_producer_weak = pipe_producer.downgrade(); move || { if let Some(pipe_producer) = pipe_producer_weak.upgrade() { executor .spawn(async move { let _ = pipe_producer.resume().await; }) .detach(); } } }) .detach(); // Pipe events from the pipe Producer to the pipe Consumer. pipe_producer .on_close({ let pipe_consumer = pipe_consumer.clone(); move || { // Just to make sure consumer on local router outlives producer on the other // router drop(pipe_consumer); } }) .detach(); let pipe_producer = PipedProducer::new(pipe_producer, { let weak_producer = producer.downgrade(); move |pipe_producer| { if let Some(producer) = weak_producer.upgrade() { // In case `PipedProducer` was dropped without transforming into regular // `Producer` first, we need to tie underlying pipe producer lifetime to the // lifetime of original source producer in another router producer .on_close(move || { pipe_producer.close(); }) .detach(); } } }); Ok(PipeProducerToRouterPair { pipe_consumer, pipe_producer, }) } /// Pipes [`DataProducer`] with the given `data_producer_id` into another [`Router`] on same /// host. /// /// # Example /// ```rust /// use mediasoup::prelude::*; /// use std::net::{IpAddr, Ipv4Addr}; /// /// # async fn f(worker_manager: WorkerManager) -> Result<(), Box> { /// // Have two workers. /// let worker1 = worker_manager.create_worker(WorkerSettings::default()).await?; /// let worker2 = worker_manager.create_worker(WorkerSettings::default()).await?; /// /// // Create a router in each worker. /// let router1 = worker1.create_router(RouterOptions::default()).await?; /// let router2 = worker2.create_router(RouterOptions::default()).await?; /// /// // Produce in router1. /// let transport1 = router1 /// .create_webrtc_transport({ /// let mut options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new( /// ListenInfo { /// protocol: Protocol::Udp, /// ip: IpAddr::V4(Ipv4Addr::LOCALHOST), /// announced_address: Some("9.9.9.1".to_string()), /// expose_internal_ip: false, /// port: None, /// port_range: None, /// flags: None, /// send_buffer_size: None, /// recv_buffer_size: None, /// }, /// )); /// options.enable_sctp = true; /// options /// }) /// .await?; /// let data_producer1 = transport1 /// .produce_data(DataProducerOptions::new_sctp( /// SctpStreamParameters::new_unordered_with_life_time(666, 5000), /// )) /// .await?; /// /// // Pipe data_producer1 into router2. /// router1 /// .pipe_data_producer_to_router( /// data_producer1.id(), /// PipeToRouterOptions::new(router2.clone()) /// ) /// .await?; /// /// // Consume data_producer1 from router2. /// let transport2 = router2 /// .create_webrtc_transport({ /// let mut options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new( /// ListenInfo { /// protocol: Protocol::Udp, /// ip: IpAddr::V4(Ipv4Addr::LOCALHOST), /// announced_address: Some("9.9.9.1".to_string()), /// expose_internal_ip: false, /// port: None, /// port_range: None, /// flags: None, /// send_buffer_size: None, /// recv_buffer_size: None, /// }, /// )); /// options.enable_sctp = true; /// options /// }) /// .await?; /// let data_consumer2 = transport2 /// .consume_data(DataConsumerOptions::new_sctp(data_producer1.id())) /// .await?; /// /// # Ok(()) /// # } /// ``` pub async fn pipe_data_producer_to_router( &self, data_producer_id: DataProducerId, pipe_to_router_options: PipeToRouterOptions, ) -> Result { debug!("pipe_data_producer_to_router()"); let PipeToRouterOptions { ref router, keep_id, .. } = pipe_to_router_options; if keep_id && router.id() == self.id() { return Err(PipeDataProducerToRouterError::SameRouter); } let data_producer = match self .inner .data_producers .read() .get(&data_producer_id) .and_then(WeakDataProducer::upgrade) { Some(data_producer) => data_producer, None => { return Err(PipeDataProducerToRouterError::DataProducerNotFound( data_producer_id, )); } }; let pipe_transport_pair = self .get_or_create_pipe_transport_pair(pipe_to_router_options) .await?; let pipe_data_consumer = pipe_transport_pair .local .consume_data(DataConsumerOptions::new_sctp(data_producer_id)) .await?; let pipe_data_producer = pipe_transport_pair .remote .produce_data({ let mut producer_options = DataProducerOptions::new_pipe_transport( // Generate a new id for the pipeDataProducer if requested. if keep_id { data_producer_id } else { DataProducerId::new() }, // We've created `DataConsumer` with SCTP above, so this should never panic pipe_data_consumer.sctp_stream_parameters().unwrap(), ); producer_options .label .clone_from(pipe_data_consumer.label()); producer_options .protocol .clone_from(pipe_data_consumer.protocol()); producer_options.app_data = data_producer.app_data().clone(); producer_options }) .await?; // Pipe events from the pipe DataConsumer to the pipe DataProducer. pipe_data_consumer .on_close({ let pipe_data_producer_weak = pipe_data_producer.downgrade(); move || { if let Some(pipe_data_producer) = pipe_data_producer_weak.upgrade() { pipe_data_producer.close(); } } }) .detach(); // Pipe events from the pipe DataProducer to the pipe Consumer. pipe_data_producer .on_close({ let pipe_data_consumer = pipe_data_consumer.clone(); move || { // Just to make sure consumer on local router outlives producer on the other // router drop(pipe_data_consumer); } }) .detach(); let pipe_data_producer = NonClosingDataProducer::new(pipe_data_producer, { let weak_data_producer = data_producer.downgrade(); move |pipe_data_producer| { if let Some(data_producer) = weak_data_producer.upgrade() { // In case `NonClosingDataProducer` was dropped without transforming into // regular `DataProducer` first, we need to tie underlying pipe data producer // lifetime to the lifetime of original source data producer in another router data_producer .on_close(move || { pipe_data_producer.close(); }) .detach(); } } }); Ok(PipeDataProducerToRouterPair { pipe_data_consumer, pipe_data_producer, }) } /// Check whether the given RTP capabilities are valid to consume the given producer. #[must_use] pub fn can_consume( &self, producer_id: &ProducerId, rtp_capabilities: &RtpCapabilities, ) -> bool { if let Some(producer) = self.get_producer(producer_id) { match ortc::can_consume(producer.consumable_rtp_parameters(), rtp_capabilities) { Ok(result) => result, Err(error) => { error!("can_consume() | unexpected error: {}", error); false } } } else { error!( "can_consume() | Producer with id \"{}\" not found", producer_id ); false } } /// Update the Router media codecs. Once called, the return value of /// router.rtp_capabilities() changes. pub fn update_media_codecs( &mut self, media_codecs: Vec, ) -> Result<(), UpdateMediaCodecsError> { debug!("update_media_codecs()"); let rtp_capabilities = ortc::generate_router_rtp_capabilities(media_codecs) .map_err(UpdateMediaCodecsError::FailedRtpCapabilitiesGeneration)?; let mut locked = self.inner.rtp_capabilities.lock().unwrap(); *locked = rtp_capabilities; Ok(()) } /// Callback is called when a new transport is created. pub fn on_new_transport) + Send + Sync + 'static>( &self, callback: F, ) -> HandlerId { self.inner.handlers.new_transport.add(Arc::new(callback)) } /// Callback is called when a new RTP observer is created. pub fn on_new_rtp_observer) + Send + Sync + 'static>( &self, callback: F, ) -> HandlerId { self.inner.handlers.new_rtp_observer.add(Arc::new(callback)) } /// Callback is called when the worker this router belongs to is closed for whatever reason. /// The router itself is also closed. A `on_router_close` callbacks are triggered in all its /// transports all RTP observers. pub fn on_worker_close(&self, callback: F) -> HandlerId { self.inner.handlers.worker_close.add(Box::new(callback)) } /// Callback is called when the router is closed for whatever reason. /// /// NOTE: Callback will be called in place if router is already closed. pub fn on_close(&self, callback: F) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } async fn get_or_create_pipe_transport_pair( &self, pipe_to_router_options: PipeToRouterOptions, ) -> Result { // Here we may have to create a new PipeTransport pair to connect source and // destination Routers. We just want to keep a PipeTransport pair for each // pair of Routers. Since this operation is async, it may happen that two // simultaneous calls piping to the same router would end up generating two pairs of // `PipeTransport`. To prevent that, we have `HashedMap` with mapping behind `Mutex` and // another `Mutex` on the pair of `PipeTransport`. let pipe_transport_pair_mutex = self .inner .mapped_pipe_transports .lock() .entry(pipe_to_router_options.router.id()) .or_default() .clone(); let mut pipe_transport_pair_guard = pipe_transport_pair_mutex.lock().await; let pipe_transport_pair = match pipe_transport_pair_guard .as_ref() .and_then(WeakPipeTransportPair::upgrade) { Some(pipe_transport_pair) => pipe_transport_pair, None => { let pipe_transport_pair = self .create_pipe_transport_pair(pipe_to_router_options) .await?; pipe_transport_pair_guard.replace(pipe_transport_pair.downgrade()); pipe_transport_pair } }; Ok(pipe_transport_pair) } async fn create_pipe_transport_pair( &self, pipe_to_router_options: PipeToRouterOptions, ) -> Result { let PipeToRouterOptions { router, keep_id: _, listen_info, enable_sctp, num_sctp_streams, enable_rtx, enable_srtp, } = pipe_to_router_options; let remote_router_id = router.id(); let transport_options = PipeTransportOptions { enable_sctp, num_sctp_streams, enable_rtx, enable_srtp, app_data: AppData::default(), ..PipeTransportOptions::new(listen_info) }; let local_pipe_transport_fut = self.create_pipe_transport(transport_options.clone()); let remote_pipe_transport_fut = router.create_pipe_transport(transport_options); let (local_pipe_transport, remote_pipe_transport) = future::try_zip(local_pipe_transport_fut, remote_pipe_transport_fut).await?; let local_connect_fut = local_pipe_transport.connect({ let tuple = remote_pipe_transport.tuple(); PipeTransportRemoteParameters { ip: tuple.local_address().parse::().unwrap(), port: tuple.local_port(), srtp_parameters: remote_pipe_transport.srtp_parameters(), } }); let remote_connect_fut = remote_pipe_transport.connect({ let tuple = local_pipe_transport.tuple(); PipeTransportRemoteParameters { ip: tuple.local_address().parse::().unwrap(), port: tuple.local_port(), srtp_parameters: local_pipe_transport.srtp_parameters(), } }); future::try_zip(local_connect_fut, remote_connect_fut).await?; local_pipe_transport .on_close({ let mapped_pipe_transports = Arc::clone(&self.inner.mapped_pipe_transports); Box::new(move || { mapped_pipe_transports.lock().remove(&remote_router_id); }) }) .detach(); remote_pipe_transport .on_close({ let mapped_pipe_transports = Arc::clone(&self.inner.mapped_pipe_transports); Box::new(move || { mapped_pipe_transports.lock().remove(&remote_router_id); }) }) .detach(); Ok(PipeTransportPair { local: local_pipe_transport, remote: remote_pipe_transport, }) } fn after_transport_creation(&self, transport: &impl TransportGeneric) { { let producers_weak = Arc::downgrade(&self.inner.producers); transport .on_new_producer(Arc::new(move |producer| { let producer_id = producer.id(); if let Some(producers) = producers_weak.upgrade() { producers.write().insert(producer_id, producer.downgrade()); } { let producers_weak = producers_weak.clone(); producer .on_close(move || { if let Some(producers) = producers_weak.upgrade() { producers.write().remove(&producer_id); } }) .detach(); } })) .detach(); } { let data_producers_weak = Arc::downgrade(&self.inner.data_producers); transport .on_new_data_producer(Arc::new(move |data_producer| { let data_producer_id = data_producer.id(); if let Some(data_producers) = data_producers_weak.upgrade() { data_producers .write() .insert(data_producer_id, data_producer.downgrade()); } { let data_producers_weak = data_producers_weak.clone(); data_producer .on_close(move || { if let Some(data_producers) = data_producers_weak.upgrade() { data_producers.write().remove(&data_producer_id); } }) .detach(); } })) .detach(); } } fn has_producer(&self, producer_id: &ProducerId) -> bool { self.inner.producers.read().contains_key(producer_id) } fn get_producer(&self, producer_id: &ProducerId) -> Option { self.inner.producers.read().get(producer_id)?.upgrade() } fn has_data_producer(&self, data_producer_id: &DataProducerId) -> bool { self.inner .data_producers .read() .contains_key(data_producer_id) } fn get_data_producer(&self, data_producer_id: &DataProducerId) -> Option { self.inner .data_producers .read() .get(data_producer_id)? .upgrade() } #[cfg(test)] fn close(&self) { self.inner.close(); } } ================================================ FILE: rust/src/rtp_parameters.rs ================================================ //! Collection of RTP-related data structures that are used to specify codec parameters and //! capabilities of various endpoints. use crate::fbs::{FromFbs, ToFbs, TryFromFbs}; use mediasoup_sys::fbs::rtp_parameters; use mediasoup_types::rtp_parameters::*; use std::borrow::Cow; use std::error::Error; use std::str::FromStr; impl<'a> TryFromFbs<'a> for RtpParameters { type FbsType = rtp_parameters::RtpParametersRef<'a>; type Error = Box; fn try_from_fbs(rtp_parameters: Self::FbsType) -> Result { Ok(Self { mid: rtp_parameters.mid()?.map(|mid| mid.to_string()), codecs: rtp_parameters .codecs()? .into_iter() .map(|codec| { let parameters = codec? .parameters()? .unwrap_or(planus::Vector::new_empty()) .into_iter() .map(|parameters| { Ok(( Cow::Owned(parameters?.name()?.to_string()), match parameters?.value()? { rtp_parameters::ValueRef::Boolean(_) | rtp_parameters::ValueRef::Double(_) | rtp_parameters::ValueRef::Integer32Array(_) => { // TODO: Above value variant should not exist in the // first place panic!("Invalid parameter") } rtp_parameters::ValueRef::Integer32(n) => { RtpCodecParametersParametersValue::Number( n.value()?.try_into()?, ) } rtp_parameters::ValueRef::String(s) => { RtpCodecParametersParametersValue::String( s.value()?.to_string().into(), ) } }, )) }) .collect::>>()?; let rtcp_feedback = codec? .rtcp_feedback()? .unwrap_or(planus::Vector::new_empty()) .into_iter() .map(|rtcp_feedback| { Ok(RtcpFeedback::from_type_parameter( rtcp_feedback?.type_()?, rtcp_feedback?.parameter()?.unwrap_or_default(), )?) }) .collect::>>()?; Ok(match MimeType::from_str(codec?.mime_type()?)? { MimeType::Audio(mime_type) => RtpCodecParameters::Audio { mime_type, payload_type: codec?.payload_type()?, clock_rate: codec?.clock_rate()?.try_into()?, channels: codec? .channels()? .ok_or("Audio must have channels specified")? .try_into()?, parameters, rtcp_feedback: vec![], }, MimeType::Video(mime_type) => RtpCodecParameters::Video { mime_type, payload_type: codec?.payload_type()?, clock_rate: codec?.clock_rate()?.try_into()?, parameters, rtcp_feedback, }, }) }) .collect::>>()?, header_extensions: rtp_parameters .header_extensions()? .into_iter() .map(|header_extension_parameters| { Ok(RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::from_fbs(&header_extension_parameters?.uri()?), id: u16::from(header_extension_parameters?.id()?), encrypt: header_extension_parameters?.encrypt()?, }) }) .collect::>>()?, encodings: rtp_parameters .encodings()? .into_iter() .map(|encoding| { Ok(RtpEncodingParameters { ssrc: encoding?.ssrc()?, rid: encoding?.rid()?.map(|rid| rid.to_string()), codec_payload_type: encoding?.codec_payload_type()?, rtx: encoding?.rtx()?.map(|rtx| RtpEncodingParametersRtx { ssrc: rtx.ssrc().unwrap(), }), dtx: { match encoding?.dtx()? { true => Some(true), false => None, } }, scalability_mode: encoding? .scalability_mode()? .unwrap_or(String::from("S1T1").as_str()) .parse()?, max_bitrate: encoding?.max_bitrate()?, }) }) .collect::>>()?, rtcp: RtcpParameters { cname: rtp_parameters .rtcp()? .cname()? .map(|cname| cname.to_string()), reduced_size: rtp_parameters.rtcp()?.reduced_size()?, }, msid: rtp_parameters.msid()?.map(|msid| msid.to_string()), }) } } impl<'a> TryFromFbs<'a> for RtpEncodingParameters { type FbsType = rtp_parameters::RtpEncodingParametersRef<'a>; type Error = Box; fn try_from_fbs(encoding_parameters: Self::FbsType) -> Result { Ok(Self { ssrc: encoding_parameters.ssrc()?, rid: encoding_parameters.rid()?.map(|rid| rid.to_string()), codec_payload_type: encoding_parameters.codec_payload_type()?, rtx: if let Some(rtx) = encoding_parameters.rtx()? { Some(RtpEncodingParametersRtx { ssrc: rtx.ssrc()? }) } else { None }, dtx: { match encoding_parameters.dtx()? { true => Some(true), false => None, } }, scalability_mode: encoding_parameters .scalability_mode()? .map(|maybe_scalability_mode| maybe_scalability_mode.parse()) .transpose()? .unwrap_or_default(), max_bitrate: encoding_parameters.max_bitrate()?, }) } } impl FromFbs for MediaKind { type FbsType = rtp_parameters::MediaKind; fn from_fbs(kind: &Self::FbsType) -> Self { match kind { rtp_parameters::MediaKind::Audio => MediaKind::Audio, rtp_parameters::MediaKind::Video => MediaKind::Video, } } } impl ToFbs for MediaKind { type FbsType = rtp_parameters::MediaKind; fn to_fbs(&self) -> Self::FbsType { match self { MediaKind::Audio => rtp_parameters::MediaKind::Audio, MediaKind::Video => rtp_parameters::MediaKind::Video, } } } impl FromFbs for RtpHeaderExtensionUri { type FbsType = rtp_parameters::RtpHeaderExtensionUri; fn from_fbs(uri: &Self::FbsType) -> Self { match uri { rtp_parameters::RtpHeaderExtensionUri::Mid => RtpHeaderExtensionUri::Mid, rtp_parameters::RtpHeaderExtensionUri::RtpStreamId => { RtpHeaderExtensionUri::RtpStreamId } rtp_parameters::RtpHeaderExtensionUri::RepairRtpStreamId => { RtpHeaderExtensionUri::RepairRtpStreamId } rtp_parameters::RtpHeaderExtensionUri::AbsSendTime => { RtpHeaderExtensionUri::AbsSendTime } rtp_parameters::RtpHeaderExtensionUri::TransportWideCcDraft01 => { RtpHeaderExtensionUri::TransportWideCcDraft01 } rtp_parameters::RtpHeaderExtensionUri::SsrcAudioLevel => { RtpHeaderExtensionUri::SsrcAudioLevel } rtp_parameters::RtpHeaderExtensionUri::DependencyDescriptor => { RtpHeaderExtensionUri::DependencyDescriptor } rtp_parameters::RtpHeaderExtensionUri::VideoOrientation => { RtpHeaderExtensionUri::VideoOrientation } rtp_parameters::RtpHeaderExtensionUri::TimeOffset => RtpHeaderExtensionUri::TimeOffset, rtp_parameters::RtpHeaderExtensionUri::AbsCaptureTime => { RtpHeaderExtensionUri::AbsCaptureTime } rtp_parameters::RtpHeaderExtensionUri::PlayoutDelay => { RtpHeaderExtensionUri::PlayoutDelay } rtp_parameters::RtpHeaderExtensionUri::MediasoupPacketId => { RtpHeaderExtensionUri::MediasoupPacketId } } } } impl ToFbs for RtpHeaderExtensionUri { type FbsType = rtp_parameters::RtpHeaderExtensionUri; fn to_fbs(&self) -> Self::FbsType { match self { RtpHeaderExtensionUri::Mid => rtp_parameters::RtpHeaderExtensionUri::Mid, RtpHeaderExtensionUri::RtpStreamId => { rtp_parameters::RtpHeaderExtensionUri::RtpStreamId } RtpHeaderExtensionUri::RepairRtpStreamId => { rtp_parameters::RtpHeaderExtensionUri::RepairRtpStreamId } RtpHeaderExtensionUri::AbsSendTime => { rtp_parameters::RtpHeaderExtensionUri::AbsSendTime } RtpHeaderExtensionUri::TransportWideCcDraft01 => { rtp_parameters::RtpHeaderExtensionUri::TransportWideCcDraft01 } RtpHeaderExtensionUri::SsrcAudioLevel => { rtp_parameters::RtpHeaderExtensionUri::SsrcAudioLevel } RtpHeaderExtensionUri::DependencyDescriptor => { rtp_parameters::RtpHeaderExtensionUri::DependencyDescriptor } RtpHeaderExtensionUri::VideoOrientation => { rtp_parameters::RtpHeaderExtensionUri::VideoOrientation } RtpHeaderExtensionUri::TimeOffset => rtp_parameters::RtpHeaderExtensionUri::TimeOffset, RtpHeaderExtensionUri::AbsCaptureTime => { rtp_parameters::RtpHeaderExtensionUri::AbsCaptureTime } RtpHeaderExtensionUri::PlayoutDelay => { rtp_parameters::RtpHeaderExtensionUri::PlayoutDelay } RtpHeaderExtensionUri::MediasoupPacketId => { rtp_parameters::RtpHeaderExtensionUri::MediasoupPacketId } RtpHeaderExtensionUri::Unsupported => panic!("Invalid RTP extension header URI"), } } } impl ToFbs for RtpParameters { type FbsType = rtp_parameters::RtpParameters; fn to_fbs(&self) -> rtp_parameters::RtpParameters { rtp_parameters::RtpParameters { mid: self.mid.clone(), codecs: self .codecs .iter() .map(|codec| rtp_parameters::RtpCodecParameters { mime_type: codec.mime_type().as_str().to_string(), payload_type: codec.payload_type(), clock_rate: codec.clock_rate().get(), channels: match &codec { RtpCodecParameters::Audio { channels, .. } => Some(channels.get()), RtpCodecParameters::Video { .. } => None, }, parameters: Some( codec .parameters() .iter() .map(|(name, value)| rtp_parameters::Parameter { name: name.to_string(), value: match value { RtpCodecParametersParametersValue::String(s) => { rtp_parameters::Value::String(Box::new( rtp_parameters::String { value: s.to_string(), }, )) } RtpCodecParametersParametersValue::Number(n) => { rtp_parameters::Value::Integer32(Box::new( rtp_parameters::Integer32 { value: *n as i32 }, )) } }, }) .collect(), ), rtcp_feedback: Some( codec .rtcp_feedback() .iter() .map(|rtcp_feedback| { let (r#type, parameter) = rtcp_feedback.as_type_parameter(); rtp_parameters::RtcpFeedback { type_: r#type.to_string(), parameter: Some(parameter.to_string()), } }) .collect(), ), }) .collect(), header_extensions: self .header_extensions .iter() .map( |header_extension_parameters| rtp_parameters::RtpHeaderExtensionParameters { uri: header_extension_parameters.uri.to_fbs(), id: header_extension_parameters.id as u8, encrypt: header_extension_parameters.encrypt, parameters: None, }, ) .collect(), encodings: self .encodings .iter() .map(|encoding| rtp_parameters::RtpEncodingParameters { ssrc: encoding.ssrc, rid: encoding.rid.clone(), codec_payload_type: encoding.codec_payload_type, rtx: encoding .rtx .map(|rtx| Box::new(rtp_parameters::Rtx { ssrc: rtx.ssrc })), dtx: encoding.dtx.unwrap_or_default(), scalability_mode: if encoding.scalability_mode.is_none() { None } else { Some(encoding.scalability_mode.as_str().to_string()) }, max_bitrate: encoding.max_bitrate, }) .collect(), rtcp: Box::new(rtp_parameters::RtcpParameters { cname: self.rtcp.cname.clone(), reduced_size: self.rtcp.reduced_size, }), msid: self.msid.clone(), } } } impl ToFbs for RtpEncodingParameters { type FbsType = rtp_parameters::RtpEncodingParameters; fn to_fbs(&self) -> Self::FbsType { rtp_parameters::RtpEncodingParameters { ssrc: self.ssrc, rid: self.rid.clone(), codec_payload_type: self.codec_payload_type, rtx: self .rtx .map(|rtx| Box::new(rtp_parameters::Rtx { ssrc: rtx.ssrc })), dtx: self.dtx.unwrap_or_default(), scalability_mode: if self.scalability_mode.is_none() { None } else { Some(self.scalability_mode.as_str().to_string()) }, max_bitrate: self.max_bitrate, } } } ================================================ FILE: rust/src/scalability_modes.rs ================================================ //! Scalability mode. use once_cell::sync::OnceCell; use regex::Regex; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; use std::num::NonZeroU8; use std::str::FromStr; use thiserror::Error; /// Scalability mode. /// /// Most modes match [webrtc-svc](https://w3c.github.io/webrtc-svc/), but custom ones are also /// supported by mediasoup. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum ScalabilityMode { /// No scalability used, there is just one spatial and one temporal layer. None, /// L1T2. L1T2, /// L1T2h. L1T2h, /// L1T3. L1T3, /// L1T3h. L1T3h, /// L2T1. L2T1, /// L2T1h. L2T1h, /// L2T1_KEY. L2T1Key, /// L2T2. L2T2, /// L2T2h. L2T2h, /// L2T2_KEY. L2T2Key, /// L2T2_KEY_SHIFT. L2T2KeyShift, /// L2T3. L2T3, /// L2T3h. L2T3h, /// L2T3_KEY. L2T3Key, /// L2T3_KEY_SHIFT. L2T3KeyShift, /// L3T1. L3T1, /// L3T1h. L3T1h, /// L3T1_KEY. L3T1Key, /// L3T2. L3T2, /// L3T2h. L3T2h, /// L3T2_KEY. L3T2Key, /// L3T2_KEY_SHIFT. L3T2KeyShift, /// L3T3. L3T3, /// L3T3h. L3T3h, /// L3T3_KEY. L3T3Key, /// L3T3_KEY_SHIFT. L3T3KeyShift, /// S2T1. S2T1, /// S2T1h. S2T1h, /// S2T2. S2T2, /// S2T2h. S2T2h, /// S2T3. S2T3, /// S2T3h. S2T3h, /// S3T1. S3T1, /// S3T1h. S3T1h, /// S3T2. S3T2, /// S3T2h. S3T2h, /// S3T3. S3T3, /// S3T3h. S3T3h, /// Custom scalability mode not defined in [webrtc-svc](https://w3c.github.io/webrtc-svc/). Custom { /// Scalability mode as string scalability_mode: String, /// Number of spatial layers. spatial_layers: NonZeroU8, /// Number of temporal layers. temporal_layers: NonZeroU8, /// K-SVC mode. ksvc: bool, }, } impl Default for ScalabilityMode { fn default() -> Self { Self::None } } /// Error that caused [`ScalabilityMode`] parsing error. #[derive(Debug, Error, Eq, PartialEq)] pub enum ParseScalabilityModeError { /// Invalid input string #[error("Invalid Scalability Mode input string")] InvalidInput, } impl FromStr for ScalabilityMode { type Err = ParseScalabilityModeError; fn from_str(scalability_mode: &str) -> Result { Ok(match scalability_mode { "S1T1" => Self::None, "L1T2" => Self::L1T2, "L1T2h" => Self::L1T2h, "L1T3" => Self::L1T3, "L1T3h" => Self::L1T3h, "L2T1" => Self::L2T1, "L2T1h" => Self::L2T1h, "L2T1_KEY" => Self::L2T1Key, "L2T2" => Self::L2T2, "L2T2h" => Self::L2T2h, "L2T2_KEY" => Self::L2T2Key, "L2T2_KEY_SHIFT" => Self::L2T2KeyShift, "L2T3" => Self::L2T3, "L2T3h" => Self::L2T3h, "L2T3_KEY" => Self::L2T3Key, "L2T3_KEY_SHIFT" => Self::L2T3KeyShift, "L3T1" => Self::L3T1, "L3T1h" => Self::L3T1h, "L3T1_KEY" => Self::L3T1Key, "L3T2" => Self::L3T2, "L3T2h" => Self::L3T2h, "L3T2_KEY" => Self::L3T2Key, "L3T2_KEY_SHIFT" => Self::L3T2KeyShift, "L3T3" => Self::L3T3, "L3T3h" => Self::L3T3h, "L3T3_KEY" => Self::L3T3Key, "L3T3_KEY_SHIFT" => Self::L3T3KeyShift, "S2T1" => Self::S2T1, "S2T1h" => Self::S2T1h, "S2T2" => Self::S2T2, "S2T2h" => Self::S2T2h, "S2T3" => Self::S2T3, "S2T3h" => Self::S2T3h, "S3T1" => Self::S3T1, "S3T1h" => Self::S3T1h, "S3T2" => Self::S3T2, "S3T2h" => Self::S3T2h, "S3T3" => Self::S3T3, "S3T3h" => Self::S3T3h, scalability_mode => { static SCALABILITY_MODE_REGEX: OnceCell = OnceCell::new(); SCALABILITY_MODE_REGEX .get_or_init(|| Regex::new(r"^[LS]([1-9][0-9]?)T([1-9][0-9]?)(_KEY)?").unwrap()) .captures(scalability_mode) .map(|captures| Self::Custom { scalability_mode: scalability_mode.to_string(), spatial_layers: captures.get(1).unwrap().as_str().parse().unwrap(), temporal_layers: captures.get(2).unwrap().as_str().parse().unwrap(), ksvc: captures.get(3).is_some(), }) .ok_or(ParseScalabilityModeError::InvalidInput)? } }) } } impl std::fmt::Display for ScalabilityMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } impl ScalabilityMode { /// Returns true if there is no scalability. pub fn is_none(&self) -> bool { matches!(self, Self::None) } /// Number of spatial layers. pub fn spatial_layers(&self) -> NonZeroU8 { match self { Self::None | Self::L1T2 | Self::L1T2h | Self::L1T3 | Self::L1T3h => { NonZeroU8::new(1).unwrap() } Self::L2T1 | Self::L2T1h | Self::L2T1Key | Self::L2T2 | Self::L2T2h | Self::L2T2Key | Self::L2T2KeyShift | Self::L2T3 | Self::L2T3h | Self::L2T3Key | Self::L2T3KeyShift | Self::S2T1 | Self::S2T1h | Self::S2T2 | Self::S2T2h | Self::S2T3 | Self::S2T3h => NonZeroU8::new(2).unwrap(), Self::L3T1 | Self::L3T1h | Self::L3T1Key | Self::L3T2 | Self::L3T2h | Self::L3T2Key | Self::L3T2KeyShift | Self::L3T3 | Self::L3T3h | Self::L3T3Key | Self::L3T3KeyShift | Self::S3T1 | Self::S3T1h | Self::S3T2 | Self::S3T2h | Self::S3T3 | Self::S3T3h => NonZeroU8::new(3).unwrap(), Self::Custom { spatial_layers, .. } => *spatial_layers, } } /// Number of temporal layers. pub fn temporal_layers(&self) -> NonZeroU8 { match self { Self::None | Self::L2T1 | Self::L2T1h | Self::L2T1Key | Self::L3T1 | Self::L3T1h | Self::L3T1Key | Self::S2T1 | Self::S2T1h | Self::S3T1 | Self::S3T1h => NonZeroU8::new(1).unwrap(), Self::L1T2 | Self::L1T2h | Self::L2T2 | Self::L2T2h | Self::L2T2Key | Self::L2T2KeyShift | Self::L3T2 | Self::L3T2h | Self::L3T2Key | Self::L3T2KeyShift | Self::S2T2 | Self::S2T2h | Self::S3T2 | Self::S3T2h => NonZeroU8::new(2).unwrap(), Self::L1T3 | Self::L1T3h | Self::L2T3 | Self::L2T3h | Self::L2T3Key | Self::L2T3KeyShift | Self::L3T3 | Self::L3T3h | Self::L3T3Key | Self::L3T3KeyShift | Self::S2T3 | Self::S2T3h | Self::S3T3 | Self::S3T3h => NonZeroU8::new(3).unwrap(), Self::Custom { temporal_layers, .. } => *temporal_layers, } } /// K-SVC mode. pub fn ksvc(&self) -> bool { match self { Self::L2T2Key | Self::L2T2KeyShift | Self::L2T3Key | Self::L2T3KeyShift | Self::L3T1Key | Self::L3T2Key | Self::L3T2KeyShift | Self::L3T3Key | Self::L3T3KeyShift => true, Self::Custom { ksvc, .. } => *ksvc, _ => false, } } /// String representation of scalability mode. pub fn as_str(&self) -> &str { match self { Self::None => "S1T1", Self::L1T2 => "L1T2", Self::L1T2h => "L1T2h", Self::L1T3 => "L1T3", Self::L1T3h => "L1T3h", Self::L2T1 => "L2T1", Self::L2T1h => "L2T1h", Self::L2T1Key => "L2T1_KEY", Self::L2T2 => "L2T2", Self::L2T2h => "L2T2h", Self::L2T2Key => "L2T2_KEY", Self::L2T2KeyShift => "L2T2_KEY_SHIFT", Self::L2T3 => "L2T3", Self::L2T3h => "L2T3h", Self::L2T3Key => "L2T3_KEY", Self::L2T3KeyShift => "L2T3_KEY_SHIFT", Self::L3T1 => "L3T1", Self::L3T1h => "L3T1h", Self::L3T1Key => "L3T1_KEY", Self::L3T2 => "L3T2", Self::L3T2h => "L3T2h", Self::L3T2Key => "L3T2_KEY", Self::L3T2KeyShift => "L3T2_KEY_SHIFT", Self::L3T3 => "L3T3", Self::L3T3h => "L3T3h", Self::L3T3Key => "L3T3_KEY", Self::L3T3KeyShift => "L3T3_KEY_SHIFT", Self::S2T1 => "S2T1", Self::S2T1h => "S2T1h", Self::S2T2 => "S2T2", Self::S2T2h => "S2T2h", Self::S2T3 => "S2T3", Self::S2T3h => "S2T3h", Self::S3T1 => "S3T1", Self::S3T1h => "S3T1h", Self::S3T2 => "S3T2", Self::S3T2h => "S3T2h", Self::S3T3 => "S3T3", Self::S3T3h => "S3T3h", Self::Custom { scalability_mode, .. } => scalability_mode, } } } impl<'de> Deserialize<'de> for ScalabilityMode { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct ScalabilityModeVisitor; impl<'de> de::Visitor<'de> for ScalabilityModeVisitor { type Value = ScalabilityMode; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str(r#"Scalability mode string like "S1T3""#) } #[inline] fn visit_none(self) -> Result where E: de::Error, { Ok(ScalabilityMode::None) } fn visit_str(self, v: &str) -> Result where E: de::Error, { v.parse().map_err(de::Error::custom) } } deserializer.deserialize_str(ScalabilityModeVisitor) } } impl Serialize for ScalabilityMode { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(self.as_str()) } } ================================================ FILE: rust/src/sctp_parameters.rs ================================================ //! SCTP parameters. use crate::fbs::{FromFbs, ToFbs}; use mediasoup_sys::fbs::sctp_parameters; use mediasoup_types::sctp_parameters::*; impl ToFbs for NumSctpStreams { type FbsType = sctp_parameters::NumSctpStreams; fn to_fbs(&self) -> Self::FbsType { sctp_parameters::NumSctpStreams { os: self.os, mis: self.mis, } } } impl FromFbs for SctpParameters { type FbsType = sctp_parameters::SctpParameters; fn from_fbs(parameters: &Self::FbsType) -> Self { Self { port: parameters.port, os: parameters.os, mis: parameters.mis, max_message_size: parameters.max_message_size, } } } impl FromFbs for SctpStreamParameters { type FbsType = sctp_parameters::SctpStreamParameters; fn from_fbs(stream_parameters: &Self::FbsType) -> Self { Self { stream_id: stream_parameters.stream_id, ordered: stream_parameters.ordered.unwrap_or(false), max_packet_life_time: stream_parameters.max_packet_life_time, max_retransmits: stream_parameters.max_retransmits, } } } impl ToFbs for SctpStreamParameters { type FbsType = sctp_parameters::SctpStreamParameters; fn to_fbs(&self) -> Self::FbsType { sctp_parameters::SctpStreamParameters { stream_id: self.stream_id, ordered: Some(self.ordered), max_packet_life_time: self.max_packet_life_time, max_retransmits: self.max_retransmits, } } } ================================================ FILE: rust/src/srtp_parameters.rs ================================================ //! SRTP parameters. use crate::fbs::{FromFbs, ToFbs}; use mediasoup_sys::fbs::srtp_parameters; use mediasoup_types::srtp_parameters::*; impl FromFbs for SrtpParameters { type FbsType = srtp_parameters::SrtpParameters; fn from_fbs(tuple: &Self::FbsType) -> Self { Self { crypto_suite: SrtpCryptoSuite::from_fbs(&tuple.crypto_suite), key_base64: String::from(tuple.key_base64.as_str()), } } } impl ToFbs for SrtpParameters { type FbsType = srtp_parameters::SrtpParameters; fn to_fbs(&self) -> Self::FbsType { srtp_parameters::SrtpParameters { crypto_suite: SrtpCryptoSuite::to_fbs(&self.crypto_suite), key_base64: String::from(self.key_base64.as_str()), } } } impl FromFbs for SrtpCryptoSuite { type FbsType = srtp_parameters::SrtpCryptoSuite; fn from_fbs(crypto_suite: &Self::FbsType) -> Self { match crypto_suite { srtp_parameters::SrtpCryptoSuite::AeadAes256Gcm => Self::AeadAes256Gcm, srtp_parameters::SrtpCryptoSuite::AeadAes128Gcm => Self::AeadAes128Gcm, srtp_parameters::SrtpCryptoSuite::AesCm128HmacSha180 => Self::AesCm128HmacSha180, srtp_parameters::SrtpCryptoSuite::AesCm128HmacSha132 => Self::AesCm128HmacSha132, } } } impl ToFbs for SrtpCryptoSuite { type FbsType = srtp_parameters::SrtpCryptoSuite; fn to_fbs(&self) -> Self::FbsType { match self { Self::AeadAes256Gcm => srtp_parameters::SrtpCryptoSuite::AeadAes256Gcm, Self::AeadAes128Gcm => srtp_parameters::SrtpCryptoSuite::AeadAes128Gcm, Self::AesCm128HmacSha180 => srtp_parameters::SrtpCryptoSuite::AesCm128HmacSha180, Self::AesCm128HmacSha132 => srtp_parameters::SrtpCryptoSuite::AesCm128HmacSha132, } } } ================================================ FILE: rust/src/supported_rtp_capabilities.rs ================================================ //! RTP capabilities supported by mediasoup. use mediasoup_types::rtp_parameters::{ MediaKind, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtpCapabilities, RtpCodecCapability, RtpCodecParametersParameters, RtpHeaderExtension, RtpHeaderExtensionDirection, RtpHeaderExtensionUri, }; use std::num::{NonZeroU32, NonZeroU8}; /// Get a mediasoup supported RTP capabilities. /// /// # Notes on usage /// Those are NOT the RTP capabilities needed by mediasoup-client's /// [device.load()](https://mediasoup.org/documentation/v3/mediasoup-client/api/#device-load) and /// libmediasoupclient's /// [device.Load()](https://mediasoup.org/documentation/v3/libmediasoupclient/api/#device-Load) /// methods. There you must use /// [`Router::rtp_capabilities`](crate::router::Router::rtp_capabilities) getter instead. #[must_use] pub fn get_supported_rtp_capabilities() -> RtpCapabilities { RtpCapabilities { codecs: vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(4).unwrap(), // Quad channel parameters: RtpCodecParametersParameters::from([ ("channel_mapping", "0,1,2,3".into()), ("num_streams", 2_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), // 5.1 parameters: RtpCodecParametersParameters::from([ ("channel_mapping", "0,4,1,2,3,5".into()), ("num_streams", 4_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(8).unwrap(), // 7.1 parameters: RtpCodecParametersParameters::from([ ("channel_mapping", "0,6,1,2,3,4,5,7".into()), ("num_streams", 5_u32.into()), ("coupled_streams", 3_u32.into()), ]), rtcp_feedback: vec![RtcpFeedback::Nack, RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Pcmu, preferred_payload_type: Some(0), clock_rate: NonZeroU32::new(8000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Pcma, preferred_payload_type: Some(8), clock_rate: NonZeroU32::new(8000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Isac, preferred_payload_type: None, clock_rate: NonZeroU32::new(32000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Isac, preferred_payload_type: None, clock_rate: NonZeroU32::new(16000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::G722, preferred_payload_type: Some(9), clock_rate: NonZeroU32::new(8000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Ilbc, preferred_payload_type: None, clock_rate: NonZeroU32::new(8000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Silk, preferred_payload_type: None, clock_rate: NonZeroU32::new(24000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Silk, preferred_payload_type: None, clock_rate: NonZeroU32::new(16000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Silk, preferred_payload_type: None, clock_rate: NonZeroU32::new(12000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Silk, preferred_payload_type: None, clock_rate: NonZeroU32::new(8000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::TransportCc], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Cn, preferred_payload_type: Some(13), clock_rate: NonZeroU32::new(32000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Cn, preferred_payload_type: Some(13), clock_rate: NonZeroU32::new(16000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Cn, preferred_payload_type: Some(13), clock_rate: NonZeroU32::new(8000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::TelephoneEvent, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::TelephoneEvent, preferred_payload_type: None, clock_rate: NonZeroU32::new(32000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::TelephoneEvent, preferred_payload_type: None, clock_rate: NonZeroU32::new(16000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Audio { mime_type: MimeTypeAudio::TelephoneEvent, preferred_payload_type: None, clock_rate: NonZeroU32::new(8000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp9, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([( "level-asymmetry-allowed", 1_u32.into(), )]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::AV1, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, RtcpFeedback::TransportCc, ], }, ], header_extensions: vec![ RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::Mid, preferred_id: 1, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::Mid, preferred_id: 1, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::RtpStreamId, preferred_id: 2, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::RecvOnly, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::RepairRtpStreamId, preferred_id: 3, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::RecvOnly, }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::AbsSendTime, preferred_id: 4, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::AbsSendTime, preferred_id: 4, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, // NOTE: For audio we just enable transport-wide-cc-01 when receiving media. RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::TransportWideCcDraft01, preferred_id: 5, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::RecvOnly, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::TransportWideCcDraft01, preferred_id: 5, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::SsrcAudioLevel, preferred_id: 6, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::DependencyDescriptor, preferred_id: 7, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::RecvOnly, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::VideoOrientation, preferred_id: 8, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::TimeOffset, preferred_id: 9, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::AbsCaptureTime, preferred_id: 10, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::AbsCaptureTime, preferred_id: 10, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::PlayoutDelay, preferred_id: 11, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::PlayoutDelay, preferred_id: 11, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::MediasoupPacketId, preferred_id: 12, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::MediasoupPacketId, preferred_id: 12, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, ], } } ================================================ FILE: rust/src/webrtc_server/tests.rs ================================================ use crate::webrtc_server::{WebRtcServerListenInfos, WebRtcServerOptions}; use crate::worker::{Worker, WorkerSettings}; use crate::worker_manager::WorkerManager; use futures_lite::future; use mediasoup_types::data_structures::{ListenInfo, Protocol}; use portpicker::pick_unused_port; use std::env; use std::net::{IpAddr, Ipv4Addr}; async fn init() -> Worker { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker") } #[test] fn worker_close_event() { future::block_on(async move { let worker = init().await; let port = pick_unused_port().unwrap(); let webrtc_server = worker .create_webrtc_server(WebRtcServerOptions::new(WebRtcServerListenInfos::new( ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }, ))) .await .expect("Failed to create WebRTC server"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = webrtc_server.on_close(move || { let _ = close_tx.send(()); }); let (mut worker_close_tx, worker_close_rx) = async_oneshot::oneshot::<()>(); let _handler = webrtc_server.on_worker_close(move || { let _ = worker_close_tx.send(()); }); worker.close(); worker_close_rx .await .expect("Failed to receive worker_close event"); close_rx.await.expect("Failed to receive close event"); assert!(webrtc_server.closed()); }); } ================================================ FILE: rust/src/webrtc_server.rs ================================================ //! A WebRTC server brings the ability to listen on a single UDP/TCP port for multiple //! `WebRtcTransport`s. //! //! A WebRTC server exists within the context of a [`Worker`], meaning that if your app launches N //! workers it also needs to create N WebRTC servers listening on different ports (to not collide). //! The WebRTC transport implementation of mediasoup is //! [ICE Lite](https://tools.ietf.org/html/rfc5245#section-2.7), meaning that it does not initiate //! ICE connections but expects ICE Binding Requests from endpoints. #[cfg(test)] mod tests; use crate::messages::{WebRtcServerCloseRequest, WebRtcServerDumpRequest}; use crate::transport::TransportId; use crate::uuid_based_wrapper_type; use crate::webrtc_transport::WebRtcTransport; use crate::worker::{Channel, RequestError, Worker}; use async_executor::Executor; use event_listener_primitives::{BagOnce, HandlerId}; use hash_hasher::HashedSet; use log::{debug, error}; use mediasoup_types::data_structures::{AppData, ListenInfo}; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::fmt; use std::net::IpAddr; use std::ops::Deref; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; use thiserror::Error; uuid_based_wrapper_type!( /// [`WebRtcServer`] identifier. WebRtcServerId ); #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[doc(hidden)] pub struct WebRtcServerIpPort { pub ip: IpAddr, pub port: u16, } #[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct WebRtcServerIceUsernameFragment { pub local_ice_username_fragment: String, #[serde(rename = "webRtcTransportId")] pub webrtc_transport_id: TransportId, } #[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct WebRtcServerTupleHash { pub tuple_hash: u64, #[serde(rename = "webRtcTransportId")] pub webrtc_transport_id: TransportId, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct WebRtcServerDump { pub id: WebRtcServerId, pub udp_sockets: Vec, pub tcp_servers: Vec, #[serde(rename = "webRtcTransportIds")] pub webrtc_transport_ids: HashedSet, pub local_ice_username_fragments: Vec, pub tuple_hashes: Vec, } /// Struct that protects an invariant of having non-empty list of listen infos. #[derive(Debug, Clone, Eq, PartialEq, Serialize)] pub struct WebRtcServerListenInfos(Vec); impl WebRtcServerListenInfos { /// Create WebRTC server listen infos with given info populated initially. #[must_use] pub fn new(listen_info: ListenInfo) -> Self { Self(vec![listen_info]) } /// Insert another listen info. #[must_use] pub fn insert(mut self, listen_info: ListenInfo) -> Self { self.0.push(listen_info); self } } impl Deref for WebRtcServerListenInfos { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } /// Empty list of listen infos provided, should have at least one element. #[derive(Error, Debug, Eq, PartialEq)] #[error("Empty list of listen infos provided, should have at least one element")] pub struct EmptyListError; impl TryFrom> for WebRtcServerListenInfos { type Error = EmptyListError; fn try_from(listen_infos: Vec) -> Result { if listen_infos.is_empty() { Err(EmptyListError) } else { Ok(Self(listen_infos)) } } } /// [`WebRtcServer`] options. #[derive(Debug)] #[non_exhaustive] pub struct WebRtcServerOptions { /// Listening infos in order of preference (first one is the preferred one). pub listen_infos: WebRtcServerListenInfos, /// Custom application data. pub app_data: AppData, } impl WebRtcServerOptions { /// Create [`WebRtcServer`] options with given listen infos. pub fn new(listen_infos: WebRtcServerListenInfos) -> Self { Self { listen_infos, app_data: AppData::default(), } } } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { new_webrtc_transport: BagOnce>, worker_close: BagOnce>, close: BagOnce>, } struct Inner { id: WebRtcServerId, executor: Arc>, channel: Channel, handlers: Arc, app_data: AppData, worker: Worker, closed: AtomicBool, _on_worker_close_handler: Mutex, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(); } } impl Inner { fn close(&self) { if !self.closed.swap(true, Ordering::SeqCst) { self.handlers.close.call_simple(); { let channel = self.channel.clone(); let request = WebRtcServerCloseRequest { webrtc_server_id: self.id, }; self.executor .spawn(async move { match channel.request("", request).await { Err(RequestError::ChannelClosed) => { debug!( "WebRTC server closing failed on drop: Channel already closed" ); } Err(error) => { error!("WebRTC server closing failed on drop: {}", error); } Ok(_) => {} } }) .detach(); } } } } /// A WebRTC server brings the ability to listen on a single UDP/TCP port for multiple /// `WebRtcTransport`s. /// /// A WebRTC server exists within the context of a [`Worker`], meaning that if your app launches N /// workers it also needs to create N WebRTC servers listening on different ports (to not collide). /// The WebRTC transport implementation of mediasoup is /// [ICE Lite](https://tools.ietf.org/html/rfc5245#section-2.7), meaning that it does not initiate /// ICE connections but expects ICE Binding Requests from endpoints. #[derive(Clone)] pub struct WebRtcServer { inner: Arc, } impl fmt::Debug for WebRtcServer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WebRtcServer") .field("id", &self.inner.id) .field("worker", &self.inner.worker) .field("closed", &self.inner.closed) .finish() } } impl WebRtcServer { pub(crate) fn new( id: WebRtcServerId, executor: Arc>, channel: Channel, app_data: AppData, worker: Worker, ) -> Self { let handlers = Arc::::default(); let inner_weak = Arc::>>>::default(); let on_worker_close_handler = worker.on_close({ let inner_weak = Arc::clone(&inner_weak); move || { let maybe_inner = inner_weak.lock().as_ref().and_then(Weak::upgrade); if let Some(inner) = maybe_inner { inner.handlers.worker_close.call_simple(); if !inner.closed.swap(true, Ordering::SeqCst) { inner.handlers.close.call_simple(); } } } }); let inner = Arc::new(Inner { id, executor, channel, handlers, app_data, worker, closed: AtomicBool::new(false), _on_worker_close_handler: Mutex::new(on_worker_close_handler), }); inner_weak.lock().replace(Arc::downgrade(&inner)); Self { inner } } /// Router id. #[must_use] pub fn id(&self) -> WebRtcServerId { self.inner.id } /// Worker to which WebRTC server belongs. pub fn worker(&self) -> &Worker { &self.inner.worker } /// Custom application data. #[must_use] pub fn app_data(&self) -> &AppData { &self.inner.app_data } /// Whether WebRTC server is closed. #[must_use] pub fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } /// Dump WebRTC server. #[doc(hidden)] pub async fn dump(&self) -> Result { debug!("dump()"); self.inner .channel .request(self.id(), WebRtcServerDumpRequest {}) .await } /// Callback is called when the worker this WebRTC server belongs to is closed for whatever /// reason. /// The WebRtc server itself is also closed. A `on_webrtc_server_close` callbacks are /// triggered in all relevant WebRTC transports. pub fn on_worker_close(&self, callback: F) -> HandlerId { self.inner.handlers.worker_close.add(Box::new(callback)) } /// Callback is called when new [`WebRtcTransport`] is added that uses this WebRTC server. pub fn on_new_webrtc_transport(&self, callback: F) -> HandlerId where F: Fn(&WebRtcTransport) + Send + 'static, { self.inner .handlers .new_webrtc_transport .add(Box::new(callback)) } /// Callback is called when the WebRTC server is closed for whatever reason. /// /// NOTE: Callback will be called in place if WebRTC server is already closed. pub fn on_close(&self, callback: F) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } pub(crate) fn notify_new_webrtc_transport(&self, webrtc_transport: &WebRtcTransport) { self.inner .handlers .new_webrtc_transport .call(|callback| callback(webrtc_transport)); } #[cfg(test)] pub(crate) fn close(&self) { self.inner.close(); } } ================================================ FILE: rust/src/worker/channel.rs ================================================ use crate::messages::{Notification, Request}; use crate::worker::common::{EventHandlers, SubscriptionTarget, WeakEventHandlers}; use crate::worker::utils; use crate::worker::utils::{PreparedChannelRead, PreparedChannelWrite}; use crate::worker::{RequestError, SubscriptionHandler}; use atomic_take::AtomicTake; use hash_hasher::HashedMap; use log::{debug, error, trace, warn}; use lru::LruCache; use mediasoup_sys::fbs::{message, notification, response}; use mediasoup_sys::UvAsyncT; use parking_lot::Mutex; use planus::ReadAsRoot; use serde::Deserialize; use std::collections::VecDeque; use std::fmt::{Debug, Display}; use std::num::NonZeroUsize; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Weak}; use thiserror::Error; use uuid::Uuid; #[derive(Debug, Deserialize)] #[serde(untagged)] pub(super) enum InternalMessage { /// Debug log #[serde(skip)] Debug(String), /// Warn log #[serde(skip)] Warn(String), /// Error log #[serde(skip)] Error(String), /// Dump log #[serde(skip)] Dump(String), /// Unknown #[serde(skip)] Unexpected(Vec), } #[derive(Debug, Error, Eq, PartialEq)] pub enum NotificationError { #[error("Channel already closed")] ChannelClosed, } /// Flabtuffers notification parse error. #[derive(Debug, Error, Eq, PartialEq)] pub enum NotificationParseError { /// Invalid event #[error("Invalid event")] InvalidEvent, } #[allow(clippy::type_complexity)] pub(crate) struct BufferMessagesGuard { target_id: SubscriptionTarget, buffered_notifications_for: Arc>>>>, event_handlers_weak: WeakEventHandlers) + Send + Sync + 'static>>, } impl Drop for BufferMessagesGuard { fn drop(&mut self) { let mut buffered_notifications_for = self.buffered_notifications_for.lock(); if let Some(notifications) = buffered_notifications_for.remove(&self.target_id) { if let Some(event_handlers) = self.event_handlers_weak.upgrade() { for bytes in notifications { let message_ref = message::MessageRef::read_as_root(&bytes).unwrap(); let message::BodyRef::Notification(notification_ref) = message_ref.data().unwrap() else { panic!("Wrong notification stored: {message_ref:?}"); }; event_handlers .call_callbacks_with_single_value(&self.target_id, notification_ref); } } } } } #[derive(Debug)] enum ChannelReceiveMessage<'a> { Notification(notification::NotificationRef<'a>), Response(response::ResponseRef<'a>), Event(InternalMessage), } // Remove the first 4 bytes which represent the buffer size. // NOTE: The prefix is only needed for NodeJS. fn unprefix_message(bytes: &[u8]) -> &[u8] { &bytes[4..] } fn deserialize_message(bytes: &[u8]) -> ChannelReceiveMessage<'_> { let message_ref = message::MessageRef::read_as_root(bytes).unwrap(); match message_ref.data().unwrap() { message::BodyRef::Log(data) => match data.data().unwrap().chars().next() { // Debug log Some('D') => ChannelReceiveMessage::Event(InternalMessage::Debug( String::from_utf8(Vec::from(&data.data().unwrap().as_bytes()[1..])).unwrap(), )), // Warn log Some('W') => ChannelReceiveMessage::Event(InternalMessage::Warn( String::from_utf8(Vec::from(&data.data().unwrap().as_bytes()[1..])).unwrap(), )), // Error log Some('E') => ChannelReceiveMessage::Event(InternalMessage::Error( String::from_utf8(Vec::from(&data.data().unwrap().as_bytes()[1..])).unwrap(), )), // Dump log Some('X') => ChannelReceiveMessage::Event(InternalMessage::Dump( String::from_utf8(Vec::from(&data.data().unwrap().as_bytes()[1..])).unwrap(), )), // This should never happen. _ => ChannelReceiveMessage::Event(InternalMessage::Unexpected(Vec::from(bytes))), }, message::BodyRef::Notification(data) => ChannelReceiveMessage::Notification(data), message::BodyRef::Response(data) => ChannelReceiveMessage::Response(data), _ => ChannelReceiveMessage::Event(InternalMessage::Unexpected(Vec::from(bytes))), } } struct ResponseError { reason: String, } type FBSResponseResult = Result>, ResponseError>; struct RequestDropGuard<'a> { id: u32, message: Arc>>, channel: &'a Channel, removed: bool, } impl<'a> Drop for RequestDropGuard<'a> { fn drop(&mut self) { if self.removed { return; } // Drop pending message from memory self.message.take(); // Remove request handler from the container if let Some(requests_container) = self.channel.inner.requests_container_weak.upgrade() { requests_container.lock().handlers.remove(&self.id); } } } impl<'a> RequestDropGuard<'a> { fn remove(mut self) { self.removed = true; } } #[derive(Default)] struct FBSRequestsContainer { next_id: u32, handlers: HashedMap>, } struct OutgoingMessageBuffer { handle: Option, messages: VecDeque>>>, } // TODO: use 'close' in 'request' method. #[allow(clippy::type_complexity, dead_code)] struct Inner { outgoing_message_buffer: Arc>, internal_message_receiver: async_channel::Receiver, requests_container_weak: Weak>, buffered_notifications_for: Arc>>>>, event_handlers_weak: WeakEventHandlers) + Send + Sync + 'static>>, worker_closed: Arc, closed: AtomicBool, } impl Drop for Inner { fn drop(&mut self) { self.internal_message_receiver.close(); } } #[derive(Clone)] pub(crate) struct Channel { inner: Arc, } impl Channel { pub(super) fn new( worker_closed: Arc, ) -> (Self, PreparedChannelRead, PreparedChannelWrite) { let outgoing_message_buffer = Arc::new(Mutex::new(OutgoingMessageBuffer { handle: None, messages: VecDeque::with_capacity(10), })); let requests_container = Arc::>::default(); let requests_container_weak = Arc::downgrade(&requests_container); let buffered_notifications_for = Arc::>>>>::default(); let event_handlers = EventHandlers::new(); let event_handlers_weak = event_handlers.downgrade(); let prepared_channel_read = utils::prepare_channel_read_fn({ let outgoing_message_buffer = Arc::clone(&outgoing_message_buffer); move |handle| { let mut outgoing_message_buffer = outgoing_message_buffer.lock(); if outgoing_message_buffer.handle.is_none() { outgoing_message_buffer.handle.replace(handle); } while let Some(maybe_message) = outgoing_message_buffer.messages.pop_front() { // Request might have already been cancelled if let Some(message) = maybe_message.take() { return Some(message); } } None } }); let (internal_message_sender, internal_message_receiver) = async_channel::unbounded(); let prepared_channel_write = utils::prepare_channel_write_fn({ let buffered_notifications_for = Arc::clone(&buffered_notifications_for); // This this contain cache of targets that are known to not have buffering, so // that we can avoid Mutex locking overhead for them let mut non_buffered_notifications = LruCache::::new( NonZeroUsize::new(1000).expect("Not zero; qed"), ); move |message| { trace!("received raw message: {}", String::from_utf8_lossy(message)); let message = unprefix_message(message); match deserialize_message(message) { ChannelReceiveMessage::Notification(notification) => { let target_id = notification.handler_id().unwrap(); // Target id can be either the worker PID or a UUID. let target_id = match target_id.parse::() { Ok(_) => SubscriptionTarget::String(target_id.to_string()), Err(_) => SubscriptionTarget::Uuid(Uuid::parse_str(target_id).unwrap()), }; if !non_buffered_notifications.contains(&target_id) { let mut buffer_notifications_for = buffered_notifications_for.lock(); // Check if we need to buffer notifications for this // target_id if let Some(list) = buffer_notifications_for.get_mut(&target_id) { // Store the whole message removing the size prefix. list.push(Vec::from(message)); return; } // Remember we don't need to buffer these non_buffered_notifications.put(target_id.clone(), ()); } event_handlers.call_callbacks_with_single_value(&target_id, notification); } ChannelReceiveMessage::Response(response) => { let sender = requests_container .lock() .handlers .remove(&response.id().unwrap()); if let Some(mut sender) = sender { // Request did not succeed. if let Ok(Some(reason)) = response.reason() { let _ = sender.send(Err(ResponseError { reason: reason.to_string(), })); } // Request succeeded. else { match response.body().expect("failed accessing response body") { // Response has body. Some(_) => { let _ = sender.send(Ok(Some(Vec::from(message)))); } // Response does not have body. None => { let _ = sender.send(Ok(None)); } } } } else { warn!( "received success response does not match any sent request [id:{}]", response.id().unwrap(), ); } } ChannelReceiveMessage::Event(event_message) => { let _ = internal_message_sender.try_send(event_message); } } } }); let inner = Arc::new(Inner { outgoing_message_buffer, internal_message_receiver, requests_container_weak, buffered_notifications_for, event_handlers_weak, worker_closed, closed: AtomicBool::new(false), }); ( Self { inner }, prepared_channel_read, prepared_channel_write, ) } pub(super) fn get_internal_message_receiver(&self) -> async_channel::Receiver { self.inner.internal_message_receiver.clone() } /// This allows to enable buffering for messages for specific target while the target itself is /// being created. This allows to avoid missing notifications due to race conditions. pub(crate) fn buffer_messages_for(&self, target_id: SubscriptionTarget) -> BufferMessagesGuard { let buffered_notifications_for = Arc::clone(&self.inner.buffered_notifications_for); let event_handlers_weak = self.inner.event_handlers_weak.clone(); buffered_notifications_for .lock() .entry(target_id.clone()) .or_default(); BufferMessagesGuard { target_id, buffered_notifications_for, event_handlers_weak, } } pub(crate) async fn request( &self, handler_id: HandlerId, request: R, ) -> Result where R: Request + 'static, HandlerId: Display, { let id; let (result_sender, result_receiver) = async_oneshot::oneshot(); { let requests_container = match self.inner.requests_container_weak.upgrade() { Some(requests_container_lock) => requests_container_lock, None => { return Err(RequestError::ChannelClosed); } }; let mut requests_container_lock = requests_container.lock(); id = requests_container_lock.next_id; requests_container_lock.next_id = requests_container_lock.next_id.wrapping_add(1); requests_container_lock.handlers.insert(id, result_sender); } debug!("request() [method:{:?}, id:{}]", R::METHOD, id); let data = request.into_bytes(id, handler_id); let buffer = Arc::new(AtomicTake::new(data)); { let mut outgoing_message_buffer = self.inner.outgoing_message_buffer.lock(); outgoing_message_buffer .messages .push_back(Arc::clone(&buffer)); if let Some(handle) = outgoing_message_buffer.handle { if self.inner.worker_closed.load(Ordering::Acquire) { // Forbid all requests after worker closing return Err(RequestError::ChannelClosed); } unsafe { // Notify worker that there is something to read let ret = mediasoup_sys::uv_async_send(handle); if ret != 0 { error!("uv_async_send call failed with code {}", ret); return Err(RequestError::ChannelClosed); } } } } // Drop guard to make sure to drop pending request when future is cancelled let request_drop_guard = RequestDropGuard { id, message: buffer, channel: self, removed: false, }; let response_result_fut = result_receiver.await; request_drop_guard.remove(); let response_result = match response_result_fut { Ok(response_result) => response_result, Err(_closed) => Err(ResponseError { reason: String::from("ChannelClosed"), }), }; match response_result { Ok(bytes) => { debug!("request succeeded [method:{:?}, id:{}]", R::METHOD, id); match bytes { Some(bytes) => { let message_ref = message::MessageRef::read_as_root(&bytes).unwrap(); let message::BodyRef::Response(response_ref) = message_ref.data().unwrap() else { panic!("Wrong response stored: {message_ref:?}"); }; Ok(R::convert_response(response_ref.body().unwrap()) .map_err(RequestError::ResponseConversion)?) } None => { Ok(R::convert_response(None).map_err(RequestError::ResponseConversion)?) } } } Err(ResponseError { reason }) => { debug!( "request failed [method:{:?}, id:{}]: {}", R::METHOD, id, reason ); if reason.contains("not found") { if let Some(default_response) = R::default_for_soft_error() { Ok(default_response) } else { Err(RequestError::ChannelClosed) } } else { Err(RequestError::Response { reason }) } } } } pub(crate) fn notify( &self, handler_id: HandlerId, notification: N, ) -> Result<(), NotificationError> where N: Notification, HandlerId: Display, { debug!("notify() [{notification:?}]"); let data = notification.into_bytes(handler_id); let message = Arc::new(AtomicTake::new(data)); { let mut outgoing_message_buffer = self.inner.outgoing_message_buffer.lock(); outgoing_message_buffer .messages .push_back(Arc::clone(&message)); if let Some(handle) = outgoing_message_buffer.handle { if self.inner.worker_closed.load(Ordering::Acquire) { // Forbid all notifications after worker closing except one // worker closing request if N::EVENT != notification::Event::WorkerClose { return Err(NotificationError::ChannelClosed); } } unsafe { // Notify worker that there is something to read let ret = mediasoup_sys::uv_async_send(handle); if ret != 0 { error!("uv_async_send call failed with code {}", ret); return Err(NotificationError::ChannelClosed); } } } } Ok(()) } pub(crate) fn subscribe_to_notifications( &self, target_id: SubscriptionTarget, callback: F, ) -> Option where F: Fn(notification::NotificationRef<'_>) + Send + Sync + 'static, { self.inner .event_handlers_weak .upgrade() .map(|event_handlers| event_handlers.add(target_id, Arc::new(callback))) } } ================================================ FILE: rust/src/worker/common.rs ================================================ use hash_hasher::HashedMap; use mediasoup_sys::fbs::notification; use nohash_hasher::IntMap; use parking_lot::Mutex; use serde::Deserialize; use std::sync::{Arc, Weak}; use uuid::Uuid; struct EventHandlersList { index: usize, callbacks: IntMap, } impl Default for EventHandlersList { fn default() -> Self { Self { index: 0, callbacks: IntMap::default(), } } } #[derive(Clone)] pub(super) struct EventHandlers { handlers: Arc>>>, } impl EventHandlers { pub(super) fn new() -> Self { let handlers = Arc::>>>::default(); Self { handlers } } pub(super) fn add(&self, target_id: SubscriptionTarget, callback: F) -> SubscriptionHandler { let index; { let mut event_handlers = self.handlers.lock(); let list = event_handlers.entry(target_id.clone()).or_default(); index = list.index; list.index += 1; list.callbacks.insert(index, callback); drop(event_handlers); } SubscriptionHandler::new({ let event_handlers_weak = Arc::downgrade(&self.handlers); Box::new(move || { if let Some(event_handlers) = event_handlers_weak.upgrade() { // We store removed handler here in order to drop after `event_handlers` lock is // released, otherwise handler will be dropped on removal from callbacks // immediately and if it happens to hold another entity that held subscription // handler, we may arrive here again trying to acquire lock that we didn't // release yet. By dropping callback only when lock is released we avoid // deadlocking. let removed_handler; { let mut handlers = event_handlers.lock(); let is_empty = { let list = handlers.get_mut(&target_id).unwrap(); removed_handler = list.callbacks.remove(&index); list.callbacks.is_empty() }; if is_empty { handlers.remove(&target_id); } } drop(removed_handler); } }) }) } pub(super) fn downgrade(&self) -> WeakEventHandlers { WeakEventHandlers { handlers: Arc::downgrade(&self.handlers), } } } impl EventHandlers) + Send + Sync + 'static>> { pub(super) fn call_callbacks_with_single_value( &self, target_id: &SubscriptionTarget, value: notification::NotificationRef<'_>, ) { let handlers = self.handlers.lock(); if let Some(list) = handlers.get(target_id) { for callback in list.callbacks.values() { callback(value); } } } } #[derive(Clone)] pub(super) struct WeakEventHandlers { handlers: Weak>>>, } impl WeakEventHandlers { pub(super) fn upgrade(&self) -> Option> { self.handlers .upgrade() .map(|handlers| EventHandlers { handlers }) } } #[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize)] #[serde(untagged)] pub(crate) enum SubscriptionTarget { Uuid(Uuid), String(String), } /// Subscription handler, will remove corresponding subscription when dropped pub(crate) struct SubscriptionHandler { remove_callback: Option>, } impl SubscriptionHandler { fn new(remove_callback: Box) -> Self { Self { remove_callback: Some(remove_callback), } } } impl Drop for SubscriptionHandler { fn drop(&mut self) { let remove_callback = self.remove_callback.take().unwrap(); remove_callback(); } } ================================================ FILE: rust/src/worker/utils/channel_read_fn.rs ================================================ pub(super) use mediasoup_sys::{ChannelReadCtx, ChannelReadFn}; use mediasoup_sys::{ChannelReadFreeFn, UvAsyncT}; use std::mem; use std::os::raw::c_void; unsafe extern "C" fn free_vec(message: *mut u8, message_len: u32, message_capacity: usize) { // Create and drop vector to free its memory Vec::from_raw_parts(message, message_len as usize, message_capacity); } pub(super) struct ChannelReadCallback { // Silence clippy warnings _callback: Box Option>) + Send + 'static>, } impl ChannelReadCallback { pub(super) fn new( _callback: Box Option>) + Send + 'static>, ) -> Self { Self { _callback } } } pub(crate) struct PreparedChannelRead { channel_read_fn: ChannelReadFn, channel_read_ctx: ChannelReadCtx, write_callback: ChannelReadCallback, } impl PreparedChannelRead { /// SAFETY: /// 1) `ChannelReadCallback` returned must be dropped AFTER last usage of returned function and /// context pointers /// 2) `ChannelReadCtx` should not be called from multiple threads concurrently pub(super) unsafe fn deconstruct(self) -> (ChannelReadFn, ChannelReadCtx, ChannelReadCallback) { let Self { channel_read_fn, channel_read_ctx, write_callback, } = self; (channel_read_fn, channel_read_ctx, write_callback) } } /// Given callback function, prepares a pair of channel read function and context, which can be /// provided to of C++ worker and worker will effectively call the callback whenever it wants to /// read something from Rust (so it is reading from C++ point of view and writing from Rust). pub(crate) fn prepare_channel_read_fn(read_callback: F) -> PreparedChannelRead where F: (FnMut(UvAsyncT) -> Option>) + Send + 'static, { unsafe extern "C" fn wrapper( message: *mut *mut u8, message_len: *mut u32, message_capacity: *mut usize, handle: UvAsyncT, ChannelReadCtx(ctx): ChannelReadCtx, ) -> ChannelReadFreeFn where F: (FnMut(UvAsyncT) -> Option>) + Send + 'static, { // Call Rust and try to get a new message (if there is any) let mut new_message = (*(ctx as *mut F))(handle)?; // Set pointers, give out control over memory to C++ *message = new_message.as_mut_ptr(); *message_len = new_message.len() as u32; *message_capacity = new_message.capacity(); // Forget about vector in Rust mem::forget(new_message); // Function pointer that C++ can use to free vector later Some(free_vec) } // Move to heap to make sure it doesn't change address later on let read_callback = Box::new(read_callback); PreparedChannelRead { channel_read_fn: wrapper::, channel_read_ctx: ChannelReadCtx(read_callback.as_ref() as *const F as *const c_void), write_callback: ChannelReadCallback::new(read_callback), } } ================================================ FILE: rust/src/worker/utils/channel_write_fn.rs ================================================ pub(super) use mediasoup_sys::{ChannelWriteCtx, ChannelWriteFn}; use std::os::raw::c_void; use std::slice; /// TypeAlias to silience clippy::type_complexity warnings type CallbackType = Box; pub(super) struct ChannelReadCallback { // Silence clippy warnings _callback: CallbackType, } impl ChannelReadCallback { pub(super) fn new(_callback: CallbackType) -> Self { Self { _callback } } } pub(crate) struct PreparedChannelWrite { channel_write_fn: ChannelWriteFn, channel_write_ctx: ChannelWriteCtx, read_callback: ChannelReadCallback, } unsafe impl Send for PreparedChannelWrite {} impl PreparedChannelWrite { /// SAFETY: /// 1) `ChannelReadCallback` returned must be dropped AFTER last usage of returned function and /// context pointers /// 2) `ChannelWriteCtx` should not be called from multiple threads concurrently pub(super) unsafe fn deconstruct( self, ) -> (ChannelWriteFn, ChannelWriteCtx, ChannelReadCallback) { let Self { channel_write_fn, channel_write_ctx, read_callback, } = self; (channel_write_fn, channel_write_ctx, read_callback) } } /// Given callback function, prepares a pair of channel write function and context, which can be /// provided to of C++ worker and worker will effectively call the callback whenever it needs to /// send something to Rust (so it is writing from C++ point of view and reading from Rust). pub(crate) fn prepare_channel_write_fn(read_callback: F) -> PreparedChannelWrite where F: FnMut(&[u8]) + Send + 'static, { unsafe extern "C" fn wrapper( message: *const u8, message_len: u32, ChannelWriteCtx(ctx): ChannelWriteCtx, ) where F: FnMut(&[u8]) + Send + 'static, { let message = slice::from_raw_parts(message, message_len as usize); (*(ctx as *mut F))(message); } // Move to heap to make sure it doesn't change address later on let read_callback = Box::new(read_callback); PreparedChannelWrite { channel_write_fn: wrapper::, channel_write_ctx: ChannelWriteCtx(read_callback.as_ref() as *const F as *const c_void), read_callback: ChannelReadCallback::new(read_callback), } } ================================================ FILE: rust/src/worker/utils.rs ================================================ mod channel_read_fn; mod channel_write_fn; use crate::worker::channel::BufferMessagesGuard; use crate::worker::{Channel, SubscriptionTarget, WorkerId}; pub(super) use channel_read_fn::{prepare_channel_read_fn, PreparedChannelRead}; pub(super) use channel_write_fn::{prepare_channel_write_fn, PreparedChannelWrite}; use std::ffi::CString; use std::os::raw::{c_char, c_int}; use std::sync::atomic::AtomicBool; use std::sync::Arc; use thiserror::Error; /// Worker exit error #[derive(Debug, Copy, Clone, Error)] pub enum ExitError { /// Generic error. #[error("Worker exited with generic error")] Generic, /// Settings error. #[error("Worker exited with settings error")] Settings, /// Unknown error. #[error("Worker exited with unknown error and status code {status_code}")] Unknown { /// Status code returned by worker status_code: i32, }, /// Unexpected error. #[error("Worker exited unexpectedly")] Unexpected, } pub(super) struct WorkerRunResult { pub(super) channel: Channel, pub(super) buffer_worker_messages_guard: BufferMessagesGuard, } pub(super) fn run_worker_with_channels( id: WorkerId, thread_initializer: Option>, args: Vec, worker_closed: Arc, on_exit: OE, ) -> WorkerRunResult where OE: FnOnce(Result<(), ExitError>) + Send + 'static, { let (channel, prepared_channel_read, prepared_channel_write) = Channel::new(Arc::clone(&worker_closed)); let buffer_worker_messages_guard = channel.buffer_messages_for(SubscriptionTarget::String(std::process::id().to_string())); std::thread::Builder::new() .name(format!("mediasoup-worker-{id}")) .spawn(move || { if let Some(thread_initializer) = thread_initializer { thread_initializer(); } let argc = args.len() as c_int; let args_cstring = args .into_iter() .map(|s| -> CString { CString::new(s).unwrap() }) .collect::>(); let argv = args_cstring .iter() .map(|arg| arg.as_ptr().cast::()) .collect::>(); let version = CString::new(env!("CARGO_PKG_VERSION")).unwrap(); let status_code = unsafe { let (channel_read_fn, channel_read_ctx, _channel_write_callback) = prepared_channel_read.deconstruct(); let (channel_write_fn, channel_write_ctx, _channel_read_callback) = prepared_channel_write.deconstruct(); mediasoup_sys::mediasoup_worker_run( argc, argv.as_ptr(), version.as_ptr(), /*consumerChannelFd*/ 0, /*producerChannelFd*/ 0, channel_read_fn, channel_read_ctx, channel_write_fn, channel_write_ctx, ) }; on_exit(match status_code { 0 => Ok(()), 1 => Err(ExitError::Generic), 42 => Err(ExitError::Settings), status_code => Err(ExitError::Unknown { status_code }), }); }) .expect("Failed to spawn mediasoup-worker thread"); WorkerRunResult { channel, buffer_worker_messages_guard, } } ================================================ FILE: rust/src/worker.rs ================================================ //! A worker represents a mediasoup C++ thread that runs on a single CPU core and handles //! [`Router`] instances. mod channel; mod common; mod utils; use crate::messages::{ WorkerCloseNotification, WorkerCreateRouterRequest, WorkerCreateWebRtcServerRequest, WorkerDumpRequest, WorkerUpdateSettingsRequest, }; pub use crate::ortc::RtpCapabilitiesError; use crate::router::{Router, RouterId, RouterOptions}; use crate::webrtc_server::{WebRtcServer, WebRtcServerId, WebRtcServerOptions}; use crate::worker::channel::BufferMessagesGuard; pub use crate::worker::utils::ExitError; use crate::worker_manager::WorkerManager; use crate::{ortc, uuid_based_wrapper_type}; use async_executor::Executor; pub(crate) use channel::{Channel, NotificationError, NotificationParseError}; pub(crate) use common::{SubscriptionHandler, SubscriptionTarget}; use event_listener_primitives::{Bag, BagOnce, HandlerId}; use futures_lite::FutureExt; use log::{debug, error, warn}; use mediasoup_sys::fbs; use mediasoup_types::data_structures::AppData; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use std::error::Error; use std::ops::RangeInclusive; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use std::{fmt, io}; use thiserror::Error; use utils::WorkerRunResult; use uuid::Uuid; uuid_based_wrapper_type!( /// Worker identifier. WorkerId ); /// Error that caused request to mediasoup-worker request to fail. #[derive(Debug, Error)] pub enum RequestError { /// Channel already closed. #[error("Channel already closed")] ChannelClosed, /// Request timed out. #[error("Request timed out")] TimedOut, /// Received response error. #[error("Received response error: {reason}")] Response { /// Error reason. reason: String, }, /// Failed to parse response from worker. #[error("Failed to parse response from worker: {error}")] FailedToParse { /// Error reason. error: String, }, /// Worker did not return any data in response. #[error("Worker did not return any data in response")] NoData, /// Response conversion error. #[error("Response conversion error: {0}")] ResponseConversion(Box), } /// Logging level for logs generated by the media worker thread (check the /// [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging/) /// documentation on TypeScript implementation and generic /// [Rust-specific](https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/log.html) [docs](https://docs.rs/env_logger)). /// /// Default [`WorkerLogLevel::Error`]. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Default)] #[serde(rename_all = "lowercase")] pub enum WorkerLogLevel { /// Log all severities. Debug, /// Log "warn" and "error" severities. Warn, /// Log "error" severity. #[default] Error, /// Do not log anything. None, } impl WorkerLogLevel { pub(crate) fn as_str(self) -> &'static str { match self { Self::Debug => "debug", Self::Warn => "warn", Self::Error => "error", Self::None => "none", } } } /// Log tags for debugging. Check the meaning of each available tag in the /// [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging/) documentation. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)] #[serde(rename_all = "lowercase")] pub enum WorkerLogTag { /// Logs about software/library versions, configuration and process information. Info, /// Logs about ICE. Ice, /// Logs about DTLS. Dtls, /// Logs about RTP. Rtp, /// Logs about SRTP encryption/decryption. Srtp, /// Logs about RTCP. Rtcp, /// Logs about RTP retransmission, including NACK/PLI/FIR. Rtx, /// Logs about transport bandwidth estimation. Bwe, /// Logs related to the scores of Producers and Consumers. Score, /// Logs about video simulcast. Simulcast, /// Logs about video SVC. Svc, /// Logs about SCTP (DataChannel). Sctp, /// Logs about messages (can be SCTP messages or direct messages). Message, } impl WorkerLogTag { pub(crate) fn as_str(self) -> &'static str { match self { Self::Info => "info", Self::Ice => "ice", Self::Dtls => "dtls", Self::Rtp => "rtp", Self::Srtp => "srtp", Self::Rtcp => "rtcp", Self::Rtx => "rtx", Self::Bwe => "bwe", Self::Score => "score", Self::Simulcast => "simulcast", Self::Svc => "svc", Self::Sctp => "sctp", Self::Message => "message", } } } /// DTLS certificate and private key. #[derive(Debug, Clone)] pub struct WorkerDtlsFiles { /// Path to the DTLS public certificate file in PEM format. pub certificate: PathBuf, /// Path to the DTLS certificate private key file in PEM format. pub private_key: PathBuf, } /// Settings for worker to be created with. #[derive(Clone)] #[non_exhaustive] pub struct WorkerSettings { /// Logging level for logs generated by the media worker thread. /// /// Default [`WorkerLogLevel::Error`]. pub log_level: WorkerLogLevel, /// Log tags for debugging. Check the meaning of each available tag in the /// [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging/) documentation. pub log_tags: Vec, /// RTC port range for ICE, DTLS, RTP, etc. Default 10000..=59999. pub rtc_port_range: RangeInclusive, /// DTLS certificate and private key. /// /// If `None`, a certificate is dynamically created. pub dtls_files: Option, /// Field trials for libwebrtc. /// /// NOTE: For advanced users only. An invalid value will make the worker crash. /// Default value is /// "WebRTC-Bwe-AlrLimitedBackoff/Enabled/". #[doc(hidden)] pub libwebrtc_field_trials: Option, /// Enable liburing This option is ignored if io_uring is not supported by /// current host. /// /// Default `true`. pub enable_liburing: bool, /// Use the mediasoup built-in SCTP stack instead usrsctp. /// /// Default `false`. pub use_built_in_sctp_stack: bool, /// Function that will be called under worker thread before worker starts, can be used for /// pinning worker threads to CPU cores. pub thread_initializer: Option>, /// Custom application data. pub app_data: AppData, } impl Default for WorkerSettings { fn default() -> Self { Self { log_level: WorkerLogLevel::Debug, log_tags: vec![ WorkerLogTag::Info, WorkerLogTag::Ice, WorkerLogTag::Dtls, WorkerLogTag::Rtp, WorkerLogTag::Srtp, WorkerLogTag::Rtcp, WorkerLogTag::Rtx, WorkerLogTag::Bwe, WorkerLogTag::Score, WorkerLogTag::Simulcast, WorkerLogTag::Svc, WorkerLogTag::Sctp, WorkerLogTag::Message, ], rtc_port_range: 10000..=59999, dtls_files: None, libwebrtc_field_trials: None, enable_liburing: true, use_built_in_sctp_stack: false, thread_initializer: None, app_data: AppData::default(), } } } impl fmt::Debug for WorkerSettings { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let WorkerSettings { log_level, log_tags, rtc_port_range, dtls_files, libwebrtc_field_trials, enable_liburing, use_built_in_sctp_stack, thread_initializer, app_data, } = self; f.debug_struct("WorkerSettings") .field("log_level", &log_level) .field("log_tags", &log_tags) .field("rtc_port_range", &rtc_port_range) .field("dtls_files", &dtls_files) .field("libwebrtc_field_trials", &libwebrtc_field_trials) .field("enable_liburing", &enable_liburing) .field("use_built_in_sctp_stack", &use_built_in_sctp_stack) .field( "thread_initializer", &thread_initializer.as_ref().map(|_| "ThreadInitializer"), ) .field("app_data", &app_data) .finish() } } /// Worker settings that can be updated in runtime. #[derive(Default, Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] #[non_exhaustive] pub struct WorkerUpdateSettings { /// Logging level for logs generated by the media worker thread. /// /// If `None`, logging level will not be updated. pub log_level: Option, /// Log tags for debugging. Check the meaning of each available tag in the /// [Debugging](https://mediasoup.org/documentation/v3/mediasoup/debugging/) documentation. /// /// If `None`, log tags will not be updated. pub log_tags: Option>, } #[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] #[doc(hidden)] pub struct ChannelMessageHandlers { pub channel_request_handlers: Vec, pub channel_notification_handlers: Vec, } #[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] #[doc(hidden)] pub struct LibUringDump { pub sqe_process_count: u64, pub sqe_miss_count: u64, pub user_data_miss_count: u64, } #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] #[doc(hidden)] #[non_exhaustive] pub struct WorkerDump { // Dump has `pid` field too, but it is useless here because of thead-based worker usage pub router_ids: Vec, #[serde(rename = "webRtcServerIds")] pub webrtc_server_ids: Vec, pub channel_message_handlers: ChannelMessageHandlers, pub liburing: Option, } /// Error that caused [`Worker::create_webrtc_server`] to fail. #[derive(Debug, Error)] pub enum CreateWebRtcServerError { /// Request to worker failed #[error("Request to worker failed: {0}")] Request(RequestError), } /// Error that caused [`Worker::create_router`] to fail. #[derive(Debug, Error)] pub enum CreateRouterError { /// RTP capabilities generation error #[error("RTP capabilities generation error: {0}")] FailedRtpCapabilitiesGeneration(RtpCapabilitiesError), /// Request to worker failed #[error("Request to worker failed: {0}")] Request(RequestError), } #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { new_router: Bag, Router>, new_webrtc_server: Bag, WebRtcServer>, #[allow(clippy::type_complexity)] dead: BagOnce) + Send>>, close: BagOnce>, } struct Inner { id: WorkerId, channel: Channel, executor: Arc>, handlers: Handlers, app_data: AppData, closed: Arc, // Make sure worker is not dropped until this worker manager is not dropped _worker_manager: WorkerManager, } impl Drop for Inner { fn drop(&mut self) { debug!("drop()"); self.close(); } } impl Inner { async fn new( executor: Arc>, WorkerSettings { log_level, log_tags, rtc_port_range, dtls_files, libwebrtc_field_trials, enable_liburing, use_built_in_sctp_stack, thread_initializer, app_data, }: WorkerSettings, worker_manager: WorkerManager, on_exit: OE, ) -> io::Result> { debug!("new()"); let mut spawn_args: Vec = vec!["".to_string()]; spawn_args.push(format!("--logLevel={}", log_level.as_str())); for log_tag in log_tags { spawn_args.push(format!("--logTag={}", log_tag.as_str())); } if rtc_port_range.is_empty() { return Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid RTC ports range", )); } spawn_args.push(format!("--rtcMinPort={}", rtc_port_range.start())); spawn_args.push(format!("--rtcMaxPort={}", rtc_port_range.end())); if let Some(dtls_files) = dtls_files { spawn_args.push(format!( "--dtlsCertificateFile={}", dtls_files .certificate .to_str() .expect("Paths are only expected to be utf8") )); spawn_args.push(format!( "--dtlsPrivateKeyFile={}", dtls_files .private_key .to_str() .expect("Paths are only expected to be utf8") )); } if let Some(libwebrtc_field_trials) = libwebrtc_field_trials { spawn_args.push(format!( "--libwebrtcFieldTrials={}", libwebrtc_field_trials.as_str() )); } if !enable_liburing { spawn_args.push("--disableLiburing=true".to_string()); } if use_built_in_sctp_stack { spawn_args.push("--useBuiltInSctpStack=true".to_string()); } else { spawn_args.push("--useBuiltInSctpStack=false".to_string()); } let id = WorkerId::new(); debug!( "spawning worker with arguments [id:{}]: {}", id, spawn_args.join(" ") ); let closed = Arc::new(AtomicBool::new(false)); let (mut status_sender, status_receiver) = async_oneshot::oneshot(); let WorkerRunResult { channel, buffer_worker_messages_guard, } = utils::run_worker_with_channels( id, thread_initializer, spawn_args, Arc::clone(&closed), move |result| { let _ = status_sender.send(result); on_exit(); }, ); let handlers = Handlers::default(); let mut inner = Self { id, channel, executor, handlers, app_data, closed, _worker_manager: worker_manager, }; inner.setup_message_handling(); let (mut early_status_sender, early_status_receiver) = async_oneshot::oneshot(); let inner = Arc::new(inner); { let inner_weak = Arc::downgrade(&inner); inner .executor .spawn(async move { let status = status_receiver.await.unwrap_or(Err(ExitError::Unexpected)); let _ = early_status_sender.send(status); if let Some(inner) = inner_weak.upgrade() { warn!("worker exited [id:{}]: {:?}", id, status); if !inner.closed.swap(true, Ordering::SeqCst) { inner.handlers.dead.call(|callback| { callback(status); }); inner.handlers.close.call_simple(); } } }) .detach(); } inner .wait_for_worker_ready(buffer_worker_messages_guard) .or(async { let status = early_status_receiver .await .unwrap_or(Err(ExitError::Unexpected)); let error_message = format!( "worker thread exited before being ready [id:{}]: exit status {:?}", inner.id, status, ); Err(io::Error::new(io::ErrorKind::NotFound, error_message)) }) .await?; Ok(inner) } async fn wait_for_worker_ready( &self, buffer_worker_messages_guard: BufferMessagesGuard, ) -> io::Result<()> { let (sender, receiver) = async_oneshot::oneshot(); let id = self.id; let sender = Mutex::new(Some(sender)); let _handler = self.channel.subscribe_to_notifications( SubscriptionTarget::String(std::process::id().to_string()), move |notification| { let result = match notification.event().unwrap() { fbs::notification::Event::WorkerRunning => { debug!("worker thread running [id:{}]", id); Ok(()) } _ => Err(io::Error::other(format!( "unexpected first notification from worker [id:{id}]" ))), }; let _ = sender .lock() .take() .expect("Receiving more than one worker notification") .send(result); }, ); // Allow worker messages to go through drop(buffer_worker_messages_guard); receiver .await .map_err(|_closed| io::Error::other("Worker dropped before it is ready"))? } fn setup_message_handling(&mut self) { let channel_receiver = self.channel.get_internal_message_receiver(); let id = self.id; let closed = Arc::clone(&self.closed); self.executor .spawn(async move { while let Ok(message) = channel_receiver.recv().await { match message { channel::InternalMessage::Debug(text) => debug!("[id:{}] {}", id, text), channel::InternalMessage::Warn(text) => warn!("[id:{}] {}", id, text), channel::InternalMessage::Error(text) => { if !closed.load(Ordering::SeqCst) { error!("[id:{}] {}", id, text) } } channel::InternalMessage::Dump(text) => eprintln!("{text}"), channel::InternalMessage::Unexpected(data) => error!( "worker[id:{}] unexpected channel data: {}", id, String::from_utf8_lossy(&data) ), } } }) .detach(); } fn close(&self) { let already_closed = self.closed.swap(true, Ordering::SeqCst); if !already_closed { let channel = self.channel.clone(); self.executor .spawn(async move { let _ = channel.notify("", WorkerCloseNotification {}); // Drop channels in here after having sent the notification drop(channel); }) .detach(); self.handlers.close.call_simple(); } } } /// A worker represents a mediasoup C++ thread that runs on a single CPU core and handles /// [`Router`] instances. #[derive(Clone)] #[must_use = "Worker will be destroyed on drop, make sure to keep it around for as long as needed"] pub struct Worker { inner: Arc, } impl fmt::Debug for Worker { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Worker") .field("id", &self.inner.id) .field("closed", &self.inner.closed) .finish() } } impl Worker { pub(super) async fn new( executor: Arc>, worker_settings: WorkerSettings, worker_manager: WorkerManager, on_exit: OE, ) -> io::Result { let inner = Inner::new(executor, worker_settings, worker_manager, on_exit).await?; Ok(Self { inner }) } /// Worker id. #[must_use] pub fn id(&self) -> WorkerId { self.inner.id } /// Worker manager to which worker belongs. pub fn worker_manager(&self) -> &WorkerManager { &self.inner._worker_manager } /// Custom application data. #[must_use] pub fn app_data(&self) -> &AppData { &self.inner.app_data } /// Whether the worker is closed. #[must_use] pub fn closed(&self) -> bool { self.inner.closed.load(Ordering::SeqCst) } /// Dump Worker. #[doc(hidden)] pub async fn dump(&self) -> Result { debug!("dump()"); self.inner.channel.request("", WorkerDumpRequest {}).await } /// Updates the worker settings in runtime. Just a subset of the worker settings can be updated. pub async fn update_settings(&self, data: WorkerUpdateSettings) -> Result<(), RequestError> { debug!("update_settings()"); match self .inner .channel .request("", WorkerUpdateSettingsRequest { data }) .await { Ok(_) => Ok(()), Err(error) => Err(error), } } /// Create a WebRtcServer. /// /// Worker will be kept alive as long as at least one WebRTC server instance is alive. pub async fn create_webrtc_server( &self, webrtc_server_options: WebRtcServerOptions, ) -> Result { debug!("create_webrtc_server()"); let WebRtcServerOptions { listen_infos, app_data, } = webrtc_server_options; let webrtc_server_id = WebRtcServerId::new(); let _buffer_guard = self .inner .channel .buffer_messages_for(webrtc_server_id.into()); self.inner .channel .request( "", WorkerCreateWebRtcServerRequest { webrtc_server_id, listen_infos, }, ) .await .map_err(CreateWebRtcServerError::Request)?; let webrtc_server = WebRtcServer::new( webrtc_server_id, Arc::clone(&self.inner.executor), self.inner.channel.clone(), app_data, self.clone(), ); self.inner .handlers .new_webrtc_server .call_simple(&webrtc_server); Ok(webrtc_server) } /// Create a Router. /// /// Worker will be kept alive as long as at least one router instance is alive. pub async fn create_router( &self, router_options: RouterOptions, ) -> Result { debug!("create_router()"); let RouterOptions { app_data, media_codecs, } = router_options; let rtp_capabilities = ortc::generate_router_rtp_capabilities(media_codecs) .map_err(CreateRouterError::FailedRtpCapabilitiesGeneration)?; let router_id = RouterId::new(); let _buffer_guard = self.inner.channel.buffer_messages_for(router_id.into()); self.inner .channel .request("", WorkerCreateRouterRequest { router_id }) .await .map_err(CreateRouterError::Request)?; let router = Router::new( router_id, Arc::clone(&self.inner.executor), self.inner.channel.clone(), rtp_capabilities, app_data, self.clone(), ); self.inner.handlers.new_router.call_simple(&router); Ok(router) } /// Callback is called when a new WebRTC server is created. pub fn on_new_webrtc_server( &self, callback: F, ) -> HandlerId { self.inner .handlers .new_webrtc_server .add(Arc::new(callback)) } /// Callback is called when a new router is created. pub fn on_new_router(&self, callback: F) -> HandlerId { self.inner.handlers.new_router.add(Arc::new(callback)) } /// Callback is called when the worker thread unexpectedly dies. pub fn on_dead) + Send + Sync + 'static>( &self, callback: F, ) -> HandlerId { self.inner.handlers.dead.add(Box::new(callback)) } /// Callback is called when the worker is closed for whatever reason. /// /// NOTE: Callback will be called in place if worker is already closed. pub fn on_close(&self, callback: F) -> HandlerId { let handler_id = self.inner.handlers.close.add(Box::new(callback)); if self.inner.closed.load(Ordering::Relaxed) { self.inner.handlers.close.call_simple(); } handler_id } #[cfg(test)] pub(crate) fn close(&self) { self.inner.close(); } } ================================================ FILE: rust/src/worker_manager/tests.rs ================================================ use super::*; use std::env; fn init() { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } } #[test] fn worker_manager_test() { init(); let worker_manager = WorkerManager::new(); future::block_on(async move { let _ = worker_manager .create_worker(WorkerSettings::default()) .await .unwrap(); }); } ================================================ FILE: rust/src/worker_manager.rs ================================================ //! Container that creates [`Worker`] instances. #[cfg(test)] mod tests; use crate::worker::{Worker, WorkerId, WorkerSettings}; use async_executor::Executor; use async_oneshot::Sender; use event_listener_primitives::{Bag, HandlerId}; use futures_lite::future; use log::debug; use parking_lot::Mutex; use std::collections::HashMap; use std::ops::DerefMut; use std::sync::mpsc; use std::sync::Arc; use std::{fmt, io, mem}; #[derive(Default)] #[allow(clippy::type_complexity)] struct Handlers { new_worker: Bag, Worker>, } struct Inner { executor: Arc>, handlers: Handlers, /// Mapping from worker ID to the close event receiver workers: Arc>>>, /// This field is only used in order to be dropped with the worker manager itself to stop the /// thread created with `WorkerManager::new()` call _stop_sender: Option>, } impl Drop for Inner { fn drop(&mut self) { let workers = mem::take(self.workers.lock().deref_mut()); for exit_receiver in workers.into_values() { let _ = exit_receiver.recv(); } } } /// Container that creates [`Worker`] instances. /// /// # Examples /// ```no_run /// use futures_lite::future; /// use mediasoup::worker::WorkerSettings; /// use mediasoup::worker_manager::WorkerManager; /// /// // Create a manager that will use specified binary for spawning new worker thread /// let worker_manager = WorkerManager::new(); /// /// future::block_on(async move { /// // Create a new worker with default settings /// let worker = worker_manager /// .create_worker(WorkerSettings::default()) /// .await /// .unwrap(); /// }) /// ``` /// /// If you already happen to have [`async_executor::Executor`] instance available or need a /// multi-threaded executor, [`WorkerManager::with_executor()`] can be used to create an instance /// instead. #[derive(Clone)] #[must_use] pub struct WorkerManager { inner: Arc, } impl fmt::Debug for WorkerManager { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("WorkerManager").finish() } } impl Default for WorkerManager { fn default() -> Self { Self::new() } } impl WorkerManager { /// Create new worker manager, internally a new single-threaded executor will be created. pub fn new() -> Self { let executor = Arc::new(Executor::new()); let (stop_sender, stop_receiver) = async_oneshot::oneshot::<()>(); { let executor = Arc::clone(&executor); std::thread::spawn(move || { // Will return Err(Closed) when `WorkerManager` struct is dropped let _ = future::block_on(executor.run(stop_receiver)); }); } let handlers = Handlers::default(); let inner = Arc::new(Inner { executor, handlers, workers: Arc::default(), _stop_sender: Some(stop_sender), }); Self { inner } } /// Create new worker manager, uses externally provided executor. pub fn with_executor(executor: Arc>) -> Self { let handlers = Handlers::default(); let inner = Arc::new(Inner { executor, handlers, workers: Arc::default(), _stop_sender: None, }); Self { inner } } /// Creates a new worker with the given settings. /// /// Worker manager will be kept alive as long as at least one worker instance is alive. pub async fn create_worker(&self, worker_settings: WorkerSettings) -> io::Result { debug!("create_worker()"); let (exit_sender, exit_receiver) = mpsc::channel(); let id = Arc::new(Mutex::new(None)); let worker = Worker::new( Arc::clone(&self.inner.executor), worker_settings, self.clone(), { let id = Arc::clone(&id); let workers = Arc::clone(&self.inner.workers); move || { let _ = exit_sender.send(()); if let Some(id) = id.lock().take() { workers.lock().remove(&id); } } }, ) .await?; self.inner.handlers.new_worker.call_simple(&worker); id.lock().replace(worker.id()); self.inner.workers.lock().insert(worker.id(), exit_receiver); Ok(worker) } /// Callback is called when a new worker is created. pub fn on_new_worker(&self, callback: F) -> HandlerId { self.inner.handlers.new_worker.add(Arc::new(callback)) } } ================================================ FILE: rust/tests/integration/active_speaker_observer.rs ================================================ use async_io::Timer; use futures_lite::future; use mediasoup::active_speaker_observer::ActiveSpeakerObserverOptions; use mediasoup::router::RouterOptions; use mediasoup::rtp_observer::RtpObserver; use mediasoup::worker::{Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::rtp_parameters::{ MimeTypeAudio, RtpCodecCapability, RtpCodecParametersParameters, }; use std::env; use std::num::{NonZeroU32, NonZeroU8}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; fn media_codecs() -> Vec { vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }] } async fn init() -> Worker { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker") } #[test] fn create() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let new_observer_count = Arc::new(AtomicUsize::new(0)); router .on_new_rtp_observer({ let new_observer_count = Arc::clone(&new_observer_count); move |_new_rtp_observer| { new_observer_count.fetch_add(1, Ordering::SeqCst); } }) .detach(); let active_speaker_observer = router .create_active_speaker_observer(ActiveSpeakerObserverOptions::default()) .await .expect("Failed to create ActiveSpeakerObserver"); assert_eq!(new_observer_count.load(Ordering::SeqCst), 1); assert!(!active_speaker_observer.closed()); assert!(!active_speaker_observer.paused()); let dump = router.dump().await.expect("Failed to get router dump"); assert_eq!( dump.rtp_observer_ids.into_iter().collect::>(), vec![active_speaker_observer.id()] ); }); } #[test] fn weak() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let active_speaker_observer = router .create_active_speaker_observer(ActiveSpeakerObserverOptions::default()) .await .expect("Failed to create ActiveSpeakerObserver"); let weak_active_speaker_observer = active_speaker_observer.downgrade(); assert!(weak_active_speaker_observer.upgrade().is_some()); drop(active_speaker_observer); assert!(weak_active_speaker_observer.upgrade().is_none()); }); } #[test] fn pause_resume() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let active_speaker_observer = router .create_active_speaker_observer(ActiveSpeakerObserverOptions::default()) .await .expect("Failed to create ActiveSpeakerObserver"); active_speaker_observer .pause() .await .expect("Failed to pause"); assert!(active_speaker_observer.paused()); active_speaker_observer .resume() .await .expect("Failed to resume"); assert!(!active_speaker_observer.paused()); }); } #[test] fn close_event() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let active_speaker_observer = router .create_active_speaker_observer(ActiveSpeakerObserverOptions::default()) .await .expect("Failed to create ActiveSpeakerObserver"); let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = active_speaker_observer.on_close(Box::new(move || { let _ = tx.send(()); })); drop(active_speaker_observer); rx.await.expect("Failed to receive close event"); }); } #[test] fn drop_test() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let _active_speaker_observer = router .create_active_speaker_observer(ActiveSpeakerObserverOptions::default()) .await .expect("Failed to create ActiveSpeakerObserver"); let active_speaker_observer_2 = router .create_active_speaker_observer(ActiveSpeakerObserverOptions::default()) .await .expect("Failed to create ActiveSpeakerObserver"); let dump = router.dump().await.expect("Failed to get router dump"); assert_eq!(dump.rtp_observer_ids.len(), 2); drop(active_speaker_observer_2); // Drop is async, give it a bit of time to finish Timer::after(Duration::from_millis(200)).await; let dump = router.dump().await.expect("Failed to get router dump"); assert_eq!(dump.rtp_observer_ids.len(), 1); }); } ================================================ FILE: rust/tests/integration/audio_level_observer.rs ================================================ use async_io::Timer; use futures_lite::future; use mediasoup::audio_level_observer::AudioLevelObserverOptions; use mediasoup::prelude::*; use mediasoup::router::RouterOptions; use mediasoup::worker::{Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::rtp_parameters::{ MimeTypeAudio, RtpCodecCapability, RtpCodecParametersParameters, }; use std::env; use std::num::{NonZeroU32, NonZeroU8}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; fn media_codecs() -> Vec { vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }] } async fn init() -> Worker { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker") } #[test] fn create() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let new_observer_count = Arc::new(AtomicUsize::new(0)); router .on_new_rtp_observer({ let new_observer_count = Arc::clone(&new_observer_count); move |_new_rtp_observer| { new_observer_count.fetch_add(1, Ordering::SeqCst); } }) .detach(); let audio_level_observer = router .create_audio_level_observer(AudioLevelObserverOptions::default()) .await .expect("Failed to create AudioLevelObserver"); assert_eq!(new_observer_count.load(Ordering::SeqCst), 1); assert!(!audio_level_observer.closed()); assert!(!audio_level_observer.paused()); let dump = router.dump().await.expect("Failed to get router dump"); assert_eq!( dump.rtp_observer_ids.into_iter().collect::>(), vec![audio_level_observer.id()] ); }); } #[test] fn weak() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let audio_level_observer = router .create_audio_level_observer(AudioLevelObserverOptions::default()) .await .expect("Failed to create AudioLevelObserver"); let weak_audio_level_observer = audio_level_observer.downgrade(); assert!(weak_audio_level_observer.upgrade().is_some()); drop(audio_level_observer); assert!(weak_audio_level_observer.upgrade().is_none()); }); } #[test] fn pause_resume() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let audio_level_observer = router .create_audio_level_observer(AudioLevelObserverOptions::default()) .await .expect("Failed to create AudioLevelObserver"); audio_level_observer.pause().await.expect("Failed to pause"); assert!(audio_level_observer.paused()); audio_level_observer .resume() .await .expect("Failed to resume"); assert!(!audio_level_observer.paused()); }); } #[test] fn close_event() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let audio_level_observer = router .create_audio_level_observer(AudioLevelObserverOptions::default()) .await .expect("Failed to create AudioLevelObserver"); let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = audio_level_observer.on_close(Box::new(move || { let _ = tx.send(()); })); drop(audio_level_observer); rx.await.expect("Failed to receive close event"); }); } #[test] fn drop_test() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let _audio_level_observer = router .create_audio_level_observer(AudioLevelObserverOptions::default()) .await .expect("Failed to create AudioLevelObserver"); let audio_level_observer_2 = router .create_audio_level_observer(AudioLevelObserverOptions::default()) .await .expect("Failed to create AudioLevelObserver"); let dump = router.dump().await.expect("Failed to get router dump"); assert_eq!(dump.rtp_observer_ids.len(), 2); drop(audio_level_observer_2); // Drop is async, give it a bit of time to finish Timer::after(Duration::from_millis(200)).await; let dump = router.dump().await.expect("Failed to get router dump"); assert_eq!(dump.rtp_observer_ids.len(), 1); }); } ================================================ FILE: rust/tests/integration/consumer.rs ================================================ use async_executor::Executor; use async_io::Timer; use futures_lite::future; use hash_hasher::{HashedMap, HashedSet}; use mediasoup::consumer::{ConsumerLayers, ConsumerOptions, ConsumerScore, ConsumerType}; use mediasoup::prelude::*; use mediasoup::producer::ProducerOptions; use mediasoup::router::{Router, RouterOptions}; use mediasoup::transport::ConsumeError; use mediasoup::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use mediasoup::worker::{Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{AppData, ListenInfo, Protocol}; use mediasoup_types::rtp_parameters::{ MediaKind, MimeType, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters, RtpCapabilities, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters, RtpEncodingParameters, RtpEncodingParametersRtx, RtpHeaderExtension, RtpHeaderExtensionDirection, RtpHeaderExtensionParameters, RtpHeaderExtensionUri, RtpParameters, }; use mediasoup_types::scalability_modes::ScalabilityMode; use parking_lot::Mutex; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; use std::{env, thread}; struct ProducerAppData { _foo: i32, _bar: &'static str, } struct ConsumerAppData { baz: &'static str, } fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([("foo", "bar".into())]), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("level-asymmetry-allowed", 1_u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }, ] } fn audio_producer_options() -> ProducerOptions { let mut options = ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 111, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("usedtx", 1_u32.into()), ("foo", "222.222".into()), ("bar", "333".into()), ]), rtcp_feedback: vec![], }], header_extensions: vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::SsrcAudioLevel, id: 12, encrypt: false, }, ], encodings: vec![RtpEncodingParameters { ssrc: Some(11111111), ..RtpEncodingParameters::default() }], rtcp: RtcpParameters { cname: Some("FOOBAR".to_string()), ..RtcpParameters::default() }, msid: Some("1111-1111-1111-1111 2222-2222-2222-2222".to_string()), }, ); options.app_data = AppData::new(ProducerAppData { _foo: 1, _bar: "2" }); options } fn video_producer_options() -> ProducerOptions { let mut options = ProducerOptions::new( MediaKind::Video, RtpParameters { mid: Some("VIDEO".to_string()), codecs: vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::GoogRemb, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 113, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 112u32.into())]), rtcp_feedback: vec![], }, ], header_extensions: vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 13, encrypt: false, }, ], encodings: vec![ RtpEncodingParameters { ssrc: Some(22222222), scalability_mode: "L1T5".parse().unwrap(), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222223 }), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(22222224), scalability_mode: "L1T5".parse().unwrap(), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222225 }), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(22222226), scalability_mode: "L1T5".parse().unwrap(), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222227 }), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(22222228), scalability_mode: "L1T5".parse().unwrap(), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222229 }), ..RtpEncodingParameters::default() }, ], rtcp: RtcpParameters { cname: Some("FOOBAR".to_string()), ..RtcpParameters::default() }, msid: None, }, ); options.app_data = AppData::new(ProducerAppData { _foo: 1, _bar: "2" }); options } fn consumer_device_capabilities() -> RtpCapabilities { RtpCapabilities { codecs: vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: Some(100), clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::Nack], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: Some(101), clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("level-asymmetry-allowed", 1_u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, ], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Rtx, preferred_payload_type: Some(102), clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 101_u32.into())]), rtcp_feedback: vec![], }, ], header_extensions: vec![ RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::Mid, preferred_id: 1, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::Mid, preferred_id: 1, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::RtpStreamId, preferred_id: 2, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::AbsSendTime, preferred_id: 4, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::AbsSendTime, preferred_id: 4, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::SsrcAudioLevel, preferred_id: 6, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::VideoOrientation, preferred_id: 8, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::TimeOffset, preferred_id: 9, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, ], } } // Keeps executor threads running until dropped struct ExecutorGuard { // Silence clippy warnings _senders: Vec>, } impl ExecutorGuard { fn new(_senders: Vec>) -> Self { Self { _senders } } } fn create_executor() -> (ExecutorGuard, Arc>) { let executor = Arc::new(Executor::new()); let thread_count = 4; let senders = (0..thread_count) .map(|_| { let (tx, rx) = async_oneshot::oneshot::<()>(); thread::Builder::new() .name("ex-mediasoup-worker".into()) .spawn({ let executor = Arc::clone(&executor); move || { future::block_on(executor.run(async move { let _ = rx.await; })); } }) .unwrap(); tx }) .collect(); (ExecutorGuard::new(senders), executor) } async fn init() -> ( ExecutorGuard, Worker, Router, WebRtcTransport, WebRtcTransport, ) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let (executor_guard, executor) = create_executor(); // Use multi-threaded executor in this module as a regression test for the crashes we had before let worker_manager = WorkerManager::with_executor(executor); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); let transport_1 = router .create_webrtc_transport(transport_options.clone()) .await .expect("Failed to create transport1"); let transport_2 = router .create_webrtc_transport(transport_options) .await .expect("Failed to create transport2"); (executor_guard, worker, router, transport_1, transport_2) } #[test] fn consume_succeeds() { future::block_on(async move { let (_executor_guard, _worker, router, transport_1, transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let video_producer = transport_1 .produce(video_producer_options()) .await .expect("Failed to produce video"); video_producer .pause() .await .expect("Failed to pause video producer"); let new_consumer_count = Arc::new(AtomicUsize::new(0)); transport_2 .on_new_consumer({ let new_consumer_count = Arc::clone(&new_consumer_count); Arc::new(move |_consumer| { new_consumer_count.fetch_add(1, Ordering::SeqCst); }) }) .detach(); let consumer_device_capabilities = consumer_device_capabilities(); let audio_consumer; { assert!(router.can_consume(&audio_producer.id(), &consumer_device_capabilities)); audio_consumer = transport_2 .consume({ let mut options = ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities.clone(), ); options.app_data = AppData::new(ConsumerAppData { baz: "LOL" }); options }) .await .expect("Failed to consume audio"); assert_eq!(new_consumer_count.load(Ordering::SeqCst), 1); assert_eq!(audio_consumer.producer_id(), audio_producer.id()); assert!(!audio_consumer.closed()); assert_eq!(audio_consumer.kind(), MediaKind::Audio); assert_eq!(audio_consumer.rtp_parameters().mid, Some("0".to_string())); assert_eq!( audio_consumer.rtp_parameters().codecs, vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 100, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("usedtx", 1_u32.into()), ("foo", "222.222".into()), ("bar", "333".into()), ]), rtcp_feedback: vec![], }] ); assert_eq!( audio_consumer.rtp_parameters().msid, Some("1111-1111-1111-1111 2222-2222-2222-2222".to_string()) ); assert_eq!(audio_consumer.r#type(), ConsumerType::Simple); assert!(!audio_consumer.paused()); assert!(!audio_consumer.producer_paused()); assert_eq!(audio_consumer.priority(), 1); assert_eq!( audio_consumer.score(), ConsumerScore { score: 10, producer_score: 0, producer_scores: vec![0] } ); assert_eq!(audio_consumer.preferred_layers(), None); assert_eq!(audio_consumer.current_layers(), None); assert_eq!( audio_consumer .app_data() .downcast_ref::() .unwrap() .baz, "LOL" ); let router_dump = router.dump().await.expect("Failed to get router dump"); assert_eq!(router_dump.map_producer_id_consumer_ids, { let mut map = HashedMap::default(); map.insert(audio_producer.id(), { let mut set = HashedSet::default(); set.insert(audio_consumer.id()); set }); map.insert(video_producer.id(), HashedSet::default()); map }); let transport_2_dump = transport_2 .dump() .await .expect("Failed to get transport 2 dump"); assert_eq!(transport_2_dump.producer_ids, vec![]); assert_eq!(transport_2_dump.consumer_ids, vec![audio_consumer.id()]); } let video_consumer; { assert!(router.can_consume(&video_producer.id(), &consumer_device_capabilities)); video_consumer = transport_2 .consume({ let mut options = ConsumerOptions::new( video_producer.id(), consumer_device_capabilities.clone(), ); options.paused = true; options.preferred_layers = Some(ConsumerLayers { spatial_layer: 12, temporal_layer: Some(0), }); options.app_data = AppData::new(ConsumerAppData { baz: "LOL" }); options }) .await .expect("Failed to consume video"); assert_eq!(new_consumer_count.load(Ordering::SeqCst), 2); assert_eq!(video_consumer.producer_id(), video_producer.id()); assert!(!video_consumer.closed()); assert_eq!(video_consumer.kind(), MediaKind::Video); assert_eq!(video_consumer.rtp_parameters().mid, Some("1".to_string())); assert_eq!( video_consumer.rtp_parameters().codecs, vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 103, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 104, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 103u32.into())]), rtcp_feedback: vec![], }, ] ); assert_eq!(video_consumer.rtp_parameters().msid, None); assert_eq!(video_consumer.r#type(), ConsumerType::Simulcast); assert!(video_consumer.paused()); assert!(video_consumer.producer_paused()); assert_eq!(video_consumer.priority(), 1); assert_eq!( video_consumer.score(), ConsumerScore { score: 10, producer_score: 0, producer_scores: vec![0, 0, 0, 0] } ); assert_eq!( video_consumer.preferred_layers(), Some(ConsumerLayers { spatial_layer: 3, temporal_layer: Some(0) }) ); assert_eq!(video_consumer.current_layers(), None); assert_eq!( video_consumer .app_data() .downcast_ref::() .unwrap() .baz, "LOL" ); video_consumer .get_stats() .await .expect("Failed to get consumer stats"); let router_dump = router.dump().await.expect("Failed to get router dump"); assert_eq!(router_dump.map_producer_id_consumer_ids, { let mut map = HashedMap::default(); map.insert(audio_producer.id(), { let mut set = HashedSet::default(); set.insert(audio_consumer.id()); set }); map.insert(video_producer.id(), { let mut set = HashedSet::default(); set.insert(video_consumer.id()); set }); map }); let mut transport_2_dump = transport_2 .dump() .await .expect("Failed to get transport 2 dump"); assert_eq!(transport_2_dump.producer_ids, vec![]); { transport_2_dump.consumer_ids.sort(); let mut expected_consumer_ids = vec![audio_consumer.id(), video_consumer.id()]; expected_consumer_ids.sort(); assert_eq!(transport_2_dump.consumer_ids, expected_consumer_ids); } } let video_pipe_consumer; { assert!(router.can_consume(&video_producer.id(), &consumer_device_capabilities)); video_pipe_consumer = transport_2 .consume({ let mut options = ConsumerOptions::new( video_producer.id(), consumer_device_capabilities.clone(), ); options.pipe = true; options }) .await .expect("Failed to consume video"); assert_eq!(new_consumer_count.load(Ordering::SeqCst), 3); assert_eq!(video_pipe_consumer.producer_id(), video_producer.id()); assert!(!video_pipe_consumer.closed()); assert_eq!(video_pipe_consumer.kind(), MediaKind::Video); assert_eq!(video_pipe_consumer.rtp_parameters().mid, None); assert_eq!( video_pipe_consumer.rtp_parameters().codecs, vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 103, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 104, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 103u32.into())]), rtcp_feedback: vec![], }, ] ); assert_eq!(video_pipe_consumer.r#type(), ConsumerType::Pipe); assert!(!video_pipe_consumer.paused()); assert!(video_pipe_consumer.producer_paused()); assert_eq!(video_pipe_consumer.priority(), 1); assert_eq!( video_pipe_consumer.score(), ConsumerScore { score: 10, producer_score: 10, producer_scores: vec![0, 0, 0, 0] }, ); assert_eq!(video_pipe_consumer.preferred_layers(), None); assert_eq!(video_pipe_consumer.current_layers(), None); assert_eq!( video_pipe_consumer.app_data().downcast_ref::<()>().unwrap(), &(), ); video_pipe_consumer .get_stats() .await .expect("Failed to get consumer stats"); let router_dump = router.dump().await.expect("Failed to get router dump"); assert_eq!(router_dump.map_producer_id_consumer_ids, { let mut map = HashedMap::default(); map.insert(audio_producer.id(), { let mut set = HashedSet::default(); set.insert(audio_consumer.id()); set }); map.insert(video_producer.id(), { let mut set = HashedSet::default(); set.insert(video_consumer.id()); set.insert(video_pipe_consumer.id()); set }); map }); let mut transport_2_dump = transport_2 .dump() .await .expect("Failed to get transport 2 dump"); assert_eq!(transport_2_dump.producer_ids, vec![]); { transport_2_dump.consumer_ids.sort(); let mut expected_consumer_ids = vec![ audio_consumer.id(), video_consumer.id(), video_pipe_consumer.id(), ]; expected_consumer_ids.sort(); assert_eq!(transport_2_dump.consumer_ids, expected_consumer_ids); } } }); } #[test] fn consume_with_enable_rtx_succeeds() { future::block_on(async move { let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let consumer_device_capabilities = consumer_device_capabilities(); let audio_consumer = transport_2 .consume({ let mut options = ConsumerOptions::new(audio_producer.id(), consumer_device_capabilities.clone()); options.enable_rtx = Some(true); options }) .await .expect("Failed to consume audio"); assert_eq!(audio_consumer.kind(), MediaKind::Audio); assert_eq!(audio_consumer.rtp_parameters().mid, Some("0".to_string())); assert_eq!( audio_consumer.rtp_parameters().codecs, vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 100, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("usedtx", 1_u32.into()), ("foo", "222.222".into()), ("bar", "333".into()), ]), rtcp_feedback: vec![RtcpFeedback::Nack], }] ); }); } #[test] fn consumer_with_user_defined_mid() { future::block_on(async move { let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await; let producer_1 = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let consumer_2_1 = transport_2 .consume(ConsumerOptions::new( producer_1.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume audio"); assert_eq!( consumer_2_1.rtp_parameters().mid, Some("0".to_string()), "MID automatically assigned to sequential number" ); let consumer_2_2 = transport_2 .consume({ let mut options = ConsumerOptions::new(producer_1.id(), consumer_device_capabilities()); options.mid = Some("custom-mid".to_owned()); options }) .await .expect("Failed to consume audio"); assert_eq!( consumer_2_2.rtp_parameters().mid, Some("custom-mid".to_string()), "MID is assigned to user-provided value" ); let consumer_2_3 = transport_2 .consume(ConsumerOptions::new( producer_1.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume audio"); assert_eq!( consumer_2_3.rtp_parameters().mid, Some("1".to_string()), "MID automatically assigned to next sequential number" ); }) } #[test] fn weak() { future::block_on(async move { let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await; let producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let consumer = transport_2 .consume({ let mut options = ConsumerOptions::new(producer.id(), consumer_device_capabilities()); options.app_data = AppData::new(ConsumerAppData { baz: "LOL" }); options }) .await .expect("Failed to consume audio"); let weak_consumer = consumer.downgrade(); assert!(weak_consumer.upgrade().is_some()); drop(consumer); assert!(weak_consumer.upgrade().is_none()); }); } #[test] fn consume_incompatible_rtp_capabilities() { future::block_on(async move { let (_executor_guard, _worker, router, transport_1, transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); { let incompatible_device_capabilities = RtpCapabilities { codecs: vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Isac, preferred_payload_type: Some(100), clock_rate: NonZeroU32::new(32_000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }], header_extensions: vec![], }; assert!(!router.can_consume(&audio_producer.id(), &incompatible_device_capabilities)); assert!(matches!( transport_2 .consume(ConsumerOptions::new( audio_producer.id(), incompatible_device_capabilities, )) .await, Err(ConsumeError::BadConsumerRtpParameters(_)) )); } { let invalid_device_capabilities = RtpCapabilities { codecs: vec![], header_extensions: vec![], }; assert!(!router.can_consume(&audio_producer.id(), &invalid_device_capabilities)); assert!(matches!( transport_2 .consume(ConsumerOptions::new( audio_producer.id(), invalid_device_capabilities, )) .await, Err(ConsumeError::BadConsumerRtpParameters(_)) )); } }); } #[test] fn dump_succeeds() { future::block_on(async move { let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let video_producer = transport_1 .produce(video_producer_options()) .await .expect("Failed to produce video"); video_producer .pause() .await .expect("Failed to pause video producer"); let consumer_device_capabilities = consumer_device_capabilities(); { let audio_consumer = transport_2 .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities.clone(), )) .await .expect("Failed to consume audio"); let dump = audio_consumer .dump() .await .expect("Audio consumer dump failed"); assert_eq!(dump.id, audio_consumer.id()); assert_eq!(dump.producer_id, audio_consumer.producer_id()); assert_eq!(dump.kind, audio_consumer.kind()); assert_eq!( dump.rtp_parameters.codecs, vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 100, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("usedtx", 1_u32.into()), ("foo", "222.222".into()), ("bar", "333".into()), ]), rtcp_feedback: vec![], }], ); assert_eq!( dump.rtp_parameters.header_extensions, vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 1, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::AbsSendTime, id: 4, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::SsrcAudioLevel, id: 6, encrypt: false, }, ], ); assert_eq!( dump.rtp_parameters.encodings, vec![RtpEncodingParameters { codec_payload_type: Some(100), rtx: None, dtx: None, scalability_mode: ScalabilityMode::None, ssrc: audio_consumer .rtp_parameters() .encodings .first() .unwrap() .ssrc, rid: None, max_bitrate: None, }], ); assert_eq!(dump.r#type, ConsumerType::Simple); assert_eq!( dump.consumable_rtp_encodings, audio_producer .consumable_rtp_parameters() .encodings .iter() .map(|encoding| RtpEncodingParameters { ssrc: encoding.ssrc, rid: None, codec_payload_type: None, rtx: None, max_bitrate: None, dtx: None, scalability_mode: ScalabilityMode::None, }) .collect::>() ); } { let video_consumer = transport_2 .consume({ let mut options = ConsumerOptions::new(video_producer.id(), consumer_device_capabilities); options.paused = true; options.preferred_layers = Some(ConsumerLayers { spatial_layer: 12, temporal_layer: None, }); options }) .await .expect("Failed to consume video"); let dump = video_consumer .dump() .await .expect("Video consumer dump failed"); assert_eq!(dump.id, video_consumer.id()); assert_eq!(dump.producer_id, video_consumer.producer_id()); assert_eq!(dump.kind, video_consumer.kind()); assert_eq!( dump.rtp_parameters.codecs, vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 103, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir, RtcpFeedback::GoogRemb, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 104, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 103u32.into())]), rtcp_feedback: vec![], } ], ); assert_eq!( dump.rtp_parameters.header_extensions, vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 1, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::AbsSendTime, id: 4, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 8, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::TimeOffset, id: 9, encrypt: false, }, ], ); assert_eq!( dump.rtp_parameters.encodings, vec![RtpEncodingParameters { codec_payload_type: Some(103), ssrc: video_consumer .rtp_parameters() .encodings .first() .unwrap() .ssrc, rtx: video_consumer .rtp_parameters() .encodings .first() .unwrap() .rtx, dtx: None, scalability_mode: "L4T5".parse().unwrap(), rid: None, max_bitrate: None, }], ); assert_eq!(dump.r#type, ConsumerType::Simulcast); assert_eq!( dump.consumable_rtp_encodings, video_producer .consumable_rtp_parameters() .encodings .iter() .map(|encoding| RtpEncodingParameters { ssrc: encoding.ssrc, rid: None, codec_payload_type: None, rtx: None, max_bitrate: None, dtx: None, scalability_mode: "L1T5".parse().unwrap(), }) .collect::>() ); assert_eq!(dump.supported_codec_payload_types, vec![103]); assert!(dump.paused); assert!(dump.producer_paused); assert_eq!(dump.priority, 1); } }); } #[test] fn get_stats_succeeds() { future::block_on(async move { let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await; let consumer_device_capabilities = consumer_device_capabilities(); { let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let audio_consumer = transport_2 .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities.clone(), )) .await .expect("Failed to consume audio"); let stats = audio_consumer .get_stats() .await .expect("Audio consumer get_stats failed"); let consumer_stat = stats.consumer_stats(); assert_eq!(consumer_stat.kind, MediaKind::Audio); assert_eq!( consumer_stat.mime_type, MimeType::Audio(MimeTypeAudio::Opus) ); assert_eq!( consumer_stat.ssrc, audio_consumer .rtp_parameters() .encodings .first() .unwrap() .ssrc .unwrap() ); } { let video_producer = transport_1 .produce(video_producer_options()) .await .expect("Failed to produce video"); video_producer .pause() .await .expect("Failed to pause video producer"); let video_consumer = transport_2 .consume({ let mut options = ConsumerOptions::new(video_producer.id(), consumer_device_capabilities); options.paused = true; options.preferred_layers = Some(ConsumerLayers { spatial_layer: 12, temporal_layer: None, }); options }) .await .expect("Failed to consume video"); let stats = video_consumer .get_stats() .await .expect("Video consumer get_stats failed"); let consumer_stat = stats.consumer_stats(); assert_eq!(consumer_stat.kind, MediaKind::Video); assert_eq!( consumer_stat.mime_type, MimeType::Video(MimeTypeVideo::H264) ); assert_eq!( consumer_stat.ssrc, video_consumer .rtp_parameters() .encodings .first() .unwrap() .ssrc .unwrap() ); } }); } #[test] fn pause_resume_succeeds() { future::block_on(async move { let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let audio_consumer = transport_2 .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume audio"); { audio_consumer .pause() .await .expect("Failed to pause consumer"); let dump = audio_consumer.dump().await.expect("Consumer dump failed"); assert!(dump.paused); } { audio_consumer .resume() .await .expect("Failed to resume consumer"); let dump = audio_consumer.dump().await.expect("Consumer dump failed"); assert!(!dump.paused); } }); } #[test] fn set_preferred_layers_succeeds() { future::block_on(async move { let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await; let consumer_device_capabilities = consumer_device_capabilities(); { let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let audio_consumer = transport_2 .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities.clone(), )) .await .expect("Failed to consume audio"); audio_consumer .set_preferred_layers(ConsumerLayers { spatial_layer: 1, temporal_layer: Some(1), }) .await .expect("Failed to set preferred layers consumer"); assert_eq!(audio_consumer.preferred_layers(), None); } { let video_producer = transport_1 .produce(video_producer_options()) .await .expect("Failed to produce audio"); let video_consumer = transport_2 .consume({ let mut options = ConsumerOptions::new(video_producer.id(), consumer_device_capabilities); options.paused = true; options.preferred_layers = Some(ConsumerLayers { spatial_layer: 12, temporal_layer: None, }); options }) .await .expect("Failed to consume video"); video_consumer .set_preferred_layers(ConsumerLayers { spatial_layer: 2, temporal_layer: Some(3), }) .await .expect("Failed to set preferred layers consumer"); assert_eq!( video_consumer.preferred_layers(), Some(ConsumerLayers { spatial_layer: 2, temporal_layer: Some(3), }) ); video_consumer .set_preferred_layers(ConsumerLayers { spatial_layer: 3, temporal_layer: None, }) .await .expect("Failed to set preferred layers consumer"); assert_eq!( video_consumer.preferred_layers(), Some(ConsumerLayers { spatial_layer: 3, temporal_layer: Some(4), }) ); video_consumer .set_preferred_layers(ConsumerLayers { spatial_layer: 3, temporal_layer: Some(0), }) .await .expect("Failed to set preferred layers consumer"); assert_eq!( video_consumer.preferred_layers(), Some(ConsumerLayers { spatial_layer: 3, temporal_layer: Some(0), }) ); video_consumer .set_preferred_layers(ConsumerLayers { spatial_layer: 66, temporal_layer: Some(66), }) .await .expect("Failed to set preferred layers consumer"); assert_eq!( video_consumer.preferred_layers(), Some(ConsumerLayers { spatial_layer: 3, temporal_layer: Some(4), }) ); } }); } #[test] fn set_unset_priority_succeeds() { future::block_on(async move { let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await; let video_producer = transport_1 .produce(video_producer_options()) .await .expect("Failed to produce audio"); let video_consumer = transport_2 .consume({ let mut options = ConsumerOptions::new(video_producer.id(), consumer_device_capabilities()); options.paused = true; options.preferred_layers = Some(ConsumerLayers { spatial_layer: 12, temporal_layer: None, }); options }) .await .expect("Failed to consume video"); video_consumer .set_priority(2) .await .expect("Failed to ser priority"); assert_eq!(video_consumer.priority(), 2); video_consumer .unset_priority() .await .expect("Failed to ser priority"); assert_eq!(video_consumer.priority(), 1); }); } #[test] fn producer_pause_resume_events() { future::block_on(async move { let (_executor_guard, _worker, _router, transport_1, transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let audio_consumer = transport_2 .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume audio"); { let (tx, rx) = async_oneshot::oneshot::<()>(); let _handler = audio_consumer.on_producer_pause({ let tx = Mutex::new(Some(tx)); move || { let _ = tx.lock().take().unwrap().send(()); } }); audio_producer .pause() .await .expect("Failed to pause producer"); rx.await.expect("Failed to receive producer paused event"); assert!(!audio_consumer.paused()); assert!(audio_consumer.producer_paused()); } { let (tx, rx) = async_oneshot::oneshot::<()>(); let _handler = audio_consumer.on_producer_resume({ let tx = Mutex::new(Some(tx)); move || { let _ = tx.lock().take().unwrap().send(()); } }); audio_producer .resume() .await .expect("Failed to pause producer"); rx.await.expect("Failed to receive producer paused event"); assert!(!audio_consumer.paused()); assert!(!audio_consumer.producer_paused()); } }); } #[test] fn close_event() { future::block_on(async move { let (_executor_guard, _worker, router, transport_1, transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let audio_consumer = transport_2 .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume audio"); { let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = audio_consumer.on_close(move || { let _ = tx.send(()); }); drop(audio_consumer); rx.await.expect("Failed to receive close event"); } // Drop is async, give consumer a bit of time to finish Timer::after(Duration::from_millis(200)).await; { let dump = router.dump().await.expect("Failed to dump router"); assert_eq!(dump.map_producer_id_consumer_ids, { let mut map = HashedMap::default(); map.insert(audio_producer.id(), HashedSet::default()); map }); assert_eq!(dump.map_consumer_id_producer_id, HashedMap::default()); } { let dump = transport_2.dump().await.expect("Failed to dump transport"); assert_eq!(dump.producer_ids, vec![]); assert_eq!(dump.consumer_ids, vec![]); } }); } ================================================ FILE: rust/tests/integration/data/dtls-cert.pem ================================================ -----BEGIN CERTIFICATE----- MIIFazCCA1OgAwIBAgIUXy3udbf5+Rvhx3MaNGn7vj+zi+UwDQYJKoZIhvcNAQEL BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMjA2MjQyMTQwNTZaFw0yMzA2 MjQyMTQwNTZaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB AQUAA4ICDwAwggIKAoICAQDLXJlS6702GKmSsfOFWzMP2+NvlgNySLiqnAf6bBuX Vx4UN7feOIHz4mFh4OUtxNzrB44XMzRMZKf21qaY7VTUbUag6i+Ghc4I15ZRzONP gv/2BlQ7PR8LqMTDJ6xMmIZfJPsmvpm/8VQa+h1spKyBHs96pFQQzpVNAnGSifk7 hPPWEy60fLQ4hxDPxXUKj/z/j5c8W9mqt8KlJgm1fIbxdtVn60T7hz54/OuikHBD jgBNjGwLeyybU0dGuXHBiriOLlVmtPNMpyjptUGA3BUISf66SS31rP+ol2frlOY9 QZGG7Z5jzhfNvgIAQKTJmVOhpEmT6NbE7uQKMO76LaFcRnkbO0GxDnKw/T+1LGqb JOdHW0m7xYkoLLpsjnF8ROFP7EbP6lLtaFtOzDxueJT9fXBAW5etrp7KvWmDY6/5 Hn3gX/1nvLzqFF2itze+11o4BeE+Oj9XUSXDtZzACRaZ9hjCe8WmzPXVEEOvVIP9 h5pVp7qW6rIxwNPTE25GPzbdquJmfjauHMKuf/ax+0gpeu5VpHNkvOIUyM/QTiBi D8Z62iWpooHXr3P3qbp4/qa3g0bQZ6uNveTT112E3RJJueYTc3IzUPefiHFmCfFZ eUmOhqf9v2eYroVq0MKSnw2+1MQxdYDWV7QpKOLIJik9tPm8hScggwLvq0YIwnvN VQIDAQABo1MwUTAdBgNVHQ4EFgQUe/9tLjutYDz+myVgALKjHDuXm2QwHwYDVR0j BBgwFoAUe/9tLjutYDz+myVgALKjHDuXm2QwDwYDVR0TAQH/BAUwAwEB/zANBgkq hkiG9w0BAQsFAAOCAgEAXi18lNqjeOXV6P/snVs4b34OiWpkRlDxKKMG95rdRKeF kEmPpO7T293nXCGvFmGfME6KBhio4w0MVMlbtC5TVdTFJk6mSgkCEtncA5yEv8Ga w2HCoEWfkOpea3S1XL9i5EWVPKvJG/rJ3YMZuh1BvppfC73dHVFSdik5SXNCGqEy 9OuhJHbpbdlMuwFTLKwQCKDwh5Yvzd0eASyYlJ6Ytpf7TLCc3nvUS9haSOKEfnHQ v3TLt2WA+xHK7XIj9qoYuWIsnkXAWIsSJy/utJrDhtym7BcxB+ss7doHkS0LCTHd DiAhJwvTMKPYSRA55oF9iejynrcBOidH+tQxYMXHaDisNtH+6ZOtxkcLleneKhNB 9EAMw9qeyiVl+MHDQi+sA5ksMfVoXzvxqObNGSz9g5z1AfnNuiaX1z9ajXsiHJ5e 7EQFVCU4Id519RcSEUY2qcKTNMBPyXNbTQfd/oV1C6pzF01tVaNpl12dsXdx+JTh 9vbK9tI2U+jzIb0Y8ersKrAjRQO/1lfIbjdJ+e0rFlxX1+SmlKlcArOfd8PSuifx wbzCJeRjFdOdf2D6mVHO0uMghjnVTHxB3KX6QonKOFzkupwWBeVqginXm4pEaUhK nssuSpkKrVshQ3RSEq3H9yRdQQ0qqwdOd3dISEqucVvaEsxhUwHBX/R5p8W0OCo= -----END CERTIFICATE----- ================================================ FILE: rust/tests/integration/data/dtls-key.pem ================================================ -----BEGIN PRIVATE KEY----- MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDLXJlS6702GKmS sfOFWzMP2+NvlgNySLiqnAf6bBuXVx4UN7feOIHz4mFh4OUtxNzrB44XMzRMZKf2 1qaY7VTUbUag6i+Ghc4I15ZRzONPgv/2BlQ7PR8LqMTDJ6xMmIZfJPsmvpm/8VQa +h1spKyBHs96pFQQzpVNAnGSifk7hPPWEy60fLQ4hxDPxXUKj/z/j5c8W9mqt8Kl Jgm1fIbxdtVn60T7hz54/OuikHBDjgBNjGwLeyybU0dGuXHBiriOLlVmtPNMpyjp tUGA3BUISf66SS31rP+ol2frlOY9QZGG7Z5jzhfNvgIAQKTJmVOhpEmT6NbE7uQK MO76LaFcRnkbO0GxDnKw/T+1LGqbJOdHW0m7xYkoLLpsjnF8ROFP7EbP6lLtaFtO zDxueJT9fXBAW5etrp7KvWmDY6/5Hn3gX/1nvLzqFF2itze+11o4BeE+Oj9XUSXD tZzACRaZ9hjCe8WmzPXVEEOvVIP9h5pVp7qW6rIxwNPTE25GPzbdquJmfjauHMKu f/ax+0gpeu5VpHNkvOIUyM/QTiBiD8Z62iWpooHXr3P3qbp4/qa3g0bQZ6uNveTT 112E3RJJueYTc3IzUPefiHFmCfFZeUmOhqf9v2eYroVq0MKSnw2+1MQxdYDWV7Qp KOLIJik9tPm8hScggwLvq0YIwnvNVQIDAQABAoICAAd5PTdDa5431M+L06fEfMFp 8tdQe4rxKjw25MIqw+7RaE0U6SB1Ei2M1chblACVGgtXKT0oCBWAo32aUOAQ5Muz wmM6iAmZFEPV7HPQJFBxP4finqjYq7Hpf2WuqRHdjx6jBMndOlhH/a/KHle2S5Kp N7XJoT9G4EzGuLbKdErgLXfiF5bReGwVZqREHPOI7CLWO5gfxgWUoEYiejvdujXY iKo7hrr5su2OWfiM91s8Nj2jWfsoCTEiI93xBcG3n+W1xTSzlLdt8z53h1M9g1Zd Jcvh0ZsUQwcGnW6Wd8mrhbYgOHBxjAVnJLqupX+1L1Ii8ANMDMyK/P104+t0zte9 BpuBzerm9h69UQq7d728YKW2KfY4M5+oLG2l4PKH6YDhoymIso38TYMixutkXpIs xZXATFA82sZlcpbSjLJypeJnKxBCbKxu8eYTq5K2SDdq8r82DwZZT/N8FfQMGZoO YYtP2KfUSxc2azpQtEHmB00fiPXgCdJNU2I8+J84Ve/ltBDlJcNkJN09h5ob9q/g Q8qJQRrzpLedBRhyqharYyDS9oGDUlR9OfmR7vFzUNP5npgNts0TDGy/JpVdY9aC YmqWsC8c/Kmms7X+4KaXMsBEpaVNSx9FsoQiPi1CMdy/KpWGcshusdVwgdklrOnz iKBvvMNvP+gu7bV+uoR3AoIBAQDqBkUaMbVUVup94WqOIFjEl13CQwVwLgmLQaw0 9BjPkv8BaPiMUfg7ANqEi8V+iJX5m27fcsvWIMUKBe0FvLdo/kH/NahNxD0bub3S OikGeJEo0QDsIKhVXTLjYJ/N+qk2P3WYWy/7yGV3kVDkEam0oebmdYNQsfpXK+wu Gt0hpjU2RDMB2NCmtLHQXCI5DUxX0CtfJD5JTop9tLTRJKnputsIsQZPSmNSTuTl FVO0JdyJNv97Xhc8YUzwTfwl73EW7srcJNKO2ZwkzOZ8EghaEa1c5BZ6oV2MWe5f HIrRbW9LCYnBavoTC7VGWYkKiklAm5pRfvj9fQylv4tiSRUfAoIBAQDedTl3fAFf i/8ak0WfJKn/H0Z2W3AcgeChyBAWdrOrTDZUdhV0olcIT7m1v2qbWQju8boPdJiY BqG2DD3CZHS+qETt4lP5r+9j2gvy5g3wdQ4bYpjyr2hRXkwMK2yYQbXCmgmDETb+ yNTj2avhAA32BmTH9QyxXSGN7ympVwEP1kDcXHM8vDGL7FvGGdRPFg0h4LKHrCHa xCQEWPjBv8oPo8Gb2ateSZjeWCraRhTXd7yVWC5s+3uEfW/h+g/QJVrRh2p8ipGv zlWDR1BaF+t4racWHFeftrSkc3ZpN/eAVyLXBSTo7plOx8tFujrakJedmZssrHDc Rc0JeDOrjnsLAoIBAQDGXoo0qe4Kj6I1Ed5Amyqjear//8+cR2nPoNtYB5EAYpnF mDUWvGStnwubTt8ZYq295wMUZTpjR2O+G0fOlSji1qMasWD4il9CIS/GA4bC9XAW KROfFA+cTGPWWREciFzmnuQPQTxrMHLR51up907izlnq/7FPtY1+VrzcV+kZnMl+ NlEGP8KdjI0tEOvxcFRGGy6odxBVEz5RT9v1bB6bAMiplWTD0UpfeoCLrohFK9LE fNoSuK75f4C4MWKKxWwXBFLwSEYy0EKK7yRwBtkNf+5zzuM/D4k8bv6foJIK87hi 4rLiQMu5WTNPbpW7WXy+RyeH7Rkhxd3yoWqE5W4BAoIBADjNOdU2hqs89fB1NkvC ct2/wKAsDN5ak1771I/H02yj0yOR2zyizxJCOSsdKz1raIqKknWr0eLPnq77RTHD sMOV97O+HK8eq0OVw4NMFrcVTHrVnDQrcbmFGGnrFJlz/dMovdEHrkE0Spe7VtXm y6nMTCN6gLkxDIZPURX6Lz05+enKeWpCq2wM+AoHQlzHRqcl1rAp1aMkfgXWKf5e 2FtR9veyhr1WkYAEhzygtGWoHzELCR+uvwU/ejf7P9poD15880XFpBl91/vjU7MN dISl4ooUxpLzdgCfstZ/AeV1WmII4DnR4rdo8JBnUuvIC86kEClCBrdX41jNpnPh t60CggEAQnioYh4vwJctZENvO6nKlexCui2sQmHi4hWwzzA0s8tG2iG++nWXUEV+ T+SBuCnIZMhnXlFIbqx+gjke3xLxJVbsgeRHDwDMZbkcaE35L1ZdAjIhuWgoNa20 RSXApmp376VNEoU3lJH6jK0Wa67N9IaD3P5Mb+jekU16beqVgMYWf/YXycvD6bEp L9CODZuTPS/Ue1waBP7bKWAH8PRKAdXQACkn+aRBqYm9a5o7Z5jD/Qzo9ia5VaK4 j+EQ0Gk4BaVBnvIu4LQ/HKuZm4v1NWNy3UhFuoTAzxNoCrQamYII/EaCMqiLATGH jBJvmV3y/KoVcZGzNGFVB29Ns8VK1g== -----END PRIVATE KEY----- ================================================ FILE: rust/tests/integration/data_consumer.rs ================================================ use async_io::Timer; use futures_lite::future; use hash_hasher::{HashedMap, HashedSet}; use mediasoup::data_consumer::{DataConsumerOptions, DataConsumerType}; use mediasoup::data_producer::{DataProducer, DataProducerOptions}; use mediasoup::direct_transport::DirectTransportOptions; use mediasoup::plain_transport::PlainTransportOptions; use mediasoup::prelude::*; use mediasoup::router::{Router, RouterOptions}; use mediasoup::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use mediasoup::worker::{Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{AppData, ListenInfo, Protocol}; use mediasoup_types::sctp_parameters::SctpStreamParameters; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; struct CustomAppData { baz: &'static str, } struct CustomAppData2 { hehe: &'static str, } fn sctp_data_producer_options() -> DataProducerOptions { let mut options = DataProducerOptions::new_sctp( SctpStreamParameters::new_unordered_with_life_time(12345, 5000), ); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options } async fn init() -> (Worker, Router, WebRtcTransport, DataProducer) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); let webrtc_transport = router .create_webrtc_transport({ let mut transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); let sctp_data_producer = webrtc_transport .produce_data(sctp_data_producer_options()) .await .expect("Failed to create data producer"); (worker, router, webrtc_transport, sctp_data_producer) } #[test] fn consume_data_succeeds() { future::block_on(async move { let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await; let plain_transport = router .create_plain_transport({ let mut transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); let new_data_consumer_count = Arc::new(AtomicUsize::new(0)); plain_transport .on_new_data_consumer({ let new_data_consumer_count = Arc::clone(&new_data_consumer_count); Arc::new(move |_data_consumer| { new_data_consumer_count.fetch_add(1, Ordering::SeqCst); }) }) .detach(); let data_consumer = plain_transport .consume_data({ let mut options = DataConsumerOptions::new_sctp(sctp_data_producer.id()); options.subchannels = Some(vec![0, 1, 1, 1, 2, 65535, 100]); options.app_data = AppData::new(CustomAppData { baz: "LOL" }); options }) .await .expect("Failed to consume data"); assert_eq!(data_consumer.data_producer_id(), sctp_data_producer.id()); assert!(!data_consumer.closed()); assert_eq!(data_consumer.r#type(), DataConsumerType::Sctp); { let sctp_stream_parameters = data_consumer.sctp_stream_parameters(); assert!(sctp_stream_parameters.is_some()); assert!(!sctp_stream_parameters.unwrap().ordered()); assert_eq!( sctp_stream_parameters.unwrap().max_packet_life_time(), Some(5000) ); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None); } assert_eq!(data_consumer.label().as_str(), "foo"); assert_eq!(data_consumer.protocol().as_str(), "bar"); let mut sorted_subchannels = data_consumer.subchannels(); sorted_subchannels.sort(); assert_eq!(sorted_subchannels, [0, 1, 2, 100, 65535]); assert_eq!( data_consumer .app_data() .downcast_ref::() .unwrap() .baz, "LOL", ); { let dump = router.dump().await.expect("Failed to dump router"); assert_eq!(dump.map_data_producer_id_data_consumer_ids, { let mut map = HashedMap::default(); map.insert(sctp_data_producer.id(), { let mut set = HashedSet::default(); set.insert(data_consumer.id()); set }); map }); assert_eq!(dump.map_data_consumer_id_data_producer_id, { let mut map = HashedMap::default(); map.insert(data_consumer.id(), sctp_data_producer.id()); map }); } { let dump = plain_transport .dump() .await .expect("Failed to dump transport"); assert_eq!(dump.id, plain_transport.id()); assert_eq!(dump.data_producer_ids, vec![]); assert_eq!(dump.data_consumer_ids, vec![data_consumer.id()]); } }); } #[test] fn weak() { future::block_on(async move { let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await; let plain_transport = router .create_plain_transport({ let mut transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); let data_consumer = plain_transport .consume_data({ let mut options = DataConsumerOptions::new_sctp_unordered_with_life_time( sctp_data_producer.id(), 4000, ); options.app_data = AppData::new(CustomAppData { baz: "LOL" }); options }) .await .expect("Failed to consume data"); let weak_data_consumer = data_consumer.downgrade(); assert!(weak_data_consumer.upgrade().is_some()); drop(data_consumer); assert!(weak_data_consumer.upgrade().is_none()); }); } #[test] fn dump_succeeds() { future::block_on(async move { let (_worker, _router, webrtc_transport, sctp_data_producer) = init().await; let data_consumer = webrtc_transport .consume_data({ let mut options = DataConsumerOptions::new_sctp_ordered(sctp_data_producer.id()); options.app_data = AppData::new(CustomAppData { baz: "LOL" }); options }) .await .expect("Failed to consume data"); let dump = data_consumer .dump() .await .expect("Data consumer dump failed"); assert_eq!(dump.id, data_consumer.id()); assert_eq!(dump.data_producer_id, data_consumer.data_producer_id()); assert_eq!(dump.r#type, DataConsumerType::Sctp); { let sctp_stream_parameters = dump.sctp_stream_parameters; assert!(sctp_stream_parameters.is_some()); assert_eq!( sctp_stream_parameters.unwrap().stream_id(), data_consumer.sctp_stream_parameters().unwrap().stream_id(), ); assert!(sctp_stream_parameters.unwrap().ordered()); assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None); } assert_eq!(dump.label.as_str(), "foo"); assert_eq!(dump.protocol.as_str(), "bar"); }); } #[test] fn get_stats_succeeds() { future::block_on(async move { let (_worker, _router, webrtc_transport, sctp_data_producer) = init().await; let data_consumer = webrtc_transport .consume_data({ let mut options = DataConsumerOptions::new_sctp_unordered_with_life_time( sctp_data_producer.id(), 4000, ); options.app_data = AppData::new(CustomAppData { baz: "LOL" }); options }) .await .expect("Failed to consume data"); let stats = data_consumer .get_stats() .await .expect("Failed to get data consumer stats"); assert_eq!(stats.len(), 1); assert_eq!(&stats[0].label, data_consumer.label()); assert_eq!(&stats[0].protocol, data_consumer.protocol()); assert_eq!(stats[0].messages_sent, 0); assert_eq!(stats[0].bytes_sent, 0); }); } #[test] fn set_subchannels() { future::block_on(async move { let (_worker, _router, transport1, sctp_data_producer) = init().await; let data_consumer = transport1 .consume_data(DataConsumerOptions::new_sctp_unordered_with_life_time( sctp_data_producer.id(), 4000, )) .await .expect("Failed to consume data"); data_consumer .set_subchannels([999, 999, 998, 0].to_vec()) .await .expect("Failed to set data consumer subchannels"); let mut sorted_subchannels = data_consumer.subchannels(); sorted_subchannels.sort(); assert_eq!(sorted_subchannels, [0, 998, 999]); }); } #[test] fn add_and_remove_subchannel() { future::block_on(async move { let (_worker, _router, transport1, sctp_data_producer) = init().await; let data_consumer = transport1 .consume_data(DataConsumerOptions::new_sctp_unordered_with_life_time( sctp_data_producer.id(), 4000, )) .await .expect("Failed to consume data"); data_consumer .set_subchannels([].to_vec()) .await .expect("Failed to set data consumer subchannels"); assert_eq!(data_consumer.subchannels(), []); data_consumer .add_subchannel(5) .await .expect("Failed to add data consumer subchannel"); assert_eq!(data_consumer.subchannels(), [5]); data_consumer .add_subchannel(10) .await .expect("Failed to add data consumer subchannel"); let mut sorted_subchannels = data_consumer.subchannels(); sorted_subchannels.sort(); assert_eq!(sorted_subchannels, [5, 10]); data_consumer .add_subchannel(5) .await .expect("Failed to add data consumer subchannel"); sorted_subchannels = data_consumer.subchannels(); sorted_subchannels.sort(); assert_eq!(sorted_subchannels, [5, 10]); data_consumer .remove_subchannel(666) .await .expect("Failed to remove data consumer subchannel"); sorted_subchannels = data_consumer.subchannels(); sorted_subchannels.sort(); assert_eq!(sorted_subchannels, [5, 10]); data_consumer .remove_subchannel(5) .await .expect("Failed to remove data consumer subchannel"); sorted_subchannels = data_consumer.subchannels(); sorted_subchannels.sort(); assert_eq!(sorted_subchannels, [10]); data_consumer .add_subchannel(5) .await .expect("Failed to add data consumer subchannel"); sorted_subchannels = data_consumer.subchannels(); sorted_subchannels.sort(); assert_eq!(sorted_subchannels, [5, 10]); data_consumer .set_subchannels([].to_vec()) .await .expect("Failed to set data consumer subchannels"); assert_eq!(data_consumer.subchannels(), []); }); } #[test] fn consume_data_from_a_direct_data_producer_succeeds() { future::block_on(async move { let (_worker, router, webrtc_transport, sctp_data_producer) = init().await; let direct_transport = router .create_direct_transport(DirectTransportOptions::default()) .await .expect("Failed to create Direct transport"); let direct_data_producer = direct_transport .produce_data(DataProducerOptions::new_direct()) .await .expect("Failed to create data producer"); let data_consumer = webrtc_transport .consume_data(DataConsumerOptions::new_direct( direct_data_producer.id(), None, )) .await .expect("Failed to consume data"); assert_eq!(data_consumer.data_producer_id(), direct_data_producer.id()); assert!(!data_consumer.closed()); assert_eq!(data_consumer.r#type(), DataConsumerType::Sctp); { let sctp_stream_parameters = data_consumer.sctp_stream_parameters(); assert!(sctp_stream_parameters.is_some()); assert!(sctp_stream_parameters.unwrap().ordered()); assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None); } assert_eq!(data_consumer.label().as_str(), ""); assert_eq!(data_consumer.protocol().as_str(), ""); { let dump = webrtc_transport .dump() .await .expect("Failed to dump transport"); assert_eq!(dump.id, webrtc_transport.id()); assert_eq!(dump.data_producer_ids, vec![sctp_data_producer.id()]); assert_eq!(dump.data_consumer_ids, vec![data_consumer.id()]); } }); } #[test] fn dump_consuming_from_a_direct_data_producer_succeeds() { future::block_on(async move { let (_worker, router, webrtc_transport, _sctp_data_producer) = init().await; let direct_transport = router .create_direct_transport(DirectTransportOptions::default()) .await .expect("Failed to create Direct transport"); let direct_data_producer = direct_transport .produce_data(DataProducerOptions::new_direct()) .await .expect("Failed to create data producer"); let data_consumer = webrtc_transport .consume_data(DataConsumerOptions::new_sctp_unordered_with_retransmits( direct_data_producer.id(), 2, )) .await .expect("Failed to consume data"); let dump = data_consumer .dump() .await .expect("Data consumer dump failed"); assert_eq!(dump.id, data_consumer.id()); assert_eq!(dump.data_producer_id, data_consumer.data_producer_id()); assert_eq!(dump.r#type, DataConsumerType::Sctp); { let sctp_stream_parameters = data_consumer.sctp_stream_parameters(); assert!(sctp_stream_parameters.is_some()); assert!(!sctp_stream_parameters.unwrap().ordered()); assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), Some(2)); } assert_eq!(dump.label.as_str(), ""); assert_eq!(dump.protocol.as_str(), ""); }); } #[test] fn consume_data_on_direct_transport_succeeds() { future::block_on(async move { let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await; let direct_transport = router .create_direct_transport(DirectTransportOptions::default()) .await .expect("Failed to create Direct transport"); let new_data_consumer_count = Arc::new(AtomicUsize::new(0)); direct_transport .on_new_data_consumer({ let new_data_consumer_count = Arc::clone(&new_data_consumer_count); Arc::new(move |_data_consumer| { new_data_consumer_count.fetch_add(1, Ordering::SeqCst); }) }) .detach(); let data_consumer = direct_transport .consume_data({ let mut options = DataConsumerOptions::new_direct(sctp_data_producer.id(), None); options.app_data = AppData::new(CustomAppData2 { hehe: "HEHE" }); options }) .await .expect("Failed to consume data"); assert_eq!(new_data_consumer_count.load(Ordering::SeqCst), 1); assert_eq!(data_consumer.data_producer_id(), sctp_data_producer.id()); assert!(!data_consumer.closed()); assert_eq!(data_consumer.r#type(), DataConsumerType::Direct); assert_eq!(data_consumer.sctp_stream_parameters(), None); assert_eq!(data_consumer.label().as_str(), "foo"); assert_eq!(data_consumer.protocol().as_str(), "bar"); assert_eq!( data_consumer .app_data() .downcast_ref::() .unwrap() .hehe, "HEHE", ); { let dump = direct_transport .dump() .await .expect("Failed to dump transport"); assert_eq!(dump.id, direct_transport.id()); assert_eq!(dump.data_producer_ids, vec![]); assert_eq!(dump.data_consumer_ids, vec![data_consumer.id()]); } }); } #[test] fn dump_on_direct_transport_succeeds() { future::block_on(async move { let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await; let direct_transport = router .create_direct_transport(DirectTransportOptions::default()) .await .expect("Failed to create Direct transport"); let data_consumer = direct_transport .consume_data({ let mut options = DataConsumerOptions::new_direct(sctp_data_producer.id(), None); options.app_data = AppData::new(CustomAppData2 { hehe: "HEHE" }); options }) .await .expect("Failed to consume data"); let dump = data_consumer .dump() .await .expect("Data consumer dump failed"); assert_eq!(dump.id, data_consumer.id()); assert_eq!(dump.data_producer_id, data_consumer.data_producer_id()); assert_eq!(dump.r#type, DataConsumerType::Direct); assert_eq!(dump.sctp_stream_parameters, None); assert_eq!(dump.label.as_str(), "foo"); assert_eq!(dump.protocol.as_str(), "bar"); }); } #[test] fn get_stats_on_direct_transport_succeeds() { future::block_on(async move { let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await; let direct_transport = router .create_direct_transport(DirectTransportOptions::default()) .await .expect("Failed to create Direct transport"); let data_consumer = direct_transport .consume_data({ let mut options = DataConsumerOptions::new_direct(sctp_data_producer.id(), None); options.app_data = AppData::new(CustomAppData2 { hehe: "HEHE" }); options }) .await .expect("Failed to consume data"); let stats = data_consumer .get_stats() .await .expect("Failed to get data consumer stats"); assert_eq!(stats.len(), 1); assert_eq!(&stats[0].label, data_consumer.label()); assert_eq!(&stats[0].protocol, data_consumer.protocol()); assert_eq!(stats[0].messages_sent, 0); assert_eq!(stats[0].bytes_sent, 0); }); } #[test] fn close_event() { future::block_on(async move { let (_worker, router, _webrtc_transport, sctp_data_producer) = init().await; let plain_transport = router .create_plain_transport({ let mut transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); let data_consumer = plain_transport .consume_data({ let mut options = DataConsumerOptions::new_sctp_unordered_with_life_time( sctp_data_producer.id(), 4000, ); options.app_data = AppData::new(CustomAppData { baz: "LOL" }); options }) .await .expect("Failed to consume data"); { let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = data_consumer.on_close(move || { let _ = tx.send(()); }); drop(data_consumer); rx.await.expect("Failed to receive close event"); } // Drop is async, give consumer a bit of time to finish Timer::after(Duration::from_millis(200)).await; { let dump = router.dump().await.expect("Failed to dump router"); assert_eq!(dump.map_data_producer_id_data_consumer_ids, { let mut map = HashedMap::default(); map.insert(sctp_data_producer.id(), HashedSet::default()); map }); assert_eq!( dump.map_data_consumer_id_data_producer_id, HashedMap::default() ); } { let dump = plain_transport .dump() .await .expect("Failed to dump transport"); assert_eq!(dump.data_producer_ids, vec![]); assert_eq!(dump.data_consumer_ids, vec![]); } }); } ================================================ FILE: rust/tests/integration/data_producer.rs ================================================ use async_io::Timer; use futures_lite::future; use hash_hasher::{HashedMap, HashedSet}; use mediasoup::data_producer::{DataProducerOptions, DataProducerType}; use mediasoup::plain_transport::{PlainTransport, PlainTransportOptions}; use mediasoup::prelude::*; use mediasoup::router::{Router, RouterOptions}; use mediasoup::transport::ProduceDataError; use mediasoup::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use mediasoup::worker::{RequestError, Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{AppData, ListenInfo, Protocol}; use mediasoup_types::sctp_parameters::SctpStreamParameters; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; #[derive(Debug, PartialEq)] struct CustomAppData { foo: u8, baz: &'static str, } async fn init() -> (Worker, Router, WebRtcTransport, PlainTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); let transport1 = router .create_webrtc_transport({ let mut transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); let transport2 = router .create_plain_transport({ let mut transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); transport_options.enable_sctp = true; transport_options }) .await .expect("Failed to create transport1"); (worker, router, transport1, transport2) } #[test] fn transport_1_produce_data_succeeds() { future::block_on(async move { let (_worker, router, transport1, _transport2) = init().await; let new_data_producer_count = Arc::new(AtomicUsize::new(0)); transport1 .on_new_data_producer({ let new_data_producer_count = Arc::clone(&new_data_producer_count); Arc::new(move |_data_producer| { new_data_producer_count.fetch_add(1, Ordering::SeqCst); }) }) .detach(); let data_producer1 = transport1 .produce_data({ let mut options = DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666)); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.app_data = AppData::new(CustomAppData { foo: 1, baz: "2" }); options }) .await .expect("Failed to produce data"); assert_eq!(new_data_producer_count.load(Ordering::SeqCst), 1); assert!(!data_producer1.closed()); assert_eq!(data_producer1.r#type(), DataProducerType::Sctp); { let sctp_stream_parameters = data_producer1.sctp_stream_parameters(); assert!(sctp_stream_parameters.is_some()); assert_eq!(sctp_stream_parameters.unwrap().stream_id(), 666); assert!(sctp_stream_parameters.unwrap().ordered()); assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None); } assert_eq!(data_producer1.label().as_str(), "foo"); assert_eq!(data_producer1.protocol().as_str(), "bar"); assert!(!data_producer1.paused()); assert_eq!( data_producer1 .app_data() .downcast_ref::() .unwrap(), &CustomAppData { foo: 1, baz: "2" }, ); { let dump = router.dump().await.expect("Failed to dump router"); assert_eq!(dump.map_data_producer_id_data_consumer_ids, { let mut map = HashedMap::default(); map.insert(data_producer1.id(), HashedSet::default()); map }); assert_eq!( dump.map_data_consumer_id_data_producer_id, HashedMap::default() ); } { let dump = transport1.dump().await.expect("Failed to dump transport"); assert_eq!(dump.id, transport1.id()); assert_eq!(dump.data_producer_ids, vec![data_producer1.id()]); assert_eq!(dump.data_consumer_ids, vec![]); } }); } #[test] fn transport_2_produce_data_succeeds() { future::block_on(async move { let (_worker, router, _transport1, transport2) = init().await; let new_data_producer_count = Arc::new(AtomicUsize::new(0)); transport2 .on_new_data_producer({ let new_data_producer_count = Arc::clone(&new_data_producer_count); Arc::new(move |_data_producer| { new_data_producer_count.fetch_add(1, Ordering::SeqCst); }) }) .detach(); let data_producer2 = transport2 .produce_data({ let mut options = DataProducerOptions::new_sctp( SctpStreamParameters::new_unordered_with_retransmits(777, 3), ); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.paused = true; options.app_data = AppData::new(CustomAppData { foo: 1, baz: "2" }); options }) .await .expect("Failed to produce data"); assert_eq!(new_data_producer_count.load(Ordering::SeqCst), 1); assert!(!data_producer2.closed()); assert_eq!(data_producer2.r#type(), DataProducerType::Sctp); { let sctp_stream_parameters = data_producer2.sctp_stream_parameters(); assert!(sctp_stream_parameters.is_some()); assert_eq!(sctp_stream_parameters.unwrap().stream_id(), 777); assert!(!sctp_stream_parameters.unwrap().ordered()); assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), Some(3)); } assert_eq!(data_producer2.label().as_str(), "foo"); assert_eq!(data_producer2.protocol().as_str(), "bar"); assert!(data_producer2.paused()); assert_eq!( data_producer2 .app_data() .downcast_ref::() .unwrap(), &CustomAppData { foo: 1, baz: "2" }, ); { let dump = router.dump().await.expect("Failed to dump router"); assert_eq!(dump.map_data_producer_id_data_consumer_ids, { let mut map = HashedMap::default(); map.insert(data_producer2.id(), HashedSet::default()); map }); assert_eq!( dump.map_data_consumer_id_data_producer_id, HashedMap::default() ); } { let dump = transport2.dump().await.expect("Failed to dump transport"); assert_eq!(dump.id, transport2.id()); assert_eq!(dump.data_producer_ids, vec![data_producer2.id()]); assert_eq!(dump.data_consumer_ids, vec![]); } }); } #[test] fn weak() { future::block_on(async move { let (_worker, _router, transport1, _transport2) = init().await; let data_producer = transport1 .produce_data({ let mut options = DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666)); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.app_data = AppData::new(CustomAppData { foo: 1, baz: "2" }); options }) .await .expect("Failed to produce data"); let weak_data_producer = data_producer.downgrade(); assert!(weak_data_producer.upgrade().is_some()); drop(data_producer); assert!(weak_data_producer.upgrade().is_none()); }); } #[test] fn produce_data_used_stream_id_rejects() { future::block_on(async move { let (_worker, _router, transport1, _transport2) = init().await; let _data_producer1 = transport1 .produce_data(DataProducerOptions::new_sctp( SctpStreamParameters::new_ordered(666), )) .await .expect("Failed to produce data"); assert!(matches!( transport1 .produce_data(DataProducerOptions::new_sctp( SctpStreamParameters::new_ordered(666), )) .await, Err(ProduceDataError::Request(RequestError::Response { .. })), )); }); } #[test] fn dump_succeeds() { future::block_on(async move { let (_worker, _router, transport1, transport2) = init().await; { let data_producer1 = transport1 .produce_data({ let mut options = DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666)); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.app_data = AppData::new(CustomAppData { foo: 1, baz: "2" }); options }) .await .expect("Failed to produce data"); let dump = data_producer1 .dump() .await .expect("Data producer dump failed"); assert_eq!(dump.id, data_producer1.id()); assert_eq!(dump.r#type, DataProducerType::Sctp); { let sctp_stream_parameters = dump.sctp_stream_parameters; assert!(sctp_stream_parameters.is_some()); assert_eq!(sctp_stream_parameters.unwrap().stream_id(), 666); assert!(sctp_stream_parameters.unwrap().ordered()); assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None); } assert_eq!(dump.label.as_str(), "foo"); assert_eq!(dump.protocol.as_str(), "bar"); assert!(!dump.paused); } { let data_producer2 = transport2 .produce_data({ let mut options = DataProducerOptions::new_sctp( SctpStreamParameters::new_unordered_with_retransmits(777, 3), ); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.paused = true; options.app_data = AppData::new(CustomAppData { foo: 1, baz: "2" }); options }) .await .expect("Failed to produce data"); let dump = data_producer2 .dump() .await .expect("Data producer dump failed"); assert_eq!(dump.id, data_producer2.id()); assert_eq!(dump.r#type, DataProducerType::Sctp); { let sctp_stream_parameters = dump.sctp_stream_parameters; assert!(sctp_stream_parameters.is_some()); assert_eq!(sctp_stream_parameters.unwrap().stream_id(), 777); assert!(!sctp_stream_parameters.unwrap().ordered()); assert_eq!(sctp_stream_parameters.unwrap().max_packet_life_time(), None); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), Some(3)); } assert_eq!(dump.label.as_str(), "foo"); assert_eq!(dump.protocol.as_str(), "bar"); assert!(dump.paused); } }); } #[test] fn get_stats_succeeds() { future::block_on(async move { let (_worker, _router, transport1, transport2) = init().await; { let data_producer1 = transport1 .produce_data({ let mut options = DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666)); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.app_data = AppData::new(CustomAppData { foo: 1, baz: "2" }); options }) .await .expect("Failed to produce data"); let stats = data_producer1 .get_stats() .await .expect("Failed to get data producer stats"); assert_eq!(stats.len(), 1); assert_eq!(&stats[0].label, data_producer1.label()); assert_eq!(&stats[0].protocol, data_producer1.protocol()); assert_eq!(stats[0].messages_received, 0); assert_eq!(stats[0].bytes_received, 0); } { let data_producer2 = transport2 .produce_data({ let mut options = DataProducerOptions::new_sctp( SctpStreamParameters::new_unordered_with_retransmits(777, 3), ); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.app_data = AppData::new(CustomAppData { foo: 1, baz: "2" }); options }) .await .expect("Failed to produce data"); let stats = data_producer2 .get_stats() .await .expect("Failed to get data producer stats"); assert_eq!(stats.len(), 1); assert_eq!(&stats[0].label, data_producer2.label()); assert_eq!(&stats[0].protocol, data_producer2.protocol()); assert_eq!(stats[0].messages_received, 0); assert_eq!(stats[0].bytes_received, 0); } }); } #[test] fn pause_and_resume_succeed() { future::block_on(async move { let (_worker, _router, transport1, _) = init().await; { let data_producer1 = transport1 .produce_data({ let mut options = DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666)); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.app_data = AppData::new(CustomAppData { foo: 1, baz: "2" }); options }) .await .expect("Failed to produce data"); { data_producer1 .pause() .await .expect("Failed to pause data producer"); assert!(data_producer1.paused()); let dump = data_producer1 .dump() .await .expect("Failed to dump data producer"); assert!(dump.paused); } { data_producer1 .resume() .await .expect("Failed to resume data producer"); assert!(!data_producer1.paused()); let dump = data_producer1 .dump() .await .expect("Failed to dump data producer"); assert!(!dump.paused); } } }); } #[test] fn close_event() { future::block_on(async move { let (_worker, router, transport1, _transport2) = init().await; let data_producer = transport1 .produce_data({ let mut options = DataProducerOptions::new_sctp(SctpStreamParameters::new_ordered(666)); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.app_data = AppData::new(CustomAppData { foo: 1, baz: "2" }); options }) .await .expect("Failed to produce data"); { let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = data_producer.on_close(move || { let _ = tx.send(()); }); drop(data_producer); rx.await.expect("Failed to receive close event"); } // Drop is async, give consumer a bit of time to finish Timer::after(Duration::from_millis(200)).await; { let dump = router.dump().await.expect("Failed to dump router"); assert_eq!( dump.map_data_producer_id_data_consumer_ids, HashedMap::default() ); assert_eq!( dump.map_data_consumer_id_data_producer_id, HashedMap::default() ); } { let dump = transport1.dump().await.expect("Failed to dump transport"); assert_eq!(dump.data_producer_ids, vec![]); assert_eq!(dump.data_consumer_ids, vec![]); } }); } ================================================ FILE: rust/tests/integration/direct_transport.rs ================================================ use futures_lite::future; use hash_hasher::HashedSet; use mediasoup::data_consumer::DataConsumerOptions; use mediasoup::data_producer::{DataProducer, DataProducerOptions}; use mediasoup::direct_transport::{DirectTransport, DirectTransportOptions}; use mediasoup::prelude::*; use mediasoup::router::{Router, RouterOptions}; use mediasoup::worker::{Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{AppData, WebRtcMessage}; use parking_lot::Mutex; use std::borrow::Cow; use std::env; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; struct CustomAppData { foo: &'static str, } async fn init() -> (Worker, Router, DirectTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::default()) .await .expect("Failed to create router"); let transport = router .create_direct_transport(DirectTransportOptions::default()) .await .expect("Failed to create transport1"); (worker, router, transport) } #[test] fn create_succeeds() { future::block_on(async move { let (_worker, router, transport) = init().await; { let dump = router.dump().await.expect("Failed to dump router"); assert_eq!(dump.transport_ids, { let mut set = HashedSet::default(); set.insert(transport.id()); set }); } let new_transports_count = Arc::new(AtomicUsize::new(0)); router .on_new_transport({ let new_transports_count = Arc::clone(&new_transports_count); move |_transport| { new_transports_count.fetch_add(1, Ordering::SeqCst); } }) .detach(); let transport1 = router .create_direct_transport({ let mut direct_transport_options = DirectTransportOptions::default(); direct_transport_options.max_message_size = 1024; direct_transport_options.app_data = AppData::new(CustomAppData { foo: "bar" }); direct_transport_options }) .await .expect("Failed to create Direct transport"); assert_eq!(new_transports_count.load(Ordering::SeqCst), 1); assert!(!transport1.closed()); assert_eq!( transport1 .app_data() .downcast_ref::() .unwrap() .foo, "bar", ); { let dump = transport1.dump().await.expect("Failed to dump transport"); assert_eq!(dump.id, transport1.id()); assert!(dump.direct); assert_eq!(dump.producer_ids, vec![]); assert_eq!(dump.consumer_ids, vec![]); assert_eq!(dump.data_producer_ids, vec![]); assert_eq!(dump.data_consumer_ids, vec![]); } }); } #[test] fn weak() { future::block_on(async move { let (_worker, _router, transport) = init().await; let weak_transport = transport.downgrade(); assert!(weak_transport.upgrade().is_some()); drop(transport); assert!(weak_transport.upgrade().is_none()); }); } #[test] fn get_stats_succeeds() { future::block_on(async move { let (_worker, _router, transport) = init().await; let stats = transport .get_stats() .await .expect("Failed to get stats on Direct transport"); assert_eq!(stats.len(), 1); assert_eq!(stats[0].transport_id, transport.id()); assert_eq!(stats[0].bytes_received, 0); assert_eq!(stats[0].recv_bitrate, 0); assert_eq!(stats[0].bytes_sent, 0); assert_eq!(stats[0].send_bitrate, 0); assert_eq!(stats[0].rtp_bytes_received, 0); assert_eq!(stats[0].rtp_recv_bitrate, 0); assert_eq!(stats[0].rtp_bytes_sent, 0); assert_eq!(stats[0].rtp_send_bitrate, 0); assert_eq!(stats[0].rtx_bytes_received, 0); assert_eq!(stats[0].rtx_recv_bitrate, 0); assert_eq!(stats[0].rtx_bytes_sent, 0); assert_eq!(stats[0].rtx_send_bitrate, 0); assert_eq!(stats[0].probation_bytes_sent, 0); assert_eq!(stats[0].probation_send_bitrate, 0); assert_eq!(stats[0].rtp_packet_loss_received, None); assert_eq!(stats[0].rtp_packet_loss_sent, None); }); } #[test] fn send_succeeds() { future::block_on(async move { let (_worker, _router, transport) = init().await; let data_producer = transport .produce_data({ let mut options = DataProducerOptions::new_direct(); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.app_data = AppData::new(CustomAppData { foo: "bar" }); options }) .await .expect("Failed to produce data"); let data_consumer = transport .consume_data(DataConsumerOptions::new_direct(data_producer.id(), None)) .await .expect("Failed to consume data"); let num_messages = 200_usize; let pause_sending_at_message = 10_usize; let resume_sending_at_message = 20_usize; let pause_receiving_at_message = 40_usize; let resume_receiving_at_message = 60_usize; let expected_received_num_messages = num_messages - (resume_sending_at_message - pause_sending_at_message) - (resume_receiving_at_message - pause_receiving_at_message); let mut sent_message_bytes = 0_usize; let mut effectively_sent_message_bytes = 0_usize; let recv_message_bytes = Arc::new(AtomicUsize::new(0)); let mut last_sent_message_id = 0_usize; let last_recv_message_id = Arc::new(AtomicUsize::new(0)); let (received_messages_tx, received_messages_rx) = async_oneshot::oneshot::<()>(); let _handler = data_consumer.on_message({ let received_messages_tx = Mutex::new(Some(received_messages_tx)); let recv_message_bytes = Arc::clone(&recv_message_bytes); let last_recv_message_id = Arc::clone(&last_recv_message_id); move |message| { let id: usize = match message { WebRtcMessage::String(binary) => { recv_message_bytes.fetch_add(binary.len(), Ordering::SeqCst); String::from_utf8(binary.to_vec()).unwrap().parse().unwrap() } WebRtcMessage::Binary(binary) => { recv_message_bytes.fetch_add(binary.len(), Ordering::SeqCst); String::from_utf8(binary.to_vec()).unwrap().parse().unwrap() } WebRtcMessage::EmptyString => { panic!("Unexpected empty message!"); } WebRtcMessage::EmptyBinary => { panic!("Unexpected empty message!"); } }; if id < num_messages / 2 { assert!(matches!(message, &WebRtcMessage::String(_))); } else { assert!(matches!(message, &WebRtcMessage::Binary(_))); } last_recv_message_id.fetch_add(1, Ordering::SeqCst); if id == num_messages { let _ = received_messages_tx.lock().take().unwrap().send(()); } } }); let direct_data_producer = match &data_producer { DataProducer::Direct(direct_data_producer) => direct_data_producer, _ => { panic!("Expected direct data producer") } }; loop { last_sent_message_id += 1; let id = last_sent_message_id; if id == pause_sending_at_message { data_producer .pause() .await .expect("Failed to pause data producer"); } else if id == resume_sending_at_message { data_producer .resume() .await .expect("Failed to resume data producer"); } else if id == pause_receiving_at_message { data_consumer .pause() .await .expect("Failed to pause data consumer"); } else if id == resume_receiving_at_message { data_consumer .resume() .await .expect("Failed to resume data consumer"); } let message = if id < num_messages / 2 { let content = id.to_string().into_bytes(); sent_message_bytes += content.len(); if !data_producer.paused() && !data_consumer.paused() { effectively_sent_message_bytes += content.len(); } WebRtcMessage::String(Cow::from(content)) } else { let content = id.to_string().into_bytes(); sent_message_bytes += content.len(); if !data_producer.paused() && !data_consumer.paused() { effectively_sent_message_bytes += content.len(); } WebRtcMessage::Binary(Cow::from(content)) }; direct_data_producer .send(message, None, None) .expect("Failed to send message"); if id == num_messages { break; } } received_messages_rx .await .expect("Failed tor receive all messages"); assert_eq!(last_sent_message_id, num_messages); assert_eq!( last_recv_message_id.load(Ordering::SeqCst), expected_received_num_messages ); assert_eq!( recv_message_bytes.load(Ordering::SeqCst), effectively_sent_message_bytes, ); { let stats = data_producer .get_stats() .await .expect("Failed to get stats on data producer"); assert_eq!(stats.len(), 1); assert_eq!(&stats[0].label, data_producer.label()); assert_eq!(&stats[0].protocol, data_producer.protocol()); assert_eq!(stats[0].messages_received, num_messages as u64); assert_eq!(stats[0].bytes_received, sent_message_bytes as u64); } { let stats = data_consumer .get_stats() .await .expect("Failed to get stats on data consumer"); assert_eq!(stats.len(), 1); assert_eq!(&stats[0].label, data_consumer.label()); assert_eq!(&stats[0].protocol, data_consumer.protocol()); assert_eq!( stats[0].messages_sent, expected_received_num_messages as u64 ); assert_eq!( stats[0].bytes_sent, recv_message_bytes.load(Ordering::SeqCst) as u64, ); } }); } #[test] fn send_with_subchannels_succeeds() { future::block_on(async move { let (_worker, _router, transport) = init().await; let data_producer = transport .produce_data({ let mut options = DataProducerOptions::new_direct(); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options.app_data = AppData::new(CustomAppData { foo: "bar" }); options }) .await .expect("Failed to produce data"); let data_consumer_1 = transport .consume_data(DataConsumerOptions::new_direct( data_producer.id(), Some(vec![1, 11, 666]), )) .await .expect("Failed to consume data"); let data_consumer_2 = transport .consume_data(DataConsumerOptions::new_direct( data_producer.id(), Some(vec![2, 22, 666]), )) .await .expect("Failed to consume data"); let expected_received_num_messages_1 = 7; let expected_received_num_messages_2 = 5; let received_messages_1: Arc>> = Arc::new(Mutex::new(vec![])); let received_messages_2: Arc>> = Arc::new(Mutex::new(vec![])); let (received_messages_tx_1, received_messages_rx_1) = async_oneshot::oneshot::<()>(); let _handler_1 = data_consumer_1.on_message({ let received_messages_tx_1 = Mutex::new(Some(received_messages_tx_1)); let received_messages_1 = Arc::clone(&received_messages_1); move |message| { let text: String = match message { WebRtcMessage::String(binary) => String::from_utf8(binary.to_vec()).unwrap(), _ => { panic!("Unexpected empty message!"); } }; received_messages_1.lock().push(text); if received_messages_1.lock().len() == expected_received_num_messages_1 { let _ = received_messages_tx_1.lock().take().unwrap().send(()); } } }); let (received_messages_tx_2, received_messages_rx_2) = async_oneshot::oneshot::<()>(); let _handler_2 = data_consumer_2.on_message({ let received_messages_tx_2 = Mutex::new(Some(received_messages_tx_2)); let received_messages_2 = Arc::clone(&received_messages_2); move |message| { let text: String = match message { WebRtcMessage::String(binary) => String::from_utf8(binary.to_vec()).unwrap(), _ => { panic!("Unexpected empty message!"); } }; received_messages_2.lock().push(text); if received_messages_2.lock().len() == expected_received_num_messages_2 { let _ = received_messages_tx_2.lock().take().unwrap().send(()); } } }); let direct_data_producer = match &data_producer { DataProducer::Direct(direct_data_producer) => direct_data_producer, _ => { panic!("Expected direct data producer") } }; let _ = match &data_consumer_2 { DataConsumer::Direct(direct_data_consumer) => direct_data_consumer, _ => { panic!("Expected direct data consumer") } }; let both: &'static str = "both"; let none: &'static str = "none"; let dc1: &'static str = "dc1"; let dc2: &'static str = "dc2"; // Must be received by dataConsumer1 and dataConsumer2. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(both.as_bytes())), None, None, ) .expect("Failed to send message"); // Must be received by dataConsumer1 and dataConsumer2. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(both.as_bytes())), Some(vec![1, 2]), None, ) .expect("Failed to send message"); // Must be received by dataConsumer1 and dataConsumer2. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(both.as_bytes())), Some(vec![11, 22, 33]), Some(666), ) .expect("Failed to send message"); // Must not be received by neither dataConsumer1 nor dataConsumer2. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(none.as_bytes())), Some(vec![3]), Some(666), ) .expect("Failed to send message"); // Must not be received by neither dataConsumer1 nor dataConsumer2. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(none.as_bytes())), Some(vec![666]), Some(3), ) .expect("Failed to send message"); // Must be received by dataConsumer1. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(dc1.as_bytes())), Some(vec![1]), None, ) .expect("Failed to send message"); // Must be received by dataConsumer1. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(dc1.as_bytes())), Some(vec![11]), None, ) .expect("Failed to send message"); // Must be received by dataConsumer1. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(dc1.as_bytes())), Some(vec![666]), Some(11), ) .expect("Failed to send message"); // Must be received by dataConsumer2. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(dc2.as_bytes())), Some(vec![666]), Some(2), ) .expect("Failed to send message"); let mut subchannels = data_consumer_2.subchannels(); subchannels.push(1); data_consumer_2 .set_subchannels(subchannels) .await .expect("Failed to set subchannels"); // Must be received by dataConsumer2. direct_data_producer .send( WebRtcMessage::String(Cow::Borrowed(both.as_bytes())), Some(vec![1]), Some(666), ) .expect("Failed to send message"); received_messages_rx_1 .await .expect("Failed tor receive all messages"); received_messages_rx_2 .await .expect("Failed tor receive all messages"); let received_messages_1 = received_messages_1.lock().clone(); assert!(received_messages_1.contains(&both.to_string())); assert!(received_messages_1.contains(&dc1.to_string())); assert!(!received_messages_1.contains(&dc2.to_string())); let received_messages_2 = received_messages_2.lock().clone(); assert!(received_messages_2.contains(&both.to_string())); assert!(!received_messages_2.contains(&dc1.to_string())); assert!(received_messages_2.contains(&dc2.to_string())); }); } #[test] fn close_event() { future::block_on(async move { let (_worker, _router, transport) = init().await; let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_close(Box::new(move || { let _ = close_tx.send(()); })); drop(transport); close_rx.await.expect("Failed to receive close event"); }); } ================================================ FILE: rust/tests/integration/main.rs ================================================ mod active_speaker_observer; mod audio_level_observer; mod consumer; mod data_consumer; mod data_producer; mod direct_transport; mod multiopus; mod pipe_transport; mod plain_transport; mod producer; mod router; mod smoke; mod webrtc_server; mod webrtc_transport; mod worker; ================================================ FILE: rust/tests/integration/multiopus.rs ================================================ use futures_lite::future; use mediasoup::prelude::*; use mediasoup::producer::ProducerOptions; use mediasoup::router::{Router, RouterOptions}; use mediasoup::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use mediasoup::worker::WorkerSettings; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{ListenInfo, Protocol}; use mediasoup_types::rtp_parameters::{ MediaKind, MimeTypeAudio, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters, RtpHeaderExtension, RtpHeaderExtensionDirection, RtpHeaderExtensionParameters, RtpHeaderExtensionUri, RtpParameters, }; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; fn media_codecs() -> Vec { vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("channel_mapping", "0,4,1,2,3,5".into()), ("num_streams", 4_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![], }] } fn audio_producer_options() -> ProducerOptions { ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, payload_type: 0, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("channel_mapping", "0,4,1,2,3,5".into()), ("num_streams", 4_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![], }], header_extensions: vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::SsrcAudioLevel, id: 12, encrypt: false, }, ], ..RtpParameters::default() }, ) } fn consumer_device_capabilities() -> RtpCapabilities { RtpCapabilities { codecs: vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, preferred_payload_type: Some(100), clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), parameters: RtpCodecParametersParameters::from([ ("channel_mapping", "0,4,1,2,3,5".into()), ("num_streams", 4_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![], }], header_extensions: vec![ RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::Mid, preferred_id: 1, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::AbsSendTime, preferred_id: 4, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::SsrcAudioLevel, preferred_id: 10, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, ], } } async fn init() -> (Router, WebRtcTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); let transport = router .create_webrtc_transport(transport_options.clone()) .await .expect("Failed to create transport"); (router, transport) } #[test] fn produce_and_consume_succeeds() { future::block_on(async move { let (router, transport) = init().await; let audio_producer = transport .produce(audio_producer_options()) .await .expect("Failed to produce audio"); assert_eq!( audio_producer.rtp_parameters().codecs, vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, payload_type: 0, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("channel_mapping", "0,4,1,2,3,5".into()), ("num_streams", 4_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![], }] ); let consumer_device_capabilities = consumer_device_capabilities(); assert!(router.can_consume(&audio_producer.id(), &consumer_device_capabilities)); let audio_consumer = transport .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities.clone(), )) .await .expect("Failed to consume audio"); assert_eq!( audio_consumer.rtp_parameters().codecs, vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, payload_type: 100, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("channel_mapping", "0,4,1,2,3,5".into()), ("num_streams", 4_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![], }] ); }); } #[test] fn fails_to_consume_wrong_parameters() { future::block_on(async move { let (_router, transport) = init().await; assert!(transport .produce(ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, payload_type: 0, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), parameters: RtpCodecParametersParameters::from([ ("channel_mapping", "0,4,1,2,3,5".into()), ("num_streams", 2_u32.into()), ("coupled_streams", 2_u32.into()), ]), rtcp_feedback: vec![], }], ..RtpParameters::default() }, )) .await .is_err()); assert!(transport .produce(ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, payload_type: 0, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(6).unwrap(), parameters: RtpCodecParametersParameters::from([ ("channel_mapping", "0,4,1,2,3,5".into()), ("num_streams", 4_u32.into()), ("coupled_streams", 1_u32.into()), ]), rtcp_feedback: vec![], }], ..RtpParameters::default() }, )) .await .is_err()); }); } #[test] fn fails_to_consume_wrong_channels() { future::block_on(async move { let (router, transport) = init().await; let audio_producer = transport .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let consumer_device_capabilities = RtpCapabilities { codecs: vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::MultiChannelOpus, preferred_payload_type: Some(100), clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(8).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }], header_extensions: Vec::new(), }; assert!(!router.can_consume(&audio_producer.id(), &consumer_device_capabilities)); let audio_consumer_result = transport .consume(ConsumerOptions::new( audio_producer.id(), consumer_device_capabilities.clone(), )) .await; assert!(audio_consumer_result.is_err()); }); } ================================================ FILE: rust/tests/integration/pipe_transport.rs ================================================ use futures_lite::future; use mediasoup::consumer::{ConsumerOptions, ConsumerScore, ConsumerType}; use mediasoup::data_consumer::{DataConsumerOptions, DataConsumerType}; use mediasoup::data_producer::{DataProducerOptions, DataProducerType}; use mediasoup::pipe_transport::{PipeTransportOptions, PipeTransportRemoteParameters}; use mediasoup::prelude::*; use mediasoup::producer::ProducerOptions; use mediasoup::router::{ PipeDataProducerToRouterPair, PipeProducerToRouterPair, PipeToRouterOptions, Router, RouterOptions, }; use mediasoup::transport::ProduceError; use mediasoup::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use mediasoup::worker::{RequestError, Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{AppData, ListenInfo, Protocol}; use mediasoup_types::rtp_parameters::{ MediaKind, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters, RtpCapabilities, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters, RtpEncodingParameters, RtpHeaderExtension, RtpHeaderExtensionDirection, RtpHeaderExtensionParameters, RtpHeaderExtensionUri, RtpParameters, }; use mediasoup_types::sctp_parameters::SctpStreamParameters; use mediasoup_types::srtp_parameters::{SrtpCryptoSuite, SrtpParameters}; use parking_lot::Mutex; use portpicker::pick_unused_port; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; struct CustomAppData { _foo: &'static str, } fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, ] } fn audio_producer_options() -> ProducerOptions { let mut options = ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 111, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar1".into()), ]), rtcp_feedback: vec![], }], header_extensions: vec![RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 10, encrypt: false, }], encodings: vec![RtpEncodingParameters { ssrc: Some(11111111), ..RtpEncodingParameters::default() }], rtcp: RtcpParameters { cname: Some("FOOBAR".to_string()), ..RtcpParameters::default() }, msid: None, }, ); options.app_data = AppData::new(CustomAppData { _foo: "bar1" }); options } fn video_producer_options() -> ProducerOptions { let mut options = ProducerOptions::new( MediaKind::Video, RtpParameters { mid: Some("VIDEO".to_string()), codecs: vec![RtpCodecParameters::Video { mime_type: MimeTypeVideo::Vp8, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::GoogRemb, ], }], header_extensions: vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::AbsSendTime, id: 11, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 13, encrypt: false, }, ], encodings: vec![ RtpEncodingParameters { ssrc: Some(22222222), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(22222223), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(22222224), ..RtpEncodingParameters::default() }, ], rtcp: RtcpParameters { cname: Some("FOOBAR".to_string()), ..RtcpParameters::default() }, msid: Some("aaaa-bbbb".to_string()), }, ); options.app_data = AppData::new(CustomAppData { _foo: "bar2" }); options } fn data_producer_options() -> DataProducerOptions { let mut options = DataProducerOptions::new_sctp( SctpStreamParameters::new_unordered_with_life_time(666, 5000), ); options.label = "foo".to_string(); options.protocol = "bar".to_string(); options } fn consumer_device_capabilities() -> RtpCapabilities { RtpCapabilities { codecs: vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: Some(100), clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: Some(101), clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::CcmFir, RtcpFeedback::TransportCc, ], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Rtx, preferred_payload_type: Some(102), clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 101_u32.into())]), rtcp_feedback: vec![], }, ], header_extensions: vec![ RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::AbsSendTime, preferred_id: 4, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::SendRecv, }, RtpHeaderExtension { kind: MediaKind::Video, uri: RtpHeaderExtensionUri::TransportWideCcDraft01, preferred_id: 5, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, RtpHeaderExtension { kind: MediaKind::Audio, uri: RtpHeaderExtensionUri::SsrcAudioLevel, preferred_id: 6, preferred_encrypt: false, direction: RtpHeaderExtensionDirection::default(), }, ], } } async fn init() -> ( Worker, Worker, Router, Router, WebRtcTransport, WebRtcTransport, ) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker1 = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let worker2 = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router1 = worker1 .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let router2 = worker2 .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let mut transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); transport_options.enable_sctp = true; let transport_1 = router1 .create_webrtc_transport(transport_options.clone()) .await .expect("Failed to create transport1"); let transport_2 = router2 .create_webrtc_transport(transport_options) .await .expect("Failed to create transport2"); (worker1, worker2, router1, router2, transport_1, transport_2) } #[test] fn pipe_to_router_succeeds_with_audio() { future::block_on(async move { let (_worker1, _worker2, router1, router2, transport1, _transport2) = init().await; let audio_producer = transport1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let PipeProducerToRouterPair { pipe_producer, pipe_consumer, } = router1 .pipe_producer_to_router( audio_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .expect("Failed to pipe audio producer to router"); let pipe_producer = pipe_producer.into_inner(); { let dump = router1.dump().await.expect("Failed to dump router"); // There should be two Transports in router1: // - WebRtcTransport for audio_producer. // - PipeTransport between router1 and router2. assert_eq!(dump.transport_ids.len(), 2); } { let dump = router2.dump().await.expect("Failed to dump router"); // There should be two Transports in router2: // - WebRtcTransport for audio_consumer. // - pipeTransport between router2 and router1. assert_eq!(dump.transport_ids.len(), 2); } assert_eq!(pipe_consumer.kind(), MediaKind::Audio); assert_eq!(pipe_consumer.rtp_parameters().mid, None); assert_eq!( pipe_consumer.rtp_parameters().codecs, vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 100, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar1".into()), ]), rtcp_feedback: vec![], }], ); assert_eq!( pipe_consumer.rtp_parameters().header_extensions, vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::SsrcAudioLevel, id: 6, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::DependencyDescriptor, id: 7, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::AbsCaptureTime, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::PlayoutDelay, id: 11, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::MediasoupPacketId, id: 12, encrypt: false, }, ], ); assert_eq!(pipe_consumer.r#type(), ConsumerType::Pipe); assert!(!pipe_consumer.paused()); assert!(!pipe_consumer.producer_paused()); assert_eq!( pipe_consumer.score(), ConsumerScore { score: 10, producer_score: 10, producer_scores: vec![0], }, ); assert_eq!(pipe_consumer.app_data().downcast_ref::<()>().unwrap(), &()); assert_eq!(pipe_producer.id(), audio_producer.id()); assert_eq!(pipe_producer.kind(), MediaKind::Audio); assert_eq!(pipe_producer.rtp_parameters().mid, None); assert_eq!( pipe_producer.rtp_parameters().codecs, vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 100, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar1".into()), ]), rtcp_feedback: vec![], }], ); assert_eq!( pipe_producer.rtp_parameters().header_extensions, vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::SsrcAudioLevel, id: 6, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::DependencyDescriptor, id: 7, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::AbsCaptureTime, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::PlayoutDelay, id: 11, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::MediasoupPacketId, id: 12, encrypt: false, }, ], ); assert!(!pipe_producer.paused()); }); } #[test] fn pipe_to_router_succeeds_with_video() { future::block_on(async move { let (_worker1, _worker2, router1, router2, transport1, _transport2) = init().await; let audio_producer = transport1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); router1 .pipe_producer_to_router( audio_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .expect("Failed to pipe audio producer to router"); let video_producer = transport1 .produce(video_producer_options()) .await .expect("Failed to produce video"); // Pause the videoProducer. video_producer .pause() .await .expect("Failed to pause video producer"); let PipeProducerToRouterPair { pipe_producer, pipe_consumer, } = router1 .pipe_producer_to_router( video_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .expect("Failed to pipe video producer to router"); let pipe_producer = pipe_producer.into_inner(); { let dump = router1.dump().await.expect("Failed to dump router"); // No new PipeTransport should has been created. The existing one is used. assert_eq!(dump.transport_ids.len(), 2); } { let dump = router2.dump().await.expect("Failed to dump router"); // No new PipeTransport should has been created. The existing one is used. assert_eq!(dump.transport_ids.len(), 2); } assert_eq!(pipe_consumer.kind(), MediaKind::Video); assert_eq!(pipe_consumer.rtp_parameters().mid, None); assert_eq!( pipe_consumer.rtp_parameters().codecs, vec![RtpCodecParameters::Video { mime_type: MimeTypeVideo::Vp8, payload_type: 101, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::NackPli, RtcpFeedback::CcmFir], }], ); assert_eq!( pipe_consumer.rtp_parameters().header_extensions, vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::DependencyDescriptor, id: 7, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 8, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::TimeOffset, id: 9, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::AbsCaptureTime, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::PlayoutDelay, id: 11, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::MediasoupPacketId, id: 12, encrypt: false, }, ], ); assert_eq!(pipe_consumer.r#type(), ConsumerType::Pipe); assert!(!pipe_consumer.paused()); assert!(pipe_consumer.producer_paused()); assert_eq!( pipe_consumer.score(), ConsumerScore { score: 10, producer_score: 10, producer_scores: vec![0, 0, 0], }, ); assert_eq!(pipe_consumer.app_data().downcast_ref::<()>().unwrap(), &()); assert_eq!(pipe_producer.id(), video_producer.id()); assert_eq!(pipe_producer.kind(), MediaKind::Video); assert_eq!(pipe_producer.rtp_parameters().mid, None); assert_eq!( pipe_consumer.rtp_parameters().codecs, vec![RtpCodecParameters::Video { mime_type: MimeTypeVideo::Vp8, payload_type: 101, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![RtcpFeedback::NackPli, RtcpFeedback::CcmFir], }], ); assert_eq!( pipe_consumer.rtp_parameters().header_extensions, vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::DependencyDescriptor, id: 7, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 8, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::TimeOffset, id: 9, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::AbsCaptureTime, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::PlayoutDelay, id: 11, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::MediasoupPacketId, id: 12, encrypt: false, }, ], ); assert!(pipe_producer.paused()); }); } #[test] fn pipe_to_router_with_keep_id_true_fails_if_both_routers_belong_to_the_same_worker() { future::block_on(async move { let (worker1, _worker2, router1, _router2, transport1, _transport2) = init().await; let router1bis = worker1 .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let video_producer = transport1 .produce(video_producer_options()) .await .expect("Failed to produce video"); let result = router1 .pipe_producer_to_router( video_producer.id(), PipeToRouterOptions::new(router1bis.clone()), ) .await; if let Err(PipeProducerToRouterError::ProduceFailed(ProduceError::Request( RequestError::Response { reason }, ))) = result { assert!(reason.contains("already exists [method:transport.produce]")); } else { panic!("Unexpected result: {result:?}"); } }); } #[test] fn pipe_to_router_with_keep_id_false_does_not_fail_if_both_routers_belong_to_the_same_worker() { future::block_on(async move { let (worker1, _worker2, router1, _router2, transport1, _transport2) = init().await; let router1bis = worker1 .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let video_producer = transport1 .produce(video_producer_options()) .await .expect("Failed to produce video"); let PipeProducerToRouterPair { pipe_producer, pipe_consumer: _, } = router1 .pipe_producer_to_router(video_producer.id(), { let mut options = PipeToRouterOptions::new(router1bis.clone()); options.keep_id = false; options }) .await .expect("Failed to pipe producer to router"); let pipe_producer = pipe_producer.into_inner(); assert_ne!(pipe_producer.id(), video_producer.id()); }); } #[test] fn weak() { future::block_on(async move { let (_worker1, _worker2, router1, _router2, _transport1, _transport2) = init().await; let pipe_transport = router1 .create_pipe_transport({ let mut options = PipeTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); options.enable_rtx = true; options }) .await .expect("Failed to create Pipe transport"); let weak_pipe_transport = pipe_transport.downgrade(); assert!(weak_pipe_transport.upgrade().is_some()); drop(pipe_transport); assert!(weak_pipe_transport.upgrade().is_none()); }); } #[test] fn create_with_fixed_port_succeeds() { future::block_on(async move { let (_worker1, _worker2, router1, _router2, _transport1, _transport2) = init().await; let port = pick_unused_port().unwrap(); let pipe_transport = router1 .create_pipe_transport({ PipeTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }) }) .await .expect("Failed to create Pipe transport"); assert_eq!(pipe_transport.tuple().local_port(), port); }); } #[test] fn create_with_enable_rtx_succeeds() { future::block_on(async move { let (_worker1, _worker2, router1, _router2, transport1, _transport2) = init().await; let pipe_transport = router1 .create_pipe_transport({ let mut options = PipeTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); options.enable_rtx = true; options }) .await .expect("Failed to create Pipe transport"); let video_producer = transport1 .produce(video_producer_options()) .await .expect("Failed to produce video"); // Pause the videoProducer. video_producer .pause() .await .expect("Failed to pause video producer"); let pipe_consumer = pipe_transport .consume(ConsumerOptions::new( video_producer.id(), RtpCapabilities::default(), )) .await .expect("Failed to create pipe consumer"); assert_eq!(pipe_consumer.kind(), MediaKind::Video); assert_eq!(pipe_consumer.rtp_parameters().mid, None); assert_eq!( pipe_consumer.rtp_parameters().codecs, vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::Vp8, payload_type: 101, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::CcmFir ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 102, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 101_u32.into())]), rtcp_feedback: vec![], }, ], ); assert_eq!( pipe_consumer.rtp_parameters().header_extensions, vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::DependencyDescriptor, id: 7, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 8, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::TimeOffset, id: 9, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::AbsCaptureTime, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::PlayoutDelay, id: 11, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::MediasoupPacketId, id: 12, encrypt: false, }, ], ); assert_eq!(pipe_consumer.r#type(), ConsumerType::Pipe); assert!(!pipe_consumer.paused()); assert!(pipe_consumer.producer_paused()); assert_eq!( pipe_consumer.score(), ConsumerScore { score: 10, producer_score: 10, producer_scores: vec![0, 0, 0], }, ); assert_eq!(pipe_consumer.app_data().downcast_ref::<()>().unwrap(), &()); }); } #[test] fn create_with_enable_srtp_succeeds() { future::block_on(async move { let (_worker1, _worker2, router1, _router2, _transport1, _transport2) = init().await; let pipe_transport = router1 .create_pipe_transport({ let mut options = PipeTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); options.enable_srtp = true; options }) .await .expect("Failed to create Pipe transport"); assert!(pipe_transport.srtp_parameters().is_some()); assert_eq!( pipe_transport.srtp_parameters().unwrap().key_base64.len(), 60 ); // Missing srtp_parameters. assert!(matches!( pipe_transport .connect(PipeTransportRemoteParameters { ip: "127.0.0.2".parse().unwrap(), port: 9999, srtp_parameters: None, }) .await, Err(RequestError::Response { .. }), )); // Valid srtp_parameters. pipe_transport .connect(PipeTransportRemoteParameters { ip: "127.0.0.2".parse().unwrap(), port: 9999, srtp_parameters: Some(SrtpParameters { crypto_suite: SrtpCryptoSuite::AeadAes256Gcm, key_base64: "YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=" .to_string(), }), }) .await .expect("Failed to establish Pipe transport connection"); }); } #[test] fn create_with_invalid_srtp_parameters_fails() { future::block_on(async move { let (_worker1, _worker2, router1, _router2, _transport1, _transport2) = init().await; let pipe_transport = router1 .create_pipe_transport(PipeTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })) .await .expect("Failed to create Pipe transport"); // No SRTP enabled so passing srtp_parameters must fail. assert!(matches!( pipe_transport .connect(PipeTransportRemoteParameters { ip: "127.0.0.2".parse().unwrap(), port: 9999, srtp_parameters: Some(SrtpParameters { crypto_suite: SrtpCryptoSuite::AeadAes256Gcm, key_base64: "YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=" .to_string(), }), }) .await, Err(RequestError::Response { .. }), )); }); } #[test] fn consume_for_pipe_producer_succeeds() { future::block_on(async move { let (_worker1, _worker2, router1, router2, transport1, transport2) = init().await; let video_producer = transport1 .produce(video_producer_options()) .await .expect("Failed to produce video"); // Pause the videoProducer. video_producer .pause() .await .expect("Failed to pause video producer"); let PipeProducerToRouterPair { pipe_producer, .. } = router1 .pipe_producer_to_router( video_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .expect("Failed to pipe video producer to router"); let pipe_video_producer = pipe_producer.into_inner(); let video_consumer = transport2 .consume(ConsumerOptions::new( pipe_video_producer.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume video"); assert_eq!(video_consumer.kind(), MediaKind::Video); assert_eq!(video_consumer.rtp_parameters().mid, Some("0".to_string())); assert_eq!( video_consumer.rtp_parameters().codecs, vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::Vp8, payload_type: 101, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::CcmFir, RtcpFeedback::TransportCc, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 102, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 101_u32.into())]), rtcp_feedback: vec![], }, ], ); assert_eq!( video_consumer.rtp_parameters().header_extensions, vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::AbsSendTime, id: 4, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::TransportWideCcDraft01, id: 5, encrypt: false, }, ], ); assert_eq!(video_consumer.rtp_parameters().encodings.len(), 1); assert!(video_consumer.rtp_parameters().encodings[0].ssrc.is_some()); assert!(video_consumer.rtp_parameters().encodings[0].rtx.is_some()); assert_eq!( video_consumer.rtp_parameters().msid, Some("aaaa-bbbb".to_string()) ); assert_eq!(video_consumer.r#type(), ConsumerType::Simulcast); assert!(!video_consumer.paused()); assert!(video_consumer.producer_paused()); assert_eq!( video_consumer.score(), ConsumerScore { score: 10, producer_score: 0, producer_scores: vec![0, 0, 0], }, ); assert_eq!(video_consumer.app_data().downcast_ref::<()>().unwrap(), &()); }); } #[test] fn producer_pause_resume_are_transmitted_to_pipe_consumer() { future::block_on(async move { let (_worker1, _worker2, router1, router2, transport1, transport2) = init().await; let video_producer = transport1 .produce(video_producer_options()) .await .expect("Failed to produce video"); // Pause the videoProducer. video_producer .pause() .await .expect("Failed to pause video producer"); let PipeProducerToRouterPair { pipe_producer, pipe_consumer: _, } = router1 .pipe_producer_to_router( video_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .expect("Failed to pipe video producer to router"); let pipe_video_producer = pipe_producer.into_inner(); let video_consumer = transport2 .consume(ConsumerOptions::new( pipe_video_producer.id(), consumer_device_capabilities(), )) .await .expect("Failed to consume video"); assert!(video_producer.paused()); assert!(video_consumer.producer_paused()); assert!(!video_consumer.paused()); let (producer_resume_tx, producer_resume_rx) = async_oneshot::oneshot::<()>(); let _handler = video_consumer.on_producer_resume({ let producer_resume_tx = Mutex::new(Some(producer_resume_tx)); move || { let _ = producer_resume_tx.lock().take().unwrap().send(()); } }); video_producer .resume() .await .expect("Failed to resume video producer"); producer_resume_rx .await .expect("Failed to receive producer resume event"); assert!(!video_consumer.producer_paused()); assert!(!video_consumer.paused()); let (producer_pause_tx, producer_pause_rx) = async_oneshot::oneshot::<()>(); let _handler = video_consumer.on_producer_pause({ let producer_pause_tx = Mutex::new(Some(producer_pause_tx)); move || { let _ = producer_pause_tx.lock().take().unwrap().send(()); } }); video_producer .pause() .await .expect("Failed to pause video producer"); producer_pause_rx .await .expect("Failed to receive producer pause event"); assert!(video_consumer.producer_paused()); assert!(!video_consumer.paused()); }); } #[test] fn pipe_to_router_succeeds_with_data() { future::block_on(async move { let (_worker1, _worker2, router1, router2, transport1, _transport2) = init().await; let data_producer = transport1 .produce_data(data_producer_options()) .await .expect("Failed to produce data"); let PipeDataProducerToRouterPair { pipe_data_producer, pipe_data_consumer, } = router1 .pipe_data_producer_to_router( data_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .expect("Failed to pipe data producer to router"); let pipe_data_producer = pipe_data_producer.into_inner(); { let dump = router1.dump().await.expect("Failed to dump router"); // There should be two Transports in router1: // - WebRtcTransport for data_producer. // - PipeTransport between router1 and router2. assert_eq!(dump.transport_ids.len(), 2); } { let dump = router2.dump().await.expect("Failed to dump router"); // There should be two Transports in router2: // - WebRtcTransport for data_consumer. // - PipeTransport between router2 and router1. assert_eq!(dump.transport_ids.len(), 2); } assert_eq!(pipe_data_consumer.r#type(), DataConsumerType::Sctp); { let sctp_stream_parameters = pipe_data_consumer.sctp_stream_parameters(); assert!(sctp_stream_parameters.is_some()); assert!(!sctp_stream_parameters.unwrap().ordered()); assert_eq!( sctp_stream_parameters.unwrap().max_packet_life_time(), Some(5000), ); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None); } assert_eq!(pipe_data_consumer.label().as_str(), "foo"); assert_eq!(pipe_data_consumer.protocol().as_str(), "bar"); assert_eq!(pipe_data_producer.r#type(), DataProducerType::Sctp); { let sctp_stream_parameters = pipe_data_producer.sctp_stream_parameters(); assert!(sctp_stream_parameters.is_some()); assert!(!sctp_stream_parameters.unwrap().ordered()); assert_eq!( sctp_stream_parameters.unwrap().max_packet_life_time(), Some(5000), ); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None); } assert_eq!(pipe_data_producer.label().as_str(), "foo"); assert_eq!(pipe_data_producer.protocol().as_str(), "bar"); }); } #[test] fn data_consume_for_pipe_data_producer_succeeds() { future::block_on(async move { let (_worker1, _worker2, router1, router2, transport1, transport2) = init().await; let data_producer = transport1 .produce_data(data_producer_options()) .await .expect("Failed to produce data"); let PipeDataProducerToRouterPair { pipe_data_producer, pipe_data_consumer: _, } = router1 .pipe_data_producer_to_router( data_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .expect("Failed to pipe data producer to router"); let pipe_data_producer = pipe_data_producer.into_inner(); let data_consumer = transport2 .consume_data(DataConsumerOptions::new_sctp(pipe_data_producer.id())) .await .expect("Failed to create data consumer"); assert_eq!(data_consumer.r#type(), DataConsumerType::Sctp); { let sctp_stream_parameters = data_consumer.sctp_stream_parameters(); assert!(sctp_stream_parameters.is_some()); assert!(!sctp_stream_parameters.unwrap().ordered()); assert_eq!( sctp_stream_parameters.unwrap().max_packet_life_time(), Some(5000), ); assert_eq!(sctp_stream_parameters.unwrap().max_retransmits(), None); } assert_eq!(data_consumer.label().as_str(), "foo"); assert_eq!(data_consumer.protocol().as_str(), "bar"); }); } #[test] fn pipe_to_router_called_twice_generates_single_pair() { future::block_on(async move { let (worker1, worker2, _router1, _router2, _transport1, _transport2) = init().await; let router_a = worker1 .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let router_b = worker2 .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let mut transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); transport_options.enable_sctp = true; let transport1 = router_a .create_webrtc_transport(transport_options.clone()) .await .expect("Failed to create transport1"); let transport2 = router_a .create_webrtc_transport(transport_options) .await .expect("Failed to create transport2"); let audio_producer1 = transport1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let audio_producer2 = transport2 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); router_a .pipe_producer_to_router( audio_producer1.id(), PipeToRouterOptions::new(router_b.clone()), ) .await .expect("Failed to pipe audio producer to router"); router_a .pipe_producer_to_router( audio_producer2.id(), PipeToRouterOptions::new(router_b.clone()), ) .await .expect("Failed to pipe audio producer to router"); { let dump = router_a.dump().await.expect("Failed to dump router"); // There should be 3 Transports in router_b: // - WebRtcTransport for audio_producer1 and audio_producer2. // - PipeTransport between router_a and router_b. assert_eq!(dump.transport_ids.len(), 3); } { let dump = router_b.dump().await.expect("Failed to dump router"); // There should be 1 Transport in router_b: // - PipeTransport between router_a and router_b. assert_eq!(dump.transport_ids.len(), 1); } }); } ================================================ FILE: rust/tests/integration/plain_transport.rs ================================================ use futures_lite::future; use hash_hasher::HashedSet; use mediasoup::plain_transport::{PlainTransportOptions, PlainTransportRemoteParameters}; use mediasoup::prelude::*; use mediasoup::router::{Router, RouterOptions}; use mediasoup::worker::{RequestError, Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; #[cfg(not(target_os = "windows"))] use mediasoup_types::data_structures::SocketFlags; use mediasoup_types::data_structures::{AppData, ListenInfo, Protocol, SctpState, TransportTuple}; use mediasoup_types::rtp_parameters::{ MimeTypeAudio, MimeTypeVideo, RtpCodecCapability, RtpCodecParametersParameters, }; use mediasoup_types::sctp_parameters::SctpParameters; use mediasoup_types::srtp_parameters::{SrtpCryptoSuite, SrtpParameters}; use portpicker::pick_unused_port; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; struct CustomAppData { foo: &'static str, } fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("level-asymmetry-allowed", 1_u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], // Will be ignored. }, ] } async fn init() -> (Worker, Router) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); (worker, router) } #[test] fn create_succeeds() { future::block_on(async move { let (_worker, router) = init().await; { let transport = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("4.4.4.4".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.rtcp_mux = false; plain_transport_options }) .await .expect("Failed to create Plain transport"); let router_dump = router.dump().await.expect("Failed to dump router"); assert_eq!(router_dump.transport_ids, { let mut set = HashedSet::default(); set.insert(transport.id()); set }); } { let new_transports_count = Arc::new(AtomicUsize::new(0)); router .on_new_transport({ let new_transports_count = Arc::clone(&new_transports_count); move |_transport| { new_transports_count.fetch_add(1, Ordering::SeqCst); } }) .detach(); let transport1 = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.rtcp_mux = true; plain_transport_options.enable_sctp = true; plain_transport_options.app_data = AppData::new(CustomAppData { foo: "bar" }); plain_transport_options }) .await .expect("Failed to create Plain transport"); assert_eq!(new_transports_count.load(Ordering::SeqCst), 1); assert!(!transport1.closed()); assert_eq!( transport1 .app_data() .downcast_ref::() .unwrap() .foo, "bar", ); assert!(matches!( transport1.tuple(), TransportTuple::LocalOnly { .. }, )); if let TransportTuple::LocalOnly { local_address, protocol, .. } = transport1.tuple() { assert_eq!(local_address, "9.9.9.1"); assert_eq!(protocol, Protocol::Udp); } assert_eq!(transport1.rtcp_tuple(), None); assert_eq!( transport1.sctp_parameters(), Some(SctpParameters { port: 5000, os: 1024, mis: 1024, max_message_size: 262_144, }), ); assert_eq!(transport1.sctp_state(), Some(SctpState::New)); assert_eq!(transport1.srtp_parameters(), None); { let transport_dump = transport1 .dump() .await .expect("Failed to dump Plain transport"); assert_eq!(transport_dump.id, transport1.id()); assert!(!transport_dump.direct); assert_eq!(transport_dump.producer_ids, vec![]); assert_eq!(transport_dump.consumer_ids, vec![]); assert_eq!(transport_dump.tuple, transport1.tuple()); assert_eq!(transport_dump.rtcp_tuple, transport1.rtcp_tuple()); assert_eq!(transport_dump.srtp_parameters, transport1.srtp_parameters()); assert_eq!(transport_dump.sctp_state, transport1.sctp_state()); } } { let rtcp_port = pick_unused_port().unwrap(); let transport2 = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.rtcp_mux = false; plain_transport_options.rtcp_listen_info = Some(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(rtcp_port), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options }) .await .expect("Failed to create Plain transport"); assert!(!transport2.closed()); assert_eq!(transport2.app_data().downcast_ref::<()>().unwrap(), &(),); assert!(matches!( transport2.tuple(), TransportTuple::LocalOnly { .. }, )); if let TransportTuple::LocalOnly { local_address, protocol, .. } = transport2.tuple() { assert_eq!(local_address, "127.0.0.1"); assert_eq!(protocol, Protocol::Udp); } assert!(transport2.rtcp_tuple().is_some()); if let TransportTuple::LocalOnly { local_address, local_port, protocol, .. } = transport2.rtcp_tuple().unwrap() { assert_eq!(local_address, "127.0.0.1"); assert_eq!(local_port, rtcp_port); assert_eq!(protocol, Protocol::Udp); } assert_eq!(transport2.srtp_parameters(), None); assert_eq!(transport2.sctp_state(), None); { let transport_dump = transport2 .dump() .await .expect("Failed to dump Plain transport"); assert_eq!(transport_dump.id, transport2.id()); assert!(!transport_dump.direct); assert_eq!(transport_dump.tuple, transport2.tuple()); assert_eq!(transport_dump.rtcp_tuple, transport2.rtcp_tuple()); assert_eq!(transport_dump.sctp_state, transport2.sctp_state()); } } }); } #[test] fn create_with_fixed_port_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let port = pick_unused_port().unwrap(); let transport = router .create_plain_transport({ PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("4.4.4.4".to_string()), expose_internal_ip: false, port: Some(port), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }) }) .await .expect("Failed to create Plain transport"); assert_eq!(transport.tuple().local_port(), port); }); } #[test] fn weak() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("4.4.4.4".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.rtcp_mux = false; plain_transport_options }) .await .expect("Failed to create Plain transport"); let weak_transport = transport.downgrade(); assert!(weak_transport.upgrade().is_some()); drop(transport); assert!(weak_transport.upgrade().is_none()); }); } #[test] fn create_enable_srtp_succeeds() { future::block_on(async move { let (_worker, router) = init().await; // Use default cryptoSuite: 'AES_CM_128_HMAC_SHA1_80'. let transport1 = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.enable_srtp = true; plain_transport_options }) .await .expect("Failed to create Plain transport"); assert!(transport1.srtp_parameters().is_some()); assert_eq!( transport1.srtp_parameters().unwrap().crypto_suite, SrtpCryptoSuite::AesCm128HmacSha180, ); assert_eq!(transport1.srtp_parameters().unwrap().key_base64.len(), 40); // Missing srtp_parameters. assert!(matches!( transport1 .connect(PlainTransportRemoteParameters { ip: Some("127.0.0.2".parse().unwrap()), port: Some(9999), rtcp_port: None, srtp_parameters: None }) .await, Err(RequestError::Response { .. }), )); // Valid srtp_parameters. And let's update the crypto suite. transport1 .connect(PlainTransportRemoteParameters { ip: Some("127.0.0.2".parse().unwrap()), port: Some(9999), rtcp_port: None, srtp_parameters: Some(SrtpParameters { crypto_suite: SrtpCryptoSuite::AeadAes256Gcm, key_base64: "YTdjcDBvY2JoMGY5YXNlNDc0eDJsdGgwaWRvNnJsamRrdG16aWVpZHphdHo=" .to_string(), }), }) .await .expect("Failed to establish Plain transport connection"); assert_eq!( transport1.srtp_parameters().unwrap().crypto_suite, SrtpCryptoSuite::AeadAes256Gcm, ); assert_eq!(transport1.srtp_parameters().unwrap().key_base64.len(), 60); }); } #[test] fn create_non_bindable_ip() { future::block_on(async move { let (_worker, router) = init().await; assert!(matches!( router .create_plain_transport(PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: "8.8.8.8".parse().unwrap(), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })) .await, Err(RequestError::Response { .. }), )); }); } #[cfg(not(target_os = "windows"))] #[test] fn create_two_transports_binding_to_same_ip_port_with_udp_reuse_port_flag_succeed() { future::block_on(async move { let (_worker, router) = init().await; let multicast_ip = "224.0.0.1".parse().unwrap(); let port = pick_unused_port().unwrap(); let transport1 = router .create_plain_transport({ PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: multicast_ip, announced_address: None, expose_internal_ip: false, port: Some(port), port_range: None, // NOTE: ipv6Only flag will be ignored since ip is IPv4. flags: Some(SocketFlags { ipv6_only: true, udp_reuse_port: true, }), send_buffer_size: None, recv_buffer_size: None, }) }) .await .expect("Failed to create first Plain transport"); let transport2 = router .create_plain_transport({ PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: multicast_ip, announced_address: None, expose_internal_ip: false, port: Some(port), port_range: None, flags: Some(SocketFlags { ipv6_only: false, udp_reuse_port: true, }), send_buffer_size: None, recv_buffer_size: None, }) }) .await .expect("Failed to create second Plain transport"); assert_eq!(transport1.tuple().local_port(), port); assert_eq!(transport2.tuple().local_port(), port); }); } #[cfg(not(target_os = "windows"))] #[test] fn create_two_transports_binding_to_same_ip_port_without_udp_reuse_port_flag_fails() { future::block_on(async move { let (_worker, router) = init().await; let multicast_ip = "224.0.0.1".parse().unwrap(); let port = pick_unused_port().unwrap(); let transport1 = router .create_plain_transport({ PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: multicast_ip, announced_address: None, expose_internal_ip: false, port: Some(port), port_range: None, flags: Some(SocketFlags { ipv6_only: false, udp_reuse_port: false, }), send_buffer_size: None, recv_buffer_size: None, }) }) .await .expect("Failed to create first Plain transport"); assert!(matches!( router .create_plain_transport(PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: multicast_ip, announced_address: None, expose_internal_ip: false, port: Some(port), port_range: None, flags: Some(SocketFlags { ipv6_only: false, udp_reuse_port: false, }), send_buffer_size: None, recv_buffer_size: None, })) .await, Err(RequestError::Response { .. }), )); assert_eq!(transport1.tuple().local_port(), port); }); } #[test] fn get_stats_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("4.4.4.4".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.rtcp_mux = false; plain_transport_options }) .await .expect("Failed to create Plain transport"); let stats = transport .get_stats() .await .expect("Failed to get stats on Plain transport"); assert_eq!(stats.len(), 1); assert_eq!(stats[0].transport_id, transport.id()); assert_eq!(stats[0].bytes_received, 0); assert_eq!(stats[0].recv_bitrate, 0); assert_eq!(stats[0].bytes_sent, 0); assert_eq!(stats[0].send_bitrate, 0); assert_eq!(stats[0].rtp_bytes_received, 0); assert_eq!(stats[0].rtp_recv_bitrate, 0); assert_eq!(stats[0].rtp_bytes_sent, 0); assert_eq!(stats[0].rtp_send_bitrate, 0); assert_eq!(stats[0].rtx_bytes_received, 0); assert_eq!(stats[0].rtx_recv_bitrate, 0); assert_eq!(stats[0].rtx_bytes_sent, 0); assert_eq!(stats[0].rtx_send_bitrate, 0); assert_eq!(stats[0].probation_bytes_sent, 0); assert_eq!(stats[0].probation_send_bitrate, 0); assert_eq!(stats[0].rtp_packet_loss_received, None); assert_eq!(stats[0].rtp_packet_loss_sent, None); assert!(matches!(stats[0].tuple, TransportTuple::LocalOnly { .. },)); if let TransportTuple::LocalOnly { local_address, protocol, .. } = &stats[0].tuple { assert_eq!(local_address, "4.4.4.4"); assert_eq!(*protocol, Protocol::Udp); } assert_eq!(stats[0].rtcp_tuple, None); }); } #[test] fn connect_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("4.4.4.4".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.rtcp_mux = false; plain_transport_options }) .await .expect("Failed to create Plain transport"); transport .connect(PlainTransportRemoteParameters { ip: Some("1.2.3.4".parse().unwrap()), port: Some(1234), rtcp_port: Some(1235), srtp_parameters: None, }) .await .expect("Failed to establish Plain transport connection"); // Must fail if connected. assert!(matches!( transport .connect(PlainTransportRemoteParameters { ip: Some("1.2.3.4".parse().unwrap()), port: Some(1234), rtcp_port: Some(1235), srtp_parameters: None, }) .await, Err(RequestError::Response { .. }), )); assert!(matches!( transport.tuple(), TransportTuple::WithRemote { .. }, )); if let TransportTuple::WithRemote { remote_ip, remote_port, protocol, .. } = transport.tuple() { assert_eq!(remote_ip, "1.2.3.4".parse::().unwrap()); assert_eq!(remote_port, 1234); assert_eq!(protocol, Protocol::Udp); } assert!(transport.rtcp_tuple().is_some()); if let TransportTuple::WithRemote { remote_ip, remote_port, protocol, .. } = transport.rtcp_tuple().unwrap() { assert_eq!(remote_ip, "1.2.3.4".parse::().unwrap()); assert_eq!(remote_port, 1235); assert_eq!(protocol, Protocol::Udp); } }); } #[test] fn connect_wrong_arguments() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("4.4.4.4".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.rtcp_mux = false; plain_transport_options }) .await .expect("Failed to create Plain transport"); // No SRTP enabled so passing srtp_parameters must fail. assert!(matches!( transport .connect(PlainTransportRemoteParameters { ip: Some("127.0.0.2".parse().unwrap()), port: Some(9998), rtcp_port: Some(9999), srtp_parameters: Some(SrtpParameters { crypto_suite: SrtpCryptoSuite::AesCm128HmacSha180, key_base64: "ZnQ3eWJraDg0d3ZoYzM5cXN1Y2pnaHU5NWxrZTVv".to_string(), }), }) .await, Err(RequestError::Response { .. }), )); }); } #[test] fn close_event() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_plain_transport({ let mut plain_transport_options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("4.4.4.4".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); plain_transport_options.rtcp_mux = false; plain_transport_options }) .await .expect("Failed to create Plain transport"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_close(Box::new(move || { let _ = close_tx.send(()); })); drop(transport); close_rx.await.expect("Failed to receive close event"); }); } ================================================ FILE: rust/tests/integration/producer.rs ================================================ use async_io::Timer; use futures_lite::future; use hash_hasher::{HashedMap, HashedSet}; use mediasoup::prelude::*; use mediasoup::producer::{ProducerOptions, ProducerTraceEventType, ProducerType}; use mediasoup::router::{Router, RouterOptions}; use mediasoup::transport::ProduceError; use mediasoup::webrtc_transport::{ WebRtcTransport, WebRtcTransportListenInfos, WebRtcTransportOptions, }; use mediasoup::worker::{Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{AppData, ListenInfo, Protocol}; use mediasoup_types::rtp_parameters::{ MediaKind, MimeTypeAudio, MimeTypeVideo, RtcpFeedback, RtcpParameters, RtpCodecCapability, RtpCodecParameters, RtpCodecParametersParameters, RtpEncodingParameters, RtpEncodingParametersRtx, RtpHeaderExtensionParameters, RtpHeaderExtensionUri, RtpParameters, }; use mediasoup_types::scalability_modes::ScalabilityMode; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; use std::time::Duration; struct ProducerAppData { foo: i32, bar: &'static str, } fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([("foo", "111".into())]), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("level-asymmetry-allowed", 1_u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }, ] } fn audio_producer_options() -> ProducerOptions { let mut options = ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 0, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("usedtx", 1_u32.into()), ("foo", "222.222".into()), ("bar", "333".into()), ]), rtcp_feedback: vec![], }], header_extensions: vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::SsrcAudioLevel, id: 12, encrypt: false, }, ], // Missing encodings on purpose. encodings: vec![], rtcp: RtcpParameters { cname: Some("audio-1".to_string()), ..RtcpParameters::default() }, msid: None, }, ); options.app_data = AppData::new(ProducerAppData { foo: 1, bar: "2" }); options } fn video_producer_options() -> ProducerOptions { let mut options = ProducerOptions::new( MediaKind::Video, RtpParameters { mid: Some("VIDEO".to_string()), codecs: vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![ RtcpFeedback::Nack, RtcpFeedback::NackPli, RtcpFeedback::GoogRemb, ], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 113, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([("apt", 112u32.into())]), rtcp_feedback: vec![], }, ], header_extensions: vec![ RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::Mid, id: 10, encrypt: false, }, RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri::VideoOrientation, id: 13, encrypt: false, }, ], encodings: vec![ RtpEncodingParameters { ssrc: Some(22222222), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222223 }), scalability_mode: "L1T3".parse().unwrap(), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(22222224), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222225 }), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(22222226), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222227 }), ..RtpEncodingParameters::default() }, RtpEncodingParameters { ssrc: Some(22222228), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222229 }), ..RtpEncodingParameters::default() }, ], rtcp: RtcpParameters { cname: Some("video-1".to_string()), ..RtcpParameters::default() }, msid: None, }, ); options.app_data = AppData::new(ProducerAppData { foo: 1, bar: "2" }); options } async fn init() -> (Worker, Router, WebRtcTransport, WebRtcTransport) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let transport_options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); let transport_1 = router .create_webrtc_transport(transport_options.clone()) .await .expect("Failed to create transport1"); let transport_2 = router .create_webrtc_transport(transport_options) .await .expect("Failed to create transport2"); (worker, router, transport_1, transport_2) } #[test] fn produce_succeeds_1() { future::block_on(async move { let (_worker, router, transport_1, _transport_2) = init().await; let new_producers_count = Arc::new(AtomicUsize::new(0)); transport_1 .on_new_producer({ let new_producers_count = Arc::clone(&new_producers_count); Arc::new(move |_producer| { new_producers_count.fetch_add(1, Ordering::SeqCst); }) }) .detach(); let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); assert_eq!(new_producers_count.load(Ordering::SeqCst), 1); assert!(!audio_producer.closed()); assert_eq!(audio_producer.kind(), MediaKind::Audio); assert_eq!(audio_producer.r#type(), ProducerType::Simple); assert!(!audio_producer.paused()); assert_eq!(audio_producer.score(), vec![]); assert_eq!( audio_producer .app_data() .downcast_ref::() .unwrap() .foo, 1 ); assert_eq!( audio_producer .app_data() .downcast_ref::() .unwrap() .bar, "2" ); let router_dump = router.dump().await.expect("Failed to get router dump"); assert_eq!(router_dump.map_producer_id_consumer_ids, { let mut map = HashedMap::default(); map.insert(audio_producer.id(), HashedSet::default()); map }); assert_eq!( router_dump.map_consumer_id_producer_id, HashedMap::default() ); let transport_1_dump = transport_1 .dump() .await .expect("Failed to get transport 1 dump"); assert_eq!(transport_1_dump.producer_ids, vec![audio_producer.id()]); assert_eq!(transport_1_dump.consumer_ids, vec![]); let (mut tx, rx) = async_oneshot::oneshot::<()>(); transport_1 .on_close(Box::new(move || { let _ = tx.send(()); })) .detach(); drop(audio_producer); drop(transport_1); // This means producer was definitely dropped rx.await.expect("Failed to receive transport close event"); }); } #[test] fn produce_succeeds_2() { future::block_on(async move { let (_worker, router, _transport_1, transport_2) = init().await; let new_producers_count = Arc::new(AtomicUsize::new(0)); transport_2 .on_new_producer({ let new_producers_count = Arc::clone(&new_producers_count); Arc::new(move |_producer| { new_producers_count.fetch_add(1, Ordering::SeqCst); }) }) .detach(); let video_producer = transport_2 .produce(video_producer_options()) .await .expect("Failed to produce video"); assert_eq!(new_producers_count.load(Ordering::SeqCst), 1); assert!(!video_producer.closed()); assert_eq!(video_producer.kind(), MediaKind::Video); assert_eq!(video_producer.r#type(), ProducerType::Simulcast); assert!(!video_producer.paused()); assert_eq!(video_producer.score(), vec![]); assert_eq!( video_producer .app_data() .downcast_ref::() .unwrap() .foo, 1 ); assert_eq!( video_producer .app_data() .downcast_ref::() .unwrap() .bar, "2" ); let router_dump = router.dump().await.expect("Failed to get router dump"); assert_eq!(router_dump.map_producer_id_consumer_ids, { let mut map = HashedMap::default(); map.insert(video_producer.id(), HashedSet::default()); map }); assert_eq!( router_dump.map_consumer_id_producer_id, HashedMap::default() ); let transport_2_dump = transport_2 .dump() .await .expect("Failed to get transport 2 dump"); assert_eq!(transport_2_dump.producer_ids, vec![video_producer.id()]); assert_eq!(transport_2_dump.consumer_ids, vec![]); }); } #[test] fn weak() { future::block_on(async move { let (_worker, _router, transport_1, _transport_2) = init().await; let producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let weak_producer = producer.downgrade(); assert!(weak_producer.upgrade().is_some()); drop(producer); assert!(weak_producer.upgrade().is_none()); }); } #[test] fn produce_wrong_arguments() { future::block_on(async move { let (_worker, _router, transport_1, _transport_2) = init().await; // Empty rtp_parameters.codecs. assert!(matches!( transport_1 .produce(ProducerOptions::new( MediaKind::Audio, RtpParameters::default() )) .await, Err(ProduceError::Request(_)), )); { // Empty rtp_parameters.encodings. let produce_result = transport_1 .produce(ProducerOptions::new(MediaKind::Video, { let mut parameters = RtpParameters::default(); parameters.codecs = vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 113, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([( "apt", 112u32.into(), )]), rtcp_feedback: vec![], }, ]; parameters.rtcp = RtcpParameters { cname: Some("qwerty".to_string()), ..RtcpParameters::default() }; parameters })) .await; assert!(matches!(produce_result, Err(ProduceError::Request(_)))); } { // Wrong apt in RTX codec. let produce_result = transport_1 .produce(ProducerOptions::new(MediaKind::Video, { let mut parameters = RtpParameters::default(); parameters.codecs = vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 113, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([( "apt", 111_u32.into(), )]), rtcp_feedback: vec![], }, ]; parameters.encodings = vec![RtpEncodingParameters { ssrc: Some(6666), rtx: Some(RtpEncodingParametersRtx { ssrc: 6667 }), ..RtpEncodingParameters::default() }]; parameters.rtcp = RtcpParameters { cname: Some("video-1".to_string()), ..RtcpParameters::default() }; parameters })) .await; assert!(matches!( produce_result, Err(ProduceError::FailedRtpParametersMapping(_)) )); } }); } #[test] fn produce_unsupported_codecs() { future::block_on(async move { let (_worker, _router, transport_1, _transport_2) = init().await; // Empty rtp_parameters.codecs. assert!(matches!( transport_1 .produce(ProducerOptions::new(MediaKind::Audio, { let mut parameters = RtpParameters::default(); parameters.codecs = vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Isac, payload_type: 108, clock_rate: NonZeroU32::new(32000).unwrap(), channels: NonZeroU8::new(1).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }]; parameters.header_extensions = vec![]; parameters.encodings = vec![RtpEncodingParameters { ssrc: Some(1111), ..RtpEncodingParameters::default() }]; parameters.rtcp = RtcpParameters { cname: Some("audio".to_string()), ..RtcpParameters::default() }; parameters })) .await, Err(ProduceError::FailedRtpParametersMapping(_)), )); { // Invalid H264 profile-level-id. let produce_result = transport_1 .produce(ProducerOptions::new(MediaKind::Video, { let mut parameters = RtpParameters::default(); parameters.codecs = vec![ RtpCodecParameters::Video { mime_type: MimeTypeVideo::H264, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("packetization-mode", 1_u32.into()), ("profile-level-id", "CHICKEN".into()), ]), rtcp_feedback: vec![], }, RtpCodecParameters::Video { mime_type: MimeTypeVideo::Rtx, payload_type: 113, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([( "apt", 112u32.into(), )]), rtcp_feedback: vec![], }, ]; parameters.encodings = vec![RtpEncodingParameters { ssrc: Some(6666), rtx: Some(RtpEncodingParametersRtx { ssrc: 6667 }), ..RtpEncodingParameters::default() }]; parameters })) .await; assert!(matches!( produce_result, Err(ProduceError::FailedRtpParametersMapping(_)) )); } }); } #[test] fn produce_already_used_mid_ssrc() { future::block_on(async move { let (_worker, _router, transport_1, transport_2) = init().await; { let _first_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let produce_result = transport_1 .produce(ProducerOptions::new(MediaKind::Audio, { let mut parameters = RtpParameters::default(); parameters.mid = Some("AUDIO".to_string()); parameters.codecs = vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 0, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }]; parameters.header_extensions = vec![]; parameters.encodings = vec![RtpEncodingParameters { ssrc: Some(33333333), ..RtpEncodingParameters::default() }]; parameters.rtcp = RtcpParameters { cname: Some("audio-2".to_string()), ..RtcpParameters::default() }; parameters })) .await; assert!(matches!(produce_result, Err(ProduceError::Request(_)),)); } { let _first_producer = transport_2 .produce(video_producer_options()) .await .expect("Failed to produce video"); // Invalid H264 profile-level-id. let produce_result = transport_2 .produce(ProducerOptions::new(MediaKind::Video, { let mut parameters = RtpParameters::default(); parameters.mid = Some("VIDEO2".to_string()); parameters.codecs = vec![RtpCodecParameters::Video { mime_type: MimeTypeVideo::Vp8, payload_type: 112, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }]; parameters.encodings = vec![RtpEncodingParameters { ssrc: Some(22222222), rtx: Some(RtpEncodingParametersRtx { ssrc: 6667 }), ..RtpEncodingParameters::default() }]; parameters })) .await; assert!(matches!(produce_result, Err(ProduceError::Request(_)))); } }); } #[test] fn produce_no_mid_single_encoding_without_rid_or_ssrc() { future::block_on(async move { let (_worker, _router, transport_1, _transport_2) = init().await; let produce_result = transport_1 .produce(ProducerOptions::new(MediaKind::Audio, { let mut parameters = RtpParameters::default(); parameters.codecs = vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 111, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }]; parameters.header_extensions = vec![]; parameters.encodings = vec![RtpEncodingParameters::default()]; parameters.rtcp = RtcpParameters { cname: Some("audio-2".to_string()), ..RtcpParameters::default() }; parameters })) .await; assert!(matches!(produce_result, Err(ProduceError::Request(_)),)); }); } #[test] fn dump_succeeds() { future::block_on(async move { let (_worker, _router, transport_1, transport_2) = init().await; { let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let dump = audio_producer .dump() .await .expect("Failed to dump audio producer"); assert_eq!(dump.id, audio_producer.id()); assert_eq!(dump.kind, audio_producer.kind()); assert_eq!( dump.rtp_parameters.codecs, audio_producer_options().rtp_parameters.codecs, ); assert_eq!( dump.rtp_parameters.header_extensions, audio_producer_options().rtp_parameters.header_extensions, ); assert_eq!( dump.rtp_parameters.encodings, vec![RtpEncodingParameters { ssrc: None, rid: None, codec_payload_type: Some(0), rtx: None, dtx: None, scalability_mode: ScalabilityMode::None, max_bitrate: None }], ); assert_eq!(dump.r#type, ProducerType::Simple); } { let video_producer = transport_2 .produce(video_producer_options()) .await .expect("Failed to produce video"); let dump = video_producer .dump() .await .expect("Failed to dump video producer"); assert_eq!(dump.id, video_producer.id()); assert_eq!(dump.kind, video_producer.kind()); assert_eq!( dump.rtp_parameters.codecs, video_producer_options().rtp_parameters.codecs, ); assert_eq!( dump.rtp_parameters.header_extensions, video_producer_options().rtp_parameters.header_extensions, ); assert_eq!( dump.rtp_parameters.encodings, vec![ RtpEncodingParameters { ssrc: Some(22222222), rid: None, codec_payload_type: Some(112), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222223 }), dtx: None, scalability_mode: "L1T3".parse().unwrap(), max_bitrate: None }, RtpEncodingParameters { ssrc: Some(22222224), rid: None, codec_payload_type: Some(112), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222225 }), dtx: None, scalability_mode: ScalabilityMode::None, max_bitrate: None }, RtpEncodingParameters { ssrc: Some(22222226), rid: None, codec_payload_type: Some(112), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222227 }), dtx: None, scalability_mode: ScalabilityMode::None, max_bitrate: None }, RtpEncodingParameters { ssrc: Some(22222228), rid: None, codec_payload_type: Some(112), rtx: Some(RtpEncodingParametersRtx { ssrc: 22222229 }), dtx: None, scalability_mode: ScalabilityMode::None, max_bitrate: None }, ], ); assert_eq!(dump.r#type, ProducerType::Simulcast); } }); } #[test] fn get_stats_succeeds() { future::block_on(async move { let (_worker, _router, transport_1, transport_2) = init().await; { let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); let stats = audio_producer .get_stats() .await .expect("Failed to get stats on audio producer"); assert_eq!(stats, vec![]); } { let video_producer = transport_2 .produce(video_producer_options()) .await .expect("Failed to produce video"); let stats = video_producer .get_stats() .await .expect("Failed to get stats on video producer"); assert_eq!(stats, vec![]); } }); } #[test] fn pause_resume_succeeds() { future::block_on(async move { let (_worker, _router, transport_1, _transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); { audio_producer .pause() .await .expect("Failed to pause audio producer"); assert!(audio_producer.paused()); let dump = audio_producer .dump() .await .expect("Failed to dump audio producer"); assert!(dump.paused); } { audio_producer .resume() .await .expect("Failed to resume audio producer"); assert!(!audio_producer.paused()); let dump = audio_producer .dump() .await .expect("Failed to dump audio producer"); assert!(!dump.paused); } }); } #[test] fn enable_trace_event_succeeds() { future::block_on(async move { let (_worker, _router, transport_1, _transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); { audio_producer .enable_trace_event(vec![ ProducerTraceEventType::Rtp, ProducerTraceEventType::Pli, ]) .await .expect("Failed to enable trace event"); let dump = audio_producer .dump() .await .expect("Failed to dump audio producer"); assert_eq!( dump.trace_event_types, vec![ProducerTraceEventType::Rtp, ProducerTraceEventType::Pli] ); } { audio_producer .enable_trace_event(vec![]) .await .expect("Failed to enable trace event"); let dump = audio_producer .dump() .await .expect("Failed to dump audio producer"); assert_eq!(dump.trace_event_types, vec![]); } }); } #[test] fn close_event() { future::block_on(async move { let (_worker, router, transport_1, _transport_2) = init().await; let audio_producer = transport_1 .produce(audio_producer_options()) .await .expect("Failed to produce audio"); { let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = audio_producer.on_close(move || { let _ = tx.send(()); }); drop(audio_producer); rx.await.expect("Failed to receive close event"); } // Drop is async, give producer a bit of time to finish Timer::after(Duration::from_millis(200)).await; { let dump = router.dump().await.expect("Failed to dump router"); assert_eq!(dump.map_producer_id_consumer_ids, HashedMap::default()); assert_eq!(dump.map_consumer_id_producer_id, HashedMap::default()); } { let dump = transport_1.dump().await.expect("Failed to dump transport"); assert_eq!(dump.producer_ids, vec![]); assert_eq!(dump.consumer_ids, vec![]); } }); } ================================================ FILE: rust/tests/integration/router.rs ================================================ use futures_lite::future; use hash_hasher::{HashedMap, HashedSet}; use mediasoup::router::RouterOptions; use mediasoup::worker::{ChannelMessageHandlers, Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::AppData; use mediasoup_types::rtp_parameters::{ MimeTypeAudio, MimeTypeVideo, RtpCodecCapability, RtpCodecParametersParameters, }; use std::env; use std::num::{NonZeroU32, NonZeroU8}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("level-asymmetry-allowed", 1_u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ]), rtcp_feedback: vec![], // Will be ignored. }, ] } async fn init() -> Worker { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker") } #[test] fn create_router_succeeds() { future::block_on(async move { let worker = init().await; let new_router_count = Arc::new(AtomicUsize::new(0)); worker .on_new_router({ let new_router_count = Arc::clone(&new_router_count); move |_router| { new_router_count.fetch_add(1, Ordering::SeqCst); } }) .detach(); #[derive(Debug, PartialEq)] struct CustomAppData { foo: u32, } let router = worker .create_router({ let mut router_options = RouterOptions::new(media_codecs()); router_options.app_data = AppData::new(CustomAppData { foo: 123 }); router_options }) .await .expect("Failed to create router"); assert_eq!(new_router_count.load(Ordering::SeqCst), 1); assert!(!router.closed()); // 3 codecs + 2 RTX codecs. assert_eq!(router.rtp_capabilities().codecs.len(), 5); assert_eq!( router.app_data().downcast_ref::(), Some(&CustomAppData { foo: 123 }), ); let worker_dump = worker.dump().await.expect("Failed to dump worker"); assert_eq!(worker_dump.router_ids, vec![router.id()]); assert_eq!(worker_dump.webrtc_server_ids, vec![]); assert_eq!( worker_dump.channel_message_handlers, ChannelMessageHandlers { channel_request_handlers: vec![router.id().into()], channel_notification_handlers: vec![] } ); let dump = router.dump().await.expect("Failed to dump router"); assert_eq!(dump.id, router.id()); assert_eq!(dump.transport_ids, HashedSet::default()); assert_eq!(dump.rtp_observer_ids, HashedSet::default()); assert_eq!(dump.map_producer_id_consumer_ids, HashedMap::default()); assert_eq!(dump.map_consumer_id_producer_id, HashedMap::default()); assert_eq!(dump.map_producer_id_observer_ids, HashedMap::default()); assert_eq!( dump.map_data_producer_id_data_consumer_ids, HashedMap::default() ); assert_eq!( dump.map_data_consumer_id_data_producer_id, HashedMap::default() ); }); } #[test] fn update_media_codecs_succeeds() { future::block_on(async move { let worker = init().await; let mut router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); assert!(!router.closed()); // 3 codecs + 2 RTX codecs. assert_eq!(router.rtp_capabilities().codecs.len(), 5); let _ = router.update_media_codecs([].to_vec()); assert_eq!(router.rtp_capabilities().codecs.len(), 0); }); } #[test] fn close_event() { future::block_on(async move { let worker = init().await; let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = router.on_close(move || { let _ = tx.send(()); }); drop(router); rx.await.expect("Failed to receive close event"); }); } ================================================ FILE: rust/tests/integration/smoke.rs ================================================ use futures_lite::future; use mediasoup::active_speaker_observer::ActiveSpeakerObserverOptions; use mediasoup::audio_level_observer::AudioLevelObserverOptions; use mediasoup::consumer::{ConsumerLayers, ConsumerOptions, ConsumerTraceEventType}; use mediasoup::data_consumer::DataConsumerOptions; use mediasoup::data_producer::DataProducerOptions; use mediasoup::direct_transport::DirectTransportOptions; use mediasoup::plain_transport::PlainTransportOptions; use mediasoup::prelude::*; use mediasoup::producer::{ProducerOptions, ProducerTraceEventType}; use mediasoup::router::{PipeToRouterOptions, RouterOptions}; use mediasoup::rtp_observer::RtpObserverAddProducerOptions; use mediasoup::transport::TransportTraceEventType; use mediasoup::webrtc_transport::{WebRtcTransportListenInfos, WebRtcTransportOptions}; use mediasoup::worker::{WorkerLogLevel, WorkerSettings, WorkerUpdateSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{ListenInfo, Protocol}; use mediasoup_types::rtp_parameters::{ MediaKind, MimeTypeAudio, RtpCapabilities, RtpCodecCapability, RtpCodecParameters, RtpParameters, }; use mediasoup_types::sctp_parameters::SctpStreamParameters; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; use std::{env, thread}; fn init() { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } } #[test] fn smoke() { init(); let worker_manager = WorkerManager::new(); future::block_on(async move { let worker = worker_manager .create_worker(WorkerSettings::default()) .await .unwrap(); println!("Worker dump: {:#?}", worker.dump().await.unwrap()); println!( "Update settings: {:?}", worker .update_settings({ let mut settings = WorkerUpdateSettings::default(); settings.log_level = Some(WorkerLogLevel::Debug); settings.log_tags = Some(vec![]); settings }) .await .unwrap() ); let router = worker .create_router({ let mut router_options = RouterOptions::default(); router_options.media_codecs = vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: Default::default(), rtcp_feedback: vec![], }]; router_options }) .await .unwrap(); println!("Router created: {:?}", router.id()); println!("Router dump: {:#?}", router.dump().await.unwrap()); let webrtc_transport = router .create_webrtc_transport({ let mut options = WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })); options.enable_sctp = true; options }) .await .unwrap(); println!("WebRTC transport created: {:?}", webrtc_transport.id()); println!( "WebRTC transport stats: {:#?}", webrtc_transport.get_stats().await.unwrap() ); println!( "WebRTC transport dump: {:#?}", webrtc_transport.dump().await.unwrap() ); println!("Router dump: {:#?}", router.dump().await.unwrap()); println!( "WebRTC transport enable trace event: {:#?}", webrtc_transport .enable_trace_event(vec![TransportTraceEventType::Bwe]) .await .unwrap() ); let producer = webrtc_transport .produce(ProducerOptions::new( MediaKind::Audio, RtpParameters { mid: Some("AUDIO".to_string()), codecs: vec![RtpCodecParameters::Audio { mime_type: MimeTypeAudio::Opus, payload_type: 111, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: Default::default(), rtcp_feedback: vec![], }], ..RtpParameters::default() }, )) .await .unwrap(); println!("Producer created: {:?}", producer.id()); println!( "WebRTC transport dump: {:#?}", webrtc_transport.dump().await.unwrap() ); println!("Router dump: {:#?}", router.dump().await.unwrap()); println!("Producer stats: {:#?}", producer.get_stats().await.unwrap()); println!("Producer dump: {:#?}", producer.dump().await.unwrap()); println!("Producer pause: {:#?}", producer.pause().await.unwrap()); println!("Producer resume: {:#?}", producer.resume().await.unwrap()); println!( "Producer enable trace event: {:#?}", producer .enable_trace_event(vec![ProducerTraceEventType::KeyFrame]) .await .unwrap() ); let consumer = webrtc_transport .consume(ConsumerOptions::new( producer.id(), RtpCapabilities { codecs: vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: Default::default(), rtcp_feedback: vec![], }], header_extensions: vec![], }, )) .await .unwrap(); println!("Consumer created: {:?}", consumer.id()); println!( "WebRTC transport dump: {:#?}", webrtc_transport.dump().await.unwrap() ); println!("Router dump: {:#?}", router.dump().await.unwrap()); println!("Consumer stats: {:#?}", consumer.get_stats().await.unwrap()); println!("Producer dump: {:#?}", producer.dump().await.unwrap()); println!("Consumer dump: {:#?}", consumer.dump().await.unwrap()); println!("Consumer pause: {:#?}", consumer.pause().await.unwrap()); println!("Consumer resume: {:#?}", consumer.resume().await.unwrap()); println!( "Consumer set preferred layers: {:#?}", consumer .set_preferred_layers(ConsumerLayers { spatial_layer: 1, temporal_layer: None }) .await .unwrap() ); println!( "Consumer set priority: {:#?}", consumer.set_priority(10).await.unwrap() ); println!( "Consumer unset priority: {:#?}", consumer.unset_priority().await.unwrap() ); println!( "Consumer enable trace event: {:#?}", consumer .enable_trace_event(vec![ConsumerTraceEventType::KeyFrame]) .await .unwrap() ); let data_producer = webrtc_transport .produce_data(DataProducerOptions::new_sctp( SctpStreamParameters::new_ordered(1), )) .await .unwrap(); println!("Data producer created: {:?}", producer.id()); println!( "WebRTC transport dump: {:#?}", webrtc_transport.dump().await.unwrap() ); println!("Router dump: {:#?}", router.dump().await.unwrap()); println!( "Data producer stats: {:#?}", data_producer.get_stats().await.unwrap() ); println!( "Data producer dump: {:#?}", data_producer.dump().await.unwrap() ); let data_consumer = webrtc_transport .consume_data(DataConsumerOptions::new_sctp(data_producer.id())) .await .unwrap(); println!("Data consumer created: {:?}", consumer.id()); println!( "WebRTC transport dump: {:#?}", webrtc_transport.dump().await.unwrap() ); println!("Router dump: {:#?}", router.dump().await.unwrap()); println!( "Data producer stats: {:#?}", data_producer.get_stats().await.unwrap() ); println!( "Data consumer stats: {:#?}", data_consumer.get_stats().await.unwrap() ); println!( "Data consumer dump: {:#?}", data_consumer.dump().await.unwrap() ); println!( "Data consumer get buffered amount: {:#?}", data_consumer.get_buffered_amount().await.unwrap() ); println!( "Data consumer set buffered amount low threshold: {:#?}", data_consumer .set_buffered_amount_low_threshold(256) .await .unwrap() ); let plain_transport = router .create_plain_transport({ let mut options = PlainTransportOptions::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); options.enable_sctp = true; options }) .await .unwrap(); println!("Plain transport created: {:?}", plain_transport.id()); println!( "Plain transport stats: {:#?}", plain_transport.get_stats().await.unwrap() ); println!( "Plain transport dump: {:#?}", plain_transport.dump().await.unwrap() ); println!("Router dump: {:#?}", router.dump().await.unwrap()); println!( "Plain transport enable trace event: {:#?}", plain_transport .enable_trace_event(vec![TransportTraceEventType::Bwe]) .await .unwrap() ); let direct_transport = router .create_direct_transport(DirectTransportOptions::default()) .await .unwrap(); println!("Direct transport created: {:?}", direct_transport.id()); println!( "Direct transport stats: {:#?}", direct_transport.get_stats().await.unwrap() ); println!( "Direct transport dump: {:#?}", direct_transport.dump().await.unwrap() ); println!("Router dump: {:#?}", router.dump().await.unwrap()); println!( "Direct transport enable trace event: {:#?}", direct_transport .enable_trace_event(vec![TransportTraceEventType::Bwe]) .await .unwrap() ); let audio_level_observer = router .create_audio_level_observer(AudioLevelObserverOptions::default()) .await .unwrap(); println!("Audio level observer: {:#?}", audio_level_observer.id()); println!( "Add producer to audio level observer: {:#?}", audio_level_observer .add_producer(RtpObserverAddProducerOptions::new(producer.id())) .await .unwrap() ); let active_speaker_observer = router .create_active_speaker_observer(ActiveSpeakerObserverOptions::default()) .await .unwrap(); println!( "Active speaker observer: {:#?}", active_speaker_observer.id() ); println!( "Add producer to active speaker observer: {:#?}", active_speaker_observer .add_producer(RtpObserverAddProducerOptions::new(producer.id())) .await .unwrap() ); println!("Router dump: {:#?}", router.dump().await.unwrap()); println!( "Remove producer from audio level observer: {:#?}", audio_level_observer .remove_producer(producer.id()) .await .unwrap() ); let worker2 = worker_manager .create_worker(WorkerSettings::default()) .await .unwrap(); let router2 = worker2 .create_router(RouterOptions::new(vec![RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: Default::default(), rtcp_feedback: vec![], }])) .await .unwrap(); println!("Second router created: {:?}", router.id()); router .pipe_producer_to_router(producer.id(), PipeToRouterOptions::new(router2.clone())) .await .unwrap(); println!("Piped producer to other router",); router .pipe_data_producer_to_router( data_producer.id(), PipeToRouterOptions::new(router2.clone()), ) .await .unwrap(); println!("Piped data producer to other router",); println!("Router dump: {:#?}", router.dump().await.unwrap()); println!("Router 2 dump: {:#?}", router2.dump().await.unwrap()); }); // Just to give it time to finish everything thread::sleep(std::time::Duration::from_millis(200)); } ================================================ FILE: rust/tests/integration/webrtc_server.rs ================================================ use futures_lite::future; use hash_hasher::HashedSet; use mediasoup::webrtc_server::{WebRtcServerIpPort, WebRtcServerListenInfos, WebRtcServerOptions}; use mediasoup::worker::{ChannelMessageHandlers, CreateWebRtcServerError, Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{AppData, ListenInfo, Protocol}; use portpicker::pick_unused_port; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; #[derive(Debug, PartialEq)] struct CustomAppData { foo: u32, } async fn init() -> (Worker, Worker) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); ( worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"), worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"), ) } #[test] fn create_webrtc_server_succeeds() { future::block_on(async move { let (worker1, _worker2) = init().await; let new_webrtc_server_count = Arc::new(AtomicUsize::new(0)); let port1 = pick_unused_port().unwrap(); let port2 = pick_unused_port().unwrap(); worker1 .on_new_webrtc_server({ let new_webrtc_server_count = Arc::clone(&new_webrtc_server_count); move |_webrtc_server| { new_webrtc_server_count.fetch_add(1, Ordering::SeqCst); } }) .detach(); let webrtc_server = worker1 .create_webrtc_server({ let listen_infos = WebRtcServerListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port1), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); let listen_infos = listen_infos.insert(ListenInfo { protocol: Protocol::Tcp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("foo.bar.org".to_string()), expose_internal_ip: false, port: Some(port2), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); let mut webrtc_server_options = WebRtcServerOptions::new(listen_infos); webrtc_server_options.app_data = AppData::new(CustomAppData { foo: 123 }); webrtc_server_options }) .await .expect("Failed to create router"); assert_eq!(new_webrtc_server_count.load(Ordering::SeqCst), 1); assert!(!webrtc_server.closed()); assert_eq!( webrtc_server.app_data().downcast_ref::(), Some(&CustomAppData { foo: 123 }), ); let worker_dump = worker1.dump().await.expect("Failed to dump worker"); assert_eq!(worker_dump.router_ids, vec![]); assert_eq!(worker_dump.webrtc_server_ids, vec![webrtc_server.id()]); assert_eq!( worker_dump.channel_message_handlers, ChannelMessageHandlers { channel_request_handlers: vec![webrtc_server.id().into()], channel_notification_handlers: vec![] } ); let dump = webrtc_server .dump() .await .expect("Failed to dump WebRTC server"); assert_eq!(dump.id, webrtc_server.id()); assert_eq!( dump.udp_sockets, vec![WebRtcServerIpPort { ip: IpAddr::V4(Ipv4Addr::LOCALHOST), port: port1 }] ); assert_eq!( dump.tcp_servers, vec![WebRtcServerIpPort { ip: IpAddr::V4(Ipv4Addr::LOCALHOST), port: port2 }] ); assert_eq!(dump.webrtc_transport_ids, HashedSet::default()); assert_eq!(dump.local_ice_username_fragments, vec![]); assert_eq!(dump.tuple_hashes, vec![]); }); } #[test] fn create_webrtc_server_without_specifying_port_succeeds() { future::block_on(async move { let (worker1, _worker2) = init().await; let new_webrtc_server_count = Arc::new(AtomicUsize::new(0)); worker1 .on_new_webrtc_server({ let new_webrtc_server_count = Arc::clone(&new_webrtc_server_count); move |_webrtc_server| { new_webrtc_server_count.fetch_add(1, Ordering::SeqCst); } }) .detach(); let webrtc_server = worker1 .create_webrtc_server({ let listen_infos = WebRtcServerListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); let listen_infos = listen_infos.insert(ListenInfo { protocol: Protocol::Tcp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("1.2.3.4".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); let mut webrtc_server_options = WebRtcServerOptions::new(listen_infos); webrtc_server_options.app_data = AppData::new(CustomAppData { foo: 123 }); webrtc_server_options }) .await .expect("Failed to create router"); assert_eq!(new_webrtc_server_count.load(Ordering::SeqCst), 1); assert!(!webrtc_server.closed()); assert_eq!( webrtc_server.app_data().downcast_ref::(), Some(&CustomAppData { foo: 123 }), ); let worker_dump = worker1.dump().await.expect("Failed to dump worker"); assert_eq!(worker_dump.router_ids, vec![]); assert_eq!(worker_dump.webrtc_server_ids, vec![webrtc_server.id()]); let dump = webrtc_server .dump() .await .expect("Failed to dump WebRTC server"); assert_eq!(dump.id, webrtc_server.id()); assert_eq!(dump.udp_sockets[0].ip, IpAddr::V4(Ipv4Addr::LOCALHOST)); assert_eq!(dump.tcp_servers[0].ip, IpAddr::V4(Ipv4Addr::LOCALHOST)); assert_eq!(dump.webrtc_transport_ids, HashedSet::default()); assert_eq!(dump.local_ice_username_fragments, vec![]); assert_eq!(dump.tuple_hashes, vec![]); }); } #[test] fn unavailable_infos_fails() { future::block_on(async move { let (worker1, worker2) = init().await; let new_webrtc_server_count = Arc::new(AtomicUsize::new(0)); let port1 = pick_unused_port().unwrap(); let port2 = pick_unused_port().unwrap(); worker1 .on_new_webrtc_server({ let new_webrtc_server_count = Arc::clone(&new_webrtc_server_count); move |_webrtc_server| { new_webrtc_server_count.fetch_add(1, Ordering::SeqCst); } }) .detach(); // Using an unavailable listen IP. { let create_result = worker1 .create_webrtc_server({ let listen_infos = WebRtcServerListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port1), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); let listen_infos = listen_infos.insert(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), announced_address: None, expose_internal_ip: false, port: Some(port2), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); WebRtcServerOptions::new(listen_infos) }) .await; assert!(matches!( create_result, Err(CreateWebRtcServerError::Request(_)) )); } // Using the same UDP port twice. { let create_result = worker1 .create_webrtc_server({ let listen_infos = WebRtcServerListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port1), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); let listen_infos = listen_infos.insert(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), announced_address: Some("1.2.3.4".to_string()), expose_internal_ip: false, port: Some(port1), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }); WebRtcServerOptions::new(listen_infos) }) .await; assert!(matches!( create_result, Err(CreateWebRtcServerError::Request(_)) )); } // Using the same UDP port in a second server. { let _webrtc_server = worker1 .create_webrtc_server(WebRtcServerOptions::new(WebRtcServerListenInfos::new( ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port1), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }, ))) .await .expect("Failed to dump WebRTC server"); let create_result = worker2 .create_webrtc_server(WebRtcServerOptions::new(WebRtcServerListenInfos::new( ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port1), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }, ))) .await; assert!(matches!( create_result, Err(CreateWebRtcServerError::Request(_)) )); } }); } #[test] fn close_event() { future::block_on(async move { let (worker1, _worker2) = init().await; let port = pick_unused_port().unwrap(); let webrtc_server = worker1 .create_webrtc_server(WebRtcServerOptions::new(WebRtcServerListenInfos::new( ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: Some(port), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }, ))) .await .expect("Failed to create WebRTC server"); let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = webrtc_server.on_close(move || { let _ = tx.send(()); }); drop(webrtc_server); rx.await.expect("Failed to receive close event"); }); } ================================================ FILE: rust/tests/integration/webrtc_transport.rs ================================================ use futures_lite::future; use hash_hasher::HashedSet; use mediasoup::prelude::*; use mediasoup::router::{Router, RouterOptions}; use mediasoup::transport::TransportTraceEventType; use mediasoup::webrtc_transport::{ WebRtcTransportListenInfos, WebRtcTransportOptions, WebRtcTransportRemoteParameters, }; use mediasoup::worker::{RequestError, Worker, WorkerSettings}; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::{ AppData, DtlsFingerprint, DtlsParameters, DtlsRole, DtlsState, IceCandidateType, IceRole, IceState, ListenInfo, Protocol, SctpState, }; use mediasoup_types::rtp_parameters::{ MimeTypeAudio, MimeTypeVideo, RtpCodecCapability, RtpCodecParametersParameters, }; use mediasoup_types::sctp_parameters::{NumSctpStreams, SctpParameters}; use portpicker::pick_unused_port; use std::convert::TryInto; use std::env; use std::net::{IpAddr, Ipv4Addr}; use std::num::{NonZeroU32, NonZeroU8}; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; struct CustomAppData { foo: &'static str, } fn media_codecs() -> Vec { vec![ RtpCodecCapability::Audio { mime_type: MimeTypeAudio::Opus, preferred_payload_type: None, clock_rate: NonZeroU32::new(48000).unwrap(), channels: NonZeroU8::new(2).unwrap(), parameters: RtpCodecParametersParameters::from([ ("useinbandfec", 1_u32.into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::Vp8, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::default(), rtcp_feedback: vec![], }, RtpCodecCapability::Video { mime_type: MimeTypeVideo::H264, preferred_payload_type: None, clock_rate: NonZeroU32::new(90000).unwrap(), parameters: RtpCodecParametersParameters::from([ ("level-asymmetry-allowed", 1_u32.into()), ("packetization-mode", 1_u32.into()), ("profile-level-id", "4d0032".into()), ("foo", "bar".into()), ]), rtcp_feedback: vec![], }, ] } async fn init() -> (Worker, Router) { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } let worker_manager = WorkerManager::new(); let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker"); let router = worker .create_router(RouterOptions::new(media_codecs())) .await .expect("Failed to create router"); (worker, router) } #[test] fn create_succeeds() { future::block_on(async move { let (_worker, router) = init().await; { let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); let router_dump = router.dump().await.expect("Failed to dump router"); assert_eq!(router_dump.transport_ids, { let mut set = HashedSet::default(); set.insert(transport.id()); set }); } { let new_transports_count = Arc::new(AtomicUsize::new(0)); router .on_new_transport({ let new_transports_count = Arc::clone(&new_transports_count); move |_transport| { new_transports_count.fetch_add(1, Ordering::SeqCst); } }) .detach(); let transport1 = router .create_webrtc_transport({ let mut webrtc_transport_options = WebRtcTransportOptions::new( vec![ ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: true, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }, ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::UNSPECIFIED), announced_address: Some("foo1.bar.org".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }, ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }, ] .try_into() .unwrap(), ); webrtc_transport_options.enable_sctp = true; webrtc_transport_options.num_sctp_streams = NumSctpStreams { os: 2048, mis: 2048, }; webrtc_transport_options.max_sctp_message_size = 1000000; webrtc_transport_options.app_data = AppData::new(CustomAppData { foo: "bar" }); webrtc_transport_options }) .await .expect("Failed to create WebRTC transport"); assert_eq!(new_transports_count.load(Ordering::SeqCst), 1); assert_eq!( transport1 .app_data() .downcast_ref::() .unwrap() .foo, "bar", ); assert_eq!(transport1.ice_role(), IceRole::Controlled); assert_eq!(transport1.ice_parameters().ice_lite, Some(true)); assert_eq!( transport1.sctp_parameters(), Some(SctpParameters { port: 5000, os: 2048, mis: 2048, max_message_size: 1000000, }), ); { let ice_candidates = transport1.ice_candidates(); assert_eq!(ice_candidates.len(), 4); assert_eq!(ice_candidates[0].address, "9.9.9.1"); assert_eq!(ice_candidates[0].protocol, Protocol::Udp); assert_eq!(ice_candidates[0].r#type, IceCandidateType::Host); assert_eq!(ice_candidates[0].tcp_type, None); assert_eq!(ice_candidates[1].address, "127.0.0.1"); assert_eq!(ice_candidates[1].protocol, Protocol::Udp); assert_eq!(ice_candidates[1].r#type, IceCandidateType::Host); assert_eq!(ice_candidates[1].tcp_type, None); assert_eq!(ice_candidates[2].address, "foo1.bar.org"); assert_eq!(ice_candidates[2].protocol, Protocol::Udp); assert_eq!(ice_candidates[2].r#type, IceCandidateType::Host); assert_eq!(ice_candidates[2].tcp_type, None); assert_eq!(ice_candidates[3].address, "127.0.0.1"); assert_eq!(ice_candidates[3].protocol, Protocol::Udp); assert_eq!(ice_candidates[3].r#type, IceCandidateType::Host); assert_eq!(ice_candidates[3].tcp_type, None); assert_eq!(ice_candidates[3].address, "127.0.0.1"); assert_eq!(ice_candidates[3].protocol, Protocol::Udp); assert_eq!(ice_candidates[3].r#type, IceCandidateType::Host); assert_eq!(ice_candidates[3].tcp_type, None); assert!(ice_candidates[0].priority > ice_candidates[1].priority); assert!(ice_candidates[1].priority > ice_candidates[2].priority); assert!(ice_candidates[2].priority > ice_candidates[3].priority); } assert_eq!(transport1.ice_state(), IceState::New); assert_eq!(transport1.ice_selected_tuple(), None); assert_eq!(transport1.dtls_parameters().role, DtlsRole::Auto); assert_eq!(transport1.dtls_state(), DtlsState::New); assert_eq!(transport1.sctp_state(), Some(SctpState::New)); { let transport_dump = transport1 .dump() .await .expect("Failed to dump WebRTC transport"); assert_eq!(transport_dump.id, transport1.id()); assert!(!transport_dump.direct); assert_eq!(transport_dump.producer_ids, vec![]); assert_eq!(transport_dump.consumer_ids, vec![]); assert_eq!(transport_dump.ice_role, transport1.ice_role()); assert_eq!(&transport_dump.ice_parameters, transport1.ice_parameters()); assert_eq!(&transport_dump.ice_candidates, transport1.ice_candidates()); assert_eq!(transport_dump.ice_state, transport1.ice_state()); assert_eq!( transport_dump.ice_selected_tuple, transport1.ice_selected_tuple(), ); assert_eq!(transport_dump.dtls_parameters, transport1.dtls_parameters()); assert_eq!(transport_dump.dtls_state, transport1.dtls_state()); assert_eq!(transport_dump.sctp_parameters, transport1.sctp_parameters()); assert_eq!(transport_dump.sctp_state, transport1.sctp_state()); } } }); } #[test] fn create_with_fixed_port_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let port = pick_unused_port().unwrap(); let transport = router .create_webrtc_transport({ WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: Some(port), port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, })) }) .await .expect("Failed to create WebRTC transport"); assert_eq!(transport.ice_candidates().first().unwrap().port, port); }); } #[test] fn create_with_port_range_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let port_range = 11111..=11112; let transport1 = router .create_webrtc_transport({ WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: Some(port_range.clone()), flags: None, send_buffer_size: None, recv_buffer_size: None, })) }) .await .expect("Failed to create WebRTC transport"); let port1 = transport1.ice_candidates().first().unwrap().port; assert!(port1 >= *port_range.start() && port1 <= *port_range.end()); let transport2 = router .create_webrtc_transport({ WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: Some(port_range.clone()), flags: None, send_buffer_size: None, recv_buffer_size: None, })) }) .await .expect("Failed to create WebRTC transport"); let port2 = transport2.ice_candidates().first().unwrap().port; assert!(port2 >= *port_range.start() && port2 <= *port_range.end()); assert!(matches!( router .create_webrtc_transport({ WebRtcTransportOptions::new(WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: Some(port_range.clone()), flags: None, send_buffer_size: None, recv_buffer_size: None, })) }) .await, Err(RequestError::Response { .. }), )); }); } #[test] fn weak() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); let weak_transport = transport.downgrade(); assert!(weak_transport.upgrade().is_some()); drop(transport); assert!(weak_transport.upgrade().is_none()); }); } #[test] fn create_non_bindable_ip() { future::block_on(async move { let (_worker, router) = init().await; assert!(matches!( router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: "8.8.8.8".parse().unwrap(), announced_address: None, expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, },) )) .await, Err(RequestError::Response { .. }), )); }); } #[test] fn get_stats_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); let stats = transport .get_stats() .await .expect("Failed to get stats on WebRTC transport"); assert_eq!(stats.len(), 1); assert_eq!(stats[0].transport_id, transport.id()); assert_eq!(stats[0].ice_role, IceRole::Controlled); assert_eq!(stats[0].ice_state, IceState::New); assert_eq!(stats[0].dtls_state, DtlsState::New); assert_eq!(stats[0].sctp_state, None); assert_eq!(stats[0].bytes_received, 0); assert_eq!(stats[0].recv_bitrate, 0); assert_eq!(stats[0].bytes_sent, 0); assert_eq!(stats[0].send_bitrate, 0); assert_eq!(stats[0].rtp_bytes_received, 0); assert_eq!(stats[0].rtp_recv_bitrate, 0); assert_eq!(stats[0].rtp_bytes_sent, 0); assert_eq!(stats[0].rtp_send_bitrate, 0); assert_eq!(stats[0].rtx_bytes_received, 0); assert_eq!(stats[0].rtx_recv_bitrate, 0); assert_eq!(stats[0].rtx_bytes_sent, 0); assert_eq!(stats[0].rtx_send_bitrate, 0); assert_eq!(stats[0].probation_bytes_sent, 0); assert_eq!(stats[0].probation_send_bitrate, 0); assert_eq!(stats[0].ice_selected_tuple, None); assert_eq!(stats[0].max_incoming_bitrate, None); assert_eq!(stats[0].rtp_packet_loss_received, None); assert_eq!(stats[0].rtp_packet_loss_sent, None); }); } #[test] fn connect_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); let dtls_parameters = DtlsParameters { role: DtlsRole::Client, fingerprints: vec![DtlsFingerprint::Sha256 { value: [ 0x82, 0x5A, 0x68, 0x3D, 0x36, 0xC3, 0x0A, 0xDE, 0xAF, 0xE7, 0x32, 0x43, 0xD2, 0x88, 0x83, 0x57, 0xAC, 0x2D, 0x65, 0xE5, 0x80, 0xC4, 0xB6, 0xFB, 0xAF, 0x1A, 0xA0, 0x21, 0x9F, 0x6D, 0x0C, 0xAD, ], }], }; transport .connect(WebRtcTransportRemoteParameters { dtls_parameters: dtls_parameters.clone(), }) .await .expect("Failed to establish WebRTC transport connection"); // Must fail if connected. assert!(matches!( transport .connect(WebRtcTransportRemoteParameters { dtls_parameters }) .await, Err(RequestError::Response { .. }), )); assert_eq!(transport.dtls_parameters().role, DtlsRole::Server); }); } #[test] fn set_max_incoming_bitrate_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); transport .set_max_incoming_bitrate(1000000) .await .expect("Failed to set max incoming bitrate on WebRTC transport"); // Remove limit. transport .set_max_incoming_bitrate(0) .await .expect("Failed to remove limit in max incoming bitrate on WebRTC transport"); }); } #[test] fn set_max_outgoing_bitrate_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); transport .set_max_outgoing_bitrate(2000000) .await .expect("Failed to set max outgoing bitrate on WebRTC transport"); // Remove limit. transport .set_max_outgoing_bitrate(0) .await .expect("Failed to remove limit in max outgoing bitrate on WebRTC transport"); }); } #[test] fn set_min_outgoing_bitrate_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); transport .set_min_outgoing_bitrate(100000) .await .expect("Failed to set min outgoing bitrate on WebRTC transport"); // Remove limit. transport .set_min_outgoing_bitrate(0) .await .expect("Failed to remove limit in min outgoing bitrate on WebRTC transport"); }); } #[test] fn set_max_outgoing_bitrate_fails_if_value_is_lower_than_current_min_limit() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); transport .set_min_outgoing_bitrate(3000000) .await .expect("Failed to set min outgoing bitrate on WebRTC transport"); assert!(matches!( transport.set_max_outgoing_bitrate(2000000).await, Err(RequestError::Response { .. }), )); // Remove limit. transport .set_min_outgoing_bitrate(0) .await .expect("Failed to remove limit in min outgoing bitrate on WebRTC transport"); }); } #[test] fn set_min_outgoing_bitrate_fails_if_value_is_higher_than_current_max_limit() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); transport .set_max_outgoing_bitrate(2000000) .await .expect("Failed to set max outgoing bitrate on WebRTC transport"); assert!(matches!( transport.set_min_outgoing_bitrate(3000000).await, Err(RequestError::Response { .. }), )); // Remove limit. transport .set_max_outgoing_bitrate(0) .await .expect("Failed to remove limit in max outgoing bitrate on WebRTC transport"); }); } #[test] fn restart_ice_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); let previous_ice_username_fragment = transport.ice_parameters().username_fragment.clone(); let previous_ice_password = transport.ice_parameters().password.clone(); let ice_parameters = transport .restart_ice() .await .expect("Failed to initiate ICE Restart on WebRTC transport"); assert_ne!( ice_parameters.username_fragment, previous_ice_username_fragment ); assert_ne!(ice_parameters.password, previous_ice_password); }); } #[test] fn enable_trace_event_succeeds() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); { transport .enable_trace_event(vec![TransportTraceEventType::Probation]) .await .expect("Failed to enable trace event"); let dump = transport .dump() .await .expect("Failed to dump WebRTC transport"); assert_eq!( dump.trace_event_types, vec![TransportTraceEventType::Probation] ); } { transport .enable_trace_event(vec![ TransportTraceEventType::Probation, TransportTraceEventType::Bwe, ]) .await .expect("Failed to enable trace event"); let dump = transport .dump() .await .expect("Failed to dump WebRTC transport"); assert_eq!( dump.trace_event_types, vec![ TransportTraceEventType::Probation, TransportTraceEventType::Bwe, ] ); } { transport .enable_trace_event(vec![]) .await .expect("Failed to enable trace event"); let dump = transport .dump() .await .expect("Failed to dump WebRTC transport"); assert_eq!(dump.trace_event_types, vec![]); } }); } #[test] fn close_event() { future::block_on(async move { let (_worker, router) = init().await; let transport = router .create_webrtc_transport(WebRtcTransportOptions::new( WebRtcTransportListenInfos::new(ListenInfo { protocol: Protocol::Udp, ip: IpAddr::V4(Ipv4Addr::LOCALHOST), announced_address: Some("9.9.9.1".to_string()), expose_internal_ip: false, port: None, port_range: None, flags: None, send_buffer_size: None, recv_buffer_size: None, }), )) .await .expect("Failed to create WebRTC transport"); let (mut close_tx, close_rx) = async_oneshot::oneshot::<()>(); let _handler = transport.on_close(Box::new(move || { let _ = close_tx.send(()); })); drop(transport); close_rx.await.expect("Failed to receive close event"); }); } ================================================ FILE: rust/tests/integration/worker.rs ================================================ use futures_lite::future; use mediasoup::worker::{ ChannelMessageHandlers, WorkerDtlsFiles, WorkerLogLevel, WorkerLogTag, WorkerSettings, WorkerUpdateSettings, }; use mediasoup::worker_manager::WorkerManager; use mediasoup_types::data_structures::AppData; use std::{env, io}; async fn init() -> WorkerManager { { let mut builder = env_logger::builder(); if env::var(env_logger::DEFAULT_FILTER_ENV).is_err() { builder.filter_level(log::LevelFilter::Off); } let _ = builder.is_test(true).try_init(); } WorkerManager::new() } #[test] fn create_worker_succeeds() { future::block_on(async move { let worker_manager = init().await; let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker with default settings"); drop(worker); #[derive(Debug, PartialEq)] struct CustomAppData { bar: u16, } let worker = worker_manager .create_worker({ let mut settings = WorkerSettings::default(); settings.log_level = WorkerLogLevel::Debug; settings.log_tags = vec![WorkerLogTag::Info]; settings.rtc_port_range = 0..=9999; settings.dtls_files = Some(WorkerDtlsFiles { certificate: "tests/integration/data/dtls-cert.pem".into(), private_key: "tests/integration/data/dtls-key.pem".into(), }); settings.libwebrtc_field_trials = Some("WebRTC-Bwe-AlrLimitedBackoff/Disabled/".to_string()); settings.app_data = AppData::new(CustomAppData { bar: 456 }); settings }) .await .expect("Failed to create worker with custom settings"); assert!(!worker.closed()); assert_eq!( worker.app_data().downcast_ref::(), Some(&CustomAppData { bar: 456 }), ); drop(worker); }); } #[test] fn create_worker_wrong_settings() { future::block_on(async move { let worker_manager = init().await; { let worker_result = worker_manager .create_worker({ let mut settings = WorkerSettings::default(); // Intentionally incorrect range #[allow(clippy::reversed_empty_ranges)] { settings.rtc_port_range = 1000..=999; } settings }) .await; assert!(matches!(worker_result, Err(io::Error { .. }))); } { let worker_result = worker_manager .create_worker({ let mut settings = WorkerSettings::default(); settings.dtls_files = Some(WorkerDtlsFiles { certificate: "/notfound/cert.pem".into(), private_key: "/notfound/priv.pem".into(), }); settings }) .await; assert!(matches!(worker_result, Err(io::Error { .. }))); } }); } #[test] fn update_settings_succeeds() { future::block_on(async move { let worker_manager = init().await; let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker with default settings"); worker .update_settings({ let mut settings = WorkerUpdateSettings::default(); settings.log_level = Some(WorkerLogLevel::Debug); settings.log_tags = Some(vec![WorkerLogTag::Ice]); settings }) .await .expect("Failed to update worker settings"); }); } #[test] fn dump_succeeds() { future::block_on(async move { let worker_manager = init().await; let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker with default settings"); let dump = worker.dump().await.expect("Failed to dump worker"); assert_eq!(dump.router_ids, vec![]); assert_eq!(dump.webrtc_server_ids, vec![]); assert_eq!( dump.channel_message_handlers, ChannelMessageHandlers { channel_request_handlers: vec![], channel_notification_handlers: vec![] } ); }); } #[test] fn close_event() { future::block_on(async move { let worker_manager = init().await; let worker = worker_manager .create_worker(WorkerSettings::default()) .await .expect("Failed to create worker with default settings"); let (mut tx, rx) = async_oneshot::oneshot::<()>(); let _handler = worker.on_close(move || { let _ = tx.send(()); }); drop(worker); rx.await.expect("Failed to receive close event"); }); } ================================================ FILE: rust/types/CHANGELOG.md ================================================ # Changelog ### NEXT ### 0.3.0 - `RtpHeaderExtensionUri`: Add `SsrcAudioLevel`, `AbsSendTime`, `TransportWideCcDraft01`, `DependencyDescriptor`, `AbsCaptureTime`, `PlayoutDelay` and `MediasoupPacketId` variants. Rename `AudioLevel` to `SsrcAudioLevel` (PR #1631). - `RtpParameters`: Add optional `msid` field (WebRTC MediaStream Identification, RFC 8830) (PR #1634). ### 0.2.1 - Initial release as a standalone crate extracted from `mediasoup` (PR #1572). ================================================ FILE: rust/types/Cargo.toml ================================================ [package] name = "mediasoup-types" version = "0.3.0" description = "Type definitions and shared data structures for the mediasoup crate" authors = [ "Nazar Mokrynskyi ", "José Luis Millán ", "Iñaki Baz Castillo " ] edition = "2021" license = "ISC" documentation = "https://docs.rs/mediasoup-types" repository = "https://github.com/versatica/mediasoup" readme = "README.md" [dependencies] once_cell = "1.16.0" serde_json = "1.0.87" thiserror = "2.0.12" [dependencies.serde] features = ["derive"] version = "1.0.190" [dependencies.regex] default-features = false features = ["std", "perf"] version = "1.6.0" ================================================ FILE: rust/types/README.md ================================================ # mediasoup-types v3 Type definitions and shared data structures for the [mediasoup](https://docs.rs/mediasoup) crate. ## Overview `mediasoup-types` provides core types, enums, and data structures used throughout the mediasoup Rust crates. This crate is intended for use by both the main mediasoup implementation and any libraries, tools, or applications that interact with mediasoup in Rust. ## Usage Add `mediasoup-types` to your `Cargo.toml`: ```toml [dependencies] mediasoup-types = "X.Y.Z" ``` Import and use the types in your Rust code: ```rust use mediasoup_types::{RtpCodecParameters, RtpCapabilities, MediaKind}; ``` ## Documentation - [API Documentation](https://docs.rs/mediasoup-types) - [mediasoup Rust repository](https://github.com/versatica/mediasoup) ## Sponsor You can support mediasoup by [sponsoring](https://mediasoup.org/sponsor) it. Thanks! ## License [ISC](./LICENSE) ================================================ FILE: rust/types/src/data_structures/tests.rs ================================================ use super::*; #[test] fn dtls_fingerprint() { { let dtls_fingerprints = &[ r#"{"algorithm":"sha-1","value":"0D:88:5B:EF:B9:86:F9:66:67:75:7A:C1:7A:78:34:E4:88:DC:44:67"}"#, r#"{"algorithm":"sha-224","value":"6E:0C:C7:23:DF:36:E1:C7:46:AB:D7:B1:CE:DD:97:C3:C1:17:25:D6:26:0A:8A:B4:50:F1:3E:BC"}"#, r#"{"algorithm":"sha-256","value":"7A:27:46:F0:7B:09:28:F0:10:E2:EC:84:60:B5:87:9A:D9:C8:8B:F3:6C:C5:5D:C3:F3:BA:2C:5B:4F:8A:3A:E3"}"#, r#"{"algorithm":"sha-384","value":"D0:B7:F7:3C:71:9F:F4:A1:48:E1:9B:13:25:59:A4:7D:06:BF:E1:1B:DC:0B:8A:8E:45:09:01:22:7E:81:68:EC:DD:B8:DD:CA:1F:F3:F2:E8:15:A5:3C:23:CF:F7:B6:38"}"#, r#"{"algorithm":"sha-512","value":"36:8B:9B:CA:2B:01:2B:33:FD:06:95:F2:CC:28:56:69:5B:DD:38:5E:88:32:9A:72:F7:B1:5D:87:9E:64:97:0B:66:A1:C7:6C:BE:4D:CD:83:90:04:AE:20:6C:6D:5F:F0:BD:4C:D9:DD:6E:8A:19:C1:C9:F6:C2:46:C8:08:94:39"}"#, ]; for dtls_fingerprint_str in dtls_fingerprints { let dtls_fingerprint = serde_json::from_str::(dtls_fingerprint_str).unwrap(); assert_eq!( dtls_fingerprint_str, &serde_json::to_string(&dtls_fingerprint).unwrap() ); } } { let bad_dtls_fingerprints = &[ r#"{"algorithm":"sha-1","value":"0D:88:5B:EF:B9:86:F9:66:67::44:67"}"#, r#"{"algorithm":"sha-200","value":"6E:0C:C7:23:DF:36:E1:C7:46:AB:D7:B1:CE:DD:97:C3:C1:17:25:D6:26:0A:8A:B4:50:F1:3E:BC"}"#, ]; for dtls_fingerprint_str in bad_dtls_fingerprints { assert!(serde_json::from_str::(dtls_fingerprint_str).is_err()); } } } ================================================ FILE: rust/types/src/data_structures.rs ================================================ //! Miscellaneous data structures. #[cfg(test)] mod tests; use serde::de::{MapAccess, Visitor}; use serde::ser::SerializeStruct; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::any::Any; use std::borrow::Cow; use std::fmt; use std::net::IpAddr; use std::ops::{Deref, DerefMut, RangeInclusive}; use std::sync::Arc; /// Container for arbitrary data attached to mediasoup entities. #[derive(Debug, Clone)] pub struct AppData(Arc); impl Default for AppData { fn default() -> Self { Self::new(()) } } impl Deref for AppData { type Target = Arc; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for AppData { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl AppData { /// Construct app data from almost anything pub fn new(app_data: T) -> Self { Self(Arc::new(app_data)) } } /// Listening protocol, IP and port for [`WebRtcServer`](https://docs.rs/mediasoup/latest/mediasoup/webrtc_server/struct.WebRtcServer.html) to listen on. /// /// # Notes on usage /// If you use "0.0.0.0" or "::" as ip value, then you need to also provide /// `announced_address`. #[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ListenInfo { /// Network protocol. pub protocol: Protocol, /// Listening IPv4 or IPv6. pub ip: IpAddr, /// Announced IPv4, IPv6 or hostname (useful when running mediasoup behind /// NAT with private IP). #[serde(skip_serializing_if = "Option::is_none")] pub announced_address: Option, /// In transports with ICE candidates, this field determines whether to also /// expose an ICE candidate with the IP of the |ip| field when /// |announcedAddress| is given. pub expose_internal_ip: bool, /// Listening port. #[serde(skip_serializing_if = "Option::is_none")] pub port: Option, /// Listening port range. If given then |port| will be ignored. #[serde(skip_serializing_if = "Option::is_none")] pub port_range: Option>, /// Socket flags. #[serde(skip_serializing_if = "Option::is_none")] pub flags: Option, /// Send buffer size (bytes). #[serde(skip_serializing_if = "Option::is_none")] pub send_buffer_size: Option, /// Recv buffer size (bytes). #[serde(skip_serializing_if = "Option::is_none")] pub recv_buffer_size: Option, } /// UDP/TCP socket flags. #[derive( Default, Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize, )] #[serde(rename_all = "camelCase")] pub struct SocketFlags { /// Disable dual-stack support so only IPv6 is used (only if ip is IPv6). /// Defaults to false. pub ipv6_only: bool, /// Make different transports bind to the same ip and port (only for UDP). /// Useful for multicast scenarios with plain transport. Use with caution. /// Defaults to false. pub udp_reuse_port: bool, } /// ICE role. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum IceRole { /// The transport is the controlled agent. Controlled, /// The transport is the controlling agent. Controlling, } /// ICE parameters. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IceParameters { /// ICE username fragment. pub username_fragment: String, /// ICE password. pub password: String, /// ICE Lite. pub ice_lite: Option, } /// ICE candidate type. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum IceCandidateType { /// The candidate is a host candidate, whose IP address as specified in the /// [`IceCandidate::address`] property is in fact the true address of the remote peer. Host, /// The candidate is a server reflexive candidate; the [`IceCandidate::address`] indicates an /// intermediary address assigned by the STUN server to represent the candidate's peer /// anonymously. Srflx, /// The candidate is a peer reflexive candidate; the [`IceCandidate::address`] is an intermediary /// address assigned by the STUN server to represent the candidate's peer anonymously. Prflx, /// The candidate is a relay candidate, obtained from a TURN server. The relay candidate's IP /// address is an address the [TURN](https://developer.mozilla.org/en-US/docs/Glossary/TURN) /// server uses to forward the media between the two peers. Relay, } /// ICE candidate TCP type (always `Passive`). #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum IceCandidateTcpType { /// Passive. Passive, } /// Transport protocol. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum Protocol { /// TCP. Tcp, /// UDP. Udp, } /// ICE candidate #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IceCandidate { /// Unique identifier that allows ICE to correlate candidates that appear on multiple /// transports. pub foundation: String, /// The assigned priority of the candidate. pub priority: u32, /// The IP address or hostname of the candidate. pub address: String, /// The protocol of the candidate. pub protocol: Protocol, /// The port for the candidate. pub port: u16, /// The type of candidate (always `Host`). pub r#type: IceCandidateType, /// The type of TCP candidate (always `Passive`). #[serde(skip_serializing_if = "Option::is_none")] pub tcp_type: Option, } /// ICE state. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum IceState { /// No ICE Binding Requests have been received yet. New, /// Valid ICE Binding Request have been received, but none with USE-CANDIDATE attribute. /// Outgoing media is allowed. Connected, /// ICE Binding Request with USE_CANDIDATE attribute has been received. Media in both directions /// is now allowed. Completed, /// ICE was `Connected` or `Completed` but it has suddenly failed (this can just happen if the /// selected tuple has `Tcp` protocol). Disconnected, } /// Tuple of local IP/port/protocol + optional remote IP/port. /// /// # Notes on usage /// Both `remote_ip` and `remote_port` are unset until the media address of the remote endpoint is /// known, which happens after calling `transport.connect()` in `PlainTransport` and /// `PipeTransport`, or via dynamic detection as it happens in `WebRtcTransport` (in which the /// remote media address is detected by ICE means), or in `PlainTransport` (when using `comedia` /// mode). #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(untagged)] pub enum TransportTuple { /// Transport tuple with remote endpoint info. #[serde(rename_all = "camelCase")] WithRemote { /// Local IP address or hostname. local_address: String, /// Local port. local_port: u16, /// Remote IP address. remote_ip: IpAddr, /// Remote port. remote_port: u16, /// Protocol protocol: Protocol, }, /// Transport tuple without remote endpoint info. #[serde(rename_all = "camelCase")] LocalOnly { /// Local IP address or hostname. local_address: String, /// Local port. local_port: u16, /// Protocol protocol: Protocol, }, } impl TransportTuple { /// Local IP address or hostname. pub fn local_address(&self) -> &String { let (Self::WithRemote { local_address, .. } | Self::LocalOnly { local_address, .. }) = self; local_address } /// Local port. pub fn local_port(&self) -> u16 { let (Self::WithRemote { local_port, .. } | Self::LocalOnly { local_port, .. }) = self; *local_port } /// Protocol. pub fn protocol(&self) -> Protocol { let (Self::WithRemote { protocol, .. } | Self::LocalOnly { protocol, .. }) = self; *protocol } /// Remote IP address. pub fn remote_ip(&self) -> Option { if let TransportTuple::WithRemote { remote_ip, .. } = self { Some(*remote_ip) } else { None } } /// Remote port. pub fn remote_port(&self) -> Option { if let TransportTuple::WithRemote { remote_port, .. } = self { Some(*remote_port) } else { None } } } /// DTLS state. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum DtlsState { /// DTLS procedures not yet initiated. New, /// DTLS connecting. Connecting, /// DTLS successfully connected (SRTP keys already extracted). Connected, /// DTLS connection failed. Failed, /// DTLS state when the `transport` has been closed. Closed, } /// SCTP state. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub enum SctpState { /// SCTP procedures not yet initiated. New, /// SCTP connecting. Connecting, /// SCTP successfully connected. Connected, /// SCTP connection failed. Failed, /// SCTP state when the transport has been closed. Closed, } /// DTLS role. #[derive( Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize, Default, )] #[serde(rename_all = "camelCase")] pub enum DtlsRole { /// The DTLS role is determined based on the resolved ICE role (the `Controlled` role acts as /// DTLS client, the `Controlling` role acts as DTLS server). /// Since mediasoup is a ICE Lite implementation it always behaves as ICE `Controlled`. #[default] Auto, /// DTLS client role. Client, /// DTLS server role. Server, } /// The hash function algorithm (as defined in the "Hash function Textual Names" registry initially /// specified in [RFC 4572](https://tools.ietf.org/html/rfc4572#section-8) Section 8) and its /// corresponding certificate fingerprint value. #[derive(Copy, Clone, PartialOrd, Eq, PartialEq)] pub enum DtlsFingerprint { /// sha-1 Sha1 { /// Certificate fingerprint value. value: [u8; 20], }, /// sha-224 Sha224 { /// Certificate fingerprint value. value: [u8; 28], }, /// sha-256 Sha256 { /// Certificate fingerprint value. value: [u8; 32], }, /// sha-384 Sha384 { /// Certificate fingerprint value. value: [u8; 48], }, /// sha-512 Sha512 { /// Certificate fingerprint value. value: [u8; 64], }, } impl fmt::Debug for DtlsFingerprint { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = match self { DtlsFingerprint::Sha1 { .. } => "Sha1", DtlsFingerprint::Sha224 { .. } => "Sha224", DtlsFingerprint::Sha256 { .. } => "Sha256", DtlsFingerprint::Sha384 { .. } => "Sha384", DtlsFingerprint::Sha512 { .. } => "Sha512", }; f.debug_struct(name) .field("value", &self.value_string()) .finish() } } impl Serialize for DtlsFingerprint { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut rtcp_feedback = serializer.serialize_struct("DtlsFingerprint", 2)?; rtcp_feedback.serialize_field("algorithm", self.algorithm_str())?; rtcp_feedback.serialize_field("value", &self.value_string())?; rtcp_feedback.end() } } impl<'de> Deserialize<'de> for DtlsFingerprint { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(field_identifier, rename_all = "lowercase")] enum Field { Algorithm, Value, } struct DtlsFingerprintVisitor; impl<'de> Visitor<'de> for DtlsFingerprintVisitor { type Value = DtlsFingerprint; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str( r#"DTLS fingerprint algorithm and value like {"algorithm": "sha-256", "value": "1B:EA:BF:33:B8:11:26:6D:91:AD:1B:A0:16:FD:5D:60:59:33:F7:46:A3:BA:99:2A:1D:04:99:A6:F2:C6:2D:43"}"#, ) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { fn parse_as_bytes(input: &str, output: &mut [u8]) -> Result<(), String> { for (i, v) in output.iter_mut().enumerate() { *v = u8::from_str_radix(&input[i * 3..(i * 3) + 2], 16).map_err( |error| { format!( "Failed to parse value {input} as series of hex bytes: {error}" ) }, )?; } Ok(()) } let mut algorithm = None::>; let mut value = None::>; while let Some(key) = map.next_key()? { match key { Field::Algorithm => { if algorithm.is_some() { return Err(de::Error::duplicate_field("algorithm")); } algorithm = Some(map.next_value()?); } Field::Value => { if value.is_some() { return Err(de::Error::duplicate_field("value")); } value = map.next_value()?; } } } let algorithm = algorithm.ok_or_else(|| de::Error::missing_field("algorithm"))?; let value = value.ok_or_else(|| de::Error::missing_field("value"))?; match algorithm.as_ref() { "sha-1" => { if value.len() == (20 * 3 - 1) { let mut value_result = [0_u8; 20]; parse_as_bytes(value.as_ref(), &mut value_result) .map_err(de::Error::custom)?; Ok(DtlsFingerprint::Sha1 { value: value_result, }) } else { Err(de::Error::custom( "Value doesn't have correct length for SHA-1", )) } } "sha-224" => { if value.len() == (28 * 3 - 1) { let mut value_result = [0_u8; 28]; parse_as_bytes(value.as_ref(), &mut value_result) .map_err(de::Error::custom)?; Ok(DtlsFingerprint::Sha224 { value: value_result, }) } else { Err(de::Error::custom( "Value doesn't have correct length for SHA-224", )) } } "sha-256" => { if value.len() == (32 * 3 - 1) { let mut value_result = [0_u8; 32]; parse_as_bytes(value.as_ref(), &mut value_result) .map_err(de::Error::custom)?; Ok(DtlsFingerprint::Sha256 { value: value_result, }) } else { Err(de::Error::custom( "Value doesn't have correct length for SHA-256", )) } } "sha-384" => { if value.len() == (48 * 3 - 1) { let mut value_result = [0_u8; 48]; parse_as_bytes(value.as_ref(), &mut value_result) .map_err(de::Error::custom)?; Ok(DtlsFingerprint::Sha384 { value: value_result, }) } else { Err(de::Error::custom( "Value doesn't have correct length for SHA-384", )) } } "sha-512" => { if value.len() == (64 * 3 - 1) { let mut value_result = [0_u8; 64]; parse_as_bytes(value.as_ref(), &mut value_result) .map_err(de::Error::custom)?; Ok(DtlsFingerprint::Sha512 { value: value_result, }) } else { Err(de::Error::custom( "Value doesn't have correct length for SHA-512", )) } } algorithm => Err(de::Error::unknown_variant( algorithm, &["sha-1", "sha-224", "sha-256", "sha-384", "sha-512"], )), } } } const FIELDS: &[&str] = &["algorithm", "value"]; deserializer.deserialize_struct("DtlsFingerprint", FIELDS, DtlsFingerprintVisitor) } } impl DtlsFingerprint { pub fn value_string(&self) -> String { match self { DtlsFingerprint::Sha1 { value } => { format!( "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], value[19], ) } DtlsFingerprint::Sha224 { value } => { format!( "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], value[19], value[20], value[21], value[22], value[23], value[24], value[25], value[26], value[27], ) } DtlsFingerprint::Sha256 { value } => { format!( "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}", value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], value[19], value[20], value[21], value[22], value[23], value[24], value[25], value[26], value[27], value[28], value[29], value[30], value[31], ) } DtlsFingerprint::Sha384 { value } => { format!( "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}", value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], value[19], value[20], value[21], value[22], value[23], value[24], value[25], value[26], value[27], value[28], value[29], value[30], value[31], value[32], value[33], value[34], value[35], value[36], value[37], value[38], value[39], value[40], value[41], value[42], value[43], value[44], value[45], value[46], value[47], ) } DtlsFingerprint::Sha512 { value } => { format!( "{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:\ {:02X}:{:02X}:{:02X}:{:02X}", value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9], value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18], value[19], value[20], value[21], value[22], value[23], value[24], value[25], value[26], value[27], value[28], value[29], value[30], value[31], value[32], value[33], value[34], value[35], value[36], value[37], value[38], value[39], value[40], value[41], value[42], value[43], value[44], value[45], value[46], value[47], value[48], value[49], value[50], value[51], value[52], value[53], value[54], value[55], value[56], value[57], value[58], value[59], value[60], value[61], value[62], value[63], ) } } } fn algorithm_str(&self) -> &'static str { match self { DtlsFingerprint::Sha1 { .. } => "sha-1", DtlsFingerprint::Sha224 { .. } => "sha-224", DtlsFingerprint::Sha256 { .. } => "sha-256", DtlsFingerprint::Sha384 { .. } => "sha-384", DtlsFingerprint::Sha512 { .. } => "sha-512", } } } /// DTLS parameters. #[derive(Debug, Clone, PartialOrd, Eq, PartialEq, Deserialize, Serialize)] pub struct DtlsParameters { /// DTLS role. pub role: DtlsRole, /// DTLS fingerprints. pub fingerprints: Vec, } /// Trace event direction #[derive(Debug, Copy, Clone, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum TraceEventDirection { /// In In, /// Out Out, } /// Container used for sending/receiving messages using `DirectTransport` data producers and data /// consumers. #[derive(Debug, Clone)] pub enum WebRtcMessage<'a> { /// String String(Cow<'a, [u8]>), /// Binary Binary(Cow<'a, [u8]>), /// EmptyString EmptyString, /// EmptyBinary EmptyBinary, } impl<'a> WebRtcMessage<'a> { // +------------------------------------+-----------+ // | Value | SCTP PPID | // +------------------------------------+-----------+ // | WebRTC String | 51 | // | WebRTC Binary Partial (Deprecated) | 52 | // | WebRTC Binary | 53 | // | WebRTC String Partial (Deprecated) | 54 | // | WebRTC String Empty | 56 | // | WebRTC Binary Empty | 57 | // +------------------------------------+-----------+ pub fn new(ppid: u32, payload: Cow<'a, [u8]>) -> Result { match ppid { 51 => Ok(WebRtcMessage::String(payload)), 53 => Ok(WebRtcMessage::Binary(payload)), 56 => Ok(WebRtcMessage::EmptyString), 57 => Ok(WebRtcMessage::EmptyBinary), ppid => Err(ppid), } } pub fn into_ppid_and_payload(self) -> (u32, Cow<'a, [u8]>) { match self { WebRtcMessage::String(binary) => (51_u32, binary), WebRtcMessage::Binary(binary) => (53_u32, binary), WebRtcMessage::EmptyString => (56_u32, Cow::from(vec![0_u8])), WebRtcMessage::EmptyBinary => (57_u32, Cow::from(vec![0_u8])), } } /// Convert to owned message pub fn into_owned(self) -> OwnedWebRtcMessage { match self { WebRtcMessage::String(binary) => OwnedWebRtcMessage::String(binary.into_owned()), WebRtcMessage::Binary(binary) => OwnedWebRtcMessage::Binary(binary.into_owned()), WebRtcMessage::EmptyString => OwnedWebRtcMessage::EmptyString, WebRtcMessage::EmptyBinary => OwnedWebRtcMessage::EmptyBinary, } } } /// Similar to WebRtcMessage but represents /// messages that have ownership over the data #[derive(Debug, Clone)] pub enum OwnedWebRtcMessage { /// String String(Vec), /// Binary Binary(Vec), /// EmptyString EmptyString, /// EmptyBinary EmptyBinary, } /// RTP packet info in trace event. #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RtpPacketTraceInfo { /// RTP payload type. pub payload_type: u8, /// Sequence number. pub sequence_number: u16, /// Timestamp. pub timestamp: u32, /// Whether packet has marker or not. pub marker: bool, /// RTP stream SSRC. pub ssrc: u32, /// Whether packet contains a key frame. pub is_key_frame: bool, /// Packet size. pub size: u64, /// Payload size. pub payload_size: u64, /// The spatial layer index (from 0 to N). pub spatial_layer: u8, /// The temporal layer index (from 0 to N). pub temporal_layer: u8, /// The MID RTP extension value as defined in the BUNDLE specification pub mid: Option, /// RTP stream RID value. pub rid: Option, /// RTP stream RRID value. pub rrid: Option, /// Transport-wide sequence number. pub wide_sequence_number: Option, /// Whether this is an RTX packet. #[serde(default)] pub is_rtx: bool, } /// SSRC info in trace event. #[derive(Debug, Copy, Clone, Deserialize, Serialize)] pub struct SsrcTraceInfo { /// RTP stream SSRC. pub ssrc: u32, } /// Bandwidth estimation type. #[derive(Debug, Copy, Clone, Deserialize, Serialize)] pub enum BweType { /// Transport-wide Congestion Control. #[serde(rename = "transport-cc")] TransportCc, /// Receiver Estimated Maximum Bitrate. #[serde(rename = "remb")] Remb, } /// BWE info in trace event. #[derive(Debug, Copy, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct BweTraceInfo { /// Bandwidth estimation type. pub r#type: BweType, /// Desired bitrate pub desired_bitrate: u32, /// Effective desired bitrate. pub effective_desired_bitrate: u32, /// Min bitrate. pub min_bitrate: u32, /// Max bitrate. pub max_bitrate: u32, /// Start bitrate. pub start_bitrate: u32, /// Max padding bitrate. pub max_padding_bitrate: u32, /// Available bitrate. pub available_bitrate: u32, } /// RTCP Sender Report info in trace event. #[derive(Debug, Copy, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SrTraceInfo { /// Stream SSRC pub ssrc: u32, /// NTP : most significant word pub ntp_sec: u32, /// NTP : least significant word pub ntp_frac: u32, /// RTP timestamp pub rtp_ts: u32, /// Sender packet count pub packet_count: u32, /// Sender octet count pub octet_count: u32, } ================================================ FILE: rust/types/src/lib.rs ================================================ #![warn(rust_2018_idioms, missing_debug_implementations)] //! MediaSoup Rust Types //! //! This library provides Rust types for interacting with [mediasoup](https://docs.rs/mediasoup) crate. pub mod data_structures; pub mod rtp_parameters; pub mod scalability_modes; pub mod sctp_parameters; pub mod srtp_parameters; ================================================ FILE: rust/types/src/rtp_parameters/tests.rs ================================================ use super::*; #[test] fn rtcp_feedback_serde() { { let nack_pli_str = r#"{"type":"nack","parameter":"pli"}"#; assert_eq!( serde_json::from_str::(nack_pli_str).unwrap(), RtcpFeedback::NackPli ); let result = serde_json::to_string(&RtcpFeedback::NackPli).unwrap(); assert_eq!(result.as_str(), nack_pli_str); } { let transport_cc_str = r#"{"type":"transport-cc","parameter":""}"#; assert_eq!( serde_json::from_str::(transport_cc_str).unwrap(), RtcpFeedback::TransportCc ); let result = serde_json::to_string(&RtcpFeedback::TransportCc).unwrap(); assert_eq!(result.as_str(), transport_cc_str); } { let nack_bar_str = r#"{"type":"nack","parameter":"bar"}"#; assert_eq!( serde_json::from_str::(nack_bar_str).unwrap(), RtcpFeedback::Unsupported ); } } ================================================ FILE: rust/types/src/rtp_parameters.rs ================================================ //! Collection of RTP-related data structures that are used to specify codec parameters and //! capabilities of various endpoints. #[cfg(test)] mod tests; use crate::scalability_modes::ScalabilityMode; use serde::de::{MapAccess, Visitor}; use serde::ser::SerializeStruct; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::borrow::Cow; use std::collections::BTreeMap; use std::fmt; use std::iter::FromIterator; use std::num::{NonZeroU32, NonZeroU8}; use std::str::FromStr; use thiserror::Error; /// Codec specific parameters. Some parameters (such as `packetization-mode` and `profile-level-id` /// in H264 or `profile-id` in VP9) are critical for codec matching. #[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] pub struct RtpCodecParametersParameters( BTreeMap, RtpCodecParametersParametersValue>, ); impl RtpCodecParametersParameters { /// Insert another parameter into collection. pub fn insert(&mut self, key: K, value: V) -> &mut Self where K: Into>, V: Into, { self.0.insert(key.into(), value.into()); self } /// Iterate over parameters in collection. pub fn iter( &self, ) -> std::collections::btree_map::Iter<'_, Cow<'static, str>, RtpCodecParametersParametersValue> { self.0.iter() } /// Get specific parameter from collection. #[must_use] pub fn get(&self, key: &str) -> Option<&RtpCodecParametersParametersValue> { self.0.get(key) } } impl From<[(K, RtpCodecParametersParametersValue); N]> for RtpCodecParametersParameters where K: Into>, { fn from(array: [(K, RtpCodecParametersParametersValue); N]) -> Self { IntoIterator::into_iter(array).collect() } } impl IntoIterator for RtpCodecParametersParameters { type Item = (Cow<'static, str>, RtpCodecParametersParametersValue); type IntoIter = std::collections::btree_map::IntoIter, RtpCodecParametersParametersValue>; fn into_iter( self, ) -> std::collections::btree_map::IntoIter, RtpCodecParametersParametersValue> { self.0.into_iter() } } impl Extend<(K, RtpCodecParametersParametersValue)> for RtpCodecParametersParameters where K: Into>, { fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|(k, v)| { self.insert(k, v); }); } } impl FromIterator<(K, RtpCodecParametersParametersValue)> for RtpCodecParametersParameters where K: Into>, { fn from_iter>(iter: T) -> Self { Self(iter.into_iter().map(|(k, v)| (k.into(), v)).collect()) } } /// Provides information on the capabilities of a codec within the RTP capabilities. The list of /// media codecs supported by mediasoup and their settings is defined in the /// `supported_rtp_capabilities.rs` file. /// /// Exactly one [`RtpCodecCapabilityFinalized`] will be present for each supported combination of /// parameters that requires a distinct value of `preferred_payload_type`. For example: /// /// - Multiple H264 codecs, each with their own distinct `packetization-mode` and `profile-level-id` /// values. /// - Multiple VP9 codecs, each with their own distinct `profile-id` value. /// /// This is similar to [`RtpCodecCapability`], but with `preferred_payload_type` field being /// required. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(tag = "kind", rename_all = "lowercase")] pub enum RtpCodecCapabilityFinalized { /// Audio codec #[serde(rename_all = "camelCase")] Audio { /// The codec MIME media type/subtype (e.g. 'audio/opus'). mime_type: MimeTypeAudio, /// The preferred RTP payload type. preferred_payload_type: u8, /// Codec clock rate expressed in Hertz. clock_rate: NonZeroU32, /// The number of channels supported (e.g. two for stereo). Just for audio. /// Default 1. channels: NonZeroU8, /// Codec specific parameters. Some parameters (such as `packetization-mode` and /// `profile-level-id` in H264 or `profile-id` in VP9) are critical for codec matching. parameters: RtpCodecParametersParameters, /// Transport layer and codec-specific feedback messages for this codec. rtcp_feedback: Vec, }, /// Video codec #[serde(rename_all = "camelCase")] Video { /// The codec MIME media type/subtype (e.g. 'video/VP8'). mime_type: MimeTypeVideo, /// The preferred RTP payload type. preferred_payload_type: u8, /// Codec clock rate expressed in Hertz. clock_rate: NonZeroU32, /// Codec specific parameters. Some parameters (such as `packetization-mode` and /// `profile-level-id` in H264 or `profile-id` in VP9) are critical for codec matching. parameters: RtpCodecParametersParameters, /// Transport layer and codec-specific feedback messages for this codec. rtcp_feedback: Vec, }, } impl RtpCodecCapabilityFinalized { pub fn is_rtx(&self) -> bool { match self { Self::Audio { mime_type, .. } => mime_type == &MimeTypeAudio::Rtx, Self::Video { mime_type, .. } => mime_type == &MimeTypeVideo::Rtx, } } pub fn clock_rate(&self) -> NonZeroU32 { let (Self::Audio { clock_rate, .. } | Self::Video { clock_rate, .. }) = self; *clock_rate } pub fn parameters(&self) -> &RtpCodecParametersParameters { let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self; parameters } pub fn parameters_mut(&mut self) -> &mut RtpCodecParametersParameters { let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self; parameters } pub fn preferred_payload_type(&self) -> u8 { match self { Self::Audio { preferred_payload_type, .. } | Self::Video { preferred_payload_type, .. } => *preferred_payload_type, } } } /// The RTP capabilities define what mediasoup or an endpoint can receive at media level. #[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RtpCapabilitiesFinalized { /// Supported media and RTX codecs. pub codecs: Vec, /// Supported RTP header extensions. pub header_extensions: Vec, } /// Media kind #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum MediaKind { /// Audio Audio, /// Video Video, } /// Error that caused [`MimeType`] parsing error. #[derive(Debug, Error, Eq, PartialEq)] pub enum ParseMimeTypeError { /// Invalid MIME type input string #[error("Invalid MIME type input string")] InvalidInput, /// Unknown MIME type #[error("Unknown MIME type")] UnknownMimeType, } /// Known Audio or Video MIME type. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(untagged)] pub enum MimeType { /// Audio Audio(MimeTypeAudio), /// Video Video(MimeTypeVideo), } impl FromStr for MimeType { type Err = ParseMimeTypeError; fn from_str(s: &str) -> Result { if s.starts_with("audio/") { MimeTypeAudio::from_str(s).map(Self::Audio) } else if s.starts_with("video/") { MimeTypeVideo::from_str(s).map(Self::Video) } else { Err(ParseMimeTypeError::InvalidInput) } } } impl MimeType { /// String representation of MIME type. pub fn as_str(&self) -> &'static str { match self { Self::Audio(mime_type) => mime_type.as_str(), Self::Video(mime_type) => mime_type.as_str(), } } } /// Known Audio MIME types. #[allow(non_camel_case_types)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)] pub enum MimeTypeAudio { /// Opus #[serde(rename = "audio/opus")] Opus, /// Multi-channel Opus (Surround sound in Chromium) #[serde(rename = "audio/multiopus")] MultiChannelOpus, /// PCMU #[serde(rename = "audio/PCMU")] Pcmu, /// PCMA #[serde(rename = "audio/PCMA")] Pcma, /// ISAC #[serde(rename = "audio/ISAC")] Isac, /// G722 #[serde(rename = "audio/G722")] G722, /// iLBC #[serde(rename = "audio/iLBC")] Ilbc, /// SILK #[serde(rename = "audio/SILK")] Silk, /// CN #[serde(rename = "audio/CN")] Cn, /// TelephoneEvent #[serde(rename = "audio/telephone-event")] TelephoneEvent, /// RTX #[serde(rename = "audio/rtx")] Rtx, /// RED #[serde(rename = "audio/red")] Red, } impl FromStr for MimeTypeAudio { type Err = ParseMimeTypeError; fn from_str(s: &str) -> Result { match s.to_ascii_lowercase().as_str() { "audio/opus" => Ok(Self::Opus), "audio/multiopus" => Ok(Self::MultiChannelOpus), "audio/pcmu" => Ok(Self::Pcmu), "audio/pcma" => Ok(Self::Pcma), "audio/isac" => Ok(Self::Isac), "audio/g722" => Ok(Self::G722), "audio/ilbc" => Ok(Self::Ilbc), "audio/silk" => Ok(Self::Silk), "audio/cn" => Ok(Self::Cn), "audio/telephone-event" => Ok(Self::TelephoneEvent), "audio/rtx" => Ok(Self::Rtx), "audio/red" => Ok(Self::Red), s => Err(if s.starts_with("audio/") { ParseMimeTypeError::UnknownMimeType } else { ParseMimeTypeError::InvalidInput }), } } } impl<'de> Deserialize<'de> for MimeTypeAudio { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; MimeTypeAudio::from_str(&s).map_err(serde::de::Error::custom) } } impl MimeTypeAudio { /// String representation of MIME type. pub fn as_str(&self) -> &'static str { match self { Self::Opus => "audio/opus", Self::MultiChannelOpus => "audio/multiopus", Self::Pcmu => "audio/PCMU", Self::Pcma => "audio/PCMA", Self::Isac => "audio/ISAC", Self::G722 => "audio/G722", Self::Ilbc => "audio/iLBC", Self::Silk => "audio/SILK", Self::Cn => "audio/CN", Self::TelephoneEvent => "audio/telephone-event", Self::Rtx => "audio/rtx", Self::Red => "audio/red", } } } /// Known Video MIME types. #[allow(non_camel_case_types)] #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize)] pub enum MimeTypeVideo { /// VP8 #[serde(rename = "video/VP8")] Vp8, /// VP9 #[serde(rename = "video/VP9")] Vp9, /// H264 #[serde(rename = "video/H264")] H264, /// AV1 #[serde(rename = "video/AV1")] AV1, /// RTX #[serde(rename = "video/rtx")] Rtx, /// RED #[serde(rename = "video/red")] Red, /// ULPFEC #[serde(rename = "video/ulpfec")] Ulpfec, } impl FromStr for MimeTypeVideo { type Err = ParseMimeTypeError; fn from_str(s: &str) -> Result { match s.to_ascii_lowercase().as_str() { "video/vp8" => Ok(Self::Vp8), "video/vp9" => Ok(Self::Vp9), "video/h264" => Ok(Self::H264), "video/av1" => Ok(Self::AV1), "video/rtx" => Ok(Self::Rtx), "video/red" => Ok(Self::Red), "video/ulpfec" => Ok(Self::Ulpfec), s => Err(if s.starts_with("video/") { ParseMimeTypeError::UnknownMimeType } else { ParseMimeTypeError::InvalidInput }), } } } impl<'de> Deserialize<'de> for MimeTypeVideo { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { let s = String::deserialize(deserializer)?; MimeTypeVideo::from_str(&s).map_err(serde::de::Error::custom) } } impl MimeTypeVideo { /// String representation of MIME type. pub fn as_str(&self) -> &'static str { match self { Self::Vp8 => "video/VP8", Self::Vp9 => "video/VP9", Self::H264 => "video/H264", Self::AV1 => "video/AV1", Self::Rtx => "video/rtx", Self::Red => "video/red", Self::Ulpfec => "video/ulpfec", } } } /// Provides information on the capabilities of a codec within the RTP capabilities. The list of /// media codecs supported by mediasoup and their settings is defined in the /// `supported_rtp_capabilities.rs` file. /// /// Exactly one [`RtpCodecCapability`] will be present for each supported combination of parameters /// that requires a distinct value of `preferred_payload_type`. For example: /// /// - Multiple H264 codecs, each with their own distinct `packetization-mode` and `profile-level-id` /// values. /// - Multiple VP9 codecs, each with their own distinct `profile-id` value. /// /// [`RtpCodecCapability`] entries in the `media_codecs` vector of /// [`RouterOptions`](https://docs.rs/mediasoup/latest/mediasoup/router/struct.RouterOptions.html) /// (if unset, mediasoup will choose a random one). If given, make sure it's in the 96-127 range. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(tag = "kind", rename_all = "lowercase")] pub enum RtpCodecCapability { /// Audio codec capability #[serde(rename_all = "camelCase")] Audio { /// The codec MIME media type/subtype (e.g. 'audio/opus'). mime_type: MimeTypeAudio, /// The preferred RTP payload type. #[serde(skip_serializing_if = "Option::is_none")] preferred_payload_type: Option, /// Codec clock rate expressed in Hertz. clock_rate: NonZeroU32, /// The number of channels supported (e.g. two for stereo). Just for audio. /// Default 1. channels: NonZeroU8, /// Codec specific parameters. Some parameters (such as `packetization-mode` and /// `profile-level-id` in H264 or `profile-id` in VP9) are critical for codec matching. #[serde(default)] parameters: RtpCodecParametersParameters, /// Transport layer and codec-specific feedback messages for this codec. #[serde(default)] rtcp_feedback: Vec, }, /// Video codec capability #[serde(rename_all = "camelCase")] Video { /// The codec MIME media type/subtype (e.g. 'video/VP8'). mime_type: MimeTypeVideo, /// The preferred RTP payload type. #[serde(skip_serializing_if = "Option::is_none")] preferred_payload_type: Option, /// Codec clock rate expressed in Hertz. clock_rate: NonZeroU32, /// Codec specific parameters. Some parameters (such as `packetization-mode` and /// `profile-level-id` in H264 or `profile-id` in VP9) are critical for codec matching. #[serde(default)] parameters: RtpCodecParametersParameters, /// Transport layer and codec-specific feedback messages for this codec. #[serde(default)] rtcp_feedback: Vec, }, } impl RtpCodecCapability { pub fn mime_type(&self) -> MimeType { match self { Self::Audio { mime_type, .. } => MimeType::Audio(*mime_type), Self::Video { mime_type, .. } => MimeType::Video(*mime_type), } } pub fn parameters(&self) -> &RtpCodecParametersParameters { let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self; parameters } pub fn parameters_mut(&mut self) -> &mut RtpCodecParametersParameters { let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self; parameters } pub fn preferred_payload_type(&self) -> Option { match self { Self::Audio { preferred_payload_type, .. } | Self::Video { preferred_payload_type, .. } => *preferred_payload_type, } } pub fn rtcp_feedback(&self) -> &Vec { let (Self::Audio { rtcp_feedback, .. } | Self::Video { rtcp_feedback, .. }) = self; rtcp_feedback } } /// The RTP capabilities define what mediasoup or an endpoint can receive at media level. #[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RtpCapabilities { /// Supported media and RTX codecs. pub codecs: Vec, /// Supported RTP header extensions. pub header_extensions: Vec, } /// Direction of RTP header extension. #[derive( Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Default, )] #[serde(rename_all = "lowercase")] pub enum RtpHeaderExtensionDirection { /// SendRecv #[default] SendRecv, /// SendOnly SendOnly, /// RecvOnly RecvOnly, /// Inactive Inactive, } /// Error that caused [`RtpHeaderExtensionUri`] parsing error. #[derive(Debug, Error, Eq, PartialEq)] pub enum RtpHeaderExtensionUriParseError { /// Unsupported #[error("Unsupported")] Unsupported, } /// URI for supported RTP header extension #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] pub enum RtpHeaderExtensionUri { /// urn:ietf:params:rtp-hdrext:sdes:mid #[serde(rename = "urn:ietf:params:rtp-hdrext:sdes:mid")] Mid, /// urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id #[serde(rename = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id")] RtpStreamId, /// urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id #[serde(rename = "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id")] RepairRtpStreamId, /// #[serde(rename = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time")] AbsSendTime, /// #[serde(rename = "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01")] TransportWideCcDraft01, /// urn:ietf:params:rtp-hdrext:ssrc-audio-level #[serde(rename = "urn:ietf:params:rtp-hdrext:ssrc-audio-level")] SsrcAudioLevel, /// #[serde( rename = "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension" )] DependencyDescriptor, /// urn:3gpp:video-orientation #[serde(rename = "urn:3gpp:video-orientation")] VideoOrientation, /// urn:ietf:params:rtp-hdrext:toffset #[serde(rename = "urn:ietf:params:rtp-hdrext:toffset")] TimeOffset, /// #[serde(rename = "http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time")] AbsCaptureTime, /// #[serde(rename = "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay")] PlayoutDelay, /// urn:mediasoup:params:rtp-hdrext:packet-id #[serde(rename = "urn:mediasoup:params:rtp-hdrext:packet-id")] MediasoupPacketId, #[doc(hidden)] #[serde(other, rename = "unsupported")] Unsupported, } impl FromStr for RtpHeaderExtensionUri { type Err = RtpHeaderExtensionUriParseError; fn from_str(s: &str) -> Result { match s { "urn:ietf:params:rtp-hdrext:sdes:mid" => Ok(Self::Mid), "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id" => Ok(Self::RtpStreamId), "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id" => Ok(Self::RepairRtpStreamId), "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" => Ok(Self::AbsSendTime), "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" => { Ok(Self::TransportWideCcDraft01) } "urn:ietf:params:rtp-hdrext:ssrc-audio-level" => Ok(Self::SsrcAudioLevel), "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension" => Ok(Self::DependencyDescriptor), "urn:3gpp:video-orientation" => Ok(Self::VideoOrientation), "urn:ietf:params:rtp-hdrext:toffset" => Ok(Self::TimeOffset), "http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" => { Ok(Self::AbsCaptureTime) } "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" => Ok(Self::PlayoutDelay), "urn:mediasoup:params:rtp-hdrext:packet-id" => Ok(Self::MediasoupPacketId), _ => Err(RtpHeaderExtensionUriParseError::Unsupported), } } } impl RtpHeaderExtensionUri { /// RTP header extension as a string #[must_use] pub fn as_str(self) -> &'static str { match self { RtpHeaderExtensionUri::Mid => "urn:ietf:params:rtp-hdrext:sdes:mid", RtpHeaderExtensionUri::RtpStreamId => "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id", RtpHeaderExtensionUri::RepairRtpStreamId => { "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id" } RtpHeaderExtensionUri::AbsSendTime => { "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" } RtpHeaderExtensionUri::TransportWideCcDraft01 => { "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" } RtpHeaderExtensionUri::SsrcAudioLevel => "urn:ietf:params:rtp-hdrext:ssrc-audio-level", RtpHeaderExtensionUri::DependencyDescriptor => "https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension", RtpHeaderExtensionUri::VideoOrientation => "urn:3gpp:video-orientation", RtpHeaderExtensionUri::TimeOffset => "urn:ietf:params:rtp-hdrext:toffset", RtpHeaderExtensionUri::AbsCaptureTime => { "http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time" } RtpHeaderExtensionUri::PlayoutDelay => { "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay" } RtpHeaderExtensionUri::MediasoupPacketId => { "urn:mediasoup:params:rtp-hdrext:packet-id" } RtpHeaderExtensionUri::Unsupported => "unsupported", } } } /// Provides information relating to supported header extensions. The list of RTP header extensions /// supported by mediasoup is defined in the `supported_rtp_capabilities.rs` file. /// /// mediasoup does not currently support encrypted RTP header extensions. The direction field is /// just present in mediasoup RTP capabilities (retrieved via /// `mediasoup::router::Router::rtp_capabilities()` or /// `mediasoup::supported_rtp_capabilities::get_supported_rtp_capabilities()`. It's ignored if /// present in endpoints' RTP capabilities. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RtpHeaderExtension { /// Media kind. pub kind: MediaKind, /// The URI of the RTP header extension, as defined in RFC 5285. pub uri: RtpHeaderExtensionUri, /// The preferred numeric identifier that goes in the RTP packet. Must be unique. pub preferred_id: u16, /// If true, it is preferred that the value in the header be encrypted as per RFC 6904. /// Default false. pub preferred_encrypt: bool, /// If `SendRecv`, mediasoup supports sending and receiving this RTP extension. `SendOnly` means /// that mediasoup can send (but not receive) it. `RecvOnly` means that mediasoup can receive /// (but not send) it. pub direction: RtpHeaderExtensionDirection, } /// The RTP send parameters describe a media stream received by mediasoup from /// an endpoint through its corresponding mediasoup Producer. These parameters /// may include a mid value that the mediasoup transport will use to match /// received RTP packets based on their MID RTP extension value. /// /// mediasoup allows RTP send parameters with a single encoding and with multiple /// encodings (simulcast). In the latter case, each entry in the encodings array /// must include a ssrc field or a rid field (the RID RTP extension value). Check /// the Simulcast and SVC sections for more information. /// /// The RTP receive parameters describe a media stream as sent by mediasoup to /// an endpoint through its corresponding mediasoup Consumer. The mid value is /// unset (mediasoup does not include the MID RTP extension into RTP packets /// being sent to endpoints). /// /// There is a single entry in the encodings array (even if the corresponding /// producer uses simulcast). The consumer sends a single and continuous RTP /// stream to the endpoint and spatial/temporal layer selection is possible via /// consumer.setPreferredLayers(). /// /// As an exception, previous bullet is not true when consuming a stream over a /// [`PipeTransport`](https://docs.rs/mediasoup/latest/mediasoup/pipe_transport/struct.PipeTransport.html) /// associated producer are forwarded verbatim through the consumer. /// /// The RTP receive parameters will always have their ssrc values randomly /// generated for all of its encodings (and optional rtx: { ssrc: XXXX } if the /// endpoint supports RTX), regardless of the original RTP send parameters in /// the associated producer. This applies even if the producer's encodings have /// rid set. #[derive(Debug, Default, Clone, PartialEq, PartialOrd, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RtpParameters { /// The MID RTP extension value as defined in the BUNDLE specification. #[serde(skip_serializing_if = "Option::is_none")] pub mid: Option, /// Media and RTX codecs in use. pub codecs: Vec, /// RTP header extensions in use. pub header_extensions: Vec, /// Transmitted RTP streams and their settings. pub encodings: Vec, /// Parameters used for RTCP. pub rtcp: RtcpParameters, /// MSID (WebRTC MediaStream Identification) as defined in /// #[serde(skip_serializing_if = "Option::is_none")] pub msid: Option, } /// Single value used in RTP codec parameters. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(untagged)] pub enum RtpCodecParametersParametersValue { /// String value String(Cow<'static, str>), /// Numerical value Number(u32), } impl From> for RtpCodecParametersParametersValue { fn from(s: Cow<'static, str>) -> Self { Self::String(s) } } impl From for RtpCodecParametersParametersValue { fn from(s: String) -> Self { Self::String(s.into()) } } impl From<&'static str> for RtpCodecParametersParametersValue { fn from(s: &'static str) -> Self { Self::String(s.into()) } } impl From for RtpCodecParametersParametersValue { fn from(n: u8) -> Self { Self::Number(u32::from(n)) } } impl From for RtpCodecParametersParametersValue { fn from(n: u16) -> Self { Self::Number(u32::from(n)) } } impl From for RtpCodecParametersParametersValue { fn from(n: u32) -> Self { Self::Number(n) } } /// Provides information on codec settings within the RTP parameters. The list /// of media codecs supported by mediasoup and their settings is defined in the /// `supported_rtp_capabilities.rs` file. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(untagged, rename_all = "lowercase")] pub enum RtpCodecParameters { /// Audio codec #[serde(rename_all = "camelCase")] Audio { /// The codec MIME media type/subtype (e.g. `audio/opus`). mime_type: MimeTypeAudio, /// The value that goes in the RTP Payload Type Field. Must be unique. payload_type: u8, /// Codec clock rate expressed in Hertz. clock_rate: NonZeroU32, /// The number of channels supported (e.g. two for stereo). /// Default 1. channels: NonZeroU8, /// Codec-specific parameters available for signaling. Some parameters (such as /// `packetization-mode` and `profile-level-id` in H264 or `profile-id` in VP9) are critical for /// codec matching. #[serde(default)] parameters: RtpCodecParametersParameters, /// Transport layer and codec-specific feedback messages for this codec. #[serde(default)] rtcp_feedback: Vec, }, /// Video codec #[serde(rename_all = "camelCase")] Video { /// The codec MIME media type/subtype (e.g. `video/VP8`). mime_type: MimeTypeVideo, /// The value that goes in the RTP Payload Type Field. Must be unique. payload_type: u8, /// Codec clock rate expressed in Hertz. clock_rate: NonZeroU32, /// Codec-specific parameters available for signaling. Some parameters (such as /// `packetization-mode` and `profile-level-id` in H264 or `profile-id` in VP9) are critical for /// codec matching. #[serde(default)] parameters: RtpCodecParametersParameters, /// Transport layer and codec-specific feedback messages for this codec. #[serde(default)] rtcp_feedback: Vec, }, } impl RtpCodecParameters { pub fn is_rtx(&self) -> bool { match self { Self::Audio { mime_type, .. } => mime_type == &MimeTypeAudio::Rtx, Self::Video { mime_type, .. } => mime_type == &MimeTypeVideo::Rtx, } } pub fn mime_type(&self) -> MimeType { match self { Self::Audio { mime_type, .. } => MimeType::Audio(*mime_type), Self::Video { mime_type, .. } => MimeType::Video(*mime_type), } } pub fn payload_type(&self) -> u8 { let (Self::Audio { payload_type, .. } | Self::Video { payload_type, .. }) = self; *payload_type } pub fn clock_rate(&self) -> NonZeroU32 { let (Self::Audio { clock_rate, .. } | Self::Video { clock_rate, .. }) = self; *clock_rate } pub fn parameters(&self) -> &RtpCodecParametersParameters { let (Self::Audio { parameters, .. } | Self::Video { parameters, .. }) = self; parameters } pub fn rtcp_feedback(&self) -> &[RtcpFeedback] { let (Self::Audio { rtcp_feedback, .. } | Self::Video { rtcp_feedback, .. }) = self; rtcp_feedback } pub fn rtcp_feedback_mut(&mut self) -> &mut Vec { let (Self::Audio { rtcp_feedback, .. } | Self::Video { rtcp_feedback, .. }) = self; rtcp_feedback } } /// Provides information on RTCP feedback messages for a specific codec. Those messages can be /// transport layer feedback messages or codec-specific feedback messages. The list of RTCP /// feedbacks supported by mediasoup is defined in the `supported_rtp_capabilities.rs` file. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum RtcpFeedback { /// NACK Nack, /// NACK PLI NackPli, /// CCM FIR CcmFir, /// goog-remb GoogRemb, /// transport-cc TransportCc, #[doc(hidden)] Unsupported, } impl Serialize for RtcpFeedback { fn serialize(&self, serializer: S) -> Result where S: Serializer, { let mut rtcp_feedback = serializer.serialize_struct("RtcpFeedback", 2)?; let (r#type, parameter) = self.as_type_parameter(); rtcp_feedback.serialize_field("type", r#type)?; rtcp_feedback.serialize_field("parameter", parameter)?; rtcp_feedback.end() } } impl<'de> Deserialize<'de> for RtcpFeedback { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(field_identifier, rename_all = "lowercase")] enum Field { Type, Parameter, } struct RtcpFeedbackVisitor; impl<'de> Visitor<'de> for RtcpFeedbackVisitor { type Value = RtcpFeedback; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str( r#"RTCP feedback type and parameter like {"type": "nack", "parameter": ""}"#, ) } fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { let mut r#type = None::>; let mut parameter = Cow::Borrowed(""); while let Some(key) = map.next_key()? { match key { Field::Type => { if r#type.is_some() { return Err(de::Error::duplicate_field("type")); } r#type = Some(map.next_value()?); } Field::Parameter => { if !parameter.is_empty() { return Err(de::Error::duplicate_field("parameter")); } parameter = map.next_value()?; } } } let r#type = r#type.ok_or_else(|| de::Error::missing_field("type"))?; Ok( RtcpFeedback::from_type_parameter(r#type.as_ref(), parameter.as_ref()) .unwrap_or(RtcpFeedback::Unsupported), ) } } const FIELDS: &[&str] = &["type", "parameter"]; deserializer.deserialize_struct("RtcpFeedback", FIELDS, RtcpFeedbackVisitor) } } /// Error of failure to create [`RtcpFeedback`] from type and parameter. #[derive(Debug, Error, Eq, PartialEq)] pub enum RtcpFeedbackFromTypeParameterError { /// Unsupported #[error("Unsupported")] Unsupported, } impl RtcpFeedback { pub fn from_type_parameter( r#type: &str, parameter: &str, ) -> Result { match (r#type, parameter) { ("nack", "") => Ok(RtcpFeedback::Nack), ("nack", "pli") => Ok(RtcpFeedback::NackPli), ("ccm", "fir") => Ok(RtcpFeedback::CcmFir), ("goog-remb", "") => Ok(RtcpFeedback::GoogRemb), ("transport-cc", "") => Ok(RtcpFeedback::TransportCc), ("unknown", "") => Ok(RtcpFeedback::Unsupported), _ => Err(RtcpFeedbackFromTypeParameterError::Unsupported), } } pub fn as_type_parameter(&self) -> (&'static str, &'static str) { match self { RtcpFeedback::Nack => ("nack", ""), RtcpFeedback::NackPli => ("nack", "pli"), RtcpFeedback::CcmFir => ("ccm", "fir"), RtcpFeedback::GoogRemb => ("goog-remb", ""), RtcpFeedback::TransportCc => ("transport-cc", ""), RtcpFeedback::Unsupported => ("unknown", ""), } } } /// RTX stream information. It must contain a numeric ssrc field indicating the RTX SSRC. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] pub struct RtpEncodingParametersRtx { /// The media SSRC. pub ssrc: u32, } /// Provides information relating to an encoding, which represents a media RTP /// stream and its associated RTX stream (if any). #[derive(Debug, Default, Clone, PartialEq, PartialOrd, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RtpEncodingParameters { /// The media SSRC. #[serde(skip_serializing_if = "Option::is_none")] pub ssrc: Option, /// The RID RTP extension value. Must be unique. #[serde(skip_serializing_if = "Option::is_none")] pub rid: Option, /// Codec payload type this encoding affects. If unset, first media codec is chosen. #[serde(skip_serializing_if = "Option::is_none")] pub codec_payload_type: Option, /// RTX stream information. It must contain a numeric ssrc field indicating the RTX SSRC. #[serde(skip_serializing_if = "Option::is_none")] pub rtx: Option, /// It indicates whether discontinuous RTP transmission will be used. Useful for audio (if the /// codec supports it) and for video screen sharing (when static content is being transmitted, /// this option disables the RTP inactivity checks in mediasoup). /// Default false. #[serde(skip_serializing_if = "Option::is_none")] pub dtx: Option, /// Number of spatial and temporal layers in the RTP stream. #[serde(default, skip_serializing_if = "ScalabilityMode::is_none")] pub scalability_mode: ScalabilityMode, /// Maximum number of bits per second to allow a track encoded with this encoding to use. #[serde(skip_serializing_if = "Option::is_none")] pub max_bitrate: Option, } /// Defines a RTP header extension within the RTP parameters. The list of RTP /// header extensions supported by mediasoup is defined in the `supported_rtp_capabilities.rs` file. /// /// mediasoup does not currently support encrypted RTP header extensions and no /// parameters are currently considered. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] pub struct RtpHeaderExtensionParameters { /// The URI of the RTP header extension, as defined in RFC 5285. pub uri: RtpHeaderExtensionUri, /// The numeric identifier that goes in the RTP packet. Must be unique. pub id: u16, /// If true, the value in the header is encrypted as per RFC 6904. /// Default false. pub encrypt: bool, // This field is not used by mediasoup currently // /// Configuration parameters for the header extension. // pub parameters: RtpCodecParametersParameters, } /// Provides information on RTCP settings within the RTP parameters. /// /// If no cname is given in a producer's RTP parameters, the mediasoup transport will choose a /// random one that will be used into RTCP SDES messages sent to all its associated consumers. /// /// mediasoup assumes `reduced_size` to always be true. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RtcpParameters { /// The Canonical Name (CNAME) used by RTCP (e.g. in SDES messages). #[serde(skip_serializing_if = "Option::is_none")] pub cname: Option, /// Whether reduced size RTCP RFC 5506 is configured (if true) or compound RTCP /// as specified in RFC 3550 (if false). Default true. pub reduced_size: bool, } impl Default for RtcpParameters { fn default() -> Self { Self { cname: None, reduced_size: true, } } } ================================================ FILE: rust/types/src/scalability_modes/tests.rs ================================================ use super::*; #[test] fn parse_scalability_modes() { let scalability_mode: ScalabilityMode = "L1T3".parse().unwrap(); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(1).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(3).unwrap() ); assert!(!scalability_mode.ksvc()); let scalability_mode: ScalabilityMode = "L3T2_KEY".parse().unwrap(); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(3).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(2).unwrap() ); assert!(scalability_mode.ksvc()); let scalability_mode: ScalabilityMode = "S2T3".parse().unwrap(); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(2).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(3).unwrap() ); assert!(!scalability_mode.ksvc()); assert_eq!( "foo".parse::(), Err(ParseScalabilityModeError::InvalidInput), ); assert_eq!( "ull".parse::(), Err(ParseScalabilityModeError::InvalidInput), ); assert_eq!( "S0T3".parse::(), Err(ParseScalabilityModeError::InvalidInput), ); assert_eq!( "S1T0".parse::(), Err(ParseScalabilityModeError::InvalidInput), ); let scalability_mode: ScalabilityMode = "L20T3".parse().unwrap(); assert!(matches!(scalability_mode, ScalabilityMode::Custom { .. })); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(20).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(3).unwrap() ); assert!(!scalability_mode.ksvc()); assert_eq!( "S200T3".parse::(), Err(ParseScalabilityModeError::InvalidInput), ); let scalability_mode: ScalabilityMode = "L4T7_KEY_SHIFT".parse().unwrap(); assert!(matches!(scalability_mode, ScalabilityMode::Custom { .. })); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(4).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(7).unwrap() ); assert!(scalability_mode.ksvc()); } #[test] fn parse_json_scalability_modes() { let scalability_mode: ScalabilityMode = serde_json::from_str("\"L1T3\"").unwrap(); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(1).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(3).unwrap() ); assert!(!scalability_mode.ksvc()); let scalability_mode: ScalabilityMode = serde_json::from_str("\"L3T2_KEY\"").unwrap(); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(3).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(2).unwrap() ); assert!(scalability_mode.ksvc()); let scalability_mode: ScalabilityMode = serde_json::from_str("\"S2T3\"").unwrap(); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(2).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(3).unwrap() ); assert!(!scalability_mode.ksvc()); assert!(serde_json::from_str::("\"foo\"").is_err()); assert!(serde_json::from_str::("\"ull\"").is_err()); assert!(serde_json::from_str::("\"S0T3\"").is_err()); assert!(serde_json::from_str::("\"S1T0\"").is_err()); let scalability_mode: ScalabilityMode = "L20T3".parse().unwrap(); assert!(matches!(scalability_mode, ScalabilityMode::Custom { .. })); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(20).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(3).unwrap() ); assert!(!scalability_mode.ksvc()); assert!(serde_json::from_str::("\"S200T3\"").is_err()); let scalability_mode: ScalabilityMode = serde_json::from_str("\"L4T7_KEY_SHIFT\"").unwrap(); assert!(matches!(scalability_mode, ScalabilityMode::Custom { .. })); assert_eq!( scalability_mode.spatial_layers(), NonZeroU8::new(4).unwrap() ); assert_eq!( scalability_mode.temporal_layers(), NonZeroU8::new(7).unwrap() ); assert!(scalability_mode.ksvc()); } ================================================ FILE: rust/types/src/scalability_modes.rs ================================================ //! Scalability mode. #[cfg(test)] mod tests; use once_cell::sync::OnceCell; use regex::Regex; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; use std::num::NonZeroU8; use std::str::FromStr; use thiserror::Error; /// Scalability mode. /// /// Most modes match [webrtc-svc](https://w3c.github.io/webrtc-svc/), but custom ones are also /// supported by mediasoup. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] pub enum ScalabilityMode { /// No scalability used, there is just one spatial and one temporal layer. #[default] None, /// L1T2. L1T2, /// L1T2h. L1T2h, /// L1T3. L1T3, /// L1T3h. L1T3h, /// L2T1. L2T1, /// L2T1h. L2T1h, /// L2T1_KEY. L2T1Key, /// L2T2. L2T2, /// L2T2h. L2T2h, /// L2T2_KEY. L2T2Key, /// L2T2_KEY_SHIFT. L2T2KeyShift, /// L2T3. L2T3, /// L2T3h. L2T3h, /// L2T3_KEY. L2T3Key, /// L2T3_KEY_SHIFT. L2T3KeyShift, /// L3T1. L3T1, /// L3T1h. L3T1h, /// L3T1_KEY. L3T1Key, /// L3T2. L3T2, /// L3T2h. L3T2h, /// L3T2_KEY. L3T2Key, /// L3T2_KEY_SHIFT. L3T2KeyShift, /// L3T3. L3T3, /// L3T3h. L3T3h, /// L3T3_KEY. L3T3Key, /// L3T3_KEY_SHIFT. L3T3KeyShift, /// S2T1. S2T1, /// S2T1h. S2T1h, /// S2T2. S2T2, /// S2T2h. S2T2h, /// S2T3. S2T3, /// S2T3h. S2T3h, /// S3T1. S3T1, /// S3T1h. S3T1h, /// S3T2. S3T2, /// S3T2h. S3T2h, /// S3T3. S3T3, /// S3T3h. S3T3h, /// Custom scalability mode not defined in [webrtc-svc](https://w3c.github.io/webrtc-svc/). Custom { /// Scalability mode as string scalability_mode: String, /// Number of spatial layers. spatial_layers: NonZeroU8, /// Number of temporal layers. temporal_layers: NonZeroU8, /// K-SVC mode. ksvc: bool, }, } /// Error that caused [`ScalabilityMode`] parsing error. #[derive(Debug, Error, Eq, PartialEq)] pub enum ParseScalabilityModeError { /// Invalid input string #[error("Invalid Scalability Mode input string")] InvalidInput, } impl FromStr for ScalabilityMode { type Err = ParseScalabilityModeError; fn from_str(scalability_mode: &str) -> Result { Ok(match scalability_mode { "S1T1" => Self::None, "L1T2" => Self::L1T2, "L1T2h" => Self::L1T2h, "L1T3" => Self::L1T3, "L1T3h" => Self::L1T3h, "L2T1" => Self::L2T1, "L2T1h" => Self::L2T1h, "L2T1_KEY" => Self::L2T1Key, "L2T2" => Self::L2T2, "L2T2h" => Self::L2T2h, "L2T2_KEY" => Self::L2T2Key, "L2T2_KEY_SHIFT" => Self::L2T2KeyShift, "L2T3" => Self::L2T3, "L2T3h" => Self::L2T3h, "L2T3_KEY" => Self::L2T3Key, "L2T3_KEY_SHIFT" => Self::L2T3KeyShift, "L3T1" => Self::L3T1, "L3T1h" => Self::L3T1h, "L3T1_KEY" => Self::L3T1Key, "L3T2" => Self::L3T2, "L3T2h" => Self::L3T2h, "L3T2_KEY" => Self::L3T2Key, "L3T2_KEY_SHIFT" => Self::L3T2KeyShift, "L3T3" => Self::L3T3, "L3T3h" => Self::L3T3h, "L3T3_KEY" => Self::L3T3Key, "L3T3_KEY_SHIFT" => Self::L3T3KeyShift, "S2T1" => Self::S2T1, "S2T1h" => Self::S2T1h, "S2T2" => Self::S2T2, "S2T2h" => Self::S2T2h, "S2T3" => Self::S2T3, "S2T3h" => Self::S2T3h, "S3T1" => Self::S3T1, "S3T1h" => Self::S3T1h, "S3T2" => Self::S3T2, "S3T2h" => Self::S3T2h, "S3T3" => Self::S3T3, "S3T3h" => Self::S3T3h, scalability_mode => { static SCALABILITY_MODE_REGEX: OnceCell = OnceCell::new(); SCALABILITY_MODE_REGEX .get_or_init(|| Regex::new(r"^[LS]([1-9][0-9]?)T([1-9][0-9]?)(_KEY)?").unwrap()) .captures(scalability_mode) .map(|captures| Self::Custom { scalability_mode: scalability_mode.to_string(), spatial_layers: captures.get(1).unwrap().as_str().parse().unwrap(), temporal_layers: captures.get(2).unwrap().as_str().parse().unwrap(), ksvc: captures.get(3).is_some(), }) .ok_or(ParseScalabilityModeError::InvalidInput)? } }) } } impl std::fmt::Display for ScalabilityMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.as_str()) } } impl ScalabilityMode { /// Returns true if there is no scalability. pub fn is_none(&self) -> bool { matches!(self, Self::None) } /// Number of spatial layers. pub fn spatial_layers(&self) -> NonZeroU8 { match self { Self::None | Self::L1T2 | Self::L1T2h | Self::L1T3 | Self::L1T3h => { NonZeroU8::new(1).unwrap() } Self::L2T1 | Self::L2T1h | Self::L2T1Key | Self::L2T2 | Self::L2T2h | Self::L2T2Key | Self::L2T2KeyShift | Self::L2T3 | Self::L2T3h | Self::L2T3Key | Self::L2T3KeyShift | Self::S2T1 | Self::S2T1h | Self::S2T2 | Self::S2T2h | Self::S2T3 | Self::S2T3h => NonZeroU8::new(2).unwrap(), Self::L3T1 | Self::L3T1h | Self::L3T1Key | Self::L3T2 | Self::L3T2h | Self::L3T2Key | Self::L3T2KeyShift | Self::L3T3 | Self::L3T3h | Self::L3T3Key | Self::L3T3KeyShift | Self::S3T1 | Self::S3T1h | Self::S3T2 | Self::S3T2h | Self::S3T3 | Self::S3T3h => NonZeroU8::new(3).unwrap(), Self::Custom { spatial_layers, .. } => *spatial_layers, } } /// Number of temporal layers. pub fn temporal_layers(&self) -> NonZeroU8 { match self { Self::None | Self::L2T1 | Self::L2T1h | Self::L2T1Key | Self::L3T1 | Self::L3T1h | Self::L3T1Key | Self::S2T1 | Self::S2T1h | Self::S3T1 | Self::S3T1h => NonZeroU8::new(1).unwrap(), Self::L1T2 | Self::L1T2h | Self::L2T2 | Self::L2T2h | Self::L2T2Key | Self::L2T2KeyShift | Self::L3T2 | Self::L3T2h | Self::L3T2Key | Self::L3T2KeyShift | Self::S2T2 | Self::S2T2h | Self::S3T2 | Self::S3T2h => NonZeroU8::new(2).unwrap(), Self::L1T3 | Self::L1T3h | Self::L2T3 | Self::L2T3h | Self::L2T3Key | Self::L2T3KeyShift | Self::L3T3 | Self::L3T3h | Self::L3T3Key | Self::L3T3KeyShift | Self::S2T3 | Self::S2T3h | Self::S3T3 | Self::S3T3h => NonZeroU8::new(3).unwrap(), Self::Custom { temporal_layers, .. } => *temporal_layers, } } /// K-SVC mode. pub fn ksvc(&self) -> bool { match self { Self::L2T2Key | Self::L2T2KeyShift | Self::L2T3Key | Self::L2T3KeyShift | Self::L3T1Key | Self::L3T2Key | Self::L3T2KeyShift | Self::L3T3Key | Self::L3T3KeyShift => true, Self::Custom { ksvc, .. } => *ksvc, _ => false, } } /// String representation of scalability mode. pub fn as_str(&self) -> &str { match self { Self::None => "S1T1", Self::L1T2 => "L1T2", Self::L1T2h => "L1T2h", Self::L1T3 => "L1T3", Self::L1T3h => "L1T3h", Self::L2T1 => "L2T1", Self::L2T1h => "L2T1h", Self::L2T1Key => "L2T1_KEY", Self::L2T2 => "L2T2", Self::L2T2h => "L2T2h", Self::L2T2Key => "L2T2_KEY", Self::L2T2KeyShift => "L2T2_KEY_SHIFT", Self::L2T3 => "L2T3", Self::L2T3h => "L2T3h", Self::L2T3Key => "L2T3_KEY", Self::L2T3KeyShift => "L2T3_KEY_SHIFT", Self::L3T1 => "L3T1", Self::L3T1h => "L3T1h", Self::L3T1Key => "L3T1_KEY", Self::L3T2 => "L3T2", Self::L3T2h => "L3T2h", Self::L3T2Key => "L3T2_KEY", Self::L3T2KeyShift => "L3T2_KEY_SHIFT", Self::L3T3 => "L3T3", Self::L3T3h => "L3T3h", Self::L3T3Key => "L3T3_KEY", Self::L3T3KeyShift => "L3T3_KEY_SHIFT", Self::S2T1 => "S2T1", Self::S2T1h => "S2T1h", Self::S2T2 => "S2T2", Self::S2T2h => "S2T2h", Self::S2T3 => "S2T3", Self::S2T3h => "S2T3h", Self::S3T1 => "S3T1", Self::S3T1h => "S3T1h", Self::S3T2 => "S3T2", Self::S3T2h => "S3T2h", Self::S3T3 => "S3T3", Self::S3T3h => "S3T3h", Self::Custom { scalability_mode, .. } => scalability_mode, } } } impl<'de> Deserialize<'de> for ScalabilityMode { fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, { struct ScalabilityModeVisitor; impl<'de> de::Visitor<'de> for ScalabilityModeVisitor { type Value = ScalabilityMode; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str(r#"Scalability mode string like "S1T3""#) } #[inline] fn visit_none(self) -> Result where E: de::Error, { Ok(ScalabilityMode::None) } fn visit_str(self, v: &str) -> Result where E: de::Error, { v.parse().map_err(de::Error::custom) } } deserializer.deserialize_str(ScalabilityModeVisitor) } } impl Serialize for ScalabilityMode { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(self.as_str()) } } ================================================ FILE: rust/types/src/sctp_parameters.rs ================================================ //! Collection of SCTP-related data structures that are used to specify SCTP association parameters. use serde::{Deserialize, Serialize}; /// Number of SCTP streams. /// /// Both OS and MIS are part of the SCTP INIT+ACK handshake. OS refers to the initial number of /// outgoing SCTP streams that the server side transport creates (to be used by /// [DataConsumer](https://docs.rs/mediasoup/latest/mediasoup/data_consumer/enum.DataConsumer.html)s), /// while MIS refers to the maximum number of incoming SCTP streams that the server side transport /// can receive (to be used by [DataProducer](https://docs.rs/mediasoup/latest/mediasoup/data_producer/enum.DataProducer.html)s). /// So, if the server side transport will just re used to create data producers (but no data consumers), /// OS can be low (~1). However, if data consumers are desired on the server side transport, OS must /// have a proper value and such a proper value depends on whether the remote endpoint supports /// `SCTP_ADD_STREAMS` extension or not. /// /// libwebrtc (Chrome, Safari, etc) does not enable `SCTP_ADD_STREAMS` so, if data consumers are /// required, OS should be 1024 (the maximum number of DataChannels that libwebrtc enables). /// /// Firefox does enable `SCTP_ADD_STREAMS` so, if data consumers are required, OS can be lower (16 /// for instance). The mediasoup transport will allocate and announce more outgoing SCTP streams /// when needed. /// /// mediasoup-client provides specific per browser/version OS and MIS values via the /// device.sctpCapabilities getter. #[derive(Debug, Serialize, Copy, Clone)] pub struct NumSctpStreams { /// Initially requested number of outgoing SCTP streams. #[serde(rename = "OS")] pub os: u16, /// Maximum number of incoming SCTP streams. #[serde(rename = "MIS")] pub mis: u16, } impl Default for NumSctpStreams { fn default() -> Self { Self { os: 1024, mis: 1024, } } } /// Parameters of the SCTP association. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SctpParameters { /// Must always equal 5000. pub port: u16, /// Initially requested number of outgoing SCTP streams. #[serde(rename = "OS")] pub os: u16, /// Maximum number of incoming SCTP streams. #[serde(rename = "MIS")] pub mis: u16, /// Maximum allowed size for SCTP messages. pub max_message_size: u32, } /// SCTP stream parameters describe the reliability of a certain SCTP stream. /// /// If ordered is true then `max_packet_life_time` and `max_retransmits` must be `false`. /// If ordered if false, only one of `max_packet_life_time` or max_retransmits can be `true`. #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SctpStreamParameters { /// SCTP stream id. pub stream_id: u16, /// Whether data messages must be received in order. If `true` the messages will be sent /// reliably. /// Default true. pub ordered: bool, /// When `ordered` is `false` indicates the time (in milliseconds) after which a SCTP packet /// will stop being retransmitted. #[serde(skip_serializing_if = "Option::is_none")] pub max_packet_life_time: Option, /// When `ordered` is `false` indicates the maximum number of times a packet will be /// retransmitted. #[serde(skip_serializing_if = "Option::is_none")] pub max_retransmits: Option, } impl SctpStreamParameters { /// SCTP stream id. #[must_use] pub fn stream_id(&self) -> u16 { self.stream_id } /// Whether data messages must be received in order. If `true` the messages will be sent /// reliably. #[must_use] pub fn ordered(&self) -> bool { self.ordered } /// When `ordered` is `false` indicates the time (in milliseconds) after which a SCTP packet /// will stop being retransmitted. #[must_use] pub fn max_packet_life_time(&self) -> Option { self.max_packet_life_time } /// When `ordered` is `false` indicates the maximum number of times a packet will be /// retransmitted. #[must_use] pub fn max_retransmits(&self) -> Option { self.max_retransmits } } impl SctpStreamParameters { /// Messages will be sent reliably in order. #[must_use] pub fn new_ordered(stream_id: u16) -> Self { Self { stream_id, ordered: true, max_packet_life_time: None, max_retransmits: None, } } /// Messages will be sent unreliably with time (in milliseconds) after which a SCTP packet will /// stop being retransmitted. #[must_use] pub fn new_unordered_with_life_time(stream_id: u16, max_packet_life_time: u16) -> Self { Self { stream_id, ordered: false, max_packet_life_time: Some(max_packet_life_time), max_retransmits: None, } } /// Messages will be sent unreliably with a limited number of retransmission attempts. #[must_use] pub fn new_unordered_with_retransmits(stream_id: u16, max_retransmits: u16) -> Self { Self { stream_id, ordered: false, max_packet_life_time: None, max_retransmits: Some(max_retransmits), } } } ================================================ FILE: rust/types/src/srtp_parameters.rs ================================================ //! Collection of SRTP-related data structures that are used to specify SRTP encryption/decryption //! parameters. use serde::{Deserialize, Serialize}; /// SRTP parameters. #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SrtpParameters { /// Encryption and authentication transforms to be used. pub crypto_suite: SrtpCryptoSuite, /// SRTP keying material (master key and salt) in Base64. pub key_base64: String, } /// SRTP crypto suite. #[derive( Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deserialize, Serialize, Default, )] pub enum SrtpCryptoSuite { /// AEAD_AES_256_GCM #[serde(rename = "AEAD_AES_256_GCM")] AeadAes256Gcm, /// AEAD_AES_128_GCM #[serde(rename = "AEAD_AES_128_GCM")] AeadAes128Gcm, /// AES_CM_128_HMAC_SHA1_80 #[serde(rename = "AES_CM_128_HMAC_SHA1_80")] #[default] AesCm128HmacSha180, /// AES_CM_128_HMAC_SHA1_32 #[serde(rename = "AES_CM_128_HMAC_SHA1_32")] AesCm128HmacSha132, } ================================================ FILE: rust-toolchain.toml ================================================ [toolchain] channel = "1.94.1" components = ["rustfmt", "clippy"] ================================================ FILE: tsconfig.json ================================================ { "compileOnSave": true, "compilerOptions": { "rootDir": "node/src", "outDir": "node/lib", "target": "ES2024", "lib": ["ES2024"], "types": ["node", "jest"], "module": "NodeNext", "moduleResolution": "NodeNext", "declaration": true, "declarationMap": true, "declarationDir": "node/lib", "isolatedModules": true, "allowUnreachableCode": false, "allowUnusedLabels": false, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true, "noImplicitReturns": true, "noPropertyAccessFromIndexSignature": true, "noUncheckedIndexedAccess": true, "noUncheckedSideEffectImports": true, "strict": true }, "include": ["node/src"] } ================================================ FILE: worker/.clang-format ================================================ Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: AlwaysBreak AlignArrayOfStructures: Left AlignConsecutiveAssignments: Consecutive AlignConsecutiveDeclarations: None AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLambdasOnASingleLine: None AllowShortLoopsOnASingleLine: false AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes BinPackArguments: false BinPackParameters: false # NOTE: This doesn't do anything because it requires Cpp11BracedListStyle: true, # which we don't want. BreakAfterOpenBracketBracedList: true BraceWrapping: AfterClass: true AfterControlStatement: Always AfterEnum: true AfterFunction: true AfterNamespace: true AfterStruct: true AfterUnion: true AfterCaseLabel: true AfterExternBlock: true BeforeCatch: true BeforeElse: true IndentBraces: false BreakAfterReturnType: Automatic BreakBeforeBraces: Allman BreakBeforeBinaryOperators: None BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakStringLiterals: false ColumnLimit: 100 QualifierAlignment: Left CommentPragmas: 'NOLINT' ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 2 ContinuationIndentWidth: 2 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: true FixNamespaceComments: true IncludeCategories: - Regex: '"common.hpp"' Priority: 1 - Regex: '^"(Channel|PayloadChannel|FBS|RTC|Utils|handles)/' Priority: 3 - Regex: '"*"' Priority: 2 - Regex: '^<(flatbuffers|uv|openssl|srtp|usrsctp|libwebrtc)(.|/)' Priority: 4 - Regex: '<*>' Priority: 5 IncludeIsMainRegex: '$' IndentCaseLabels: true IndentWidth: 2 IndentWrappedFunctionNames: false InsertBraces: true KeepEmptyLinesAtTheStartOfBlocks: false MaxEmptyLinesToKeep: 1 NamespaceIndentation: All PenaltyBreakBeforeFirstCallParameter: 5 PenaltyBreakComment: 100 PenaltyBreakFirstLessLess: 200 PenaltyBreakString: 20 PenaltyExcessCharacter: 10 PenaltyReturnTypeOnItsOwnLine: 1000 PointerAlignment: Left ReflowComments: Always SortIncludes: CaseSensitive SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInCStyleCastParentheses: false SpacesInContainerLiterals: true SpacesInParentheses: false SpacesInSquareBrackets: false Standard: c++11 TabWidth: 2 UseTab: ForIndentation ================================================ FILE: worker/.clang-tidy ================================================ --- Checks: "*,\ -altera*,\ -android*,\ -boost-use-to-string,\ -boost-use-ranges,\ -bugprone-branch-clone,\ -bugprone-easily-swappable-parameters,\ -bugprone-implicit-widening-of-multiplication-result,\ -bugprone-lambda-function-name,\ -bugprone-macro-parentheses,\ -bugprone-narrowing-conversions,\ -bugprone-reserved-identifier,\ -cert-*,\ -clang-analyzer-optin.osx.*,\ -clang-analyzer-osx.*,\ -concurrency-mt-unsafe,\ -cppcoreguidelines-avoid-c-arrays,\ -cppcoreguidelines-avoid-do-while,\ -cppcoreguidelines-avoid-goto,\ -cppcoreguidelines-avoid-non-const-global-variables,\ -cppcoreguidelines-avoid-magic-numbers,\ -cppcoreguidelines-init-variables,\ -cppcoreguidelines-narrowing-conversions,\ -cppcoreguidelines-no-malloc,\ -cppcoreguidelines-non-private-member-variables-in-classes,\ -cppcoreguidelines-owning-memory,\ -cppcoreguidelines-prefer-member-initializer,\ -cppcoreguidelines-pro-bounds-array-to-pointer-decay,\ -cppcoreguidelines-pro-bounds-constant-array-index,\ -cppcoreguidelines-pro-bounds-pointer-arithmetic,\ -cppcoreguidelines-pro-type-const-cast,\ -cppcoreguidelines-pro-type-reinterpret-cast,\ -cppcoreguidelines-pro-type-static-cast-downcast,\ -cppcoreguidelines-pro-type-union-access,\ -cppcoreguidelines-pro-type-vararg,\ -cppcoreguidelines-special-member-functions,\ -hicpp-exception-baseclass,\ -hicpp-no-malloc,\ -hicpp-function-size,\ -fuchsia-default-arguments-calls,\ -fuchsia-default-arguments-declarations,\ -fuchsia-overloaded-operator,\ -fuchsia-statically-constructed-objects,\ -google-build-using-namespace,\ -google-default-arguments,\ -google-readability-*,\ -google-runtime-int,\ -google-upgrade-googletest-case,\ -hicpp-avoid-c-arrays,\ -hicpp-avoid-goto,\ -hicpp-braces-around-statements,\ -hicpp-no-array-decay,\ -hicpp-signed-bitwise,\ -hicpp-special-member-functions,\ -hicpp-uppercase-literal-suffix,\ -hicpp-vararg,\ -llvm-include-order,\ -llvm-header-guard,\ -llvm-else-after-return,\ -llvm-prefer-static-over-anonymous-namespace,\ -llvmlibc-*,\ -misc-confusable-identifiers,\ -misc-include-cleaner,\ -misc-non-private-member-variables-in-classes,\ -misc-use-anonymous-namespace,\ -modernize-avoid-c-arrays,\ -modernize-concat-nested-namespaces,\ -modernize-make-unique,\ -modernize-pass-by-value,\ -modernize-use-nodiscard, \ -modernize-use-trailing-return-type,\ -performance-avoid-endl,\ -performance-no-int-to-ptr,\ -portability-template-virtual-member-function,\ -readability-convert-member-functions-to-static,\ -readability-else-after-return,\ -readability-function-cognitive-complexity,\ -readability-function-size,\ -readability-identifier-length,\ -readability-implicit-bool-conversion,\ -readability-magic-numbers,\ -readability-redundant-access-specifiers, \ -readability-simplify-boolean-expr,\ -readability-uppercase-literal-suffix,\ " HeaderFilterRegex: '.*/worker/(include|test/include|fuzzer/include)/.*' FormatStyle: 'file' User: mediasoup CheckOptions: - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader value: '' - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle value: '0' - key: cppcoreguidelines-macro-usage.AllowedRegexp value: ^MS_* - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries value: '1' - key: modernize-loop-convert.MaxCopySize value: '16' - key: modernize-loop-convert.MinConfidence value: reasonable - key: modernize-loop-convert.NamingStyle value: CamelCase - key: modernize-replace-auto-ptr.IncludeStyle value: llvm - key: modernize-use-nullptr.NullMacros value: 'NULL' - key: readability-braces-around-statements.ShortStatementLines value: '3' - key: readability-identifier-naming.AbstractClassCase value: CamelCase - key: readability-identifier-naming.AbstractClassPrefix value: '' - key: readability-identifier-naming.AbstractClassSuffix value: '' - key: readability-identifier-naming.ClassCase value: CamelCase - key: readability-identifier-naming.ClassConstantCase value: CamelCase - key: readability-identifier-naming.ClassConstantPrefix value: '' - key: readability-identifier-naming.ClassConstantSuffix value: '' - key: readability-identifier-naming.ClassMemberCase value: camelBack - key: readability-identifier-naming.ClassMemberPrefix value: '' - key: readability-identifier-naming.ClassMemberSuffix value: '' - key: readability-identifier-naming.ClassMethodCase value: CamelCase - key: readability-identifier-naming.ClassMethodPrefix value: '' - key: readability-identifier-naming.ClassMethodSuffix value: '' - key: readability-identifier-naming.ClassPrefix value: '' - key: readability-identifier-naming.ClassSuffix value: '' - key: readability-identifier-naming.ConstantCase value: UPPER_CASE - key: readability-identifier-naming.ConstantMemberCase value: camelBack - key: readability-identifier-naming.ConstantMemberPrefix value: '' - key: readability-identifier-naming.ConstantMemberSuffix value: '' - key: readability-identifier-naming.ConstantParameterCase value: camelBack - key: readability-identifier-naming.ConstantParameterPrefix value: '' - key: readability-identifier-naming.ConstantParameterSuffix value: '' - key: readability-identifier-naming.ConstantPrefix value: '' - key: readability-identifier-naming.ConstantSuffix value: '' - key: readability-identifier-naming.ConstexprFunctionCase value: camelBack - key: readability-identifier-naming.ConstexprFunctionPrefix value: '' - key: readability-identifier-naming.ConstexprFunctionSuffix value: '' - key: readability-identifier-naming.ConstexprMethodCase value: camelBack - key: readability-identifier-naming.ConstexprMethodPrefix value: '' - key: readability-identifier-naming.ConstexprMethodSuffix value: '' - key: readability-identifier-naming.ConstexprVariableCase value: CamelCase - key: readability-identifier-naming.ConstexprVariablePrefix value: '' - key: readability-identifier-naming.ConstexprVariableSuffix value: '' - key: readability-identifier-naming.EnumCase value: aNy_CasE - key: readability-identifier-naming.EnumConstantCase value: aNy_CasE - key: readability-identifier-naming.EnumConstantPrefix value: '' - key: readability-identifier-naming.EnumConstantSuffix value: '' - key: readability-identifier-naming.EnumPrefix value: '' - key: readability-identifier-naming.EnumSuffix value: '' - key: readability-identifier-naming.FunctionCase value: camelBack - key: readability-identifier-naming.FunctionPrefix value: '' - key: readability-identifier-naming.FunctionSuffix value: '' - key: readability-identifier-naming.GlobalConstantCase value: CamelCase - key: readability-identifier-naming.GlobalConstantPrefix value: '' - key: readability-identifier-naming.GlobalConstantSuffix value: '' - key: readability-identifier-naming.GlobalFunctionCase value: CamelCase - key: readability-identifier-naming.GlobalFunctionPrefix value: '' - key: readability-identifier-naming.GlobalFunctionSuffix value: '' - key: readability-identifier-naming.GlobalVariableCase value: CamelCase - key: readability-identifier-naming.GlobalVariablePrefix value: '' - key: readability-identifier-naming.GlobalVariableSuffix value: '' - key: readability-identifier-naming.IgnoreFailedSplit value: '0' - key: readability-identifier-naming.InlineNamespaceCase value: aNy_CasE - key: readability-identifier-naming.InlineNamespacePrefix value: '' - key: readability-identifier-naming.InlineNamespaceSuffix value: '' - key: readability-identifier-naming.LocalConstantCase value: camelBack - key: readability-identifier-naming.LocalConstantPrefix value: '' - key: readability-identifier-naming.LocalConstantSuffix value: '' - key: readability-identifier-naming.LocalVariableCase value: camelBack - key: readability-identifier-naming.LocalVariablePrefix value: '' - key: readability-identifier-naming.LocalVariableSuffix value: '' - key: readability-identifier-naming.MemberCase value: camelBack - key: readability-identifier-naming.MemberPrefix value: '' - key: readability-identifier-naming.MemberSuffix value: '' - key: readability-identifier-naming.MethodCase value: CamelCase - key: readability-identifier-naming.MethodPrefix value: '' - key: readability-identifier-naming.MethodSuffix value: '' - key: readability-identifier-naming.NamespaceCase value: aNy_CasE - key: readability-identifier-naming.NamespacePrefix value: '' - key: readability-identifier-naming.NamespaceSuffix value: '' - key: readability-identifier-naming.ParameterCase value: camelBack - key: readability-identifier-naming.ParameterPackCase value: camelBack - key: readability-identifier-naming.ParameterPackPrefix value: '' - key: readability-identifier-naming.ParameterPackSuffix value: '' - key: readability-identifier-naming.ParameterPrefix value: '' - key: readability-identifier-naming.ParameterSuffix value: '' - key: readability-identifier-naming.PrivateMemberCase value: camelBack - key: readability-identifier-naming.PrivateMemberPrefix value: '' - key: readability-identifier-naming.PrivateMemberSuffix value: '' - key: readability-identifier-naming.PrivateMethodCase value: CamelCase - key: readability-identifier-naming.PrivateMethodPrefix value: '' - key: readability-identifier-naming.PrivateMethodSuffix value: '' - key: readability-identifier-naming.ProtectedMemberCase value: camelBack - key: readability-identifier-naming.ProtectedMemberPrefix value: '' - key: readability-identifier-naming.ProtectedMemberSuffix value: '' - key: readability-identifier-naming.ProtectedMethodCase value: CamelCase - key: readability-identifier-naming.ProtectedMethodPrefix value: '' - key: readability-identifier-naming.ProtectedMethodSuffix value: '' - key: readability-identifier-naming.PublicMemberCase value: camelBack - key: readability-identifier-naming.PublicMemberPrefix value: '' - key: readability-identifier-naming.PublicMemberSuffix value: '' - key: readability-identifier-naming.PublicMethodCase value: CamelCase - key: readability-identifier-naming.PublicMethodPrefix value: '' - key: readability-identifier-naming.PublicMethodSuffix value: '' - key: readability-identifier-naming.StaticConstantCase value: CamelCase - key: readability-identifier-naming.StaticConstantPrefix value: '' - key: readability-identifier-naming.StaticConstantSuffix value: '' - key: readability-identifier-naming.StaticVariableCase value: camelBack - key: readability-identifier-naming.StaticVariablePrefix value: '' - key: readability-identifier-naming.StaticVariableSuffix value: '' - key: readability-identifier-naming.StructCase value: CamelCase - key: readability-identifier-naming.StructPrefix value: '' - key: readability-identifier-naming.StructSuffix value: '' - key: readability-identifier-naming.TemplateParameterCase value: CamelCase - key: readability-identifier-naming.TemplateParameterPrefix value: '' - key: readability-identifier-naming.TemplateParameterSuffix value: '' - key: readability-identifier-naming.TemplateTemplateParameterCase value: CamelCase - key: readability-identifier-naming.TemplateTemplateParameterPrefix value: '' - key: readability-identifier-naming.TemplateTemplateParameterSuffix value: '' - key: readability-identifier-naming.TypeTemplateParameterCase value: CamelCase - key: readability-identifier-naming.TypeTemplateParameterPrefix value: '' - key: readability-identifier-naming.TypeTemplateParameterSuffix value: '' - key: readability-identifier-naming.TypedefCase value: CamelCase - key: readability-identifier-naming.TypedefPrefix value: '' - key: readability-identifier-naming.TypedefSuffix value: '' - key: readability-identifier-naming.UnionCase value: CamelCase - key: readability-identifier-naming.UnionPrefix value: '' - key: readability-identifier-naming.UnionSuffix value: '' - key: readability-identifier-naming.ValueTemplateParameterCase value: CamelCase - key: readability-identifier-naming.ValueTemplateParameterPrefix value: '' - key: readability-identifier-naming.ValueTemplateParameterSuffix value: '' - key: readability-identifier-naming.VariableCase value: camelBack - key: readability-identifier-naming.VariablePrefix value: '' - key: readability-identifier-naming.VariableSuffix value: '' - key: readability-identifier-naming.VirtualMethodCase value: CamelCase - key: readability-identifier-naming.VirtualMethodPrefix value: '' - key: readability-identifier-naming.VirtualMethodSuffix value: '' - key: readability-simplify-boolean-expr.ChainedConditionalAssignment value: '1' - key: readability-simplify-boolean-expr.ChainedConditionalReturn value: '1' ================================================ FILE: worker/.clangd ================================================ CompileFlags: CompilationDatabase: "out/Release/build" CompletionOptions: HeaderInsertion: Never ================================================ FILE: worker/Cargo.toml ================================================ [package] name = "mediasoup-sys" version = "0.11.0" description = "FFI bindings to C++ libmediasoup-worker" authors = [ "Nazar Mokrynskyi ", "José Luis Millán ", "Iñaki Baz Castillo " ] edition = "2021" license = "ISC" documentation = "https://docs.rs/mediasoup-sys" repository = "https://github.com/versatica/mediasoup" include = [ "/deps/libwebrtc", "/fbs", "/fuzzer/include", "/fuzzer/src", "/include", "/scripts", "!/scripts/node_modules", "/src", "/subprojects/*.wrap", "/test/include", "/test/src", "/build.rs", "/Cargo.toml", "/meson.build", "/meson_options.txt", "/tasks.py" ] [package.metadata.docs.rs] default-target = "x86_64-unknown-linux-gnu" targets = [] [build-dependencies] planus-codegen = "0.4.0" planus-translation = "0.4.0" [dependencies] planus = "0.4.0" [dependencies.serde] features = ["derive"] version = "1.0.190" ================================================ FILE: worker/Dockerfile ================================================ FROM ubuntu:26.04 # Install dependencies. RUN set -x \ && apt-get update \ && apt-get install --yes \ clang-21 make pkg-config bash-completion wget curl git screen python3-pip python3-yaml \ zlib1g-dev libgss-dev libssl-dev libxml2-dev gdb procps file clang-format-22 clang-tidy-21 # Install node 24. RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \ && apt-get install --yes nodejs # Enable core dumps. RUN set -x \ && echo "mkdir -p /tmp/cores && chmod 777 /tmp/cores && echo \"/tmp/cores/core.%e.sig%s.%p\" > /proc/sys/kernel/core_pattern && ulimit -c unlimited" >> ~/.bashrc # Make CC and CXX point to clang/clang++ installed above. ENV LANG="C.UTF-8" ENV CC="clang-21" ENV CXX="clang++-21" ENV MEDIASOUP_LOCAL_DEV="true" ENV KEEP_BUILD_ARTIFACTS="1" WORKDIR "/foo bar/mediasoup" CMD ["bash"] ================================================ FILE: worker/Dockerfile.386 ================================================ # # This container installs a 32-bits Linux Debian. # FROM debian:bookworm # Install dependencies. RUN set -x \ && apt-get update \ && apt-get install --yes \ make pkg-config bash-completion wget curl git screen python3-pip python3-yaml \ zlib1g-dev libgss-dev libssl-dev libxml2-dev gdb nodejs npm # Enable core dumps. RUN set -x \ && echo "mkdir -p /tmp/cores && chmod 777 /tmp/cores && echo \"/tmp/cores/core.%e.sig%s.%p\" > /proc/sys/kernel/core_pattern && ulimit -c unlimited" >> ~/.bashrc ENV LANG="C.UTF-8" ENV MEDIASOUP_LOCAL_DEV="true" ENV KEEP_BUILD_ARTIFACTS="1" WORKDIR "/foo bar/mediasoup" CMD ["bash"] ================================================ FILE: worker/Dockerfile.alpine ================================================ FROM alpine # Install dependencies. RUN set -x \ && apk add gcc g++ make nodejs-current npm python3 py3-pip linux-headers # Make CC and CXX point to gcc/g++. ENV LANG="C.UTF-8" ENV CC="gcc" ENV CXX="g++" ENV MEDIASOUP_LOCAL_DEV="true" ENV KEEP_BUILD_ARTIFACTS="1" WORKDIR "/foo bar/mediasoup" CMD ["ash"] ================================================ FILE: worker/Makefile ================================================ # # make tasks for mediasoup-worker. # # NOTE: This Makefile is a proxy to pip invoke commands (see tasks.py). # PYTHON ?= $(shell command -v python3 2> /dev/null || echo python) PIP_INVOKE_DIR = $(shell pwd)/pip_invoke # Instruct Python where to look for invoke module. ifeq ($(OS),Windows_NT) export PYTHONPATH := $(PIP_INVOKE_DIR);${PYTHONPATH} else export PYTHONPATH := $(PIP_INVOKE_DIR):${PYTHONPATH} endif .PHONY: \ default \ invoke \ meson-ninja \ setup \ clean \ clean-build \ clean-pip \ clean-subprojects \ clean-all \ update-wrap-file \ mediasoup-worker \ libmediasoup-worker \ flatc \ xcode \ lint \ format \ tidy \ tidy-fix \ test \ test-asan-address \ test-asan-undefined \ fuzzer \ fuzzer-run-all \ docker \ docker-run \ docker-alpine \ docker-alpine-run \ docker-386 \ docker-386-run default: mediasoup-worker invoke: ifeq ($(wildcard $(PIP_INVOKE_DIR)),) # Install pip invoke into custom location, so we don't depend on system-wide # installation. "$(PYTHON)" -m pip install --upgrade --no-user --target "$(PIP_INVOKE_DIR)" invoke endif meson-ninja: invoke "$(PYTHON)" -m invoke meson-ninja setup: invoke "$(PYTHON)" -m invoke setup clean: invoke "$(PYTHON)" -m invoke clean clean-build: invoke "$(PYTHON)" -m invoke clean-build clean-pip: invoke "$(PYTHON)" -m invoke clean-pip clean-subprojects: invoke "$(PYTHON)" -m invoke clean-subprojects clean-all: invoke "$(PYTHON)" -m invoke clean-all # It requires the SUBPROJECT environment variable. update-wrap-file: invoke "$(PYTHON)" -m invoke subprojects $(SUBPROJECT) mediasoup-worker: invoke "$(PYTHON)" -m invoke mediasoup-worker libmediasoup-worker: invoke "$(PYTHON)" -m invoke libmediasoup-worker flatc: invoke "$(PYTHON)" -m invoke flatc xcode: invoke "$(PYTHON)" -m invoke xcode lint: invoke "$(PYTHON)" -m invoke lint format: invoke "$(PYTHON)" -m invoke format tidy: invoke "$(PYTHON)" -m invoke tidy tidy-fix: invoke "$(PYTHON)" -m invoke tidy-fix test: invoke "$(PYTHON)" -m invoke test test-asan-address: invoke "$(PYTHON)" -m invoke test-asan-address test-asan-undefined: invoke "$(PYTHON)" -m invoke test-asan-undefined fuzzer: invoke "$(PYTHON)" -m invoke fuzzer fuzzer-run-all: invoke "$(PYTHON)" -m invoke fuzzer-run-all docker: invoke "$(PYTHON)" -m invoke docker docker-run: invoke "$(PYTHON)" -m invoke docker-run docker-alpine: invoke "$(PYTHON)" -m invoke docker-alpine docker-alpine-run: invoke "$(PYTHON)" -m invoke docker-alpine-run docker-386: invoke "$(PYTHON)" -m invoke docker-386 docker-386-run: invoke "$(PYTHON)" -m invoke docker-386-run ================================================ FILE: worker/build.rs ================================================ use std::process::Command; use std::{env, fs}; fn main() { // On Windows Rust always links against release version of MSVC runtime, thus requires // Release build here let build_type = if cfg!(all(debug_assertions, not(windows))) { "Debug" } else { "Release" }; let out_dir = env::var("OUT_DIR").unwrap(); // Compile Rust flatbuffers let flatbuffers_declarations = planus_translation::translate_files( &fs::read_dir("fbs") .expect("Failed to read `fbs` directory") .filter_map(|maybe_entry| { maybe_entry .map(|entry| { let path = entry.path(); if path.extension() == Some("fbs".as_ref()) { Some(path) } else { None } }) .transpose() }) .collect::, _>>() .expect("Failed to collect flatbuffers files"), ) .expect("Failed to translate flatbuffers files"); fs::write( format!("{out_dir}/fbs.rs"), planus_codegen::generate_rust(&flatbuffers_declarations) .expect("Failed to generate Rust code from flatbuffers"), ) .expect("Failed to write generated Rust flatbuffers into fbs.rs"); if env::var("DOCS_RS").is_ok() { // Skip everything when building docs on docs.rs return; } // Force forward slashes on Windows too so that is plays well with our tasks.py let mediasoup_out_dir = format!("{}/out", out_dir.replace('\\', "/")); // Add C++ std lib #[cfg(target_os = "linux")] { let path = Command::new(env::var("CXX").unwrap_or_else(|_| "c++".to_string())) .arg("--print-file-name=libstdc++.a") .output() .expect("Failed to start") .stdout; println!( "cargo:rustc-link-search=native={}", String::from_utf8_lossy(&path) .trim() .strip_suffix("libstdc++.a") .expect("Failed to strip suffix"), ); println!("cargo:rustc-link-lib=static=stdc++"); } #[cfg(any( target_os = "freebsd", target_os = "dragonfly", target_os = "openbsd", target_os = "netbsd" ))] { let path = Command::new(env::var("CXX").unwrap_or_else(|_| "c++".to_string())) .arg("--print-file-name=libc++.a") .output() .expect("Failed to start") .stdout; println!( "cargo:rustc-link-search=native={}", String::from_utf8_lossy(&path) .trim() .strip_suffix("libc++.a") .expect("Failed to strip suffix"), ); println!("cargo:rustc-link-lib=static=c++"); } #[cfg(target_os = "macos")] { let path = Command::new("xcrun") .arg("--show-sdk-path") .output() .expect("Failed to start") .stdout; let libpath = format!( "{}/usr/lib", String::from_utf8(path) .expect("Failed to decode path") .trim() ); println!("cargo:rustc-link-search={libpath}"); println!("cargo:rustc-link-lib=dylib=c++"); println!("cargo:rustc-link-lib=dylib=c++abi"); } // Install Python invoke package in custom folder let pip_invoke_dir = format!("{out_dir}/pip_invoke"); let python = env::var("PYTHON").unwrap_or("python3".to_string()); let mut pythonpath = if let Ok(original_pythonpath) = env::var("PYTHONPATH") { format!("{pip_invoke_dir}:{original_pythonpath}") } else { pip_invoke_dir.clone() }; // Force ";" in PYTHONPATH on Windows if cfg!(target_os = "windows") { pythonpath = pythonpath.replace(':', ";"); } if !Command::new(&python) .arg("-m") .arg("pip") .arg("install") .arg("--upgrade") .arg("--target") .arg(pip_invoke_dir) .arg("invoke") .spawn() .expect("Failed to start") .wait() .expect("Wasn't running") .success() { panic!("Failed to install Python invoke package") } // Build if !Command::new(&python) .arg("-m") .arg("invoke") .arg("libmediasoup-worker") .env("PYTHONPATH", &pythonpath) .env("MEDIASOUP_OUT_DIR", &mediasoup_out_dir) .env("MEDIASOUP_BUILDTYPE", build_type) // Force forward slashes on Windows too, otherwise Meson thinks path is not absolute 🤷 .env("MEDIASOUP_INSTALL_DIR", out_dir.replace('\\', "/")) .spawn() .expect("Failed to start") .wait() .expect("Wasn't running") .success() { panic!("Failed to build libmediasoup-worker") } #[cfg(target_os = "windows")] { let dot_a = format!("{out_dir}/libmediasoup-worker.a"); let dot_lib = format!("{out_dir}/mediasoup-worker.lib"); // Meson builds `libmediasoup-worker.a` on Windows instead of `*.lib` file under MinGW if std::path::Path::new(&dot_a).exists() { std::fs::copy(&dot_a, &dot_lib).unwrap_or_else(|error| { panic!("Failed to copy static library from {dot_a} to {dot_lib}: {error}"); }); } // These are required by libuv on Windows println!("cargo:rustc-link-lib=psapi"); println!("cargo:rustc-link-lib=user32"); println!("cargo:rustc-link-lib=advapi32"); println!("cargo:rustc-link-lib=iphlpapi"); println!("cargo:rustc-link-lib=userenv"); println!("cargo:rustc-link-lib=ws2_32"); println!("cargo:rustc-link-lib=dbghelp"); println!("cargo:rustc-link-lib=ole32"); println!("cargo:rustc-link-lib=uuid"); println!("cargo:rustc-link-lib=shell32"); // These are required by OpenSSL on Windows println!("cargo:rustc-link-lib=ws2_32"); println!("cargo:rustc-link-lib=gdi32"); println!("cargo:rustc-link-lib=advapi32"); println!("cargo:rustc-link-lib=crypt32"); println!("cargo:rustc-link-lib=user32"); } // Remove subprojects/.wraplock created by Meson's directory locking // mechanism. It lives in the source tree and would cause `cargo package` // verification to fail with "Source directory was modified by build.rs". let wraplock = std::path::Path::new("subprojects/.wraplock"); if wraplock.exists() { fs::remove_file(wraplock).expect("Failed to remove subprojects/.wraplock"); } if env::var("KEEP_BUILD_ARTIFACTS") != Ok("1".to_string()) { // Clean if !Command::new(python) .arg("-m") .arg("invoke") .arg("clean-all") .env("PYTHONPATH", &pythonpath) .env("MEDIASOUP_OUT_DIR", &mediasoup_out_dir) .spawn() .expect("Failed to start") .wait() .expect("Wasn't running") .success() { panic!("Failed to clean libmediasoup-worker") } } println!("cargo:rustc-link-lib=static=mediasoup-worker"); println!("cargo:rustc-link-search=native={out_dir}"); } ================================================ FILE: worker/deps/libwebrtc/LICENSE ================================================ Copyright (c) 2011, The WebRTC project authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Google nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: worker/deps/libwebrtc/PATENTS ================================================ Additional IP Rights Grant (Patents) "This implementation" means the copyrightable works distributed by Google as part of the WebRTC code package. Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, transfer, and otherwise run, modify and propagate the contents of this implementation of the WebRTC code package, where such license applies only to those patent claims, both currently owned by Google and acquired in the future, licensable by Google that are necessarily infringed by this implementation of the WebRTC code package. This grant does not include claims that would be infringed only as a consequence of further modification of this implementation. If you or your agent or exclusive licensee institute or order or agree to the institution of patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that this implementation of the WebRTC code package or any code incorporated within this implementation of the WebRTC code package constitutes direct or contributory patent infringement, or inducement of patent infringement, then any patent rights granted to you under this License for this implementation of the WebRTC code package shall terminate as of the date such litigation is filed. ================================================ FILE: worker/deps/libwebrtc/README.md ================================================ # README This folder contains a modified/adapted subset of the libwebrtc library, which is used by mediasoup for transport congestion purposes. * libwebrtc branch: m77 * libwebrtc commit: 2bac7da1349c75e5cf89612ab9619a1920d5d974 The file `libwebrtc/mediasoup_helpers.h` includes some utilities to plug mediasoup classes into libwebrtc. The file `worker/deps/libwebrtc/deps/abseil-cpp/abseil-cpp/absl/synchronization/internal/graphcycles.cc` has `#include ` added to it to fix CI builds with Clang. The file `meson.build` is written for using with Meson build system. ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/bitrate_constraints.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_BITRATE_CONSTRAINTS_H_ #define API_BITRATE_CONSTRAINTS_H_ #include namespace webrtc { // TODO(srte): BitrateConstraints and BitrateSettings should be merged. // Both represent the same kind data, but are using different default // initializer and representation of unset values. struct BitrateConstraints { int min_bitrate_bps = 0; int start_bitrate_bps = kDefaultStartBitrateBps; int max_bitrate_bps = -1; private: static constexpr int kDefaultStartBitrateBps = 300000; }; // Like std::min, but considers non-positive values to be unset. template static T MinPositive(T a, T b) { if (a <= 0) { return b; } if (b <= 0) { return a; } return std::min(a, b); } } // namespace webrtc #endif // API_BITRATE_CONSTRAINTS_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/network_state_predictor.cc ================================================ #include "api/network_state_predictor.h" namespace webrtc { // MS_NOTE: added function. std::string BandwidthUsage2String(BandwidthUsage bandwidthUsage) { switch (bandwidthUsage) { case BandwidthUsage::kBwNormal: return "normal"; case BandwidthUsage::kBwUnderusing: return "underusing"; case BandwidthUsage::kBwOverusing: return "overusing"; default: return "unknown"; } } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/network_state_predictor.h ================================================ /* * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_NETWORK_STATE_PREDICTOR_H_ #define API_NETWORK_STATE_PREDICTOR_H_ #include #include #include #include namespace webrtc { enum class BandwidthUsage { kBwNormal = 0, kBwUnderusing = 1, kBwOverusing = 2, kLast }; // MS_NOTE: added function. std::string BandwidthUsage2String(BandwidthUsage bandwidthUsage); // TODO(yinwa): work in progress. API in class NetworkStatePredictor should not // be used by other users until this comment is removed. // NetworkStatePredictor predict network state based on current network metrics. // Usage: // Setup by calling Initialize. // For each update, call Update. Update returns network state // prediction. class NetworkStatePredictor { public: virtual ~NetworkStatePredictor() {} // Returns current network state prediction. // Inputs: send_time_ms - packet send time. // arrival_time_ms - packet arrival time. // network_state - computed network state. virtual BandwidthUsage Update(int64_t send_time_ms, int64_t arrival_time_ms, BandwidthUsage network_state) = 0; }; class NetworkStatePredictorFactoryInterface { public: virtual std::unique_ptr CreateNetworkStatePredictor() = 0; virtual ~NetworkStatePredictorFactoryInterface() = default; }; } // namespace webrtc #endif // API_NETWORK_STATE_PREDICTOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/bitrate_settings.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/transport/bitrate_settings.h" namespace webrtc { BitrateSettings::BitrateSettings() = default; BitrateSettings::~BitrateSettings() = default; BitrateSettings::BitrateSettings(const BitrateSettings&) = default; } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/bitrate_settings.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_TRANSPORT_BITRATE_SETTINGS_H_ #define API_TRANSPORT_BITRATE_SETTINGS_H_ #include namespace webrtc { // Configuration of send bitrate. The |start_bitrate_bps| value is // used for multiple purposes, both as a prior in the bandwidth // estimator, and for initial configuration of the encoder. We may // want to create separate apis for those, and use a smaller struct // with only the min and max constraints. struct BitrateSettings { BitrateSettings(); ~BitrateSettings(); BitrateSettings(const BitrateSettings&); // 0 <= min <= start <= max should hold for set parameters. absl::optional min_bitrate_bps; absl::optional start_bitrate_bps; absl::optional max_bitrate_bps; }; } // namespace webrtc #endif // API_TRANSPORT_BITRATE_SETTINGS_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/field_trial_based_config.cc ================================================ /* * Copyright 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/transport/field_trial_based_config.h" #include "system_wrappers/source/field_trial.h" namespace webrtc { std::string FieldTrialBasedConfig::Lookup(absl::string_view key) const { return webrtc::field_trial::FindFullName(std::string(key)); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/field_trial_based_config.h ================================================ /* * Copyright 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_TRANSPORT_FIELD_TRIAL_BASED_CONFIG_H_ #define API_TRANSPORT_FIELD_TRIAL_BASED_CONFIG_H_ #include "api/transport/webrtc_key_value_config.h" #include #include namespace webrtc { // Implementation using the field trial API fo the key value lookup. class FieldTrialBasedConfig : public WebRtcKeyValueConfig { public: std::string Lookup(absl::string_view key) const override; }; } // namespace webrtc #endif // API_TRANSPORT_FIELD_TRIAL_BASED_CONFIG_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/goog_cc_factory.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/transport/goog_cc_factory.h" #include "modules/congestion_controller/goog_cc/goog_cc_network_control.h" #include #include namespace webrtc { GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory( NetworkStatePredictorFactoryInterface* network_state_predictor_factory) { factory_config_.network_state_predictor_factory = network_state_predictor_factory; } GoogCcNetworkControllerFactory::GoogCcNetworkControllerFactory( GoogCcFactoryConfig config) : factory_config_(std::move(config)) {} std::unique_ptr GoogCcNetworkControllerFactory::Create(NetworkControllerConfig config) { GoogCcConfig goog_cc_config; goog_cc_config.feedback_only = factory_config_.feedback_only; if (factory_config_.network_state_estimator_factory) { // RTC_DCHECK(config.key_value_config); goog_cc_config.network_state_estimator = factory_config_.network_state_estimator_factory->Create( config.key_value_config); } if (factory_config_.network_state_predictor_factory) { goog_cc_config.network_state_predictor = factory_config_.network_state_predictor_factory ->CreateNetworkStatePredictor(); } return absl::make_unique(config, std::move(goog_cc_config)); } TimeDelta GoogCcNetworkControllerFactory::GetProcessInterval() const { const int64_t kUpdateIntervalMs = 25; return TimeDelta::ms(kUpdateIntervalMs); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/goog_cc_factory.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_TRANSPORT_GOOG_CC_FACTORY_H_ #define API_TRANSPORT_GOOG_CC_FACTORY_H_ #include "api/network_state_predictor.h" #include "api/transport/network_control.h" #include namespace webrtc { struct GoogCcFactoryConfig { std::unique_ptr network_state_estimator_factory = nullptr; NetworkStatePredictorFactoryInterface* network_state_predictor_factory = nullptr; bool feedback_only = false; }; class GoogCcNetworkControllerFactory : public NetworkControllerFactoryInterface { public: GoogCcNetworkControllerFactory() = default; explicit GoogCcNetworkControllerFactory( NetworkStatePredictorFactoryInterface* network_state_predictor_factory); explicit GoogCcNetworkControllerFactory(GoogCcFactoryConfig config); std::unique_ptr Create( NetworkControllerConfig config) override; TimeDelta GetProcessInterval() const override; protected: GoogCcFactoryConfig factory_config_; }; } // namespace webrtc #endif // API_TRANSPORT_GOOG_CC_FACTORY_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/network_control.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_TRANSPORT_NETWORK_CONTROL_H_ #define API_TRANSPORT_NETWORK_CONTROL_H_ #include "api/transport/network_types.h" #include "api/transport/webrtc_key_value_config.h" #include #include namespace webrtc { // TODO(srte): Remove this forward declaration when this is in api. class RtcEventLog; class TargetTransferRateObserver { public: virtual ~TargetTransferRateObserver() = default; // Called to indicate target transfer rate as well as giving information about // the current estimate of network parameters. virtual void OnTargetTransferRate(TargetTransferRate) = 0; // Called to provide updates to the expected target rate in case it changes // before the first call to OnTargetTransferRate. virtual void OnStartRateUpdate(DataRate) {} }; // Configuration sent to factory create function. The parameters here are // optional to use for a network controller implementation. struct NetworkControllerConfig { // The initial constraints to start with, these can be changed at any later // time by calls to OnTargetRateConstraints. Note that the starting rate // has to be set initially to provide a starting state for the network // controller, even though the field is marked as optional. TargetRateConstraints constraints; // Initial stream specific configuration, these are changed at any later time // by calls to OnStreamsConfig. StreamsConfig stream_based_config; // Optional override of configuration of WebRTC internals. Using nullptr here // indicates that the field trial API will be used. const WebRtcKeyValueConfig* key_value_config = nullptr; // Optional override of event log. RtcEventLog* event_log = nullptr; }; // NetworkControllerInterface is implemented by network controllers. A network // controller is a class that uses information about network state and traffic // to estimate network parameters such as round trip time and bandwidth. Network // controllers does not guarantee thread safety, the interface must be used in a // non-concurrent fashion. class NetworkControllerInterface { public: virtual ~NetworkControllerInterface() = default; // Called when network availabilty changes. virtual NetworkControlUpdate OnNetworkAvailability(NetworkAvailability) = 0; // Called when the receiving or sending endpoint changes address. virtual NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange) = 0; // Called periodically with a periodicy as specified by // NetworkControllerFactoryInterface::GetProcessInterval. virtual NetworkControlUpdate OnProcessInterval(ProcessInterval) = 0; // Called when remotely calculated bitrate is received. virtual NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport) = 0; // Called round trip time has been calculated by protocol specific mechanisms. virtual NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate) = 0; // Called when a packet is sent on the network. virtual NetworkControlUpdate OnSentPacket(SentPacket) = 0; // Called when the stream specific configuration has been updated. virtual NetworkControlUpdate OnStreamsConfig(StreamsConfig) = 0; // Called when target transfer rate constraints has been changed. virtual NetworkControlUpdate OnTargetRateConstraints( TargetRateConstraints) = 0; // Called when a protocol specific calculation of packet loss has been made. virtual NetworkControlUpdate OnTransportLossReport(TransportLossReport) = 0; // Called with per packet feedback regarding receive time. virtual NetworkControlUpdate OnTransportPacketsFeedback( TransportPacketsFeedback) = 0; // Called with network state estimate updates. virtual NetworkControlUpdate OnNetworkStateEstimate(NetworkStateEstimate) = 0; }; // NetworkControllerFactoryInterface is an interface for creating a network // controller. class NetworkControllerFactoryInterface { public: virtual ~NetworkControllerFactoryInterface() = default; // Used to create a new network controller, requires an observer to be // provided to handle callbacks. virtual std::unique_ptr Create( NetworkControllerConfig config) = 0; // Returns the interval by which the network controller expects // OnProcessInterval calls. virtual TimeDelta GetProcessInterval() const = 0; }; // Under development, subject to change without notice. class NetworkStateEstimator { public: // Gets the current best estimate according to the estimator. virtual absl::optional GetCurrentEstimate() = 0; // Called with per packet feedback regarding receive time. virtual void OnTransportPacketsFeedback(const TransportPacketsFeedback&) = 0; // Called when the receiving or sending endpoint changes address. virtual void OnRouteChange(const NetworkRouteChange&) = 0; virtual ~NetworkStateEstimator() = default; }; class NetworkStateEstimatorFactory { public: virtual std::unique_ptr Create( const WebRtcKeyValueConfig* key_value_config) = 0; virtual ~NetworkStateEstimatorFactory() = default; }; } // namespace webrtc #endif // API_TRANSPORT_NETWORK_CONTROL_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/network_types.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/transport/network_types.h" #include namespace webrtc { StreamsConfig::StreamsConfig() = default; StreamsConfig::StreamsConfig(const StreamsConfig&) = default; StreamsConfig::~StreamsConfig() = default; TargetRateConstraints::TargetRateConstraints() = default; TargetRateConstraints::TargetRateConstraints(const TargetRateConstraints&) = default; TargetRateConstraints::~TargetRateConstraints() = default; NetworkRouteChange::NetworkRouteChange() = default; NetworkRouteChange::NetworkRouteChange(const NetworkRouteChange&) = default; NetworkRouteChange::~NetworkRouteChange() = default; PacketResult::PacketResult() = default; PacketResult::PacketResult(const PacketResult& other) = default; PacketResult::~PacketResult() = default; bool PacketResult::ReceiveTimeOrder::operator()(const PacketResult& lhs, const PacketResult& rhs) { if (lhs.receive_time != rhs.receive_time) return lhs.receive_time < rhs.receive_time; if (lhs.sent_packet.send_time != rhs.sent_packet.send_time) return lhs.sent_packet.send_time < rhs.sent_packet.send_time; return lhs.sent_packet.sequence_number < rhs.sent_packet.sequence_number; } TransportPacketsFeedback::TransportPacketsFeedback() = default; TransportPacketsFeedback::TransportPacketsFeedback( const TransportPacketsFeedback& other) = default; TransportPacketsFeedback::~TransportPacketsFeedback() = default; std::vector TransportPacketsFeedback::ReceivedWithSendInfo() const { std::vector res; for (const PacketResult& fb : packet_feedbacks) { if (fb.receive_time.IsFinite()) { res.push_back(fb); } } return res; } std::vector TransportPacketsFeedback::LostWithSendInfo() const { std::vector res; for (const PacketResult& fb : packet_feedbacks) { if (fb.receive_time.IsPlusInfinity()) { res.push_back(fb); } } return res; } std::vector TransportPacketsFeedback::PacketsWithFeedback() const { return packet_feedbacks; } std::vector TransportPacketsFeedback::SortedByReceiveTime() const { std::vector res; for (const PacketResult& fb : packet_feedbacks) { if (fb.receive_time.IsFinite()) { res.push_back(fb); } } std::sort(res.begin(), res.end(), PacketResult::ReceiveTimeOrder()); return res; } NetworkControlUpdate::NetworkControlUpdate() = default; NetworkControlUpdate::NetworkControlUpdate(const NetworkControlUpdate&) = default; NetworkControlUpdate::~NetworkControlUpdate() = default; PacedPacketInfo::PacedPacketInfo() = default; PacedPacketInfo::PacedPacketInfo(int probe_cluster_id, int probe_cluster_min_probes, int probe_cluster_min_bytes) : probe_cluster_id(probe_cluster_id), probe_cluster_min_probes(probe_cluster_min_probes), probe_cluster_min_bytes(probe_cluster_min_bytes) {} bool PacedPacketInfo::operator==(const PacedPacketInfo& rhs) const { return send_bitrate_bps == rhs.send_bitrate_bps && probe_cluster_id == rhs.probe_cluster_id && probe_cluster_min_probes == rhs.probe_cluster_min_probes && probe_cluster_min_bytes == rhs.probe_cluster_min_bytes; } ProcessInterval::ProcessInterval() = default; ProcessInterval::ProcessInterval(const ProcessInterval&) = default; ProcessInterval::~ProcessInterval() = default; } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/network_types.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_TRANSPORT_NETWORK_TYPES_H_ #define API_TRANSPORT_NETWORK_TYPES_H_ #include "api/units/data_rate.h" #include "api/units/data_size.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include #include #include namespace webrtc { // Configuration // Use StreamsConfig for information about streams that is required for specific // adjustments to the algorithms in network controllers. Especially useful // for experiments. struct StreamsConfig { StreamsConfig(); StreamsConfig(const StreamsConfig&); ~StreamsConfig(); Timestamp at_time = Timestamp::PlusInfinity(); absl::optional requests_alr_probing; absl::optional pacing_factor; absl::optional min_total_allocated_bitrate = absl::nullopt; absl::optional max_padding_rate; absl::optional max_total_allocated_bitrate; }; struct TargetRateConstraints { TargetRateConstraints(); TargetRateConstraints(const TargetRateConstraints&); ~TargetRateConstraints(); Timestamp at_time = Timestamp::PlusInfinity(); absl::optional min_data_rate; absl::optional max_data_rate; // The initial bandwidth estimate to base target rate on. This should be used // as the basis for initial OnTargetTransferRate and OnPacerConfig callbacks. absl::optional starting_rate; }; // Send side information struct NetworkAvailability { Timestamp at_time = Timestamp::PlusInfinity(); bool network_available = false; }; struct NetworkRouteChange { NetworkRouteChange(); NetworkRouteChange(const NetworkRouteChange&); ~NetworkRouteChange(); Timestamp at_time = Timestamp::PlusInfinity(); // The TargetRateConstraints are set here so they can be changed synchronously // when network route changes. TargetRateConstraints constraints; }; struct PacedPacketInfo { PacedPacketInfo(); PacedPacketInfo(int probe_cluster_id, int probe_cluster_min_probes, int probe_cluster_min_bytes); bool operator==(const PacedPacketInfo& rhs) const; // TODO(srte): Move probing info to a separate, optional struct. static constexpr int kNotAProbe = -1; int send_bitrate_bps = -1; int probe_cluster_id = kNotAProbe; int probe_cluster_min_probes = -1; int probe_cluster_min_bytes = -1; }; struct SentPacket { Timestamp send_time = Timestamp::PlusInfinity(); DataSize size = DataSize::Zero(); DataSize prior_unacked_data = DataSize::Zero(); PacedPacketInfo pacing_info; // Transport independent sequence number, any tracked packet should have a // sequence number that is unique over the whole call and increasing by 1 for // each packet. int64_t sequence_number; // Tracked data in flight when the packet was sent, excluding unacked data. DataSize data_in_flight = DataSize::Zero(); }; struct ReceivedPacket { Timestamp send_time = Timestamp::MinusInfinity(); Timestamp receive_time = Timestamp::PlusInfinity(); DataSize size = DataSize::Zero(); }; // Transport level feedback struct RemoteBitrateReport { Timestamp receive_time = Timestamp::PlusInfinity(); DataRate bandwidth = DataRate::Infinity(); }; struct RoundTripTimeUpdate { Timestamp receive_time = Timestamp::PlusInfinity(); TimeDelta round_trip_time = TimeDelta::PlusInfinity(); bool smoothed = false; }; struct TransportLossReport { Timestamp receive_time = Timestamp::PlusInfinity(); Timestamp start_time = Timestamp::PlusInfinity(); Timestamp end_time = Timestamp::PlusInfinity(); uint64_t packets_lost_delta = 0; uint64_t packets_received_delta = 0; }; // Packet level feedback struct PacketResult { class ReceiveTimeOrder { public: bool operator()(const PacketResult& lhs, const PacketResult& rhs); }; PacketResult(); PacketResult(const PacketResult&); ~PacketResult(); SentPacket sent_packet; Timestamp receive_time = Timestamp::PlusInfinity(); }; struct TransportPacketsFeedback { TransportPacketsFeedback(); TransportPacketsFeedback(const TransportPacketsFeedback& other); ~TransportPacketsFeedback(); Timestamp feedback_time = Timestamp::PlusInfinity(); Timestamp first_unacked_send_time = Timestamp::PlusInfinity(); DataSize data_in_flight = DataSize::Zero(); DataSize prior_in_flight = DataSize::Zero(); std::vector packet_feedbacks; // Arrival times for messages without send time information. std::vector sendless_arrival_times; std::vector ReceivedWithSendInfo() const; std::vector LostWithSendInfo() const; std::vector PacketsWithFeedback() const; std::vector SortedByReceiveTime() const; }; // Network estimation struct NetworkEstimate { Timestamp at_time = Timestamp::PlusInfinity(); DataRate bandwidth = DataRate::Infinity(); TimeDelta round_trip_time = TimeDelta::PlusInfinity(); TimeDelta bwe_period = TimeDelta::PlusInfinity(); float loss_rate_ratio = 0; }; // Network control struct PacerConfig { Timestamp at_time = Timestamp::PlusInfinity(); // Pacer should send at most data_window data over time_window duration. DataSize data_window = DataSize::Infinity(); TimeDelta time_window = TimeDelta::PlusInfinity(); // Pacer should send at least pad_window data over time_window duration. DataSize pad_window = DataSize::Zero(); DataRate data_rate() const { return data_window / time_window; } DataRate pad_rate() const { return pad_window / time_window; } }; struct ProbeClusterConfig { Timestamp at_time = Timestamp::PlusInfinity(); DataRate target_data_rate = DataRate::Zero(); TimeDelta target_duration = TimeDelta::Zero(); int32_t target_probe_count = 0; int32_t id = 0; }; struct TargetTransferRate { Timestamp at_time = Timestamp::PlusInfinity(); // The estimate on which the target rate is based on. NetworkEstimate network_estimate; DataRate target_rate = DataRate::Zero(); }; // Contains updates of network controller comand state. Using optionals to // indicate whether a member has been updated. The array of probe clusters // should be used to send out probes if not empty. struct NetworkControlUpdate { NetworkControlUpdate(); NetworkControlUpdate(const NetworkControlUpdate&); ~NetworkControlUpdate(); absl::optional congestion_window; absl::optional pacer_config; std::vector probe_cluster_configs; absl::optional target_rate; }; // Process control struct ProcessInterval { ProcessInterval(); ProcessInterval(const ProcessInterval&); ~ProcessInterval(); Timestamp at_time = Timestamp::PlusInfinity(); absl::optional pacer_queue; }; // Under development, subject to change without notice. struct NetworkStateEstimate { double confidence = NAN; // The time the estimate was received/calculated. Timestamp update_time = Timestamp::MinusInfinity(); Timestamp last_receive_time = Timestamp::MinusInfinity(); Timestamp last_send_time = Timestamp::MinusInfinity(); // Total estimated link capacity. DataRate link_capacity = DataRate::MinusInfinity(); // Used as a safe measure of available capacity. DataRate link_capacity_lower = DataRate::MinusInfinity(); // Used as limit for increasing bitrate. DataRate link_capacity_upper = DataRate::MinusInfinity(); TimeDelta pre_link_buffer_delay = TimeDelta::MinusInfinity(); TimeDelta post_link_buffer_delay = TimeDelta::MinusInfinity(); TimeDelta propagation_delay = TimeDelta::MinusInfinity(); // Only for debugging TimeDelta time_delta = TimeDelta::MinusInfinity(); Timestamp last_feed_time = Timestamp::MinusInfinity(); double cross_delay_rate = NAN; double spike_delay_rate = NAN; DataRate link_capacity_std_dev = DataRate::MinusInfinity(); DataRate link_capacity_min = DataRate::MinusInfinity(); double cross_traffic_ratio = NAN; }; } // namespace webrtc #endif // API_TRANSPORT_NETWORK_TYPES_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/transport/webrtc_key_value_config.h ================================================ /* * Copyright 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_TRANSPORT_WEBRTC_KEY_VALUE_CONFIG_H_ #define API_TRANSPORT_WEBRTC_KEY_VALUE_CONFIG_H_ #include #include namespace webrtc { // An interface that provides a key-value mapping for configuring internal // details of WebRTC. Note that there's no guarantess that the meaning of a // particular key value mapping will be preserved over time and no announcements // will be made if they are changed. It's up to the library user to ensure that // the behavior does not break. class WebRtcKeyValueConfig { public: virtual ~WebRtcKeyValueConfig() = default; // The configured value for the given key. Defaults to an empty string. virtual std::string Lookup(absl::string_view key) const = 0; }; } // namespace webrtc #endif // API_TRANSPORT_WEBRTC_KEY_VALUE_CONFIG_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/data_rate.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/units/data_rate.h" #include namespace webrtc { std::string ToString(DataRate value) { std::ostringstream sb; if (value.IsPlusInfinity()) { sb << "+inf bps"; } else if (value.IsMinusInfinity()) { sb << "-inf bps"; } else { if (value.bps() == 0 || value.bps() % 1000 != 0) { sb << value.bps() << " bps"; } else { sb << value.kbps() << " kbps"; } } return sb.str(); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/data_rate.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_UNITS_DATA_RATE_H_ #define API_UNITS_DATA_RATE_H_ #include "api/units/data_size.h" #include "api/units/frequency.h" #include "api/units/time_delta.h" #include "rtc_base/units/unit_base.h" #include #include #include #ifdef UNIT_TEST #include // no-presubmit-check TODO(webrtc:8982) #endif // UNIT_TEST namespace webrtc { // DataRate is a class that represents a given data rate. This can be used to // represent bandwidth, encoding bitrate, etc. The internal storage is bits per // second (bps). class DataRate final : public rtc_units_impl::RelativeUnit { public: DataRate() = delete; static constexpr DataRate Infinity() { return PlusInfinity(); } template static constexpr DataRate BitsPerSec() { return FromStaticValue(); } template static constexpr DataRate KilobitsPerSec() { return FromStaticFraction(); } template static constexpr DataRate bps(T bits_per_second) { static_assert(std::is_arithmetic::value, ""); return FromValue(bits_per_second); } template static constexpr DataRate bytes_per_sec(T bytes_per_second) { static_assert(std::is_arithmetic::value, ""); return FromFraction<8>(bytes_per_second); } template static constexpr DataRate kbps(T kilobits_per_sec) { static_assert(std::is_arithmetic::value, ""); return FromFraction<1000>(kilobits_per_sec); } template constexpr T bps() const { return ToValue(); } template constexpr T bytes_per_sec() const { return ToFraction<8, T>(); } template T kbps() const { return ToFraction<1000, T>(); } constexpr int64_t bps_or(int64_t fallback_value) const { return ToValueOr(fallback_value); } constexpr int64_t kbps_or(int64_t fallback_value) const { return ToFractionOr<1000>(fallback_value); } private: // Bits per second used internally to simplify debugging by making the value // more recognizable. friend class rtc_units_impl::UnitBase; using RelativeUnit::RelativeUnit; static constexpr bool one_sided = true; }; namespace data_rate_impl { inline int64_t Microbits(const DataSize& size) { // MS_NOTE: check removed. // constexpr int64_t kMaxBeforeConversion = // std::numeric_limits::max() / 8000000; // RTC_DCHECK_LE(size.bytes(), kMaxBeforeConversion) // << "size is too large to be expressed in microbits"; return size.bytes() * 8000000; } inline int64_t MillibytePerSec(const DataRate& size) { // MS_NOTE: check removed. // constexpr int64_t kMaxBeforeConversion = // std::numeric_limits::max() / (1000 / 8); // RTC_DCHECK_LE(size.bps(), kMaxBeforeConversion) // << "rate is too large to be expressed in microbytes per second"; return size.bps() * (1000 / 8); } } // namespace data_rate_impl inline DataRate operator/(const DataSize size, const TimeDelta duration) { return DataRate::bps(data_rate_impl::Microbits(size) / duration.us()); } inline TimeDelta operator/(const DataSize size, const DataRate rate) { return TimeDelta::us(data_rate_impl::Microbits(size) / rate.bps()); } inline DataSize operator*(const DataRate rate, const TimeDelta duration) { int64_t microbits = rate.bps() * duration.us(); return DataSize::bytes((microbits + 4000000) / 8000000); } inline DataSize operator*(const TimeDelta duration, const DataRate rate) { return rate * duration; } inline DataSize operator/(const DataRate rate, const Frequency frequency) { int64_t millihertz = frequency.millihertz(); // Note that the value is truncated here reather than rounded, potentially // introducing an error of .5 bytes if rounding were expected. return DataSize::bytes(data_rate_impl::MillibytePerSec(rate) / millihertz); } inline Frequency operator/(const DataRate rate, const DataSize size) { return Frequency::millihertz(data_rate_impl::MillibytePerSec(rate) / size.bytes()); } inline DataRate operator*(const DataSize size, const Frequency frequency) { // RTC_DCHECK(frequency.IsZero() || // size.bytes() <= std::numeric_limits::max() / 8 / // frequency.millihertz()); int64_t millibits_per_second = size.bytes() * 8 * frequency.millihertz(); return DataRate::bps((millibits_per_second + 500) / 1000); } inline DataRate operator*(const Frequency frequency, const DataSize size) { return size * frequency; } std::string ToString(DataRate value); inline std::string ToLogString(DataRate value) { return ToString(value); } #ifdef UNIT_TEST inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) DataRate value) { return stream << ToString(value); } #endif // UNIT_TEST } // namespace webrtc #endif // API_UNITS_DATA_RATE_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/data_size.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/units/data_size.h" #include namespace webrtc { std::string ToString(DataSize value) { std::ostringstream sb; if (value.IsPlusInfinity()) { sb << "+inf bytes"; } else if (value.IsMinusInfinity()) { sb << "-inf bytes"; } else { sb << value.bytes() << " bytes"; } return sb.str(); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/data_size.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_UNITS_DATA_SIZE_H_ #define API_UNITS_DATA_SIZE_H_ #include "rtc_base/units/unit_base.h" #include #include #ifdef UNIT_TEST #include // no-presubmit-check TODO(webrtc:8982) #endif // UNIT_TEST namespace webrtc { // DataSize is a class represeting a count of bytes. class DataSize final : public rtc_units_impl::RelativeUnit { public: DataSize() = delete; static constexpr DataSize Infinity() { return PlusInfinity(); } template static constexpr DataSize Bytes() { return FromStaticValue(); } template static DataSize bytes(T bytes) { static_assert(std::is_arithmetic::value, ""); return FromValue(bytes); } template T bytes() const { return ToValue(); } constexpr int64_t bytes_or(int64_t fallback_value) const { return ToValueOr(fallback_value); } private: friend class rtc_units_impl::UnitBase; using RelativeUnit::RelativeUnit; static constexpr bool one_sided = true; }; std::string ToString(DataSize value); inline std::string ToLogString(DataSize value) { return ToString(value); } #ifdef UNIT_TEST inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) DataSize value) { return stream << ToString(value); } #endif // UNIT_TEST } // namespace webrtc #endif // API_UNITS_DATA_SIZE_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/frequency.cc ================================================ /* * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/units/frequency.h" #include #include // setfill, setw. namespace webrtc { std::string ToString(Frequency value) { std::ostringstream sb; if (value.IsPlusInfinity()) { sb << "+inf Hz"; } else if (value.IsMinusInfinity()) { sb << "-inf Hz"; } else if (value.millihertz() % 1000 != 0) { sb << std::setfill('0') << std::setw(2) << value.hertz() << " Hz"; } else { sb << value.hertz() << " Hz"; } return sb.str(); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/frequency.h ================================================ /* * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_UNITS_FREQUENCY_H_ #define API_UNITS_FREQUENCY_H_ #include "api/units/time_delta.h" #include "rtc_base/units/unit_base.h" #include #include #include #include #ifdef UNIT_TEST #include // no-presubmit-check TODO(webrtc:8982) #endif // UNIT_TEST namespace webrtc { class Frequency final : public rtc_units_impl::RelativeUnit { public: Frequency() = delete; template static constexpr Frequency Hertz() { return FromStaticFraction(); } template static Frequency hertz(T hertz) { static_assert(std::is_arithmetic::value, ""); return FromFraction<1000>(hertz); } template static Frequency millihertz(T hertz) { static_assert(std::is_arithmetic::value, ""); return FromValue(hertz); } template T hertz() const { return ToFraction<1000, T>(); } template T millihertz() const { return ToValue(); } private: friend class rtc_units_impl::UnitBase; using RelativeUnit::RelativeUnit; static constexpr bool one_sided = true; }; inline Frequency operator/(int64_t nominator, const TimeDelta& interval) { constexpr int64_t kKiloPerMicro = 1000 * 1000000; // RTC_DCHECK_LE(nominator, std::numeric_limits::max() / kKiloPerMicro); // RTC_CHECK(interval.IsFinite()); // RTC_CHECK(!interval.IsZero()); return Frequency::millihertz(nominator * kKiloPerMicro / interval.us()); } inline TimeDelta operator/(int64_t nominator, const Frequency& frequency) { constexpr int64_t kMegaPerMilli = 1000000 * 1000; // RTC_DCHECK_LE(nominator, std::numeric_limits::max() / kMegaPerMilli); // RTC_CHECK(frequency.IsFinite()); // RTC_CHECK(!frequency.IsZero()); return TimeDelta::us(nominator * kMegaPerMilli / frequency.millihertz()); } std::string ToString(Frequency value); inline std::string ToLogString(Frequency value) { return ToString(value); } #ifdef UNIT_TEST inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) Frequency value) { return stream << ToString(value); } #endif // UNIT_TEST } // namespace webrtc #endif // API_UNITS_FREQUENCY_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/time_delta.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/units/time_delta.h" #include namespace webrtc { std::string ToString(TimeDelta value) { std::ostringstream sb; if (value.IsPlusInfinity()) { sb << "+inf ms"; } else if (value.IsMinusInfinity()) { sb << "-inf ms"; } else { if (value.us() == 0 || (value.us() % 1000) != 0) sb << value.us() << " us"; else if (value.ms() % 1000 != 0) sb << value.ms() << " ms"; else sb << value.seconds() << " s"; } return sb.str(); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/time_delta.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_UNITS_TIME_DELTA_H_ #define API_UNITS_TIME_DELTA_H_ #include "rtc_base/units/unit_base.h" #include #include #include #ifdef UNIT_TEST #include // no-presubmit-check TODO(webrtc:8982) #endif // UNIT_TEST namespace webrtc { // TimeDelta represents the difference between two timestamps. Commonly this can // be a duration. However since two Timestamps are not guaranteed to have the // same epoch (they might come from different computers, making exact // synchronisation infeasible), the duration covered by a TimeDelta can be // undefined. To simplify usage, it can be constructed and converted to // different units, specifically seconds (s), milliseconds (ms) and // microseconds (us). class TimeDelta final : public rtc_units_impl::RelativeUnit { public: TimeDelta() = delete; template static constexpr TimeDelta Seconds() { return FromStaticFraction(); } template static constexpr TimeDelta Millis() { return FromStaticFraction(); } template static constexpr TimeDelta Micros() { return FromStaticValue(); } template static TimeDelta seconds(T seconds) { static_assert(std::is_arithmetic::value, ""); return FromFraction<1000000>(seconds); } template static TimeDelta ms(T milliseconds) { static_assert(std::is_arithmetic::value, ""); return FromFraction<1000>(milliseconds); } template static TimeDelta us(T microseconds) { static_assert(std::is_arithmetic::value, ""); return FromValue(microseconds); } template T seconds() const { return ToFraction<1000000, T>(); } template T ms() const { return ToFraction<1000, T>(); } template T us() const { return ToValue(); } template T ns() const { return ToMultiple<1000, T>(); } constexpr int64_t seconds_or(int64_t fallback_value) const { return ToFractionOr<1000000>(fallback_value); } constexpr int64_t ms_or(int64_t fallback_value) const { return ToFractionOr<1000>(fallback_value); } constexpr int64_t us_or(int64_t fallback_value) const { return ToValueOr(fallback_value); } TimeDelta Abs() const { return TimeDelta::us(std::abs(us())); } private: friend class rtc_units_impl::UnitBase; using RelativeUnit::RelativeUnit; static constexpr bool one_sided = false; }; std::string ToString(TimeDelta value); inline std::string ToLogString(TimeDelta value) { return ToString(value); } #ifdef UNIT_TEST inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) TimeDelta value) { return stream << ToString(value); } #endif // UNIT_TEST } // namespace webrtc #endif // API_UNITS_TIME_DELTA_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/timestamp.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "api/units/timestamp.h" #include namespace webrtc { std::string ToString(Timestamp value) { std::ostringstream sb; if (value.IsPlusInfinity()) { sb << "+inf ms"; } else if (value.IsMinusInfinity()) { sb << "-inf ms"; } else { if (value.ms() % 1000 == 0) sb << value.seconds() << " s"; else sb << value.ms() << " ms"; } return sb.str(); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/api/units/timestamp.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef API_UNITS_TIMESTAMP_H_ #define API_UNITS_TIMESTAMP_H_ #include "api/units/time_delta.h" #include #include #ifdef UNIT_TEST #include // no-presubmit-check TODO(webrtc:8982) #endif // UNIT_TEST namespace webrtc { // Timestamp represents the time that has passed since some unspecified epoch. // The epoch is assumed to be before any represented timestamps, this means that // negative values are not valid. The most notable feature is that the // difference of two Timestamps results in a TimeDelta. class Timestamp final : public rtc_units_impl::UnitBase { public: Timestamp() = delete; template static constexpr Timestamp Seconds() { return FromStaticFraction(); } template static constexpr Timestamp Millis() { return FromStaticFraction(); } template static constexpr Timestamp Micros() { return FromStaticValue(); } template static Timestamp seconds(T seconds) { static_assert(std::is_arithmetic::value, ""); return FromFraction<1000000>(seconds); } template static Timestamp ms(T milliseconds) { static_assert(std::is_arithmetic::value, ""); return FromFraction<1000>(milliseconds); } template static Timestamp us(T microseconds) { static_assert(std::is_arithmetic::value, ""); return FromValue(microseconds); } template T seconds() const { return ToFraction<1000000, T>(); } template T ms() const { return ToFraction<1000, T>(); } template T us() const { return ToValue(); } constexpr int64_t seconds_or(int64_t fallback_value) const { return ToFractionOr<1000000>(fallback_value); } constexpr int64_t ms_or(int64_t fallback_value) const { return ToFractionOr<1000>(fallback_value); } constexpr int64_t us_or(int64_t fallback_value) const { return ToValueOr(fallback_value); } Timestamp operator+(const TimeDelta delta) const { if (IsPlusInfinity() || delta.IsPlusInfinity()) { // RTC_DCHECK(!IsMinusInfinity()); // RTC_DCHECK(!delta.IsMinusInfinity()); return PlusInfinity(); } else if (IsMinusInfinity() || delta.IsMinusInfinity()) { // RTC_DCHECK(!IsPlusInfinity()); // RTC_DCHECK(!delta.IsPlusInfinity()); return MinusInfinity(); } return Timestamp::us(us() + delta.us()); } Timestamp operator-(const TimeDelta delta) const { if (IsPlusInfinity() || delta.IsMinusInfinity()) { // RTC_DCHECK(!IsMinusInfinity()); // RTC_DCHECK(!delta.IsPlusInfinity()); return PlusInfinity(); } else if (IsMinusInfinity() || delta.IsPlusInfinity()) { // RTC_DCHECK(!IsPlusInfinity()); // RTC_DCHECK(!delta.IsMinusInfinity()); return MinusInfinity(); } return Timestamp::us(us() - delta.us()); } TimeDelta operator-(const Timestamp other) const { if (IsPlusInfinity() || other.IsMinusInfinity()) { // RTC_DCHECK(!IsMinusInfinity()); // RTC_DCHECK(!other.IsPlusInfinity()); return TimeDelta::PlusInfinity(); } else if (IsMinusInfinity() || other.IsPlusInfinity()) { // RTC_DCHECK(!IsPlusInfinity()); // RTC_DCHECK(!other.IsMinusInfinity()); return TimeDelta::MinusInfinity(); } return TimeDelta::us(us() - other.us()); } Timestamp& operator-=(const TimeDelta delta) { *this = *this - delta; return *this; } Timestamp& operator+=(const TimeDelta delta) { *this = *this + delta; return *this; } private: friend class rtc_units_impl::UnitBase; using UnitBase::UnitBase; static constexpr bool one_sided = true; }; std::string ToString(Timestamp value); inline std::string ToLogString(Timestamp value) { return ToString(value); } #ifdef UNIT_TEST inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) Timestamp value) { return stream << ToString(value); } #endif // UNIT_TEST } // namespace webrtc #endif // API_UNITS_TIMESTAMP_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/call/rtp_transport_controller_send.cc ================================================ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::RtpTransportControllerSend" // #define MS_LOG_DEV_LEVEL 3 #include "call/rtp_transport_controller_send.h" #include "api/transport/network_types.h" #include "api/units/data_rate.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "system_wrappers/source/field_trial.h" #include "modules/congestion_controller/goog_cc/goog_cc_network_control.h" #include "DepLibUV.hpp" #include "Logger.hpp" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include #include #include #include namespace webrtc { namespace { static const size_t kMaxOverheadBytes = 500; TargetRateConstraints ConvertConstraints(int min_bitrate_bps, int max_bitrate_bps, int start_bitrate_bps) { TargetRateConstraints msg; msg.at_time = Timestamp::ms(DepLibUV::GetTimeMsInt64()); msg.min_data_rate = min_bitrate_bps >= 0 ? DataRate::bps(min_bitrate_bps) : DataRate::Zero(); msg.max_data_rate = max_bitrate_bps > 0 ? DataRate::bps(max_bitrate_bps) : DataRate::Infinity(); if (start_bitrate_bps > 0) msg.starting_rate = DataRate::bps(start_bitrate_bps); return msg; } TargetRateConstraints ConvertConstraints(const BitrateConstraints& contraints) { return ConvertConstraints(contraints.min_bitrate_bps, contraints.max_bitrate_bps, contraints.start_bitrate_bps); } } // namespace RtpTransportControllerSend::RtpTransportControllerSend( PacketRouter* packet_router, NetworkStatePredictorFactoryInterface* predictor_factory, NetworkControllerFactoryInterface* controller_factory, const BitrateConstraints& bitrate_config) : packet_router_(packet_router), pacer_(packet_router_), observer_(nullptr), controller_factory_override_(controller_factory), process_interval_(controller_factory_override_->GetProcessInterval()), last_report_block_time_(Timestamp::ms(DepLibUV::GetTimeMsInt64())), send_side_bwe_with_overhead_( webrtc::field_trial::IsEnabled("WebRTC-SendSideBwe-WithOverhead")), transport_overhead_bytes_per_packet_(0), network_available_(false) { initial_config_.constraints = ConvertConstraints(bitrate_config); initial_config_.key_value_config = &trial_based_config_; // RTC_DCHECK(bitrate_config.start_bitrate_bps > 0); MS_ASSERT(bitrate_config.start_bitrate_bps > 0, "start bitrate must be > 0"); pacer_.SetPacingRates(bitrate_config.start_bitrate_bps, 0); } RtpTransportControllerSend::~RtpTransportControllerSend() { } void RtpTransportControllerSend::UpdateControlState() { absl::optional update = control_handler_->GetUpdate(); if (!update) return; // We won't create control_handler_ until we have an observers. // RTC_DCHECK(observer_ != nullptr); MS_ASSERT(observer_ != nullptr, "no observer"); observer_->OnTargetTransferRate(*update); } PacketRouter* RtpTransportControllerSend::packet_router() { return this->packet_router_; } NetworkStateEstimateObserver* RtpTransportControllerSend::network_state_estimate_observer() { return this; } TransportFeedbackObserver* RtpTransportControllerSend::transport_feedback_observer() { return this; } PacedSender* RtpTransportControllerSend::packet_sender() { return &pacer_; } void RtpTransportControllerSend::SetAllocatedSendBitrateLimits( int min_send_bitrate_bps, int max_padding_bitrate_bps, int max_total_bitrate_bps) { streams_config_.min_total_allocated_bitrate = DataRate::bps(min_send_bitrate_bps); streams_config_.max_padding_rate = DataRate::bps(max_padding_bitrate_bps); streams_config_.max_total_allocated_bitrate = DataRate::bps(max_total_bitrate_bps); UpdateStreamsConfig(); } void RtpTransportControllerSend::SetClientBitratePreferences(const TargetRateConstraints& constraints) { controller_->OnTargetRateConstraints(constraints); } void RtpTransportControllerSend::SetPacingFactor(float pacing_factor) { streams_config_.pacing_factor = pacing_factor; UpdateStreamsConfig(); } void RtpTransportControllerSend::RegisterTargetTransferRateObserver( TargetTransferRateObserver* observer) { // RTC_DCHECK(observer_ == nullptr); MS_ASSERT(observer_ == nullptr, "observer already set"); observer_ = observer; observer_->OnStartRateUpdate(*initial_config_.constraints.starting_rate); MaybeCreateControllers(); } void RtpTransportControllerSend::OnNetworkAvailability(bool network_available) { MS_DEBUG_DEV("<<<<< network_available:%s", network_available ? "true" : "false"); NetworkAvailability msg; msg.at_time = Timestamp::ms(DepLibUV::GetTimeMsInt64()); msg.network_available = network_available; if (network_available_ == msg.network_available) return; network_available_ = msg.network_available; if (network_available_) { pacer_.Resume(); } else { pacer_.Pause(); } pacer_.UpdateOutstandingData(0); control_handler_->SetNetworkAvailability(network_available_); PostUpdates(controller_->OnNetworkAvailability(msg)); UpdateControlState(); } RtcpBandwidthObserver* RtpTransportControllerSend::GetBandwidthObserver() { return this; } void RtpTransportControllerSend::EnablePeriodicAlrProbing(bool enable) { streams_config_.requests_alr_probing = enable; UpdateStreamsConfig(); } void RtpTransportControllerSend::OnSentPacket( const rtc::SentPacket& sent_packet, size_t size) { MS_DEBUG_DEV("<<<<< size:%zu", size); absl::optional packet_msg = transport_feedback_adapter_.ProcessSentPacket(sent_packet); if (packet_msg) PostUpdates(controller_->OnSentPacket(*packet_msg)); pacer_.UpdateOutstandingData( transport_feedback_adapter_.GetOutstandingData().bytes()); } void RtpTransportControllerSend::OnTransportOverheadChanged( size_t transport_overhead_bytes_per_packet) { MS_DEBUG_DEV("<<<<< transport_overhead_bytes_per_packet:%zu", transport_overhead_bytes_per_packet); if (transport_overhead_bytes_per_packet >= kMaxOverheadBytes) { MS_ERROR("transport overhead exceeds: %zu", kMaxOverheadBytes); return; } } void RtpTransportControllerSend::OnReceivedEstimatedBitrate(uint32_t bitrate) { MS_DEBUG_DEV("<<<<< bitrate:%zu", bitrate); RemoteBitrateReport msg; msg.receive_time = Timestamp::ms(DepLibUV::GetTimeMsInt64()); msg.bandwidth = DataRate::bps(bitrate); PostUpdates(controller_->OnRemoteBitrateReport(msg)); } void RtpTransportControllerSend::OnReceivedRtcpReceiverReport( const ReportBlockList& report_blocks, int64_t rtt_ms, int64_t now_ms) { MS_DEBUG_DEV("<<<<< rtt_ms:%" PRIi64, rtt_ms); OnReceivedRtcpReceiverReportBlocks(report_blocks, now_ms); RoundTripTimeUpdate report; report.receive_time = Timestamp::ms(now_ms); report.round_trip_time = TimeDelta::ms(rtt_ms); report.smoothed = false; if (!report.round_trip_time.IsZero()) PostUpdates(controller_->OnRoundTripTimeUpdate(report)); } void RtpTransportControllerSend::OnAddPacket( const RtpPacketSendInfo& packet_info) { transport_feedback_adapter_.AddPacket( packet_info, send_side_bwe_with_overhead_ ? transport_overhead_bytes_per_packet_.load() : 0, Timestamp::ms(DepLibUV::GetTimeMsInt64())); } void RtpTransportControllerSend::OnTransportFeedback( const RTC::RTCP::FeedbackRtpTransportPacket& feedback) { MS_DEBUG_DEV("<<<<<"); absl::optional feedback_msg = transport_feedback_adapter_.ProcessTransportFeedback( feedback, Timestamp::ms(DepLibUV::GetTimeMsInt64())); if (feedback_msg) PostUpdates(controller_->OnTransportPacketsFeedback(*feedback_msg)); pacer_.UpdateOutstandingData( transport_feedback_adapter_.GetOutstandingData().bytes()); } void RtpTransportControllerSend::OnRemoteNetworkEstimate( NetworkStateEstimate estimate) { estimate.update_time = Timestamp::ms(DepLibUV::GetTimeMsInt64()); controller_->OnNetworkStateEstimate(estimate); } void RtpTransportControllerSend::Process() { // TODO (ibc): Must really check if we need this ugly periodic timer which is called // every 5ms. // NOTE: Yes, otherwise the pssss scenario does not work: // https://bitbucket.org/versatica/mediasoup/issues/12/no-probation-if-no-real-media UpdateControllerWithTimeInterval(); } void RtpTransportControllerSend::MaybeCreateControllers() { // RTC_DCHECK(!controller_); // RTC_DCHECK(!control_handler_); MS_ASSERT(!controller_, "controller already set"); MS_ASSERT(!control_handler_, "controller handler already set"); control_handler_ = absl::make_unique(); initial_config_.constraints.at_time = Timestamp::ms(DepLibUV::GetTimeMsInt64()); controller_ = controller_factory_override_->Create(initial_config_); process_interval_ = controller_factory_override_->GetProcessInterval(); UpdateControllerWithTimeInterval(); } void RtpTransportControllerSend::UpdateControllerWithTimeInterval() { MS_DEBUG_DEV("<<<<<"); // RTC_DCHECK(controller_); MS_ASSERT(controller_, "controller not set"); ProcessInterval msg; msg.at_time = Timestamp::ms(DepLibUV::GetTimeMsInt64()); PostUpdates(controller_->OnProcessInterval(msg)); } void RtpTransportControllerSend::UpdateStreamsConfig() { MS_DEBUG_DEV("<<<<<"); streams_config_.at_time = Timestamp::ms(DepLibUV::GetTimeMsInt64()); if (controller_) PostUpdates(controller_->OnStreamsConfig(streams_config_)); } void RtpTransportControllerSend::PostUpdates(NetworkControlUpdate update) { if (update.congestion_window) { if (update.congestion_window->IsFinite()) pacer_.SetCongestionWindow(update.congestion_window->bytes()); else pacer_.SetCongestionWindow(PacedSender::kNoCongestionWindow); } if (update.pacer_config) { pacer_.SetPacingRates(update.pacer_config->data_rate().bps(), update.pacer_config->pad_rate().bps()); } // TODO: REMOVE: this removes any probation. // update.probe_cluster_configs.clear(); for (const auto& probe : update.probe_cluster_configs) { int64_t bitrate_bps = probe.target_data_rate.bps(); pacer_.CreateProbeCluster(bitrate_bps, probe.id); } if (update.target_rate) { control_handler_->SetTargetRate(*update.target_rate); UpdateControlState(); } } void RtpTransportControllerSend::OnReceivedRtcpReceiverReportBlocks( const ReportBlockList& report_blocks, int64_t now_ms) { if (report_blocks.empty()) return; int total_packets_lost_delta = 0; int total_packets_delta = 0; // Compute the packet loss from all report blocks. for (const RTCPReportBlock& report_block : report_blocks) { auto it = last_report_blocks_.find(report_block.source_ssrc); if (it != last_report_blocks_.end()) { auto number_of_packets = report_block.extended_highest_sequence_number - it->second.extended_highest_sequence_number; total_packets_delta += number_of_packets; auto lost_delta = report_block.packets_lost - it->second.packets_lost; total_packets_lost_delta += lost_delta; } last_report_blocks_[report_block.source_ssrc] = report_block; } // Can only compute delta if there has been previous blocks to compare to. If // not, total_packets_delta will be unchanged and there's nothing more to do. if (!total_packets_delta) return; int packets_received_delta = total_packets_delta - total_packets_lost_delta; // To detect lost packets, at least one packet has to be received. This check // is needed to avoid bandwith detection update in // VideoSendStreamTest.SuspendBelowMinBitrate if (packets_received_delta < 1) return; Timestamp now = Timestamp::ms(now_ms); TransportLossReport msg; msg.packets_lost_delta = total_packets_lost_delta; msg.packets_received_delta = packets_received_delta; msg.receive_time = now; msg.start_time = last_report_block_time_; msg.end_time = now; PostUpdates(controller_->OnTransportLossReport(msg)); last_report_block_time_ = now; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/call/rtp_transport_controller_send.h ================================================ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef CALL_RTP_TRANSPORT_CONTROLLER_SEND_H_ #define CALL_RTP_TRANSPORT_CONTROLLER_SEND_H_ #include "api/network_state_predictor.h" #include "api/transport/network_control.h" #include "api/transport/field_trial_based_config.h" // #include "call/rtp_bitrate_configurator.h" #include "call/rtp_transport_controller_send_interface.h" #include "modules/congestion_controller/rtp/control_handler.h" #include "modules/congestion_controller/rtp/transport_feedback_adapter.h" #include "rtc_base/constructor_magic.h" #include "modules/pacing/packet_router.h" #include "modules/pacing/paced_sender.h" #include #include #include #include #include namespace webrtc { // TODO(nisse): When we get the underlying transports here, we should // have one object implementing RtpTransportControllerSendInterface // per transport, sharing the same congestion controller. class RtpTransportControllerSend final : public RtpTransportControllerSendInterface, public RtcpBandwidthObserver, public TransportFeedbackObserver, public NetworkStateEstimateObserver { public: RtpTransportControllerSend( PacketRouter* packet_router, NetworkStatePredictorFactoryInterface* predictor_factory, NetworkControllerFactoryInterface* controller_factory, const BitrateConstraints& bitrate_config); ~RtpTransportControllerSend() override; PacketRouter* packet_router() override; NetworkStateEstimateObserver* network_state_estimate_observer() override; TransportFeedbackObserver* transport_feedback_observer() override; PacedSender* packet_sender() override; void SetAllocatedSendBitrateLimits(int min_send_bitrate_bps, int max_padding_bitrate_bps, int max_total_bitrate_bps) override; void SetClientBitratePreferences(const TargetRateConstraints& constraints); void SetPacingFactor(float pacing_factor) override; void RegisterTargetTransferRateObserver( TargetTransferRateObserver* observer) override; void OnNetworkAvailability(bool network_available) override; RtcpBandwidthObserver* GetBandwidthObserver() override; void EnablePeriodicAlrProbing(bool enable) override; void OnSentPacket(const rtc::SentPacket& sent_packet, size_t size) override; void OnTransportOverheadChanged( size_t transport_overhead_per_packet) override; // Implements RtcpBandwidthObserver interface void OnReceivedEstimatedBitrate(uint32_t bitrate) override; void OnReceivedRtcpReceiverReport(const ReportBlockList& report_blocks, int64_t rtt, int64_t now_ms) override; // Implements TransportFeedbackObserver interface void OnAddPacket(const RtpPacketSendInfo& packet_info) override; void OnTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket& feedback) override; // Implements NetworkStateEstimateObserver interface void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) override; void Process(); private: void MaybeCreateControllers(); void UpdateControllerWithTimeInterval(); void UpdateStreamsConfig(); void OnReceivedRtcpReceiverReportBlocks(const ReportBlockList& report_blocks, int64_t now_ms); void PostUpdates(NetworkControlUpdate update); void UpdateControlState(); const FieldTrialBasedConfig trial_based_config_; PacketRouter* packet_router_; PacedSender pacer_; TargetTransferRateObserver* observer_; NetworkControllerFactoryInterface* const controller_factory_override_; TransportFeedbackAdapter transport_feedback_adapter_; std::unique_ptr control_handler_; std::unique_ptr controller_ ; TimeDelta process_interval_; std::map last_report_blocks_; Timestamp last_report_block_time_; NetworkControllerConfig initial_config_; StreamsConfig streams_config_; const bool send_side_bwe_with_overhead_; // MS_NOTE: not used. // const bool add_pacing_to_cwin_; // Transport overhead is written by OnNetworkRouteChanged and read by // AddPacket. // TODO(srte): Remove atomic when feedback adapter runs on task queue. std::atomic transport_overhead_bytes_per_packet_; bool network_available_; // TODO(perkj): |task_queue_| is supposed to replace |process_thread_|. // |task_queue_| is defined last to ensure all pending tasks are cancelled // and deleted before any other members. RTC_DISALLOW_COPY_AND_ASSIGN(RtpTransportControllerSend); }; } // namespace webrtc #endif // CALL_RTP_TRANSPORT_CONTROLLER_SEND_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/call/rtp_transport_controller_send_interface.h ================================================ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef CALL_RTP_TRANSPORT_CONTROLLER_SEND_INTERFACE_H_ #define CALL_RTP_TRANSPORT_CONTROLLER_SEND_INTERFACE_H_ #include "api/bitrate_constraints.h" #include "api/transport/bitrate_settings.h" // #include "call/rtp_config.h" // #include "modules/rtp_rtcp/include/report_block_data.h" // #include "modules/rtp_rtcp/include/rtp_packet_sender.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" // #include "modules/rtp_rtcp/source/rtp_packet_received.h" #include #include #include #include #include #include #include namespace rtc { struct SentPacket; struct NetworkRoute; } // namespace rtc namespace webrtc { class TargetTransferRateObserver; class Transport; class PacedSender; class PacketFeedbackObserver; class PacketRouter; class RateLimiter; class RtcpBandwidthObserver; class RtpPacketSender; class TransportFeedbackObserver; // An RtpTransportController should own everything related to the RTP // transport to/from a remote endpoint. We should have separate // interfaces for send and receive side, even if they are implemented // by the same class. This is an ongoing refactoring project. At some // point, this class should be promoted to a public api under // webrtc/api/rtp/. // // For a start, this object is just a collection of the objects needed // by the VideoSendStream constructor. The plan is to move ownership // of all RTP-related objects here, and add methods to create per-ssrc // objects which would then be passed to VideoSendStream. Eventually, // direct accessors like packet_router() should be removed. // // This should also have a reference to the underlying // webrtc::Transport(s). Currently, webrtc::Transport is implemented by // WebRtcVideoChannel and WebRtcVoiceMediaChannel, and owned by // WebrtcSession. Video and audio always uses different transport // objects, even in the common case where they are bundled over the // same underlying transport. // // Extracting the logic of the webrtc::Transport from BaseChannel and // subclasses into a separate class seems to be a prerequesite for // moving the transport here. class RtpTransportControllerSendInterface { public: virtual ~RtpTransportControllerSendInterface() {} virtual PacketRouter* packet_router() = 0; virtual NetworkStateEstimateObserver* network_state_estimate_observer() = 0; virtual TransportFeedbackObserver* transport_feedback_observer() = 0; virtual PacedSender* packet_sender() = 0; // SetAllocatedSendBitrateLimits sets bitrates limits imposed by send codec // settings. // |min_send_bitrate_bps| is the total minimum send bitrate required by all // sending streams. This is the minimum bitrate the PacedSender will use. // |max_padding_bitrate_bps| is the max // bitrate the send streams request for padding. This can be higher than the // current network estimate and tells the PacedSender how much it should max // pad unless there is real packets to send. virtual void SetAllocatedSendBitrateLimits(int min_send_bitrate_bps, int max_padding_bitrate_bps, int total_bitrate_bps) = 0; virtual void SetPacingFactor(float pacing_factor) = 0; // MS_NOTE: not used. // virtual void RegisterPacketFeedbackObserver( // PacketFeedbackObserver* observer) = 0; // virtual void DeRegisterPacketFeedbackObserver( // PacketFeedbackObserver* observer) = 0; virtual void RegisterTargetTransferRateObserver( TargetTransferRateObserver* observer) = 0; virtual void OnNetworkAvailability(bool network_available) = 0; virtual RtcpBandwidthObserver* GetBandwidthObserver() = 0; virtual void EnablePeriodicAlrProbing(bool enable) = 0; // MS_NOTE: signature changed. virtual void OnSentPacket(const rtc::SentPacket& sent_packet, size_t size) = 0; // MS_NOTE: not used. // virtual void SetClientBitratePreferences( // const BitrateSettings& preferences) = 0; virtual void OnTransportOverheadChanged( size_t transport_overhead_per_packet) = 0; }; } // namespace webrtc #endif // CALL_RTP_TRANSPORT_CONTROLLER_SEND_INTERFACE_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/mediasoup_helpers.h ================================================ #ifndef LIBWEBRTC_MEDIASOUP_HELPERS_H #define LIBWEBRTC_MEDIASOUP_HELPERS_H #include "modules/rtp_rtcp/source/rtp_packet/transport_feedback.h" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include #include namespace mediasoup_helpers { /** * Helpers to retrieve necessary data from mediasoup FeedbackRtpTransportPacket. */ namespace FeedbackRtpTransport { const std::vector GetReceivedPackets( const RTC::RTCP::FeedbackRtpTransportPacket* packet) { std::vector receivedPackets; for (auto& packetResult : packet->GetPacketResults()) { if (packetResult.received) receivedPackets.emplace_back(packetResult.sequenceNumber, packetResult.delta); } return receivedPackets; } // Get the reference time in microseconds, including any precision loss. int64_t GetBaseTimeUs(const RTC::RTCP::FeedbackRtpTransportPacket* packet) { return packet->GetReferenceTimestamp() * 1000; } // Get the unwrapped delta between current base time and |prev_timestamp_us|. int64_t GetBaseDeltaUs( const RTC::RTCP::FeedbackRtpTransportPacket* packet, int64_t prev_timestamp_us) { return packet->GetBaseDelta(prev_timestamp_us / 1000) * 1000; } } // namespace FeedbackRtpTransport } // namespace mediasoup_helpers #endif ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/bitrate_controller/loss_based_bandwidth_estimation.h" #include "api/units/data_rate.h" #include "api/units/time_delta.h" #include "system_wrappers/source/field_trial.h" #include #include #include namespace webrtc { namespace { const char kBweLossBasedControl[] = "WebRTC-Bwe-LossBasedControl"; // Increase slower when RTT is high. double GetIncreaseFactor(const LossBasedControlConfig& config, TimeDelta rtt) { // Clamp the RTT if (rtt < config.increase_low_rtt) { rtt = config.increase_low_rtt; } else if (rtt > config.increase_high_rtt) { rtt = config.increase_high_rtt; } auto rtt_range = config.increase_high_rtt.Get() - config.increase_low_rtt; if (rtt_range <= TimeDelta::Zero()) { // RTC_DCHECK(false); // Only on misconfiguration. return config.min_increase_factor; } auto rtt_offset = rtt - config.increase_low_rtt; auto relative_offset = std::max(0.0, std::min(rtt_offset / rtt_range, 1.0)); auto factor_range = config.max_increase_factor - config.min_increase_factor; return config.min_increase_factor + (1 - relative_offset) * factor_range; } double LossFromBitrate(DataRate bitrate, DataRate loss_bandwidth_balance, double exponent) { if (loss_bandwidth_balance >= bitrate) return 1.0; return pow(loss_bandwidth_balance / bitrate, exponent); } DataRate BitrateFromLoss(double loss, DataRate loss_bandwidth_balance, double exponent) { if (exponent <= 0) { // RTC_DCHECK(false); return DataRate::Infinity(); } if (loss < 1e-5) return DataRate::Infinity(); return loss_bandwidth_balance * pow(loss, -1.0 / exponent); } double ExponentialUpdate(TimeDelta window, TimeDelta interval) { // Use the convention that exponential window length (which is really // infinite) is the time it takes to dampen to 1/e. if (window <= TimeDelta::Zero()) { // RTC_DCHECK(false); return 1.0f; } return 1.0f - exp(interval / window * -1.0); } } // namespace LossBasedControlConfig::LossBasedControlConfig() : enabled(field_trial::IsEnabled(kBweLossBasedControl)), min_increase_factor("min_incr", 1.02), max_increase_factor("max_incr", 1.08), increase_low_rtt("incr_low_rtt", TimeDelta::ms(200)), increase_high_rtt("incr_high_rtt", TimeDelta::ms(800)), decrease_factor("decr", 0.99), loss_window("loss_win", TimeDelta::ms(800)), loss_max_window("loss_max_win", TimeDelta::ms(800)), acknowledged_rate_max_window("ackrate_max_win", TimeDelta::ms(800)), increase_offset("incr_offset", DataRate::bps(1000)), loss_bandwidth_balance_increase("balance_incr", DataRate::kbps(0.5)), loss_bandwidth_balance_decrease("balance_decr", DataRate::kbps(4)), loss_bandwidth_balance_exponent("exponent", 0.5), allow_resets("resets", false), decrease_interval("decr_intvl", TimeDelta::ms(300)), loss_report_timeout("timeout", TimeDelta::ms(6000)) { std::string trial_string = field_trial::FindFullName(kBweLossBasedControl); ParseFieldTrial( {&min_increase_factor, &max_increase_factor, &increase_low_rtt, &increase_high_rtt, &decrease_factor, &loss_window, &loss_max_window, &acknowledged_rate_max_window, &increase_offset, &loss_bandwidth_balance_increase, &loss_bandwidth_balance_decrease, &loss_bandwidth_balance_exponent, &allow_resets, &decrease_interval, &loss_report_timeout}, trial_string); } LossBasedControlConfig::LossBasedControlConfig(const LossBasedControlConfig&) = default; LossBasedControlConfig::~LossBasedControlConfig() = default; LossBasedBandwidthEstimation::LossBasedBandwidthEstimation() : config_(LossBasedControlConfig()), average_loss_(0), average_loss_max_(0), loss_based_bitrate_(DataRate::Zero()), acknowledged_bitrate_max_(DataRate::Zero()), acknowledged_bitrate_last_update_(Timestamp::MinusInfinity()), time_last_decrease_(Timestamp::MinusInfinity()), has_decreased_since_last_loss_report_(false), last_loss_packet_report_(Timestamp::MinusInfinity()), last_loss_ratio_(0) {} void LossBasedBandwidthEstimation::UpdateLossStatistics( const std::vector& packet_results, Timestamp at_time) { if (packet_results.empty()) { // RTC_DCHECK(false); return; } int loss_count = 0; for (const auto& pkt : packet_results) { loss_count += pkt.receive_time.IsInfinite() ? 1 : 0; } last_loss_ratio_ = static_cast(loss_count) / packet_results.size(); const TimeDelta time_passed = last_loss_packet_report_.IsFinite() ? at_time - last_loss_packet_report_ : TimeDelta::seconds(1); last_loss_packet_report_ = at_time; has_decreased_since_last_loss_report_ = false; average_loss_ += ExponentialUpdate(config_.loss_window, time_passed) * (last_loss_ratio_ - average_loss_); if (average_loss_ > average_loss_max_) { average_loss_max_ = average_loss_; } else { average_loss_max_ += ExponentialUpdate(config_.loss_max_window, time_passed) * (average_loss_ - average_loss_max_); } } void LossBasedBandwidthEstimation::UpdateAcknowledgedBitrate( DataRate acknowledged_bitrate, Timestamp at_time) { const TimeDelta time_passed = acknowledged_bitrate_last_update_.IsFinite() ? at_time - acknowledged_bitrate_last_update_ : TimeDelta::seconds(1); acknowledged_bitrate_last_update_ = at_time; if (acknowledged_bitrate > acknowledged_bitrate_max_) { acknowledged_bitrate_max_ = acknowledged_bitrate; } else { acknowledged_bitrate_max_ -= ExponentialUpdate(config_.acknowledged_rate_max_window, time_passed) * (acknowledged_bitrate_max_ - acknowledged_bitrate); } } void LossBasedBandwidthEstimation::Update(Timestamp at_time, DataRate min_bitrate, TimeDelta last_round_trip_time) { // Only increase if loss has been low for some time. const double loss_estimate_for_increase = average_loss_max_; // Avoid multiple decreases from averaging over one loss spike. const double loss_estimate_for_decrease = std::min(average_loss_, last_loss_ratio_); const bool allow_decrease = !has_decreased_since_last_loss_report_ && (at_time - time_last_decrease_ >= last_round_trip_time + config_.decrease_interval); if (loss_estimate_for_increase < loss_increase_threshold()) { // Increase bitrate by RTT-adaptive ratio. DataRate new_increased_bitrate = min_bitrate * GetIncreaseFactor(config_, last_round_trip_time) + config_.increase_offset; // The bitrate that would make the loss "just high enough". const DataRate new_increased_bitrate_cap = BitrateFromLoss( loss_estimate_for_increase, config_.loss_bandwidth_balance_increase, config_.loss_bandwidth_balance_exponent); new_increased_bitrate = std::min(new_increased_bitrate, new_increased_bitrate_cap); loss_based_bitrate_ = std::max(new_increased_bitrate, loss_based_bitrate_); } else if (loss_estimate_for_decrease > loss_decrease_threshold() && allow_decrease) { // The bitrate that would make the loss "just acceptable". const DataRate new_decreased_bitrate_floor = BitrateFromLoss( loss_estimate_for_decrease, config_.loss_bandwidth_balance_decrease, config_.loss_bandwidth_balance_exponent); DataRate new_decreased_bitrate = std::max(decreased_bitrate(), new_decreased_bitrate_floor); if (new_decreased_bitrate < loss_based_bitrate_) { time_last_decrease_ = at_time; has_decreased_since_last_loss_report_ = true; loss_based_bitrate_ = new_decreased_bitrate; } } } void LossBasedBandwidthEstimation::Reset(DataRate bitrate) { loss_based_bitrate_ = bitrate; average_loss_ = 0; average_loss_max_ = 0; } double LossBasedBandwidthEstimation::loss_increase_threshold() const { return LossFromBitrate(loss_based_bitrate_, config_.loss_bandwidth_balance_increase, config_.loss_bandwidth_balance_exponent); } double LossBasedBandwidthEstimation::loss_decrease_threshold() const { return LossFromBitrate(loss_based_bitrate_, config_.loss_bandwidth_balance_decrease, config_.loss_bandwidth_balance_exponent); } DataRate LossBasedBandwidthEstimation::decreased_bitrate() const { return config_.decrease_factor * acknowledged_bitrate_max_; } void LossBasedBandwidthEstimation::MaybeReset(DataRate bitrate) { if (config_.allow_resets) Reset(bitrate); } void LossBasedBandwidthEstimation::SetInitialBitrate(DataRate bitrate) { Reset(bitrate); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_BITRATE_CONTROLLER_LOSS_BASED_BANDWIDTH_ESTIMATION_H_ #define MODULES_BITRATE_CONTROLLER_LOSS_BASED_BANDWIDTH_ESTIMATION_H_ #include "api/transport/network_types.h" #include "api/units/data_rate.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "rtc_base/experiments/field_trial_parser.h" #include namespace webrtc { struct LossBasedControlConfig { LossBasedControlConfig(); LossBasedControlConfig(const LossBasedControlConfig&); LossBasedControlConfig& operator=(const LossBasedControlConfig&) = default; ~LossBasedControlConfig(); bool enabled; FieldTrialParameter min_increase_factor; FieldTrialParameter max_increase_factor; FieldTrialParameter increase_low_rtt; FieldTrialParameter increase_high_rtt; FieldTrialParameter decrease_factor; FieldTrialParameter loss_window; FieldTrialParameter loss_max_window; FieldTrialParameter acknowledged_rate_max_window; FieldTrialParameter increase_offset; FieldTrialParameter loss_bandwidth_balance_increase; FieldTrialParameter loss_bandwidth_balance_decrease; FieldTrialParameter loss_bandwidth_balance_exponent; FieldTrialParameter allow_resets; FieldTrialParameter decrease_interval; FieldTrialParameter loss_report_timeout; }; class LossBasedBandwidthEstimation { public: LossBasedBandwidthEstimation(); void Update(Timestamp at_time, DataRate min_bitrate, TimeDelta last_round_trip_time); void UpdateAcknowledgedBitrate(DataRate acknowledged_bitrate, Timestamp at_time); void MaybeReset(DataRate bitrate); void SetInitialBitrate(DataRate bitrate); bool Enabled() const { return config_.enabled; } void UpdateLossStatistics(const std::vector& packet_results, Timestamp at_time); DataRate GetEstimate() const { return loss_based_bitrate_; } private: friend class GoogCcStatePrinter; void Reset(DataRate bitrate); double loss_increase_threshold() const; double loss_decrease_threshold() const; DataRate decreased_bitrate() const; LossBasedControlConfig config_; double average_loss_; double average_loss_max_; DataRate loss_based_bitrate_; DataRate acknowledged_bitrate_max_; Timestamp acknowledged_bitrate_last_update_; Timestamp time_last_decrease_; bool has_decreased_since_last_loss_report_; Timestamp last_loss_packet_report_; double last_loss_ratio_; }; } // namespace webrtc #endif // MODULES_BITRATE_CONTROLLER_LOSS_BASED_BANDWIDTH_ESTIMATION_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.cc ================================================ /* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::SendSideBandwidthEstimation" // #define MS_LOG_DEV_LEVEL 3 #include "modules/bitrate_controller/send_side_bandwidth_estimation.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "system_wrappers/source/field_trial.h" #include "Logger.hpp" #include #include #include #include #include namespace webrtc { namespace { constexpr TimeDelta kBweIncreaseInterval = TimeDelta::Millis<1000>(); constexpr TimeDelta kBweDecreaseInterval = TimeDelta::Millis<300>(); constexpr TimeDelta kStartPhase = TimeDelta::Millis<2000>(); constexpr TimeDelta kBweConverganceTime = TimeDelta::Millis<20000>(); constexpr int kLimitNumPackets = 20; constexpr DataRate kDefaultMaxBitrate = DataRate::BitsPerSec<1000000000>(); constexpr TimeDelta kLowBitrateLogPeriod = TimeDelta::Millis<10000>(); constexpr TimeDelta kRtcEventLogPeriod = TimeDelta::Millis<5000>(); // Expecting that RTCP feedback is sent uniformly within [0.5, 1.5]s intervals. constexpr TimeDelta kMaxRtcpFeedbackInterval = TimeDelta::Millis<5000>(); constexpr int kFeedbackTimeoutIntervals = 3; constexpr TimeDelta kTimeoutInterval = TimeDelta::Millis<1000>(); constexpr float kDefaultLowLossThreshold = 0.02f; constexpr float kDefaultHighLossThreshold = 0.1f; constexpr DataRate kDefaultBitrateThreshold = DataRate::Zero(); struct UmaRampUpMetric { const char* metric_name; int bitrate_kbps; }; const UmaRampUpMetric kUmaRampupMetrics[] = { {"WebRTC.BWE.RampUpTimeTo500kbpsInMs", 500}, {"WebRTC.BWE.RampUpTimeTo1000kbpsInMs", 1000}, {"WebRTC.BWE.RampUpTimeTo2000kbpsInMs", 2000}}; const size_t kNumUmaRampupMetrics = sizeof(kUmaRampupMetrics) / sizeof(kUmaRampupMetrics[0]); const char kBweLosExperiment[] = "WebRTC-BweLossExperiment"; bool BweLossExperimentIsEnabled() { std::string experiment_string = webrtc::field_trial::FindFullName(kBweLosExperiment); // The experiment is enabled iff the field trial string begins with "Enabled". return experiment_string.find("Enabled") == 0; } bool ReadBweLossExperimentParameters(float* low_loss_threshold, float* high_loss_threshold, uint32_t* bitrate_threshold_kbps) { // RTC_DCHECK(low_loss_threshold); // RTC_DCHECK(high_loss_threshold); // RTC_DCHECK(bitrate_threshold_kbps); std::string experiment_string = webrtc::field_trial::FindFullName(kBweLosExperiment); int parsed_values = sscanf(experiment_string.c_str(), "Enabled-%f,%f,%u", low_loss_threshold, high_loss_threshold, bitrate_threshold_kbps); if (parsed_values == 3) { // RTC_CHECK_GT(*low_loss_threshold, 0.0f) // << "Loss threshold must be greater than 0."; // RTC_CHECK_LE(*low_loss_threshold, 1.0f) // << "Loss threshold must be less than or equal to 1."; // RTC_CHECK_GT(*high_loss_threshold, 0.0f) // << "Loss threshold must be greater than 0."; // RTC_CHECK_LE(*high_loss_threshold, 1.0f) // << "Loss threshold must be less than or equal to 1."; // RTC_CHECK_LE(*low_loss_threshold, *high_loss_threshold) // << "The low loss threshold must be less than or equal to the high loss " // "threshold."; // RTC_CHECK_GE(*bitrate_threshold_kbps, 0) // << "Bitrate threshold can't be negative."; // RTC_CHECK_LT(*bitrate_threshold_kbps, // std::numeric_limits::max() / 1000) // << "Bitrate must be smaller enough to avoid overflows."; return true; } MS_WARN_TAG(bwe, "Failed to parse parameters for BweLossExperiment " "experiment from field trial string. Using default"); *low_loss_threshold = kDefaultLowLossThreshold; *high_loss_threshold = kDefaultHighLossThreshold; *bitrate_threshold_kbps = kDefaultBitrateThreshold.kbps(); return false; } } // namespace LinkCapacityTracker::LinkCapacityTracker() : tracking_rate("rate", TimeDelta::seconds(10)) { ParseFieldTrial({&tracking_rate}, field_trial::FindFullName("WebRTC-Bwe-LinkCapacity")); } LinkCapacityTracker::~LinkCapacityTracker() {} void LinkCapacityTracker::OnOveruse(DataRate acknowledged_rate, Timestamp at_time) { capacity_estimate_bps_ = std::min(capacity_estimate_bps_, acknowledged_rate.bps()); last_link_capacity_update_ = at_time; } void LinkCapacityTracker::OnStartingRate(DataRate start_rate) { if (last_link_capacity_update_.IsInfinite()) capacity_estimate_bps_ = start_rate.bps(); } void LinkCapacityTracker::OnRateUpdate(DataRate acknowledged, Timestamp at_time) { if (acknowledged.bps() > capacity_estimate_bps_) { TimeDelta delta = at_time - last_link_capacity_update_; double alpha = delta.IsFinite() ? exp(-(delta / tracking_rate.Get())) : 0; capacity_estimate_bps_ = alpha * capacity_estimate_bps_ + (1 - alpha) * acknowledged.bps(); } last_link_capacity_update_ = at_time; } void LinkCapacityTracker::OnRttBackoff(DataRate backoff_rate, Timestamp at_time) { capacity_estimate_bps_ = std::min(capacity_estimate_bps_, backoff_rate.bps()); last_link_capacity_update_ = at_time; } DataRate LinkCapacityTracker::estimate() const { return DataRate::bps(capacity_estimate_bps_); } RttBasedBackoff::RttBasedBackoff() : rtt_limit_("limit", TimeDelta::PlusInfinity()), drop_fraction_("fraction", 0.5), drop_interval_("interval", TimeDelta::ms(300)), persist_on_route_change_("persist"), safe_timeout_("safe_timeout", true), bandwidth_floor_("floor", DataRate::kbps(5)), // By initializing this to plus infinity, we make sure that we never // trigger rtt backoff unless packet feedback is enabled. last_propagation_rtt_update_(Timestamp::PlusInfinity()), last_propagation_rtt_(TimeDelta::Zero()), last_packet_sent_(Timestamp::MinusInfinity()) { ParseFieldTrial( {&rtt_limit_, &drop_fraction_, &drop_interval_, &persist_on_route_change_, &safe_timeout_, &bandwidth_floor_}, field_trial::FindFullName("WebRTC-Bwe-MaxRttLimit")); } void RttBasedBackoff::OnRouteChange() { if (!persist_on_route_change_) { last_propagation_rtt_update_ = Timestamp::PlusInfinity(); last_propagation_rtt_ = TimeDelta::Zero(); } } void RttBasedBackoff::UpdatePropagationRtt(Timestamp at_time, TimeDelta propagation_rtt) { last_propagation_rtt_update_ = at_time; last_propagation_rtt_ = propagation_rtt; } TimeDelta RttBasedBackoff::CorrectedRtt(Timestamp at_time) const { TimeDelta time_since_rtt = at_time - last_propagation_rtt_update_; TimeDelta timeout_correction = time_since_rtt; if (safe_timeout_) { // Avoid timeout when no packets are being sent. TimeDelta time_since_packet_sent = at_time - last_packet_sent_; timeout_correction = std::max(time_since_rtt - time_since_packet_sent, TimeDelta::Zero()); } return timeout_correction + last_propagation_rtt_; } RttBasedBackoff::~RttBasedBackoff() = default; SendSideBandwidthEstimation::SendSideBandwidthEstimation() : lost_packets_since_last_loss_update_(0), expected_packets_since_last_loss_update_(0), current_bitrate_(DataRate::Zero()), min_bitrate_configured_( DataRate::bps(congestion_controller::GetMinBitrateBps())), max_bitrate_configured_(kDefaultMaxBitrate), last_low_bitrate_log_(Timestamp::MinusInfinity()), has_decreased_since_last_fraction_loss_(false), last_loss_feedback_(Timestamp::MinusInfinity()), last_loss_packet_report_(Timestamp::MinusInfinity()), last_timeout_(Timestamp::MinusInfinity()), last_fraction_loss_(0), last_logged_fraction_loss_(0), last_round_trip_time_(TimeDelta::Zero()), bwe_incoming_(DataRate::Zero()), delay_based_bitrate_(DataRate::Zero()), time_last_decrease_(Timestamp::MinusInfinity()), first_report_time_(Timestamp::MinusInfinity()), initially_lost_packets_(0), bitrate_at_2_seconds_(DataRate::Zero()), uma_update_state_(kNoUpdate), uma_rtt_state_(kNoUpdate), rampup_uma_stats_updated_(kNumUmaRampupMetrics, false), last_rtc_event_log_(Timestamp::MinusInfinity()), in_timeout_experiment_( webrtc::field_trial::IsEnabled("WebRTC-FeedbackTimeout")), low_loss_threshold_(kDefaultLowLossThreshold), high_loss_threshold_(kDefaultHighLossThreshold), bitrate_threshold_(kDefaultBitrateThreshold) { // RTC_DCHECK(event_log); if (BweLossExperimentIsEnabled()) { uint32_t bitrate_threshold_kbps; if (ReadBweLossExperimentParameters(&low_loss_threshold_, &high_loss_threshold_, &bitrate_threshold_kbps)) { MS_DEBUG_TAG(bwe, "Enabled BweLossExperiment with parameters %f, %f, %d", low_loss_threshold_, high_loss_threshold_, bitrate_threshold_kbps); bitrate_threshold_ = DataRate::kbps(bitrate_threshold_kbps); } } } SendSideBandwidthEstimation::~SendSideBandwidthEstimation() {} void SendSideBandwidthEstimation::OnRouteChange() { lost_packets_since_last_loss_update_ = 0; expected_packets_since_last_loss_update_ = 0; current_bitrate_ = DataRate::Zero(); min_bitrate_configured_ = DataRate::bps(congestion_controller::GetMinBitrateBps()); max_bitrate_configured_ = kDefaultMaxBitrate; last_low_bitrate_log_ = Timestamp::MinusInfinity(); has_decreased_since_last_fraction_loss_ = false; last_loss_feedback_ = Timestamp::MinusInfinity(); last_loss_packet_report_ = Timestamp::MinusInfinity(); last_timeout_ = Timestamp::MinusInfinity(); last_fraction_loss_ = 0; last_logged_fraction_loss_ = 0; last_round_trip_time_ = TimeDelta::Zero(); bwe_incoming_ = DataRate::Zero(); delay_based_bitrate_ = DataRate::Zero(); time_last_decrease_ = Timestamp::MinusInfinity(); first_report_time_ = Timestamp::MinusInfinity(); initially_lost_packets_ = 0; bitrate_at_2_seconds_ = DataRate::Zero(); uma_update_state_ = kNoUpdate; uma_rtt_state_ = kNoUpdate; last_rtc_event_log_ = Timestamp::MinusInfinity(); rtt_backoff_.OnRouteChange(); } void SendSideBandwidthEstimation::SetBitrates( absl::optional send_bitrate, DataRate min_bitrate, DataRate max_bitrate, Timestamp at_time) { SetMinMaxBitrate(min_bitrate, max_bitrate); if (send_bitrate) { link_capacity_.OnStartingRate(*send_bitrate); SetSendBitrate(*send_bitrate, at_time); } } void SendSideBandwidthEstimation::SetSendBitrate(DataRate bitrate, Timestamp at_time) { MS_DEBUG_DEV("bitrate: %lld", bitrate.bps()); // RTC_DCHECK_GT(bitrate, DataRate::Zero()); // Reset to avoid being capped by the estimate. delay_based_bitrate_ = DataRate::Zero(); if (loss_based_bandwidth_estimation_.Enabled()) { loss_based_bandwidth_estimation_.MaybeReset(bitrate); } CapBitrateToThresholds(at_time, bitrate); // Clear last sent bitrate history so the new value can be used directly // and not capped. min_bitrate_history_.clear(); } void SendSideBandwidthEstimation::SetMinMaxBitrate(DataRate min_bitrate, DataRate max_bitrate) { min_bitrate_configured_ = std::max(min_bitrate, congestion_controller::GetMinBitrate()); if (max_bitrate > DataRate::Zero() && max_bitrate.IsFinite()) { max_bitrate_configured_ = std::max(min_bitrate_configured_, max_bitrate); } else { max_bitrate_configured_ = kDefaultMaxBitrate; } } int SendSideBandwidthEstimation::GetMinBitrate() const { return min_bitrate_configured_.bps(); } void SendSideBandwidthEstimation::CurrentEstimate(int* bitrate, uint8_t* loss, int64_t* rtt) const { *bitrate = std::max(current_bitrate_.bps(), GetMinBitrate()); *loss = last_fraction_loss_; *rtt = last_round_trip_time_.ms(); MS_DEBUG_DEV("bitrate:%d (current_bitrate_:%" PRIi64 ", GetMinBitrate():%d), loss:%d, rtt:%" PRIi64, *bitrate, current_bitrate_.bps(), GetMinBitrate(), *loss, *rtt); } DataRate SendSideBandwidthEstimation::GetEstimatedLinkCapacity() const { return link_capacity_.estimate(); } void SendSideBandwidthEstimation::UpdateReceiverEstimate(Timestamp at_time, DataRate bandwidth) { bwe_incoming_ = bandwidth; CapBitrateToThresholds(at_time, current_bitrate_); } void SendSideBandwidthEstimation::UpdateDelayBasedEstimate(Timestamp at_time, DataRate bitrate) { if (acknowledged_rate_) { if (bitrate < delay_based_bitrate_) { link_capacity_.OnOveruse(*acknowledged_rate_, at_time); } } delay_based_bitrate_ = bitrate; CapBitrateToThresholds(at_time, current_bitrate_); } void SendSideBandwidthEstimation::SetAcknowledgedRate( absl::optional acknowledged_rate, Timestamp at_time) { acknowledged_rate_ = acknowledged_rate; if (acknowledged_rate && loss_based_bandwidth_estimation_.Enabled()) { loss_based_bandwidth_estimation_.UpdateAcknowledgedBitrate( *acknowledged_rate, at_time); } } void SendSideBandwidthEstimation::IncomingPacketFeedbackVector( const TransportPacketsFeedback& report) { if (loss_based_bandwidth_estimation_.Enabled()) { loss_based_bandwidth_estimation_.UpdateLossStatistics( report.packet_feedbacks, report.feedback_time); } } void SendSideBandwidthEstimation::UpdateReceiverBlock(uint8_t fraction_loss, TimeDelta rtt, int number_of_packets, Timestamp at_time) { const int kRoundingConstant = 128; int packets_lost = (static_cast(fraction_loss) * number_of_packets + kRoundingConstant) >> 8; UpdatePacketsLost(packets_lost, number_of_packets, at_time); UpdateRtt(rtt, at_time); } void SendSideBandwidthEstimation::UpdatePacketsLost(int packets_lost, int number_of_packets, Timestamp at_time) { last_loss_feedback_ = at_time; if (first_report_time_.IsInfinite()) first_report_time_ = at_time; // Check sequence number diff and weight loss report if (number_of_packets > 0) { int64_t expected = expected_packets_since_last_loss_update_ + number_of_packets; // Don't generate a loss rate until it can be based on enough packets. if (expected < kLimitNumPackets) { // Accumulate reports. expected_packets_since_last_loss_update_ = expected; lost_packets_since_last_loss_update_ += packets_lost; return; } has_decreased_since_last_fraction_loss_ = false; int64_t lost_q8 = std::max(lost_packets_since_last_loss_update_ + packets_lost, 0) << 8; last_fraction_loss_ = std::min(lost_q8 / expected, 255); // Reset accumulators. lost_packets_since_last_loss_update_ = 0; expected_packets_since_last_loss_update_ = 0; last_loss_packet_report_ = at_time; UpdateEstimate(at_time); } UpdateUmaStatsPacketsLost(at_time, packets_lost); } void SendSideBandwidthEstimation::UpdateUmaStatsPacketsLost(Timestamp at_time, int packets_lost) { DataRate bitrate_kbps = DataRate::kbps((current_bitrate_.bps() + 500) / 1000); for (size_t i = 0; i < kNumUmaRampupMetrics; ++i) { if (!rampup_uma_stats_updated_[i] && bitrate_kbps.kbps() >= kUmaRampupMetrics[i].bitrate_kbps) { // RTC_HISTOGRAMS_COUNTS_100000(i, kUmaRampupMetrics[i].metric_name, // (at_time - first_report_time_).ms()); rampup_uma_stats_updated_[i] = true; } } if (IsInStartPhase(at_time)) { initially_lost_packets_ += packets_lost; } else if (uma_update_state_ == kNoUpdate) { uma_update_state_ = kFirstDone; bitrate_at_2_seconds_ = bitrate_kbps; // RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitiallyLostPackets", // initially_lost_packets_, 0, 100, 50); // RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitialBandwidthEstimate", // bitrate_at_2_seconds_.kbps(), 0, 2000, 50); } else if (uma_update_state_ == kFirstDone && at_time - first_report_time_ >= kBweConverganceTime) { uma_update_state_ = kDone; // int bitrate_diff_kbps = std::max( // bitrate_at_2_seconds_.kbps() - bitrate_kbps.kbps(), 0); // RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitialVsConvergedDiff", bitrate_diff_kbps, // 0, 2000, 50); } } void SendSideBandwidthEstimation::UpdateRtt(TimeDelta rtt, Timestamp at_time) { // Update RTT if we were able to compute an RTT based on this RTCP. // FlexFEC doesn't send RTCP SR, which means we won't be able to compute RTT. if (rtt > TimeDelta::Zero()) last_round_trip_time_ = rtt; if (!IsInStartPhase(at_time) && uma_rtt_state_ == kNoUpdate) { uma_rtt_state_ = kDone; // RTC_HISTOGRAM_COUNTS("WebRTC.BWE.InitialRtt", rtt.ms(), 0, 2000, 50); } } void SendSideBandwidthEstimation::UpdateEstimate(Timestamp at_time) { DataRate new_bitrate = current_bitrate_; if (rtt_backoff_.CorrectedRtt(at_time) > rtt_backoff_.rtt_limit_) { if (at_time - time_last_decrease_ >= rtt_backoff_.drop_interval_ && current_bitrate_ > rtt_backoff_.bandwidth_floor_) { time_last_decrease_ = at_time; new_bitrate = std::max(current_bitrate_ * rtt_backoff_.drop_fraction_, rtt_backoff_.bandwidth_floor_.Get()); link_capacity_.OnRttBackoff(new_bitrate, at_time); } CapBitrateToThresholds(at_time, new_bitrate); return; } // We trust the REMB and/or delay-based estimate during the first 2 seconds if // we haven't had any packet loss reported, to allow startup bitrate probing. if (last_fraction_loss_ == 0 && IsInStartPhase(at_time)) { new_bitrate = std::max(bwe_incoming_, new_bitrate); new_bitrate = std::max(delay_based_bitrate_, new_bitrate); if (loss_based_bandwidth_estimation_.Enabled()) { loss_based_bandwidth_estimation_.SetInitialBitrate(new_bitrate); } if (new_bitrate != current_bitrate_) { min_bitrate_history_.clear(); if (loss_based_bandwidth_estimation_.Enabled()) { min_bitrate_history_.push_back(std::make_pair(at_time, new_bitrate)); } else { min_bitrate_history_.push_back( std::make_pair(at_time, current_bitrate_)); } CapBitrateToThresholds(at_time, new_bitrate); return; } } UpdateMinHistory(at_time); if (last_loss_packet_report_.IsInfinite()) { // No feedback received. CapBitrateToThresholds(at_time, current_bitrate_); return; } if (loss_based_bandwidth_estimation_.Enabled()) { loss_based_bandwidth_estimation_.Update( at_time, min_bitrate_history_.front().second, last_round_trip_time_); new_bitrate = MaybeRampupOrBackoff(new_bitrate, at_time); CapBitrateToThresholds(at_time, new_bitrate); return; } TimeDelta time_since_loss_packet_report = at_time - last_loss_packet_report_; TimeDelta time_since_loss_feedback = at_time - last_loss_feedback_; if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) { // We only care about loss above a given bitrate threshold. float loss = last_fraction_loss_ / 256.0f; // We only make decisions based on loss when the bitrate is above a // threshold. This is a crude way of handling loss which is uncorrelated // to congestion. if (current_bitrate_ < bitrate_threshold_ || loss <= low_loss_threshold_) { // Loss < 2%: Increase rate by 8% of the min bitrate in the last // kBweIncreaseInterval. // Note that by remembering the bitrate over the last second one can // rampup up one second faster than if only allowed to start ramping // at 8% per second rate now. E.g.: // If sending a constant 100kbps it can rampup immediately to 108kbps // whenever a receiver report is received with lower packet loss. // If instead one would do: current_bitrate_ *= 1.08^(delta time), // it would take over one second since the lower packet loss to achieve // 108kbps. new_bitrate = DataRate::bps(min_bitrate_history_.front().second.bps() * 1.08 + 0.5); // Add 1 kbps extra, just to make sure that we do not get stuck // (gives a little extra increase at low rates, negligible at higher // rates). new_bitrate += DataRate::bps(1000); } else if (current_bitrate_ > bitrate_threshold_) { if (loss <= high_loss_threshold_) { // Loss between 2% - 10%: Do nothing. } else { // Loss > 10%: Limit the rate decreases to once a kBweDecreaseInterval // + rtt. if (!has_decreased_since_last_fraction_loss_ && (at_time - time_last_decrease_) >= (kBweDecreaseInterval + last_round_trip_time_)) { time_last_decrease_ = at_time; // Reduce rate: // newRate = rate * (1 - 0.5*lossRate); // where packetLoss = 256*lossRate; new_bitrate = DataRate::bps((current_bitrate_.bps() * static_cast(512 - last_fraction_loss_)) / 512.0); has_decreased_since_last_fraction_loss_ = true; } } } } else if (time_since_loss_feedback > kFeedbackTimeoutIntervals * kMaxRtcpFeedbackInterval && (last_timeout_.IsInfinite() || at_time - last_timeout_ > kTimeoutInterval)) { if (in_timeout_experiment_) { MS_WARN_TAG(bwe, "Feedback timed out (%s), reducint bitrate", ToString(time_since_loss_feedback).c_str()); new_bitrate = new_bitrate * 0.8; // Reset accumulators since we've already acted on missing feedback and // shouldn't to act again on these old lost packets. lost_packets_since_last_loss_update_ = 0; expected_packets_since_last_loss_update_ = 0; last_timeout_ = at_time; } } CapBitrateToThresholds(at_time, new_bitrate); } void SendSideBandwidthEstimation::UpdatePropagationRtt( Timestamp at_time, TimeDelta propagation_rtt) { rtt_backoff_.UpdatePropagationRtt(at_time, propagation_rtt); } void SendSideBandwidthEstimation::OnSentPacket(const SentPacket& sent_packet) { // Only feedback-triggering packets will be reported here. rtt_backoff_.last_packet_sent_ = sent_packet.send_time; } bool SendSideBandwidthEstimation::IsInStartPhase(Timestamp at_time) const { return first_report_time_.IsInfinite() || at_time - first_report_time_ < kStartPhase; } void SendSideBandwidthEstimation::UpdateMinHistory(Timestamp at_time) { // Remove old data points from history. // Since history precision is in ms, add one so it is able to increase // bitrate if it is off by as little as 0.5ms. while (!min_bitrate_history_.empty() && at_time - min_bitrate_history_.front().first + TimeDelta::ms(1) > kBweIncreaseInterval) { min_bitrate_history_.pop_front(); } // Typical minimum sliding-window algorithm: Pop values higher than current // bitrate before pushing it. while (!min_bitrate_history_.empty() && current_bitrate_ <= min_bitrate_history_.back().second) { min_bitrate_history_.pop_back(); } min_bitrate_history_.push_back(std::make_pair(at_time, current_bitrate_)); } DataRate SendSideBandwidthEstimation::MaybeRampupOrBackoff(DataRate new_bitrate, Timestamp at_time) { // TODO(crodbro): reuse this code in UpdateEstimate instead of current // inlining of very similar functionality. const TimeDelta time_since_loss_packet_report = at_time - last_loss_packet_report_; const TimeDelta time_since_loss_feedback = at_time - last_loss_feedback_; if (time_since_loss_packet_report < 1.2 * kMaxRtcpFeedbackInterval) { new_bitrate = min_bitrate_history_.front().second * 1.08; new_bitrate += DataRate::bps(1000); } else if (time_since_loss_feedback > kFeedbackTimeoutIntervals * kMaxRtcpFeedbackInterval && (last_timeout_.IsInfinite() || at_time - last_timeout_ > kTimeoutInterval)) { if (in_timeout_experiment_) { MS_WARN_TAG(bwe,"Feedback timed out (%s), reducint bitrate", ToString(time_since_loss_feedback).c_str()); new_bitrate = new_bitrate * 0.8; // Reset accumulators since we've already acted on missing feedback and // shouldn't to act again on these old lost packets. lost_packets_since_last_loss_update_ = 0; expected_packets_since_last_loss_update_ = 0; last_timeout_ = at_time; } } return new_bitrate; } void SendSideBandwidthEstimation::CapBitrateToThresholds(Timestamp at_time, DataRate bitrate) { if (bwe_incoming_ > DataRate::Zero() && bitrate > bwe_incoming_) { MS_DEBUG_DEV("bwe_incoming_:%lld", bwe_incoming_.bps()); bitrate = bwe_incoming_; } if (delay_based_bitrate_ > DataRate::Zero() && bitrate > delay_based_bitrate_) { MS_DEBUG_DEV("delay_based_bitrate_:%lld", delay_based_bitrate_.bps()); bitrate = delay_based_bitrate_; } if (loss_based_bandwidth_estimation_.Enabled() && loss_based_bandwidth_estimation_.GetEstimate() > DataRate::Zero()) { MS_DEBUG_DEV("loss_based_bandwidth_estimation_.GetEstimate():%lld", loss_based_bandwidth_estimation_.GetEstimate().bps()); bitrate = std::min(bitrate, loss_based_bandwidth_estimation_.GetEstimate()); } if (bitrate > max_bitrate_configured_) { MS_DEBUG_DEV("bitrate > max_bitrate_configured_, setting bitrate to max_bitrate_configured_"); bitrate = max_bitrate_configured_; } if (bitrate < min_bitrate_configured_) { MS_DEBUG_DEV("bitrate < min_bitrate_configured_"); if (last_low_bitrate_log_.IsInfinite() || at_time - last_low_bitrate_log_ > kLowBitrateLogPeriod) { MS_WARN_TAG(bwe, "Estimated available bandwidth %s" " is below configured min bitrate %s", ToString(bitrate).c_str(), ToString(min_bitrate_configured_).c_str()); last_low_bitrate_log_ = at_time; } bitrate = min_bitrate_configured_; } if (bitrate != current_bitrate_ || last_fraction_loss_ != last_logged_fraction_loss_ || at_time - last_rtc_event_log_ > kRtcEventLogPeriod) { last_logged_fraction_loss_ = last_fraction_loss_; last_rtc_event_log_ = at_time; } MS_DEBUG_DEV("current_bitrate_:%lld", current_bitrate_.bps()); current_bitrate_ = bitrate; if (acknowledged_rate_) { link_capacity_.OnRateUpdate(std::min(current_bitrate_, *acknowledged_rate_), at_time); } } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.h ================================================ /* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. * * FEC and NACK added bitrate is handled outside class */ #ifndef MODULES_BITRATE_CONTROLLER_SEND_SIDE_BANDWIDTH_ESTIMATION_H_ #define MODULES_BITRATE_CONTROLLER_SEND_SIDE_BANDWIDTH_ESTIMATION_H_ #include "api/transport/network_types.h" #include "api/units/data_rate.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "modules/bitrate_controller/loss_based_bandwidth_estimation.h" #include "rtc_base/experiments/field_trial_parser.h" #include #include #include #include #include namespace webrtc { class LinkCapacityTracker { public: LinkCapacityTracker(); ~LinkCapacityTracker(); void OnOveruse(DataRate acknowledged_rate, Timestamp at_time); void OnStartingRate(DataRate start_rate); void OnRateUpdate(DataRate acknowledged, Timestamp at_time); void OnRttBackoff(DataRate backoff_rate, Timestamp at_time); DataRate estimate() const; private: FieldTrialParameter tracking_rate; double capacity_estimate_bps_ = 0; Timestamp last_link_capacity_update_ = Timestamp::MinusInfinity(); }; class RttBasedBackoff { public: RttBasedBackoff(); ~RttBasedBackoff(); void OnRouteChange(); void UpdatePropagationRtt(Timestamp at_time, TimeDelta propagation_rtt); TimeDelta CorrectedRtt(Timestamp at_time) const; FieldTrialParameter rtt_limit_; FieldTrialParameter drop_fraction_; FieldTrialParameter drop_interval_; FieldTrialFlag persist_on_route_change_; FieldTrialParameter safe_timeout_; FieldTrialParameter bandwidth_floor_; public: Timestamp last_propagation_rtt_update_; TimeDelta last_propagation_rtt_; Timestamp last_packet_sent_; }; class SendSideBandwidthEstimation { public: SendSideBandwidthEstimation(); ~SendSideBandwidthEstimation(); void OnRouteChange(); void CurrentEstimate(int* bitrate, uint8_t* loss, int64_t* rtt) const; DataRate GetEstimatedLinkCapacity() const; // Call periodically to update estimate. void UpdateEstimate(Timestamp at_time); void OnSentPacket(const SentPacket& sent_packet); void UpdatePropagationRtt(Timestamp at_time, TimeDelta propagation_rtt); // Call when we receive a RTCP message with TMMBR or REMB. void UpdateReceiverEstimate(Timestamp at_time, DataRate bandwidth); // Call when a new delay-based estimate is available. void UpdateDelayBasedEstimate(Timestamp at_time, DataRate bitrate); // Call when we receive a RTCP message with a ReceiveBlock. void UpdateReceiverBlock(uint8_t fraction_loss, TimeDelta rtt_ms, int number_of_packets, Timestamp at_time); // Call when we receive a RTCP message with a ReceiveBlock. void UpdatePacketsLost(int packets_lost, int number_of_packets, Timestamp at_time); // Call when we receive a RTCP message with a ReceiveBlock. void UpdateRtt(TimeDelta rtt, Timestamp at_time); void SetBitrates(absl::optional send_bitrate, DataRate min_bitrate, DataRate max_bitrate, Timestamp at_time); void SetSendBitrate(DataRate bitrate, Timestamp at_time); void SetMinMaxBitrate(DataRate min_bitrate, DataRate max_bitrate); int GetMinBitrate() const; void SetAcknowledgedRate(absl::optional acknowledged_rate, Timestamp at_time); void IncomingPacketFeedbackVector(const TransportPacketsFeedback& report); private: friend class GoogCcStatePrinter; enum UmaState { kNoUpdate, kFirstDone, kDone }; bool IsInStartPhase(Timestamp at_time) const; void UpdateUmaStatsPacketsLost(Timestamp at_time, int packets_lost); // Updates history of min bitrates. // After this method returns min_bitrate_history_.front().second contains the // min bitrate used during last kBweIncreaseIntervalMs. void UpdateMinHistory(Timestamp at_time); DataRate MaybeRampupOrBackoff(DataRate new_bitrate, Timestamp at_time); // Cap |bitrate| to [min_bitrate_configured_, max_bitrate_configured_] and // set |current_bitrate_| to the capped value and updates the event log. void CapBitrateToThresholds(Timestamp at_time, DataRate bitrate); RttBasedBackoff rtt_backoff_; LinkCapacityTracker link_capacity_; std::deque > min_bitrate_history_; // incoming filters int lost_packets_since_last_loss_update_; int expected_packets_since_last_loss_update_; absl::optional acknowledged_rate_; DataRate current_bitrate_; DataRate min_bitrate_configured_; DataRate max_bitrate_configured_; Timestamp last_low_bitrate_log_; bool has_decreased_since_last_fraction_loss_; Timestamp last_loss_feedback_; Timestamp last_loss_packet_report_; Timestamp last_timeout_; uint8_t last_fraction_loss_; uint8_t last_logged_fraction_loss_; TimeDelta last_round_trip_time_; DataRate bwe_incoming_; DataRate delay_based_bitrate_; Timestamp time_last_decrease_; Timestamp first_report_time_; int initially_lost_packets_; DataRate bitrate_at_2_seconds_; UmaState uma_update_state_; UmaState uma_rtt_state_; std::vector rampup_uma_stats_updated_; Timestamp last_rtc_event_log_; bool in_timeout_experiment_; float low_loss_threshold_; float high_loss_threshold_; DataRate bitrate_threshold_; LossBasedBandwidthEstimation loss_based_bandwidth_estimation_; }; } // namespace webrtc #endif // MODULES_BITRATE_CONTROLLER_SEND_SIDE_BANDWIDTH_ESTIMATION_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.cc ================================================ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h" #include "rtc_base/numerics/safe_conversions.h" #include #include #include #include namespace webrtc { AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator( const WebRtcKeyValueConfig* key_value_config) : AcknowledgedBitrateEstimator( key_value_config, absl::make_unique(key_value_config)) {} AcknowledgedBitrateEstimator::~AcknowledgedBitrateEstimator() {} AcknowledgedBitrateEstimator::AcknowledgedBitrateEstimator( const WebRtcKeyValueConfig* key_value_config, std::unique_ptr bitrate_estimator) : in_alr_(false), bitrate_estimator_(std::move(bitrate_estimator)) {} void AcknowledgedBitrateEstimator::IncomingPacketFeedbackVector( const std::vector& packet_feedback_vector) { // RTC_DCHECK(std::is_sorted(packet_feedback_vector.begin(), // packet_feedback_vector.end(), // PacketResult::ReceiveTimeOrder())); for (const auto& packet : packet_feedback_vector) { if (alr_ended_time_ && packet.sent_packet.send_time > *alr_ended_time_) { bitrate_estimator_->ExpectFastRateChange(); alr_ended_time_.reset(); } DataSize acknowledged_estimate = packet.sent_packet.size; acknowledged_estimate += packet.sent_packet.prior_unacked_data; bitrate_estimator_->Update(packet.receive_time, acknowledged_estimate, in_alr_); } } absl::optional AcknowledgedBitrateEstimator::bitrate() const { return bitrate_estimator_->bitrate(); } absl::optional AcknowledgedBitrateEstimator::PeekRate() const { return bitrate_estimator_->PeekRate(); } void AcknowledgedBitrateEstimator::SetAlrEndedTime(Timestamp alr_ended_time) { alr_ended_time_.emplace(alr_ended_time); } void AcknowledgedBitrateEstimator::SetAlr(bool in_alr) { in_alr_ = in_alr; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h ================================================ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_ #include "api/transport/network_types.h" #include "api/transport/webrtc_key_value_config.h" #include "api/units/data_rate.h" #include "modules/congestion_controller/goog_cc/bitrate_estimator.h" #include #include #include namespace webrtc { class AcknowledgedBitrateEstimator { public: AcknowledgedBitrateEstimator( const WebRtcKeyValueConfig* key_value_config, std::unique_ptr bitrate_estimator); explicit AcknowledgedBitrateEstimator( const WebRtcKeyValueConfig* key_value_config); ~AcknowledgedBitrateEstimator(); void IncomingPacketFeedbackVector( const std::vector& packet_feedback_vector); absl::optional bitrate() const; absl::optional PeekRate() const; void SetAlr(bool in_alr); void SetAlrEndedTime(Timestamp alr_ended_time); private: absl::optional alr_ended_time_; bool in_alr_; std::unique_ptr bitrate_estimator_; }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_ACKNOWLEDGED_BITRATE_ESTIMATOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/alr_detector.cc ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::AlrDetector" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/goog_cc/alr_detector.h" #include "rtc_base/numerics/safe_conversions.h" #include "DepLibUV.hpp" #include "Logger.hpp" #include #include #include namespace webrtc { namespace { absl::optional GetExperimentSettings( const WebRtcKeyValueConfig* key_value_config) { // RTC_CHECK(AlrExperimentSettings::MaxOneFieldTrialEnabled(*key_value_config)); absl::optional experiment_settings = AlrExperimentSettings::CreateFromFieldTrial( *key_value_config, AlrExperimentSettings::kScreenshareProbingBweExperimentName); if (!experiment_settings) { experiment_settings = AlrExperimentSettings::CreateFromFieldTrial( *key_value_config, AlrExperimentSettings::kStrictPacingAndProbingExperimentName); } return experiment_settings; } } // namespace AlrDetector::AlrDetector(const WebRtcKeyValueConfig* key_value_config) : AlrDetector(key_value_config, GetExperimentSettings(key_value_config)) {} AlrDetector::AlrDetector( const WebRtcKeyValueConfig* key_value_config, absl::optional experiment_settings) : bandwidth_usage_ratio_( "bw_usage", experiment_settings ? experiment_settings->alr_bandwidth_usage_percent / 100.0 : kDefaultBandwidthUsageRatio), start_budget_level_ratio_( "start", experiment_settings ? experiment_settings->alr_start_budget_level_percent / 100.0 : kDefaultStartBudgetLevelRatio), stop_budget_level_ratio_( "stop", experiment_settings ? experiment_settings->alr_stop_budget_level_percent / 100.0 : kDefaultStopBudgetLevelRatio), alr_timeout_( "alr_timeout", experiment_settings ? experiment_settings->alr_timeout : kDefaultAlrTimeout), alr_budget_(0, true) { ParseFieldTrial({&bandwidth_usage_ratio_, &start_budget_level_ratio_, &stop_budget_level_ratio_}, key_value_config->Lookup("WebRTC-AlrDetectorParameters")); } AlrDetector::~AlrDetector() {} void AlrDetector::OnBytesSent(size_t bytes_sent, int64_t send_time_ms) { if (!last_send_time_ms_.has_value()) { last_send_time_ms_ = send_time_ms; // Since the duration for sending the bytes is unknown, return without // updating alr state. return; } int64_t delta_time_ms = send_time_ms - *last_send_time_ms_; last_send_time_ms_ = send_time_ms; alr_budget_.UseBudget(bytes_sent); alr_budget_.IncreaseBudget(delta_time_ms); bool state_changed = false; if (alr_budget_.budget_ratio() > start_budget_level_ratio_ && !alr_started_time_ms_) { alr_started_time_ms_.emplace(DepLibUV::GetTimeMsInt64()); state_changed = true; } else if (alr_budget_.budget_ratio() < stop_budget_level_ratio_ && alr_started_time_ms_) { state_changed = true; alr_started_time_ms_.reset(); } if (state_changed) MS_DEBUG_DEV("state changed"); } void AlrDetector::SetEstimatedBitrate(int bitrate_bps) { //RTC_DCHECK(bitrate_bps); int target_rate_kbps = static_cast(bitrate_bps) * bandwidth_usage_ratio_ / 1000; alr_budget_.set_target_rate_kbps(target_rate_kbps); } absl::optional AlrDetector::GetApplicationLimitedRegionStartTime() const { return alr_started_time_ms_; } absl::optional AlrDetector::GetApplicationLimitedRegionStartTime( int64_t at_time_ms) { if (!alr_started_time_ms_ && last_send_time_ms_.has_value()) { int64_t delta_time_ms = at_time_ms - *last_send_time_ms_; // If ALR is stopped and we haven't sent any packets for a while, force start. if (delta_time_ms > alr_timeout_) { MS_WARN_TAG(bwe, "large delta_time_ms: %" PRIi64 ", forcing alr state change", delta_time_ms); alr_started_time_ms_.emplace(at_time_ms); } } return alr_started_time_ms_; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/alr_detector.h ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_ALR_DETECTOR_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_ALR_DETECTOR_H_ #include "api/transport/webrtc_key_value_config.h" #include "modules/pacing/interval_budget.h" #include "rtc_base/experiments/alr_experiment.h" #include "rtc_base/experiments/field_trial_units.h" #include #include #include namespace webrtc { // Application limited region detector is a class that utilizes signals of // elapsed time and bytes sent to estimate whether network traffic is // currently limited by the application's ability to generate traffic. // // AlrDetector provides a signal that can be utilized to adjust // estimate bandwidth. // Note: This class is not thread-safe. class AlrDetector { public: explicit AlrDetector(const WebRtcKeyValueConfig* key_value_config); ~AlrDetector(); void OnBytesSent(size_t bytes_sent, int64_t send_time_ms); // Set current estimated bandwidth. void SetEstimatedBitrate(int bitrate_bps); // Returns time in milliseconds when the current application-limited region // started or empty result if the sender is currently not application-limited. absl::optional GetApplicationLimitedRegionStartTime() const; absl::optional GetApplicationLimitedRegionStartTime(int64_t at_time_ms); void UpdateBudgetWithElapsedTime(int64_t delta_time_ms); void UpdateBudgetWithBytesSent(size_t bytes_sent); private: // Sent traffic ratio as a function of network capacity used to determine // application-limited region. ALR region start when bandwidth usage drops // below kAlrStartUsageRatio and ends when it raises above // kAlrEndUsageRatio. NOTE: This is intentionally conservative at the moment // until BW adjustments of application limited region is fine tuned. static constexpr double kDefaultBandwidthUsageRatio = 0.65; static constexpr double kDefaultStartBudgetLevelRatio = 0.80; static constexpr double kDefaultStopBudgetLevelRatio = 0.50; static constexpr int kDefaultAlrTimeout = 3000; AlrDetector(const WebRtcKeyValueConfig* key_value_config, absl::optional experiment_settings); friend class GoogCcStatePrinter; FieldTrialParameter bandwidth_usage_ratio_; FieldTrialParameter start_budget_level_ratio_; FieldTrialParameter stop_budget_level_ratio_; FieldTrialParameter alr_timeout_; absl::optional last_send_time_ms_; IntervalBudget alr_budget_; absl::optional alr_started_time_ms_; }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_ALR_DETECTOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.cc ================================================ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::BitrateEstimator" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/goog_cc/bitrate_estimator.h" #include "api/units/data_rate.h" #include "Logger.hpp" #include #include #include #include namespace webrtc { namespace { constexpr int kInitialRateWindowMs = 500; constexpr int kRateWindowMs = 150; constexpr int kMinRateWindowMs = 150; constexpr int kMaxRateWindowMs = 1000; const char kBweThroughputWindowConfig[] = "WebRTC-BweThroughputWindowConfig"; } // namespace BitrateEstimator::BitrateEstimator(const WebRtcKeyValueConfig* key_value_config) : sum_(0), initial_window_ms_("initial_window_ms", kInitialRateWindowMs, kMinRateWindowMs, kMaxRateWindowMs), noninitial_window_ms_("window_ms", kRateWindowMs, kMinRateWindowMs, kMaxRateWindowMs), uncertainty_scale_("scale", 10.0), uncertainty_scale_in_alr_("scale_alr", 10.0), uncertainty_symmetry_cap_("symmetry_cap", DataRate::Zero()), estimate_floor_("floor", DataRate::Zero()), current_window_ms_(0), prev_time_ms_(-1), bitrate_estimate_kbps_(-1.0f), bitrate_estimate_var_(50.0f) { // E.g WebRTC-BweThroughputWindowConfig/initial_window_ms:350,window_ms:250/ ParseFieldTrial({&initial_window_ms_, &noninitial_window_ms_, &uncertainty_scale_, &uncertainty_scale_in_alr_, &uncertainty_symmetry_cap_, &estimate_floor_}, key_value_config->Lookup(kBweThroughputWindowConfig)); } BitrateEstimator::~BitrateEstimator() = default; void BitrateEstimator::Update(Timestamp at_time, DataSize amount, bool in_alr) { int rate_window_ms = noninitial_window_ms_; // We use a larger window at the beginning to get a more stable sample that // we can use to initialize the estimate. if (bitrate_estimate_kbps_ < 0.f) rate_window_ms = initial_window_ms_; float bitrate_sample_kbps = UpdateWindow(at_time.ms(), amount.bytes(), rate_window_ms); if (bitrate_sample_kbps < 0.0f) return; if (bitrate_estimate_kbps_ < 0.0f) { // This is the very first sample we get. Use it to initialize the estimate. bitrate_estimate_kbps_ = bitrate_sample_kbps; return; } // Define the sample uncertainty as a function of how far away it is from the // current estimate. With low values of uncertainty_symmetry_cap_ we add more // uncertainty to increases than to decreases. For higher values we approach // symmetry. float scale = uncertainty_scale_; if (in_alr && bitrate_sample_kbps < bitrate_estimate_kbps_) { // Optionally use higher uncertainty for samples obtained during ALR. scale = uncertainty_scale_in_alr_; } float sample_uncertainty = scale * std::abs(bitrate_estimate_kbps_ - bitrate_sample_kbps) / (bitrate_estimate_kbps_ + std::min(bitrate_sample_kbps, uncertainty_symmetry_cap_.Get().kbps())); float sample_var = sample_uncertainty * sample_uncertainty; // Update a bayesian estimate of the rate, weighting it lower if the sample // uncertainty is large. // The bitrate estimate uncertainty is increased with each update to model // that the bitrate changes over time. float pred_bitrate_estimate_var = bitrate_estimate_var_ + 5.f; bitrate_estimate_kbps_ = (sample_var * bitrate_estimate_kbps_ + pred_bitrate_estimate_var * bitrate_sample_kbps) / (sample_var + pred_bitrate_estimate_var); bitrate_estimate_kbps_ = std::max(bitrate_estimate_kbps_, estimate_floor_.Get().kbps()); bitrate_estimate_var_ = sample_var * pred_bitrate_estimate_var / (sample_var + pred_bitrate_estimate_var); MS_DEBUG_DEV( "acknowledged_bitrate %" PRIu64", %f", at_time.ms(), bitrate_estimate_kbps_ * 1000); } float BitrateEstimator::UpdateWindow(int64_t now_ms, int bytes, int rate_window_ms) { // Reset if time moves backwards. if (now_ms < prev_time_ms_) { prev_time_ms_ = -1; sum_ = 0; current_window_ms_ = 0; } if (prev_time_ms_ >= 0) { current_window_ms_ += now_ms - prev_time_ms_; // Reset if nothing has been received for more than a full window. if (now_ms - prev_time_ms_ > rate_window_ms) { sum_ = 0; current_window_ms_ %= rate_window_ms; } } prev_time_ms_ = now_ms; float bitrate_sample = -1.0f; if (current_window_ms_ >= rate_window_ms) { bitrate_sample = 8.0f * sum_ / static_cast(rate_window_ms); current_window_ms_ -= rate_window_ms; sum_ = 0; } sum_ += bytes; return bitrate_sample; } absl::optional BitrateEstimator::bitrate() const { if (bitrate_estimate_kbps_ < 0.f) return absl::nullopt; return DataRate::kbps(bitrate_estimate_kbps_); } absl::optional BitrateEstimator::PeekRate() const { if (current_window_ms_ > 0) return DataSize::bytes(sum_) / TimeDelta::ms(current_window_ms_); return absl::nullopt; } void BitrateEstimator::ExpectFastRateChange() { // By setting the bitrate-estimate variance to a higher value we allow the // bitrate to change fast for the next few samples. bitrate_estimate_var_ += 200; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.h ================================================ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_BITRATE_ESTIMATOR_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_BITRATE_ESTIMATOR_H_ #include "api/transport/webrtc_key_value_config.h" #include "api/units/data_rate.h" #include "api/units/timestamp.h" #include "rtc_base/experiments/field_trial_parser.h" #include #include namespace webrtc { // Computes a bayesian estimate of the throughput given acks containing // the arrival time and payload size. Samples which are far from the current // estimate or are based on few packets are given a smaller weight, as they // are considered to be more likely to have been caused by, e.g., delay spikes // unrelated to congestion. class BitrateEstimator { public: explicit BitrateEstimator(const WebRtcKeyValueConfig* key_value_config); virtual ~BitrateEstimator(); virtual void Update(Timestamp at_time, DataSize amount, bool in_alr); virtual absl::optional bitrate() const; absl::optional PeekRate() const; virtual void ExpectFastRateChange(); private: float UpdateWindow(int64_t now_ms, int bytes, int rate_window_ms); int sum_; FieldTrialConstrained initial_window_ms_; FieldTrialConstrained noninitial_window_ms_; FieldTrialParameter uncertainty_scale_; FieldTrialParameter uncertainty_scale_in_alr_; FieldTrialParameter uncertainty_symmetry_cap_; FieldTrialParameter estimate_floor_; int64_t current_window_ms_; int64_t prev_time_ms_; float bitrate_estimate_kbps_; float bitrate_estimate_var_; }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_BITRATE_ESTIMATOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h" #include "rtc_base/experiments/rate_control_settings.h" #include #include #include #include namespace webrtc { CongestionWindowPushbackController::CongestionWindowPushbackController( const WebRtcKeyValueConfig* key_value_config) : add_pacing_( key_value_config->Lookup("WebRTC-AddPacingToCongestionWindowPushback") .find("Enabled") == 0), min_pushback_target_bitrate_bps_( RateControlSettings::ParseFromKeyValueConfig(key_value_config) .CongestionWindowMinPushbackTargetBitrateBps()) {} CongestionWindowPushbackController::CongestionWindowPushbackController( const WebRtcKeyValueConfig* key_value_config, uint32_t min_pushback_target_bitrate_bps) : add_pacing_( key_value_config->Lookup("WebRTC-AddPacingToCongestionWindowPushback") .find("Enabled") == 0), min_pushback_target_bitrate_bps_(min_pushback_target_bitrate_bps) {} void CongestionWindowPushbackController::UpdateOutstandingData( int64_t outstanding_bytes) { outstanding_bytes_ = outstanding_bytes; } void CongestionWindowPushbackController::UpdatePacingQueue( int64_t pacing_bytes) { pacing_bytes_ = pacing_bytes; } void CongestionWindowPushbackController::UpdateMaxOutstandingData( size_t max_outstanding_bytes) { DataSize data_window = DataSize::bytes(max_outstanding_bytes); if (current_data_window_) { data_window = (data_window + current_data_window_.value()) / 2; } current_data_window_ = data_window; } void CongestionWindowPushbackController::SetDataWindow(DataSize data_window) { current_data_window_ = data_window; } uint32_t CongestionWindowPushbackController::UpdateTargetBitrate( uint32_t bitrate_bps) { if (!current_data_window_ || current_data_window_->IsZero()) return bitrate_bps; int64_t total_bytes = outstanding_bytes_; if (add_pacing_) total_bytes += pacing_bytes_; double fill_ratio = total_bytes / static_cast(current_data_window_->bytes()); if (fill_ratio > 1.5) { encoding_rate_ratio_ *= 0.9; } else if (fill_ratio > 1) { encoding_rate_ratio_ *= 0.95; } else if (fill_ratio < 0.1) { encoding_rate_ratio_ = 1.0; } else { encoding_rate_ratio_ *= 1.05; encoding_rate_ratio_ = std::min(encoding_rate_ratio_, 1.0); } uint32_t adjusted_target_bitrate_bps = static_cast(bitrate_bps * encoding_rate_ratio_); // Do not adjust below the minimum pushback bitrate but do obey if the // original estimate is below it. bitrate_bps = adjusted_target_bitrate_bps < min_pushback_target_bitrate_bps_ ? std::min(bitrate_bps, min_pushback_target_bitrate_bps_) : adjusted_target_bitrate_bps; return bitrate_bps; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_CONGESTION_WINDOW_PUSHBACK_CONTROLLER_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_CONGESTION_WINDOW_PUSHBACK_CONTROLLER_H_ #include "api/transport/webrtc_key_value_config.h" #include "api/units/data_size.h" #include #include #include namespace webrtc { // This class enables pushback from congestion window directly to video encoder. // When the congestion window is filling up, the video encoder target bitrate // will be reduced accordingly to accommodate the network changes. To avoid // pausing video too frequently, a minimum encoder target bitrate threshold is // used to prevent video pause due to a full congestion window. class CongestionWindowPushbackController { public: explicit CongestionWindowPushbackController( const WebRtcKeyValueConfig* key_value_config); CongestionWindowPushbackController( const WebRtcKeyValueConfig* key_value_config, uint32_t min_pushback_target_bitrate_bps); void UpdateOutstandingData(int64_t outstanding_bytes); void UpdatePacingQueue(int64_t pacing_bytes); void UpdateMaxOutstandingData(size_t max_outstanding_bytes); uint32_t UpdateTargetBitrate(uint32_t bitrate_bps); void SetDataWindow(DataSize data_window); private: absl::optional current_data_window_; int64_t outstanding_bytes_ = 0; int64_t pacing_bytes_ = 0; const bool add_pacing_; const uint32_t min_pushback_target_bitrate_bps_; double encoding_rate_ratio_ = 1.0; }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_CONGESTION_WINDOW_PUSHBACK_CONTROLLER_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.cc ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::DelayBasedBwe" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/goog_cc/delay_based_bwe.h" #include "modules/congestion_controller/goog_cc/trendline_estimator.h" #include "Logger.hpp" #include #include #include #include #include #include namespace webrtc { namespace { constexpr TimeDelta kStreamTimeOut = TimeDelta::Seconds<2>(); constexpr int kTimestampGroupLengthMs = 5; constexpr int kAbsSendTimeFraction = 18; constexpr int kAbsSendTimeInterArrivalUpshift = 8; constexpr int kInterArrivalShift = kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift; constexpr double kTimestampToMs = 1000.0 / static_cast(1 << kInterArrivalShift); // This ssrc is used to fulfill the current API but will be removed // after the API has been changed. constexpr uint32_t kFixedSsrc = 0; } // namespace DelayBasedBwe::Result::Result() : updated(false), probe(false), target_bitrate(DataRate::Zero()), recovered_from_overuse(false), backoff_in_alr(false) {} DelayBasedBwe::Result::Result(bool probe, DataRate target_bitrate) : updated(true), probe(probe), target_bitrate(target_bitrate), recovered_from_overuse(false), backoff_in_alr(false) {} DelayBasedBwe::Result::~Result() {} DelayBasedBwe::DelayBasedBwe(const WebRtcKeyValueConfig* key_value_config, NetworkStatePredictor* network_state_predictor) : key_value_config_(key_value_config), network_state_predictor_(network_state_predictor), inter_arrival_(), delay_detector_( new TrendlineEstimator(network_state_predictor_)), last_seen_packet_(Timestamp::MinusInfinity()), uma_recorded_(false), rate_control_(key_value_config, /*send_side=*/true), prev_bitrate_(DataRate::Zero()), prev_state_(BandwidthUsage::kBwNormal), alr_limited_backoff_enabled_( key_value_config->Lookup("WebRTC-Bwe-AlrLimitedBackoff") .find("Enabled") == 0) {} DelayBasedBwe::~DelayBasedBwe() {} DelayBasedBwe::Result DelayBasedBwe::IncomingPacketFeedbackVector( const TransportPacketsFeedback& msg, absl::optional acked_bitrate, absl::optional probe_bitrate, absl::optional network_estimate, bool in_alr) { //RTC_DCHECK_RUNS_SERIALIZED(&network_race_); auto packet_feedback_vector = msg.SortedByReceiveTime(); // TODO(holmer): An empty feedback vector here likely means that // all acks were too late and that the send time history had // timed out. We should reduce the rate when this occurs. if (packet_feedback_vector.empty()) { MS_WARN_DEV("very late feedback received"); return DelayBasedBwe::Result(); } if (!uma_recorded_) { uma_recorded_ = true; } bool delayed_feedback = true; bool recovered_from_overuse = false; BandwidthUsage prev_detector_state = delay_detector_->State(); for (const auto& packet_feedback : packet_feedback_vector) { delayed_feedback = false; IncomingPacketFeedback(packet_feedback, msg.feedback_time); if (prev_detector_state == BandwidthUsage::kBwUnderusing && delay_detector_->State() == BandwidthUsage::kBwNormal) { recovered_from_overuse = true; } prev_detector_state = delay_detector_->State(); } if (delayed_feedback) { // TODO(bugs.webrtc.org/10125): Design a better mechanism to safe-guard // against building very large network queues. return Result(); } rate_control_.SetInApplicationLimitedRegion(in_alr); rate_control_.SetNetworkStateEstimate(network_estimate); return MaybeUpdateEstimate(acked_bitrate, probe_bitrate, std::move(network_estimate), recovered_from_overuse, in_alr, msg.feedback_time); } void DelayBasedBwe::IncomingPacketFeedback(const PacketResult& packet_feedback, Timestamp at_time) { // Reset if the stream has timed out. if (last_seen_packet_.IsInfinite() || at_time - last_seen_packet_ > kStreamTimeOut) { inter_arrival_.reset( new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000, kTimestampToMs, true)); delay_detector_.reset( new TrendlineEstimator(network_state_predictor_)); } last_seen_packet_ = at_time; uint32_t send_time_24bits = static_cast( ((static_cast(packet_feedback.sent_packet.send_time.ms()) << kAbsSendTimeFraction) + 500) / 1000) & 0x00FFFFFF; // Shift up send time to use the full 32 bits that inter_arrival works with, // so wrapping works properly. uint32_t timestamp = send_time_24bits << kAbsSendTimeInterArrivalUpshift; uint32_t ts_delta = 0; int64_t t_delta = 0; int size_delta = 0; bool calculated_deltas = inter_arrival_->ComputeDeltas( timestamp, packet_feedback.receive_time.ms(), at_time.ms(), packet_feedback.sent_packet.size.bytes(), &ts_delta, &t_delta, &size_delta); double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift); delay_detector_->Update(t_delta, ts_delta_ms, packet_feedback.sent_packet.send_time.ms(), packet_feedback.receive_time.ms(), calculated_deltas); } DataRate DelayBasedBwe::TriggerOveruse(Timestamp at_time, absl::optional link_capacity) { RateControlInput input(BandwidthUsage::kBwOverusing, link_capacity); return rate_control_.Update(&input, at_time); } DelayBasedBwe::Result DelayBasedBwe::MaybeUpdateEstimate( absl::optional acked_bitrate, absl::optional probe_bitrate, absl::optional state_estimate, bool recovered_from_overuse, bool in_alr, Timestamp at_time) { Result result; // Currently overusing the bandwidth. if (delay_detector_->State() == BandwidthUsage::kBwOverusing) { MS_DEBUG_DEV("delay_detector_->State() == BandwidthUsage::kBwOverusing"); MS_DEBUG_DEV("in_alr: %s", in_alr ? "true" : "false"); if (in_alr && alr_limited_backoff_enabled_) { if (rate_control_.TimeToReduceFurther(at_time, prev_bitrate_)) { MS_DEBUG_DEV("alr_limited_backoff_enabled_ is true, prev_bitrate:%lld, result.target_bitrate:%lld", prev_bitrate_.bps(), result.target_bitrate.bps()); result.updated = UpdateEstimate(at_time, prev_bitrate_, &result.target_bitrate); result.backoff_in_alr = true; } } else if (acked_bitrate && rate_control_.TimeToReduceFurther(at_time, *acked_bitrate)) { MS_DEBUG_DEV("acked_bitrate:%lld, result.target_bitrate:%lld", acked_bitrate.value().bps(), result.target_bitrate.bps()); result.updated = UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate); } else if (!acked_bitrate && rate_control_.ValidEstimate() && rate_control_.InitialTimeToReduceFurther(at_time)) { // Overusing before we have a measured acknowledged bitrate. Reduce send // rate by 50% every 200 ms. // TODO(tschumim): Improve this and/or the acknowledged bitrate estimator // so that we (almost) always have a bitrate estimate. MS_DEBUG_DEV("reducing send rate by 50%% every 200 ms"); rate_control_.SetEstimate(rate_control_.LatestEstimate() / 2, at_time); result.updated = true; result.probe = false; result.target_bitrate = rate_control_.LatestEstimate(); } } else { if (probe_bitrate) { MS_DEBUG_DEV("probe bitrate: %lld", probe_bitrate.value().bps()); result.probe = true; result.updated = true; result.target_bitrate = *probe_bitrate; rate_control_.SetEstimate(*probe_bitrate, at_time); } else { result.updated = UpdateEstimate(at_time, acked_bitrate, &result.target_bitrate); result.recovered_from_overuse = recovered_from_overuse; } } BandwidthUsage detector_state = delay_detector_->State(); if ((result.updated && prev_bitrate_ != result.target_bitrate) || detector_state != prev_state_) { DataRate bitrate = result.updated ? result.target_bitrate : prev_bitrate_; prev_bitrate_ = bitrate; MS_DEBUG_DEV("setting prev_bitrate to: %lld, result.updated:%s", prev_bitrate_.bps(), result.updated ? "true" : "false"); prev_state_ = detector_state; } return result; } bool DelayBasedBwe::UpdateEstimate(Timestamp at_time, absl::optional acked_bitrate, DataRate* target_rate) { const RateControlInput input(delay_detector_->State(), acked_bitrate); *target_rate = rate_control_.Update(&input, at_time); return rate_control_.ValidEstimate(); } void DelayBasedBwe::OnRttUpdate(TimeDelta avg_rtt) { rate_control_.SetRtt(avg_rtt); } bool DelayBasedBwe::LatestEstimate(std::vector* ssrcs, DataRate* bitrate) const { // Currently accessed from both the process thread (see // ModuleRtpRtcpImpl::Process()) and the configuration thread (see // Call::GetStats()). Should in the future only be accessed from a single // thread. //RTC_DCHECK(ssrcs); //RTC_DCHECK(bitrate); if (!ssrcs) { MS_ERROR("ssrcs must be != null"); return false; } if (!bitrate) { MS_ERROR("bitrate must be != null"); return false; } if (!rate_control_.ValidEstimate()) return false; *ssrcs = {kFixedSsrc}; *bitrate = rate_control_.LatestEstimate(); return true; } void DelayBasedBwe::SetStartBitrate(DataRate start_bitrate) { MS_DEBUG_DEV("BWE setting start bitrate to: %s", ToString(start_bitrate).c_str()); rate_control_.SetStartBitrate(start_bitrate); } void DelayBasedBwe::SetMinBitrate(DataRate min_bitrate) { // Called from both the configuration thread and the network thread. Shouldn't // be called from the network thread in the future. rate_control_.SetMinBitrate(min_bitrate); } TimeDelta DelayBasedBwe::GetExpectedBwePeriod() const { return rate_control_.GetExpectedBandwidthPeriod(); } void DelayBasedBwe::SetAlrLimitedBackoffExperiment(bool enabled) { alr_limited_backoff_enabled_ = enabled; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.h ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_H_ #include "api/network_state_predictor.h" #include "api/transport/network_types.h" #include "api/transport/webrtc_key_value_config.h" #include "modules/congestion_controller/goog_cc/delay_increase_detector_interface.h" #include "modules/congestion_controller/goog_cc/probe_bitrate_estimator.h" #include "modules/remote_bitrate_estimator/aimd_rate_control.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "modules/remote_bitrate_estimator/inter_arrival.h" #include "rtc_base/constructor_magic.h" #include #include #include #include #include namespace webrtc { class RtcEventLog; class DelayBasedBwe { public: struct Result { Result(); Result(bool probe, DataRate target_bitrate); ~Result(); bool updated; bool probe; DataRate target_bitrate = DataRate::Zero(); bool recovered_from_overuse; bool backoff_in_alr; }; explicit DelayBasedBwe(const WebRtcKeyValueConfig* key_value_config, NetworkStatePredictor* network_state_predictor); virtual ~DelayBasedBwe(); Result IncomingPacketFeedbackVector( const TransportPacketsFeedback& msg, absl::optional acked_bitrate, absl::optional probe_bitrate, absl::optional network_estimate, bool in_alr); void OnRttUpdate(TimeDelta avg_rtt); bool LatestEstimate(std::vector* ssrcs, DataRate* bitrate) const; void SetStartBitrate(DataRate start_bitrate); void SetMinBitrate(DataRate min_bitrate); TimeDelta GetExpectedBwePeriod() const; void SetAlrLimitedBackoffExperiment(bool enabled); DataRate TriggerOveruse(Timestamp at_time, absl::optional link_capacity); private: friend class GoogCcStatePrinter; void IncomingPacketFeedback(const PacketResult& packet_feedback, Timestamp at_time); Result MaybeUpdateEstimate( absl::optional acked_bitrate, absl::optional probe_bitrate, absl::optional state_estimate, bool recovered_from_overuse, bool in_alr, Timestamp at_time); // Updates the current remote rate estimate and returns true if a valid // estimate exists. bool UpdateEstimate(Timestamp now, absl::optional acked_bitrate, DataRate* target_bitrate); const WebRtcKeyValueConfig* const key_value_config_; NetworkStatePredictor* network_state_predictor_; std::unique_ptr inter_arrival_; std::unique_ptr delay_detector_; Timestamp last_seen_packet_; bool uma_recorded_; AimdRateControl rate_control_; DataRate prev_bitrate_; BandwidthUsage prev_state_; bool alr_limited_backoff_enabled_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(DelayBasedBwe); }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_BASED_BWE_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/delay_increase_detector_interface.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_INCREASE_DETECTOR_INTERFACE_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_INCREASE_DETECTOR_INTERFACE_H_ #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "rtc_base/constructor_magic.h" #include namespace webrtc { class DelayIncreaseDetectorInterface { public: DelayIncreaseDetectorInterface() {} virtual ~DelayIncreaseDetectorInterface() {} // Update the detector with a new sample. The deltas should represent deltas // between timestamp groups as defined by the InterArrival class. virtual void Update(double recv_delta_ms, double send_delta_ms, int64_t send_time_ms, int64_t arrival_time_ms, bool calculated_deltas) = 0; virtual BandwidthUsage State() const = 0; RTC_DISALLOW_COPY_AND_ASSIGN(DelayIncreaseDetectorInterface); }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_DELAY_INCREASE_DETECTOR_INTERFACE_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::GoogCcNetworkController" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/goog_cc/goog_cc_network_control.h" #include "api/units/time_delta.h" #include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h" #include "modules/congestion_controller/goog_cc/alr_detector.h" #include "modules/congestion_controller/goog_cc/probe_controller.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "Logger.hpp" #include #include #include #include #include #include #include #include #include #include namespace webrtc { namespace { // From RTCPSender video report interval. constexpr TimeDelta kLossUpdateInterval = TimeDelta::Millis<1000>(); // Pacing-rate relative to our target send rate. // Multiplicative factor that is applied to the target bitrate to calculate // the number of bytes that can be transmitted per interval. // Increasing this factor will result in lower delays in cases of bitrate // overshoots from the encoder. const float kDefaultPaceMultiplier = 2.5f; int64_t GetBpsOrDefault(const absl::optional& rate, int64_t fallback_bps) { if (rate && rate->IsFinite()) { return rate->bps(); } else { return fallback_bps; } } bool IsEnabled(const WebRtcKeyValueConfig* config, absl::string_view key) { return config->Lookup(key).find("Enabled") == 0; } bool IsNotDisabled(const WebRtcKeyValueConfig* config, absl::string_view key) { return config->Lookup(key).find("Disabled") != 0; } } // namespace GoogCcNetworkController::GoogCcNetworkController(NetworkControllerConfig config, GoogCcConfig congestion_controller_config) : key_value_config_(config.key_value_config ? config.key_value_config : &trial_based_config_), packet_feedback_only_(congestion_controller_config.feedback_only), safe_reset_on_route_change_("Enabled"), safe_reset_acknowledged_rate_("ack"), use_stable_bandwidth_estimate_( IsEnabled(key_value_config_, "WebRTC-Bwe-StableBandwidthEstimate")), use_downlink_delay_for_congestion_window_( IsEnabled(key_value_config_, "WebRTC-Bwe-CongestionWindowDownlinkDelay")), fall_back_to_probe_rate_( IsEnabled(key_value_config_, "WebRTC-Bwe-ProbeRateFallback")), use_min_allocatable_as_lower_bound_( IsNotDisabled(key_value_config_, "WebRTC-Bwe-MinAllocAsLowerBound")), rate_control_settings_( RateControlSettings::ParseFromKeyValueConfig(key_value_config_)), probe_controller_( new ProbeController(key_value_config_)), congestion_window_pushback_controller_( rate_control_settings_.UseCongestionWindowPushback() ? absl::make_unique( key_value_config_) : nullptr), bandwidth_estimation_( absl::make_unique()), alr_detector_( absl::make_unique(key_value_config_)), probe_bitrate_estimator_(new ProbeBitrateEstimator()), network_estimator_(std::move(congestion_controller_config.network_state_estimator)), network_state_predictor_( std::move(congestion_controller_config.network_state_predictor)), delay_based_bwe_(new DelayBasedBwe(key_value_config_, network_state_predictor_.get())), acknowledged_bitrate_estimator_( absl::make_unique(key_value_config_)), initial_config_(config), last_raw_target_rate_(*config.constraints.starting_rate), last_pushback_target_rate_(last_raw_target_rate_), pacing_factor_(config.stream_based_config.pacing_factor.value_or( kDefaultPaceMultiplier)), min_total_allocated_bitrate_( config.stream_based_config.min_total_allocated_bitrate.value_or( DataRate::Zero())), max_padding_rate_(config.stream_based_config.max_padding_rate.value_or( DataRate::Zero())), max_total_allocated_bitrate_(DataRate::Zero()) { //RTC_DCHECK(config.constraints.at_time.IsFinite()); ParseFieldTrial( {&safe_reset_on_route_change_, &safe_reset_acknowledged_rate_}, key_value_config_->Lookup("WebRTC-Bwe-SafeResetOnRouteChange")); if (delay_based_bwe_) delay_based_bwe_->SetMinBitrate(congestion_controller::GetMinBitrate()); } GoogCcNetworkController::~GoogCcNetworkController() {} NetworkControlUpdate GoogCcNetworkController::OnNetworkAvailability( NetworkAvailability msg) { NetworkControlUpdate update; update.probe_cluster_configs = probe_controller_->OnNetworkAvailability(msg); return update; } NetworkControlUpdate GoogCcNetworkController::OnNetworkRouteChange( NetworkRouteChange msg) { if (safe_reset_on_route_change_) { absl::optional estimated_bitrate; if (safe_reset_acknowledged_rate_) { estimated_bitrate = acknowledged_bitrate_estimator_->bitrate(); if (!estimated_bitrate) estimated_bitrate = acknowledged_bitrate_estimator_->PeekRate(); } else { int32_t target_bitrate_bps; uint8_t fraction_loss; int64_t rtt_ms; bandwidth_estimation_->CurrentEstimate(&target_bitrate_bps, &fraction_loss, &rtt_ms); estimated_bitrate = DataRate::bps(target_bitrate_bps); } if (estimated_bitrate) { if (msg.constraints.starting_rate) { msg.constraints.starting_rate = std::min(*msg.constraints.starting_rate, *estimated_bitrate); } else { msg.constraints.starting_rate = estimated_bitrate; } } } acknowledged_bitrate_estimator_.reset( new AcknowledgedBitrateEstimator(key_value_config_)); probe_bitrate_estimator_.reset(new ProbeBitrateEstimator()); if (network_estimator_) network_estimator_->OnRouteChange(msg); delay_based_bwe_.reset(new DelayBasedBwe(key_value_config_, network_state_predictor_.get())); bandwidth_estimation_->OnRouteChange(); probe_controller_->Reset(msg.at_time.ms()); NetworkControlUpdate update; update.probe_cluster_configs = ResetConstraints(msg.constraints); MaybeTriggerOnNetworkChanged(&update, msg.at_time); return update; } NetworkControlUpdate GoogCcNetworkController::OnProcessInterval( ProcessInterval msg) { NetworkControlUpdate update; if (initial_config_) { update.probe_cluster_configs = ResetConstraints(initial_config_->constraints); update.pacer_config = GetPacingRates(msg.at_time); if (initial_config_->stream_based_config.requests_alr_probing) { probe_controller_->EnablePeriodicAlrProbing( *initial_config_->stream_based_config.requests_alr_probing); } absl::optional total_bitrate = initial_config_->stream_based_config.max_total_allocated_bitrate; if (total_bitrate) { auto probes = probe_controller_->OnMaxTotalAllocatedBitrate( total_bitrate->bps(), msg.at_time.ms()); update.probe_cluster_configs.insert(update.probe_cluster_configs.end(), probes.begin(), probes.end()); max_total_allocated_bitrate_ = *total_bitrate; } initial_config_.reset(); } if (congestion_window_pushback_controller_ && msg.pacer_queue) { congestion_window_pushback_controller_->UpdatePacingQueue( msg.pacer_queue->bytes()); } bandwidth_estimation_->UpdateEstimate(msg.at_time); absl::optional start_time_ms = alr_detector_->GetApplicationLimitedRegionStartTime(msg.at_time.ms()); probe_controller_->SetAlrStartTimeMs(start_time_ms); auto probes = probe_controller_->Process(msg.at_time.ms()); update.probe_cluster_configs.insert(update.probe_cluster_configs.end(), probes.begin(), probes.end()); if (congestion_window_pushback_controller_ && current_data_window_) { congestion_window_pushback_controller_->SetDataWindow( *current_data_window_); } else { update.congestion_window = current_data_window_; } MaybeTriggerOnNetworkChanged(&update, msg.at_time); return update; } NetworkControlUpdate GoogCcNetworkController::OnRemoteBitrateReport( RemoteBitrateReport msg) { if (packet_feedback_only_) { MS_ERROR("Received REMB for packet feedback only GoogCC"); return NetworkControlUpdate(); } bandwidth_estimation_->UpdateReceiverEstimate(msg.receive_time, msg.bandwidth); // BWE_TEST_LOGGING_PLOT(1, "REMB_kbps", msg.receive_time.ms(), // msg.bandwidth.bps() / 1000); return NetworkControlUpdate(); } NetworkControlUpdate GoogCcNetworkController::OnRoundTripTimeUpdate( RoundTripTimeUpdate msg) { if (packet_feedback_only_ || msg.smoothed) return NetworkControlUpdate(); //RTC_DCHECK(!msg.round_trip_time.IsZero()); if (delay_based_bwe_) delay_based_bwe_->OnRttUpdate(msg.round_trip_time); bandwidth_estimation_->UpdateRtt(msg.round_trip_time, msg.receive_time); return NetworkControlUpdate(); } NetworkControlUpdate GoogCcNetworkController::OnSentPacket( SentPacket sent_packet) { alr_detector_->OnBytesSent(sent_packet.size.bytes(), sent_packet.send_time.ms()); acknowledged_bitrate_estimator_->SetAlr( alr_detector_->GetApplicationLimitedRegionStartTime().has_value()); if (!first_packet_sent_) { first_packet_sent_ = true; // Initialize feedback time to send time to allow estimation of RTT until // first feedback is received. bandwidth_estimation_->UpdatePropagationRtt(sent_packet.send_time, TimeDelta::Zero()); } bandwidth_estimation_->OnSentPacket(sent_packet); bool network_changed = false; if (congestion_window_pushback_controller_) { congestion_window_pushback_controller_->UpdateOutstandingData( sent_packet.data_in_flight.bytes()); network_changed = true; } if (network_changed) { NetworkControlUpdate update; MaybeTriggerOnNetworkChanged(&update, sent_packet.send_time); return update; } else { return NetworkControlUpdate(); } } NetworkControlUpdate GoogCcNetworkController::OnStreamsConfig( StreamsConfig msg) { NetworkControlUpdate update; if (msg.requests_alr_probing) { probe_controller_->EnablePeriodicAlrProbing(*msg.requests_alr_probing); } if (msg.max_total_allocated_bitrate && *msg.max_total_allocated_bitrate != max_total_allocated_bitrate_) { if (rate_control_settings_.TriggerProbeOnMaxAllocatedBitrateChange()) { update.probe_cluster_configs = probe_controller_->OnMaxTotalAllocatedBitrate( msg.max_total_allocated_bitrate->bps(), msg.at_time.ms()); } else { probe_controller_->SetMaxBitrate(msg.max_total_allocated_bitrate->bps()); } max_total_allocated_bitrate_ = *msg.max_total_allocated_bitrate; } bool pacing_changed = false; if (msg.pacing_factor && *msg.pacing_factor != pacing_factor_) { pacing_factor_ = *msg.pacing_factor; pacing_changed = true; } if (msg.min_total_allocated_bitrate && *msg.min_total_allocated_bitrate != min_total_allocated_bitrate_) { min_total_allocated_bitrate_ = *msg.min_total_allocated_bitrate; pacing_changed = true; if (use_min_allocatable_as_lower_bound_) { ClampConstraints(); delay_based_bwe_->SetMinBitrate(min_data_rate_); bandwidth_estimation_->SetMinMaxBitrate(min_data_rate_, max_data_rate_); } } if (msg.max_padding_rate && *msg.max_padding_rate != max_padding_rate_) { max_padding_rate_ = *msg.max_padding_rate; pacing_changed = true; } if (pacing_changed) update.pacer_config = GetPacingRates(msg.at_time); return update; } NetworkControlUpdate GoogCcNetworkController::OnTargetRateConstraints( TargetRateConstraints constraints) { NetworkControlUpdate update; update.probe_cluster_configs = ResetConstraints(constraints); MaybeTriggerOnNetworkChanged(&update, constraints.at_time); return update; } void GoogCcNetworkController::ClampConstraints() { // TODO (ibc): Remove. // MS_WARN_DEV( // "[min_data_rate_:%" PRIi64 ", min_total_allocated_bitrate_:%" PRIi64 ", max_data_rate_:%" PRIi64 ", starting_rate_:%" PRIi64 "]", // min_data_rate_.bps(), // min_total_allocated_bitrate_.bps(), // max_data_rate_.bps(), // (*starting_rate_).bps()); // TODO(holmer): We should make sure the default bitrates are set to 10 kbps, // and that we don't try to set the min bitrate to 0 from any applications. // The congestion controller should allow a min bitrate of 0. min_data_rate_ = std::max(min_data_rate_, congestion_controller::GetMinBitrate()); if (use_min_allocatable_as_lower_bound_) min_data_rate_ = std::max(min_data_rate_, min_total_allocated_bitrate_); if (max_data_rate_ < min_data_rate_) { MS_ERROR( "max bitrate smaller than min bitrate [max_data_rate_:%" PRIi64 ", min_data_rate_:%" PRIi64 "]", max_data_rate_.bps(), min_data_rate_.bps()); max_data_rate_ = min_data_rate_; } if (starting_rate_ && starting_rate_ < min_data_rate_) { MS_ERROR( "start bitrate smaller than min bitrate [starting_rate_:%" PRIi64 ", min_data_rate_:%" PRIi64 "]", starting_rate_->bps(), min_data_rate_.bps()); starting_rate_ = min_data_rate_; } } std::vector GoogCcNetworkController::ResetConstraints( TargetRateConstraints new_constraints) { min_data_rate_ = new_constraints.min_data_rate.value_or(DataRate::Zero()); max_data_rate_ = new_constraints.max_data_rate.value_or(DataRate::PlusInfinity()); starting_rate_ = new_constraints.starting_rate; ClampConstraints(); MS_DEBUG_DEV( "calling bandwidth_estimation_->SetBitrates() [max_data_rate_.bps_or(-1):%" PRIi64 "]", max_data_rate_.bps_or(-1)); bandwidth_estimation_->SetBitrates(starting_rate_, min_data_rate_, max_data_rate_, new_constraints.at_time); if (starting_rate_) delay_based_bwe_->SetStartBitrate(*starting_rate_); delay_based_bwe_->SetMinBitrate(min_data_rate_); return probe_controller_->SetBitrates( min_data_rate_.bps(), GetBpsOrDefault(starting_rate_, -1), max_data_rate_.bps_or(-1), new_constraints.at_time.ms()); } NetworkControlUpdate GoogCcNetworkController::OnTransportLossReport( TransportLossReport msg) { if (packet_feedback_only_) return NetworkControlUpdate(); int64_t total_packets_delta = msg.packets_received_delta + msg.packets_lost_delta; bandwidth_estimation_->UpdatePacketsLost( msg.packets_lost_delta, total_packets_delta, msg.receive_time); return NetworkControlUpdate(); } void GoogCcNetworkController::UpdateCongestionWindowSize( TimeDelta time_since_last_packet) { TimeDelta min_feedback_max_rtt = TimeDelta::ms( *std::min_element(feedback_max_rtts_.begin(), feedback_max_rtts_.end())); const DataSize kMinCwnd = DataSize::bytes(2 * 1500); TimeDelta time_window = min_feedback_max_rtt + TimeDelta::ms( rate_control_settings_.GetCongestionWindowAdditionalTimeMs()); if (use_downlink_delay_for_congestion_window_) { time_window += time_since_last_packet; } DataSize data_window = last_raw_target_rate_ * time_window; if (current_data_window_) { data_window = std::max(kMinCwnd, (data_window + current_data_window_.value()) / 2); } else { data_window = std::max(kMinCwnd, data_window); } current_data_window_ = data_window; } NetworkControlUpdate GoogCcNetworkController::OnTransportPacketsFeedback( TransportPacketsFeedback report) { if (report.packet_feedbacks.empty()) { // TODO(bugs.webrtc.org/10125): Design a better mechanism to safe-guard // against building very large network queues. return NetworkControlUpdate(); } if (congestion_window_pushback_controller_) { congestion_window_pushback_controller_->UpdateOutstandingData( report.data_in_flight.bytes()); } TimeDelta max_feedback_rtt = TimeDelta::MinusInfinity(); TimeDelta min_propagation_rtt = TimeDelta::PlusInfinity(); Timestamp max_recv_time = Timestamp::MinusInfinity(); std::vector feedbacks = report.ReceivedWithSendInfo(); for (const auto& feedback : feedbacks) max_recv_time = std::max(max_recv_time, feedback.receive_time); for (const auto& feedback : feedbacks) { TimeDelta feedback_rtt = report.feedback_time - feedback.sent_packet.send_time; TimeDelta min_pending_time = max_recv_time - feedback.receive_time; TimeDelta propagation_rtt = feedback_rtt - min_pending_time; max_feedback_rtt = std::max(max_feedback_rtt, feedback_rtt); min_propagation_rtt = std::min(min_propagation_rtt, propagation_rtt); } if (max_feedback_rtt.IsFinite()) { feedback_max_rtts_.push_back(max_feedback_rtt.ms()); const size_t kMaxFeedbackRttWindow = 32; if (feedback_max_rtts_.size() > kMaxFeedbackRttWindow) feedback_max_rtts_.pop_front(); // TODO(srte): Use time since last unacknowledged packet. bandwidth_estimation_->UpdatePropagationRtt(report.feedback_time, min_propagation_rtt); } if (packet_feedback_only_) { if (!feedback_max_rtts_.empty()) { int64_t sum_rtt_ms = std::accumulate(feedback_max_rtts_.begin(), feedback_max_rtts_.end(), 0); int64_t mean_rtt_ms = sum_rtt_ms / feedback_max_rtts_.size(); if (delay_based_bwe_) delay_based_bwe_->OnRttUpdate(TimeDelta::ms(mean_rtt_ms)); } TimeDelta feedback_min_rtt = TimeDelta::PlusInfinity(); for (const auto& packet_feedback : feedbacks) { TimeDelta pending_time = packet_feedback.receive_time - max_recv_time; TimeDelta rtt = report.feedback_time - packet_feedback.sent_packet.send_time - pending_time; // Value used for predicting NACK round trip time in FEC controller. feedback_min_rtt = std::min(rtt, feedback_min_rtt); } if (feedback_min_rtt.IsFinite()) { bandwidth_estimation_->UpdateRtt(feedback_min_rtt, report.feedback_time); } expected_packets_since_last_loss_update_ += report.PacketsWithFeedback().size(); for (const auto& packet_feedback : report.PacketsWithFeedback()) { if (packet_feedback.receive_time.IsInfinite()) lost_packets_since_last_loss_update_ += 1; } if (report.feedback_time > next_loss_update_) { next_loss_update_ = report.feedback_time + kLossUpdateInterval; bandwidth_estimation_->UpdatePacketsLost( lost_packets_since_last_loss_update_, expected_packets_since_last_loss_update_, report.feedback_time); expected_packets_since_last_loss_update_ = 0; lost_packets_since_last_loss_update_ = 0; } } absl::optional alr_start_time = alr_detector_->GetApplicationLimitedRegionStartTime(); if (previously_in_alr_ && !alr_start_time.has_value()) { int64_t now_ms = report.feedback_time.ms(); acknowledged_bitrate_estimator_->SetAlrEndedTime(report.feedback_time); probe_controller_->SetAlrEndedTimeMs(now_ms); } previously_in_alr_ = alr_start_time.has_value(); acknowledged_bitrate_estimator_->IncomingPacketFeedbackVector( report.SortedByReceiveTime()); auto acknowledged_bitrate = acknowledged_bitrate_estimator_->bitrate(); for (const auto& feedback : report.SortedByReceiveTime()) { if (feedback.sent_packet.pacing_info.probe_cluster_id != PacedPacketInfo::kNotAProbe) { probe_bitrate_estimator_->HandleProbeAndEstimateBitrate(feedback); } } absl::optional probe_bitrate = probe_bitrate_estimator_->FetchAndResetLastEstimatedBitrate(); if (fall_back_to_probe_rate_ && !acknowledged_bitrate) acknowledged_bitrate = probe_bitrate_estimator_->last_estimate(); bandwidth_estimation_->SetAcknowledgedRate(acknowledged_bitrate, report.feedback_time); bandwidth_estimation_->IncomingPacketFeedbackVector(report); if (network_estimator_) { network_estimator_->OnTransportPacketsFeedback(report); estimate_ = network_estimator_->GetCurrentEstimate(); } NetworkControlUpdate update; bool recovered_from_overuse = false; bool backoff_in_alr = false; DelayBasedBwe::Result result; result = delay_based_bwe_->IncomingPacketFeedbackVector( report, acknowledged_bitrate, probe_bitrate, estimate_, alr_start_time.has_value()); if (result.updated) { if (result.probe) { bandwidth_estimation_->SetSendBitrate(result.target_bitrate, report.feedback_time); } // Since SetSendBitrate now resets the delay-based estimate, we have to // call UpdateDelayBasedEstimate after SetSendBitrate. bandwidth_estimation_->UpdateDelayBasedEstimate(report.feedback_time, result.target_bitrate); // Update the estimate in the ProbeController, in case we want to probe. MaybeTriggerOnNetworkChanged(&update, report.feedback_time); } recovered_from_overuse = result.recovered_from_overuse; backoff_in_alr = result.backoff_in_alr; if (recovered_from_overuse) { probe_controller_->SetAlrStartTimeMs(alr_start_time); auto probes = probe_controller_->RequestProbe(report.feedback_time.ms()); update.probe_cluster_configs.insert(update.probe_cluster_configs.end(), probes.begin(), probes.end()); } else if (backoff_in_alr) { // If we just backed off during ALR, request a new probe. auto probes = probe_controller_->RequestProbe(report.feedback_time.ms()); update.probe_cluster_configs.insert(update.probe_cluster_configs.end(), probes.begin(), probes.end()); } // No valid RTT could be because send-side BWE isn't used, in which case // we don't try to limit the outstanding packets. if (rate_control_settings_.UseCongestionWindow() && max_feedback_rtt.IsFinite()) { UpdateCongestionWindowSize(/*time_since_last_packet*/ TimeDelta::Zero()); } if (congestion_window_pushback_controller_ && current_data_window_) { congestion_window_pushback_controller_->SetDataWindow( *current_data_window_); } else { update.congestion_window = current_data_window_; } return update; } NetworkControlUpdate GoogCcNetworkController::OnNetworkStateEstimate( NetworkStateEstimate msg) { estimate_ = msg; return NetworkControlUpdate(); } NetworkControlUpdate GoogCcNetworkController::GetNetworkState( Timestamp at_time) const { DataRate bandwidth = use_stable_bandwidth_estimate_ ? bandwidth_estimation_->GetEstimatedLinkCapacity() : last_raw_target_rate_; TimeDelta rtt = TimeDelta::ms(last_estimated_rtt_ms_); NetworkControlUpdate update; update.target_rate = TargetTransferRate(); update.target_rate->network_estimate.at_time = at_time; update.target_rate->network_estimate.bandwidth = bandwidth; update.target_rate->network_estimate.loss_rate_ratio = last_estimated_fraction_loss_ / 255.0; update.target_rate->network_estimate.round_trip_time = rtt; update.target_rate->network_estimate.bwe_period = delay_based_bwe_->GetExpectedBwePeriod(); update.target_rate->at_time = at_time; update.target_rate->target_rate = bandwidth; update.pacer_config = GetPacingRates(at_time); update.congestion_window = current_data_window_; return update; } void GoogCcNetworkController::MaybeTriggerOnNetworkChanged( NetworkControlUpdate* update, Timestamp at_time) { int32_t estimated_bitrate_bps; uint8_t fraction_loss; int64_t rtt_ms; bandwidth_estimation_->CurrentEstimate(&estimated_bitrate_bps, &fraction_loss, &rtt_ms); // BWE_TEST_LOGGING_PLOT(1, "fraction_loss_%", at_time.ms(), // (fraction_loss * 100) / 256); // BWE_TEST_LOGGING_PLOT(1, "rtt_ms", at_time.ms(), rtt_ms); // BWE_TEST_LOGGING_PLOT(1, "Target_bitrate_kbps", at_time.ms(), // estimated_bitrate_bps / 1000); DataRate target_rate = DataRate::bps(estimated_bitrate_bps); if (congestion_window_pushback_controller_) { int64_t pushback_rate = congestion_window_pushback_controller_->UpdateTargetBitrate( target_rate.bps()); pushback_rate = std::max(bandwidth_estimation_->GetMinBitrate(), pushback_rate); target_rate = DataRate::bps(pushback_rate); } if ((estimated_bitrate_bps != last_estimated_bitrate_bps_) || (fraction_loss != last_estimated_fraction_loss_) || (rtt_ms != last_estimated_rtt_ms_) || (target_rate != last_pushback_target_rate_)) { last_pushback_target_rate_ = target_rate; last_estimated_bitrate_bps_ = estimated_bitrate_bps; last_estimated_fraction_loss_ = fraction_loss; last_estimated_rtt_ms_ = rtt_ms; alr_detector_->SetEstimatedBitrate(estimated_bitrate_bps); last_raw_target_rate_ = DataRate::bps(estimated_bitrate_bps); DataRate bandwidth = use_stable_bandwidth_estimate_ ? bandwidth_estimation_->GetEstimatedLinkCapacity() : last_raw_target_rate_; TimeDelta bwe_period = delay_based_bwe_->GetExpectedBwePeriod(); TargetTransferRate target_rate_msg; target_rate_msg.at_time = at_time; target_rate_msg.target_rate = target_rate; target_rate_msg.network_estimate.at_time = at_time; target_rate_msg.network_estimate.round_trip_time = TimeDelta::ms(rtt_ms); target_rate_msg.network_estimate.bandwidth = bandwidth; target_rate_msg.network_estimate.loss_rate_ratio = fraction_loss / 255.0f; target_rate_msg.network_estimate.bwe_period = bwe_period; update->target_rate = target_rate_msg; auto probes = probe_controller_->SetEstimatedBitrate( last_raw_target_rate_.bps(), at_time.ms()); update->probe_cluster_configs.insert(update->probe_cluster_configs.end(), probes.begin(), probes.end()); update->pacer_config = GetPacingRates(at_time); MS_DEBUG_DEV("bwe [at_time:%" PRIu64", pushback_target_bps:%lld, estimate_bps:%lld]", at_time.ms(), last_pushback_target_rate_.bps(), last_raw_target_rate_.bps()); } } PacerConfig GoogCcNetworkController::GetPacingRates(Timestamp at_time) const { // Pacing rate is based on target rate before congestion window pushback, // because we don't want to build queues in the pacer when pushback occurs. DataRate pacing_rate = std::max(min_total_allocated_bitrate_, last_raw_target_rate_) * pacing_factor_; DataRate padding_rate = std::min(max_padding_rate_, last_pushback_target_rate_); PacerConfig msg; msg.at_time = at_time; msg.time_window = TimeDelta::seconds(1); msg.data_window = pacing_rate * msg.time_window; msg.pad_window = padding_rate * msg.time_window; return msg; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_ #include "api/network_state_predictor.h" #include "api/transport/field_trial_based_config.h" #include "api/transport/network_control.h" #include "api/transport/network_types.h" #include "api/transport/webrtc_key_value_config.h" #include "api/units/data_rate.h" #include "api/units/data_size.h" #include "api/units/timestamp.h" #include "modules/bitrate_controller/send_side_bandwidth_estimation.h" #include "modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h" #include "modules/congestion_controller/goog_cc/alr_detector.h" #include "modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h" #include "modules/congestion_controller/goog_cc/delay_based_bwe.h" #include "modules/congestion_controller/goog_cc/probe_controller.h" #include "rtc_base/constructor_magic.h" #include "rtc_base/experiments/field_trial_parser.h" #include "rtc_base/experiments/rate_control_settings.h" #include #include #include #include #include namespace webrtc { struct GoogCcConfig { std::unique_ptr network_state_estimator = nullptr; std::unique_ptr network_state_predictor = nullptr; bool feedback_only = false; }; class GoogCcNetworkController : public NetworkControllerInterface { public: GoogCcNetworkController(NetworkControllerConfig config, GoogCcConfig congestion_controller_config); ~GoogCcNetworkController() override; // NetworkControllerInterface NetworkControlUpdate OnNetworkAvailability(NetworkAvailability msg) override; NetworkControlUpdate OnNetworkRouteChange(NetworkRouteChange msg) override; NetworkControlUpdate OnProcessInterval(ProcessInterval msg) override; NetworkControlUpdate OnRemoteBitrateReport(RemoteBitrateReport msg) override; NetworkControlUpdate OnRoundTripTimeUpdate(RoundTripTimeUpdate msg) override; NetworkControlUpdate OnSentPacket(SentPacket msg) override; NetworkControlUpdate OnStreamsConfig(StreamsConfig msg) override; NetworkControlUpdate OnTargetRateConstraints( TargetRateConstraints msg) override; NetworkControlUpdate OnTransportLossReport(TransportLossReport msg) override; NetworkControlUpdate OnTransportPacketsFeedback( TransportPacketsFeedback msg) override; NetworkControlUpdate OnNetworkStateEstimate( NetworkStateEstimate msg) override; NetworkControlUpdate GetNetworkState(Timestamp at_time) const; private: friend class GoogCcStatePrinter; std::vector ResetConstraints( TargetRateConstraints new_constraints); void ClampConstraints(); void MaybeTriggerOnNetworkChanged(NetworkControlUpdate* update, Timestamp at_time); void UpdateCongestionWindowSize(TimeDelta time_since_last_packet); PacerConfig GetPacingRates(Timestamp at_time) const; const FieldTrialBasedConfig trial_based_config_; const WebRtcKeyValueConfig* const key_value_config_; // RtcEventLog* const event_log_; const bool packet_feedback_only_; FieldTrialFlag safe_reset_on_route_change_; FieldTrialFlag safe_reset_acknowledged_rate_; const bool use_stable_bandwidth_estimate_; const bool use_downlink_delay_for_congestion_window_; const bool fall_back_to_probe_rate_; const bool use_min_allocatable_as_lower_bound_; const RateControlSettings rate_control_settings_; const std::unique_ptr probe_controller_; const std::unique_ptr congestion_window_pushback_controller_; std::unique_ptr bandwidth_estimation_; std::unique_ptr alr_detector_; std::unique_ptr probe_bitrate_estimator_; std::unique_ptr network_estimator_; std::unique_ptr network_state_predictor_; std::unique_ptr delay_based_bwe_; std::unique_ptr acknowledged_bitrate_estimator_; absl::optional initial_config_; DataRate min_data_rate_ = DataRate::Zero(); DataRate max_data_rate_ = DataRate::PlusInfinity(); absl::optional starting_rate_; bool first_packet_sent_ = false; absl::optional estimate_; Timestamp next_loss_update_ = Timestamp::MinusInfinity(); int lost_packets_since_last_loss_update_ = 0; int expected_packets_since_last_loss_update_ = 0; std::deque feedback_max_rtts_; DataRate last_raw_target_rate_; DataRate last_pushback_target_rate_; int32_t last_estimated_bitrate_bps_ = 0; uint8_t last_estimated_fraction_loss_ = 0; int64_t last_estimated_rtt_ms_ = 0; double pacing_factor_; DataRate min_total_allocated_bitrate_; DataRate max_padding_rate_; DataRate max_total_allocated_bitrate_; bool previously_in_alr_ = false; absl::optional current_data_window_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(GoogCcNetworkController); }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_GOOG_CC_NETWORK_CONTROL_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.cc ================================================ /* * Copyright 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/congestion_controller/goog_cc/link_capacity_estimator.h" #include "rtc_base/numerics/safe_minmax.h" #include namespace webrtc { LinkCapacityEstimator::LinkCapacityEstimator() {} DataRate LinkCapacityEstimator::UpperBound() const { if (estimate_kbps_.has_value()) return DataRate::kbps(estimate_kbps_.value() + 3 * deviation_estimate_kbps()); return DataRate::Infinity(); } DataRate LinkCapacityEstimator::LowerBound() const { if (estimate_kbps_.has_value()) return DataRate::kbps( std::max(0.0, estimate_kbps_.value() - 3 * deviation_estimate_kbps())); return DataRate::Zero(); } void LinkCapacityEstimator::Reset() { estimate_kbps_.reset(); } void LinkCapacityEstimator::OnOveruseDetected(DataRate acknowledged_rate) { Update(acknowledged_rate, 0.05); } void LinkCapacityEstimator::OnProbeRate(DataRate probe_rate) { Update(probe_rate, 0.5); } void LinkCapacityEstimator::Update(DataRate capacity_sample, double alpha) { double sample_kbps = capacity_sample.kbps(); if (!estimate_kbps_.has_value()) { estimate_kbps_ = sample_kbps; } else { estimate_kbps_ = (1 - alpha) * estimate_kbps_.value() + alpha * sample_kbps; } // Estimate the variance of the link capacity estimate and normalize the // variance with the link capacity estimate. const double norm = std::max(estimate_kbps_.value(), 1.0); double error_kbps = estimate_kbps_.value() - sample_kbps; deviation_kbps_ = (1 - alpha) * deviation_kbps_ + alpha * error_kbps * error_kbps / norm; // 0.4 ~= 14 kbit/s at 500 kbit/s // 2.5f ~= 35 kbit/s at 500 kbit/s deviation_kbps_ = rtc::SafeClamp(deviation_kbps_, 0.4f, 2.5f); } bool LinkCapacityEstimator::has_estimate() const { return estimate_kbps_.has_value(); } DataRate LinkCapacityEstimator::estimate() const { return DataRate::kbps(*estimate_kbps_); } double LinkCapacityEstimator::deviation_estimate_kbps() const { // Calculate the max bit rate std dev given the normalized // variance and the current throughput bitrate. The standard deviation will // only be used if estimate_kbps_ has a value. return sqrt(deviation_kbps_ * estimate_kbps_.value()); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.h ================================================ /* * Copyright 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_ #include "api/units/data_rate.h" #include namespace webrtc { class LinkCapacityEstimator { public: LinkCapacityEstimator(); DataRate UpperBound() const; DataRate LowerBound() const; void Reset(); void OnOveruseDetected(DataRate acknowledged_rate); void OnProbeRate(DataRate probe_rate); bool has_estimate() const; DataRate estimate() const; private: friend class GoogCcStatePrinter; void Update(DataRate capacity_sample, double alpha); double deviation_estimate_kbps() const; absl::optional estimate_kbps_; double deviation_kbps_ = 0.4; }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_LINK_CAPACITY_ESTIMATOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.cc ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/congestion_controller/goog_cc/median_slope_estimator.h" #include namespace webrtc { constexpr unsigned int kDeltaCounterMax = 1000; MedianSlopeEstimator::MedianSlopeEstimator(size_t window_size, double threshold_gain) : window_size_(window_size), threshold_gain_(threshold_gain), num_of_deltas_(0), accumulated_delay_(0), delay_hist_(), median_filter_(0.5), trendline_(0) {} MedianSlopeEstimator::~MedianSlopeEstimator() {} MedianSlopeEstimator::DelayInfo::DelayInfo(int64_t time, double delay, size_t slope_count) : time(time), delay(delay) { slopes.reserve(slope_count); } MedianSlopeEstimator::DelayInfo::~DelayInfo() = default; void MedianSlopeEstimator::Update(double recv_delta_ms, double send_delta_ms, int64_t arrival_time_ms) { const double delta_ms = recv_delta_ms - send_delta_ms; ++num_of_deltas_; if (num_of_deltas_ > kDeltaCounterMax) num_of_deltas_ = kDeltaCounterMax; accumulated_delay_ += delta_ms; // BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms, // accumulated_delay_); // If the window is full, remove the |window_size_| - 1 slopes that belong to // the oldest point. if (delay_hist_.size() == window_size_) { // for (double slope : delay_hist_.front().slopes) { // const bool success = median_filter_.Erase(slope); // RTC_CHECK(success); // } delay_hist_.pop_front(); } // Add |window_size_| - 1 new slopes. for (auto& old_delay : delay_hist_) { if (arrival_time_ms - old_delay.time != 0) { // The C99 standard explicitly states that casts and assignments must // perform the associated conversions. This means that |slope| will be // a 64-bit double even if the division is computed using, e.g., 80-bit // extended precision. I believe this also holds in C++ even though the // C++11 standard isn't as explicit. Furthermore, there are good reasons // to believe that compilers couldn't perform optimizations that break // this assumption even if they wanted to. double slope = (accumulated_delay_ - old_delay.delay) / static_cast(arrival_time_ms - old_delay.time); median_filter_.Insert(slope); // We want to avoid issues with different rounding mode / precision // which we might get if we recomputed the slope when we remove it. old_delay.slopes.push_back(slope); } } delay_hist_.emplace_back(arrival_time_ms, accumulated_delay_, window_size_ - 1); // Recompute the median slope. if (delay_hist_.size() == window_size_) trendline_ = median_filter_.GetPercentileValue(); // BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trendline_); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.h ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_MEDIAN_SLOPE_ESTIMATOR_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_MEDIAN_SLOPE_ESTIMATOR_H_ #include "rtc_base/constructor_magic.h" #include "rtc_base/numerics/percentile_filter.h" #include #include #include #include namespace webrtc { class MedianSlopeEstimator { public: // |window_size| is the number of points required to compute a trend line. // |threshold_gain| is used to scale the trendline slope for comparison to // the old threshold. Once the old estimator has been removed (or the // thresholds been merged into the estimators), we can just set the // threshold instead of setting a gain. MedianSlopeEstimator(size_t window_size, double threshold_gain); ~MedianSlopeEstimator(); // Update the estimator with a new sample. The deltas should represent deltas // between timestamp groups as defined by the InterArrival class. void Update(double recv_delta_ms, double send_delta_ms, int64_t arrival_time_ms); // Returns the estimated trend k multiplied by some gain. // 0 < k < 1 -> the delay increases, queues are filling up // k == 0 -> the delay does not change // k < 0 -> the delay decreases, queues are being emptied double trendline_slope() const { return trendline_ * threshold_gain_; } // Returns the number of deltas which the current estimator state is based on. unsigned int num_of_deltas() const { return num_of_deltas_; } private: struct DelayInfo { DelayInfo(int64_t time, double delay, size_t slope_count); ~DelayInfo(); int64_t time; double delay; std::vector slopes; }; // Parameters. const size_t window_size_; const double threshold_gain_; // Used by the existing threshold. unsigned int num_of_deltas_; // Theil-Sen robust line fitting double accumulated_delay_; std::deque delay_hist_; PercentileFilter median_filter_; double trendline_; RTC_DISALLOW_COPY_AND_ASSIGN(MedianSlopeEstimator); }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_MEDIAN_SLOPE_ESTIMATOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.cc ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::ProbeBitrateEstimator" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/goog_cc/probe_bitrate_estimator.h" #include "rtc_base/numerics/safe_conversions.h" #include "Logger.hpp" #include #include namespace webrtc { namespace { // The minumum number of probes we need to receive feedback about in percent // in order to have a valid estimate. constexpr double kMinReceivedProbesRatio = .80; // The minumum number of bytes we need to receive feedback about in percent // in order to have a valid estimate. constexpr double kMinReceivedBytesRatio = .80; // The maximum |receive rate| / |send rate| ratio for a valid estimate. constexpr float kMaxValidRatio = 2.0f; // The minimum |receive rate| / |send rate| ratio assuming that the link is // not saturated, i.e. we assume that we will receive at least // kMinRatioForUnsaturatedLink * |send rate| if |send rate| is less than the // link capacity. constexpr float kMinRatioForUnsaturatedLink = 0.9f; // The target utilization of the link. If we know true link capacity // we'd like to send at 95% of that rate. constexpr float kTargetUtilizationFraction = 0.95f; // The maximum time period over which the cluster history is retained. // This is also the maximum time period beyond which a probing burst is not // expected to last. constexpr TimeDelta kMaxClusterHistory = TimeDelta::Seconds<1>(); // The maximum time interval between first and the last probe on a cluster // on the sender side as well as the receive side. constexpr TimeDelta kMaxProbeInterval = TimeDelta::Seconds<1>(); } // namespace ProbeBitrateEstimator::ProbeBitrateEstimator() { } ProbeBitrateEstimator::~ProbeBitrateEstimator() = default; absl::optional ProbeBitrateEstimator::HandleProbeAndEstimateBitrate( const PacketResult& packet_feedback) { int cluster_id = packet_feedback.sent_packet.pacing_info.probe_cluster_id; //RTC_DCHECK_NE(cluster_id, PacedPacketInfo::kNotAProbe); if (cluster_id == PacedPacketInfo::kNotAProbe) { MS_ERROR("cluster_id == kNotAProbe"); return absl::nullopt; } EraseOldClusters(packet_feedback.receive_time); AggregatedCluster* cluster = &clusters_[cluster_id]; if (packet_feedback.sent_packet.send_time < cluster->first_send) { cluster->first_send = packet_feedback.sent_packet.send_time; } if (packet_feedback.sent_packet.send_time > cluster->last_send) { cluster->last_send = packet_feedback.sent_packet.send_time; cluster->size_last_send = packet_feedback.sent_packet.size; } if (packet_feedback.receive_time < cluster->first_receive) { cluster->first_receive = packet_feedback.receive_time; cluster->size_first_receive = packet_feedback.sent_packet.size; } if (packet_feedback.receive_time > cluster->last_receive) { cluster->last_receive = packet_feedback.receive_time; } cluster->size_total += packet_feedback.sent_packet.size; cluster->num_probes += 1; // RTC_DCHECK_GT( // packet_feedback.sent_packet.pacing_info.probe_cluster_min_probes, 0); // RTC_DCHECK_GT(packet_feedback.sent_packet.pacing_info.probe_cluster_min_bytes, // 0); if (packet_feedback.sent_packet.pacing_info.probe_cluster_min_probes <= 0) { MS_ERROR("probe_cluster_min_probes must be > 0"); return absl::nullopt; } if (packet_feedback.sent_packet.pacing_info.probe_cluster_min_bytes <= 0) { MS_ERROR("probe_cluster_min_bytes must be > 0"); return absl::nullopt; } int min_probes = packet_feedback.sent_packet.pacing_info.probe_cluster_min_probes * kMinReceivedProbesRatio; DataSize min_size = DataSize::bytes( packet_feedback.sent_packet.pacing_info.probe_cluster_min_bytes) * kMinReceivedBytesRatio; if (cluster->num_probes < min_probes || cluster->size_total < min_size) return absl::nullopt; TimeDelta send_interval = cluster->last_send - cluster->first_send; TimeDelta receive_interval = cluster->last_receive - cluster->first_receive; // // TODO: TMP // MS_WARN_DEV( // "-------------- probing cluster result" // " [cluster id:%d]" // " [send interval:%s]" // " [receive interval:%s]", // cluster_id, // ToString(send_interval).c_str(), // ToString(receive_interval).c_str()); // TODO: TMP WIP cerdo to avoid that send_interval or receive_interval is zero. // // if (send_interval <= TimeDelta::Zero()) // send_interval = TimeDelta::ms(1u); // if (receive_interval <= TimeDelta::Zero()) // receive_interval = TimeDelta::ms(1u); if (send_interval <= TimeDelta::Zero() || send_interval > kMaxProbeInterval || receive_interval <= TimeDelta::Zero() || receive_interval > kMaxProbeInterval) { MS_WARN_DEV( "probing unsuccessful, invalid send/receive interval" " [cluster id:%d]" " [send interval:%s]" " [receive interval:%s]", cluster_id, ToString(send_interval).c_str(), ToString(receive_interval).c_str()); return absl::nullopt; } // Since the |send_interval| does not include the time it takes to actually // send the last packet the size of the last sent packet should not be // included when calculating the send bitrate. //RTC_DCHECK_GT(cluster->size_total, cluster->size_last_send); DataSize send_size = cluster->size_total - cluster->size_last_send; DataRate send_rate = send_size / send_interval; // Since the |receive_interval| does not include the time it takes to // actually receive the first packet the size of the first received packet // should not be included when calculating the receive bitrate. //RTC_DCHECK_GT(cluster->size_total, cluster->size_first_receive); DataSize receive_size = cluster->size_total - cluster->size_first_receive; DataRate receive_rate = receive_size / receive_interval; double ratio = receive_rate / send_rate; if (ratio > kMaxValidRatio) { MS_WARN_DEV( "probing unsuccessful, receive/send ratio too high" " [cluster id:%d, send:%s / %s = %s]" " [receive:%s / %s = %s]" " [ratio:%s / %s = %f]" " > kMaxValidRatio:%f]", cluster_id, ToString(send_size).c_str(), ToString(send_interval).c_str(), ToString(send_rate).c_str(), ToString(receive_size).c_str(), ToString(receive_interval).c_str(), ToString(receive_rate).c_str(), ToString(receive_rate).c_str(), ToString(send_rate).c_str(), ratio, kMaxValidRatio); return absl::nullopt; } MS_DEBUG_DEV( "probing successful" " [cluster id:%d]" " [send:%s / %s = %s]" " [receive:%s / %s = %s]", cluster_id, ToString(send_size).c_str(), ToString(send_interval).c_str(), ToString(send_rate).c_str(), ToString(receive_size).c_str(), ToString(receive_interval).c_str(), ToString(receive_rate).c_str()); DataRate res = std::min(send_rate, receive_rate); // If we're receiving at significantly lower bitrate than we were sending at, // it suggests that we've found the true capacity of the link. In this case, // set the target bitrate slightly lower to not immediately overuse. if (receive_rate < kMinRatioForUnsaturatedLink * send_rate) { //RTC_DCHECK_GT(send_rate, receive_rate); res = kTargetUtilizationFraction * receive_rate; } last_estimate_ = res; estimated_data_rate_ = res; return res; } absl::optional ProbeBitrateEstimator::FetchAndResetLastEstimatedBitrate() { absl::optional estimated_data_rate = estimated_data_rate_; estimated_data_rate_.reset(); return estimated_data_rate; } absl::optional ProbeBitrateEstimator::last_estimate() const { return last_estimate_; } void ProbeBitrateEstimator::EraseOldClusters(Timestamp timestamp) { for (auto it = clusters_.begin(); it != clusters_.end();) { if (it->second.last_receive + kMaxClusterHistory < timestamp) { it = clusters_.erase(it); } else { ++it; } } } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.h ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_BITRATE_ESTIMATOR_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_BITRATE_ESTIMATOR_H_ #include "api/transport/network_types.h" #include "api/units/data_rate.h" #include #include #include namespace webrtc { class RtcEventLog; class ProbeBitrateEstimator { public: ProbeBitrateEstimator(); ~ProbeBitrateEstimator(); // Should be called for every probe packet we receive feedback about. // Returns the estimated bitrate if the probe completes a valid cluster. absl::optional HandleProbeAndEstimateBitrate( const PacketResult& packet_feedback); absl::optional FetchAndResetLastEstimatedBitrate(); absl::optional last_estimate() const; private: struct AggregatedCluster { int num_probes = 0; Timestamp first_send = Timestamp::PlusInfinity(); Timestamp last_send = Timestamp::MinusInfinity(); Timestamp first_receive = Timestamp::PlusInfinity(); Timestamp last_receive = Timestamp::MinusInfinity(); DataSize size_last_send = DataSize::Zero(); DataSize size_first_receive = DataSize::Zero(); DataSize size_total = DataSize::Zero(); }; // Erases old cluster data that was seen before |timestamp|. void EraseOldClusters(Timestamp timestamp); std::map clusters_; absl::optional estimated_data_rate_; absl::optional last_estimate_; }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_BITRATE_ESTIMATOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/probe_controller.cc ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::ProbeController" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/goog_cc/probe_controller.h" #include "api/units/data_rate.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "rtc_base/numerics/safe_conversions.h" #include "Logger.hpp" #include #include #include #include namespace webrtc { namespace { // The minimum number probing packets used. constexpr int kMinProbePacketsSent = 5; // The minimum probing duration in ms. constexpr int kMinProbeDurationMs = 15; // Maximum waiting time from the time of initiating probing to getting // the measured results back. constexpr int64_t kMaxWaitingTimeForProbingResultMs = 1000; // Value of |min_bitrate_to_probe_further_bps_| that indicates // further probing is disabled. constexpr int kExponentialProbingDisabled = 0; // Default probing bitrate limit. Applied only when the application didn't // specify max bitrate. constexpr int64_t kDefaultMaxProbingBitrateBps = 5000000; // If the bitrate drops to a factor |kBitrateDropThreshold| or lower // and we recover within |kBitrateDropTimeoutMs|, then we'll send // a probe at a fraction |kProbeFractionAfterDrop| of the original bitrate. constexpr double kBitrateDropThreshold = 0.66; constexpr int kBitrateDropTimeoutMs = 5000; constexpr double kProbeFractionAfterDrop = 0.85; // Timeout for probing after leaving ALR. If the bitrate drops significantly, // (as determined by the delay based estimator) and we leave ALR, then we will // send a probe if we recover within |kLeftAlrTimeoutMs| ms. constexpr int kAlrEndedTimeoutMs = 3000; // The expected uncertainty of probe result (as a fraction of the target probe // This is a limit on how often probing can be done when there is a BW // drop detected in ALR. constexpr int64_t kMinTimeBetweenAlrProbesMs = 5000; // bitrate). Used to avoid probing if the probe bitrate is close to our current // estimate. constexpr double kProbeUncertainty = 0.05; // Use probing to recover faster after large bitrate estimate drops. constexpr char kBweRapidRecoveryExperiment[] = "WebRTC-BweRapidRecoveryExperiment"; // Never probe higher than configured by OnMaxTotalAllocatedBitrate(). constexpr char kCappedProbingFieldTrialName[] = "WebRTC-BweCappedProbing"; // Only do allocation probing when in ALR (but not when network-limited). constexpr char kAllocProbingOnlyInAlrFieldTrialName[] = "WebRTC-BweAllocProbingOnlyInAlr"; void MaybeLogProbeClusterCreated(const ProbeClusterConfig& probe) { #if MS_LOG_DEV_LEVEL == 3 size_t min_bytes = static_cast(probe.target_data_rate.bps() * probe.target_duration.ms() / 8000); #endif MS_DEBUG_DEV( "probe cluster created [id:%d, target data rate(bps):%lld, target probe count:%d, min_bytes:%zu]", probe.id, probe.target_data_rate.bps(), probe.target_probe_count, min_bytes); } } // namespace ProbeControllerConfig::ProbeControllerConfig( const WebRtcKeyValueConfig* key_value_config) : first_exponential_probe_scale("p1", 3.0), second_exponential_probe_scale("p2", 6.0), further_exponential_probe_scale("step_size", 2), further_probe_threshold("further_probe_threshold", 0.7), alr_probing_interval("alr_interval", TimeDelta::seconds(5)), alr_probe_scale("alr_scale", 2), first_allocation_probe_scale("alloc_p1", 1), second_allocation_probe_scale("alloc_p2", 2), allocation_allow_further_probing("alloc_probe_further", false) { ParseFieldTrial( {&first_exponential_probe_scale, &second_exponential_probe_scale, &further_exponential_probe_scale, &further_probe_threshold, &alr_probing_interval, &alr_probe_scale, &first_allocation_probe_scale, &second_allocation_probe_scale, &allocation_allow_further_probing}, key_value_config->Lookup("WebRTC-Bwe-ProbingConfiguration")); // Specialized keys overriding subsets of WebRTC-Bwe-ProbingConfiguration ParseFieldTrial( {&first_exponential_probe_scale, &second_exponential_probe_scale}, key_value_config->Lookup("WebRTC-Bwe-InitialProbing")); ParseFieldTrial({&further_exponential_probe_scale, &further_probe_threshold}, key_value_config->Lookup("WebRTC-Bwe-ExponentialProbing")); ParseFieldTrial({&alr_probing_interval, &alr_probe_scale}, key_value_config->Lookup("WebRTC-Bwe-AlrProbing")); ParseFieldTrial( {&first_allocation_probe_scale, &second_allocation_probe_scale, &allocation_allow_further_probing}, key_value_config->Lookup("WebRTC-Bwe-AllocationProbing")); } ProbeControllerConfig::ProbeControllerConfig(const ProbeControllerConfig&) = default; ProbeControllerConfig::~ProbeControllerConfig() = default; ProbeController::ProbeController(const WebRtcKeyValueConfig* key_value_config) : enable_periodic_alr_probing_(false), in_rapid_recovery_experiment_( key_value_config->Lookup(kBweRapidRecoveryExperiment) .find("Enabled") == 0), limit_probes_with_allocateable_rate_( key_value_config->Lookup(kCappedProbingFieldTrialName) .find("Disabled") != 0), allocation_probing_only_in_alr_( key_value_config->Lookup(kAllocProbingOnlyInAlrFieldTrialName) .find("Enabled") == 0), config_(ProbeControllerConfig(key_value_config)) { Reset(0); } ProbeController::~ProbeController() {} std::vector ProbeController::SetBitrates( int64_t min_bitrate_bps, int64_t start_bitrate_bps, int64_t max_bitrate_bps, int64_t at_time_ms) { if (start_bitrate_bps > 0) { start_bitrate_bps_ = start_bitrate_bps; estimated_bitrate_bps_ = start_bitrate_bps; } else if (start_bitrate_bps_ == 0) { start_bitrate_bps_ = min_bitrate_bps; } MS_DEBUG_DEV( "[old_max_bitrate_bps:%lld, max_bitrate_bps:%lld]", max_bitrate_bps_, max_bitrate_bps); // The reason we use the variable |old_max_bitrate_pbs| is because we // need to set |max_bitrate_bps_| before we call InitiateProbing. int64_t old_max_bitrate_bps = max_bitrate_bps_; max_bitrate_bps_ = max_bitrate_bps; switch (state_) { case State::kInit: if (network_available_) return InitiateExponentialProbing(at_time_ms); break; case State::kWaitingForProbingResult: break; case State::kProbingComplete: // If the new max bitrate is higher than both the old max bitrate and the // estimate then initiate probing. if (estimated_bitrate_bps_ != 0 && old_max_bitrate_bps < max_bitrate_bps_ && estimated_bitrate_bps_ < max_bitrate_bps_) { // The assumption is that if we jump more than 20% in the bandwidth // estimate or if the bandwidth estimate is within 90% of the new // max bitrate then the probing attempt was successful. mid_call_probing_succcess_threshold_ = std::min(estimated_bitrate_bps_ * 1.2, max_bitrate_bps_ * 0.9); mid_call_probing_waiting_for_result_ = true; mid_call_probing_bitrate_bps_ = max_bitrate_bps_; // RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.MidCallProbing.Initiated", // max_bitrate_bps_ / 1000); return InitiateProbing(at_time_ms, {max_bitrate_bps_}, false); } break; } return std::vector(); } std::vector ProbeController::OnMaxTotalAllocatedBitrate( int64_t max_total_allocated_bitrate, int64_t at_time_ms) { MS_DEBUG_DEV("[max_total_allocated_bitrate:%" PRIi64 "]", max_total_allocated_bitrate); const bool in_alr = alr_start_time_ms_.has_value(); const bool allow_allocation_probe = allocation_probing_only_in_alr_ ? in_alr : true; if (state_ == State::kProbingComplete && max_total_allocated_bitrate != max_total_allocated_bitrate_ && estimated_bitrate_bps_ != 0 && (max_bitrate_bps_ <= 0 || estimated_bitrate_bps_ < max_bitrate_bps_) && estimated_bitrate_bps_ < max_total_allocated_bitrate && allow_allocation_probe) { max_total_allocated_bitrate_ = max_total_allocated_bitrate; if (!config_.first_allocation_probe_scale) return std::vector(); std::vector probes = { static_cast(config_.first_allocation_probe_scale.Value() * max_total_allocated_bitrate)}; if (config_.second_allocation_probe_scale) { probes.push_back(config_.second_allocation_probe_scale.Value() * max_total_allocated_bitrate); } return InitiateProbing(at_time_ms, probes, config_.allocation_allow_further_probing); } max_total_allocated_bitrate_ = max_total_allocated_bitrate; return std::vector(); } std::vector ProbeController::OnNetworkAvailability( NetworkAvailability msg) { network_available_ = msg.network_available; if (!network_available_ && state_ == State::kWaitingForProbingResult) { state_ = State::kProbingComplete; min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled; } if (network_available_ && state_ == State::kInit && start_bitrate_bps_ > 0) return InitiateExponentialProbing(msg.at_time.ms()); return std::vector(); } std::vector ProbeController::InitiateExponentialProbing( int64_t at_time_ms) { //RTC_DCHECK(network_available_); //RTC_DCHECK(state_ == State::kInit); //RTC_DCHECK_GT(start_bitrate_bps_, 0); if (!network_available_) { MS_ERROR("network not available"); } if (state_ != State::kInit) { MS_ERROR("state_ must be State::kInit"); } if (start_bitrate_bps_ <= 0) { MS_ERROR("start_bitrate_bps_ must be > 0"); } // When probing at 1.8 Mbps ( 6x 300), this represents a threshold of // 1.2 Mbps to continue probing. std::vector probes = {static_cast( config_.first_exponential_probe_scale * start_bitrate_bps_)}; if (config_.second_exponential_probe_scale) { probes.push_back(config_.second_exponential_probe_scale.Value() * start_bitrate_bps_); } return InitiateProbing(at_time_ms, probes, true); } std::vector ProbeController::SetEstimatedBitrate( int64_t bitrate_bps, int64_t at_time_ms) { if (mid_call_probing_waiting_for_result_ && bitrate_bps >= mid_call_probing_succcess_threshold_) { // RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.MidCallProbing.Success", // mid_call_probing_bitrate_bps_ / 1000); // RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.MidCallProbing.ProbedKbps", // bitrate_bps / 1000); mid_call_probing_waiting_for_result_ = false; } std::vector pending_probes; if (state_ == State::kWaitingForProbingResult) { // Continue probing if probing results indicate channel has greater // capacity. // MS_DEBUG_DEV( // "[measured bitrate:%" PRIi64 ", minimum to probe further:%" PRIi64 "]", // bitrate_bps, min_bitrate_to_probe_further_bps_); if (min_bitrate_to_probe_further_bps_ != kExponentialProbingDisabled && bitrate_bps > min_bitrate_to_probe_further_bps_) { pending_probes = InitiateProbing( at_time_ms, {static_cast(config_.further_exponential_probe_scale * bitrate_bps)}, true); } } if (bitrate_bps < kBitrateDropThreshold * estimated_bitrate_bps_) { time_of_last_large_drop_ms_ = at_time_ms; bitrate_before_last_large_drop_bps_ = estimated_bitrate_bps_; } estimated_bitrate_bps_ = bitrate_bps; return pending_probes; } void ProbeController::EnablePeriodicAlrProbing(bool enable) { enable_periodic_alr_probing_ = enable; } void ProbeController::SetAlrStartTimeMs( absl::optional alr_start_time_ms) { alr_start_time_ms_ = alr_start_time_ms; } void ProbeController::SetAlrEndedTimeMs(int64_t alr_end_time_ms) { alr_end_time_ms_.emplace(alr_end_time_ms); } std::vector ProbeController::RequestProbe( int64_t at_time_ms) { // Called once we have returned to normal state after a large drop in // estimated bandwidth. The current response is to initiate a single probe // session (if not already probing) at the previous bitrate. // // If the probe session fails, the assumption is that this drop was a // real one from a competing flow or a network change. bool in_alr = alr_start_time_ms_.has_value(); bool alr_ended_recently = (alr_end_time_ms_.has_value() && at_time_ms - alr_end_time_ms_.value() < kAlrEndedTimeoutMs); if (in_alr || alr_ended_recently || in_rapid_recovery_experiment_) { if (state_ == State::kProbingComplete) { uint32_t suggested_probe_bps = kProbeFractionAfterDrop * bitrate_before_last_large_drop_bps_; uint32_t min_expected_probe_result_bps = (1 - kProbeUncertainty) * suggested_probe_bps; int64_t time_since_drop_ms = at_time_ms - time_of_last_large_drop_ms_; int64_t time_since_probe_ms = at_time_ms - last_bwe_drop_probing_time_ms_; if (min_expected_probe_result_bps > estimated_bitrate_bps_ && time_since_drop_ms < kBitrateDropTimeoutMs && time_since_probe_ms > kMinTimeBetweenAlrProbesMs) { MS_WARN_TAG(bwe, "detected big bandwidth drop, start probing"); // Track how often we probe in response to bandwidth drop in ALR. // RTC_HISTOGRAM_COUNTS_10000( // "WebRTC.BWE.BweDropProbingIntervalInS", // (at_time_ms - last_bwe_drop_probing_time_ms_) / 1000); last_bwe_drop_probing_time_ms_ = at_time_ms; return InitiateProbing(at_time_ms, {suggested_probe_bps}, false); } } } return std::vector(); } void ProbeController::SetMaxBitrate(int64_t max_bitrate_bps) { MS_DEBUG_DEV("[max_bitrate_bps:%" PRIi64 "]", max_bitrate_bps); max_bitrate_bps_ = max_bitrate_bps; } void ProbeController::Reset(int64_t at_time_ms) { MS_DEBUG_DEV("resetted"); network_available_ = true; state_ = State::kInit; min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled; time_last_probing_initiated_ms_ = 0; estimated_bitrate_bps_ = 0; start_bitrate_bps_ = 0; max_bitrate_bps_ = 0; int64_t now_ms = at_time_ms; last_bwe_drop_probing_time_ms_ = now_ms; alr_end_time_ms_.reset(); mid_call_probing_waiting_for_result_ = false; time_of_last_large_drop_ms_ = now_ms; bitrate_before_last_large_drop_bps_ = 0; max_total_allocated_bitrate_ = 0; } std::vector ProbeController::Process(int64_t at_time_ms) { if (at_time_ms - time_last_probing_initiated_ms_ > kMaxWaitingTimeForProbingResultMs) { mid_call_probing_waiting_for_result_ = false; if (state_ == State::kWaitingForProbingResult) { MS_WARN_TAG(bwe, "kWaitingForProbingResult: timeout"); state_ = State::kProbingComplete; min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled; } } if (enable_periodic_alr_probing_ && state_ == State::kProbingComplete) { // Probe bandwidth periodically when in ALR state. if (alr_start_time_ms_ && estimated_bitrate_bps_ > 0) { int64_t next_probe_time_ms = std::max(*alr_start_time_ms_, time_last_probing_initiated_ms_) + config_.alr_probing_interval->ms(); if (at_time_ms >= next_probe_time_ms) { return InitiateProbing(at_time_ms, {static_cast(estimated_bitrate_bps_ * config_.alr_probe_scale)}, true); } } } return std::vector(); } std::vector ProbeController::InitiateProbing( int64_t now_ms, std::vector bitrates_to_probe, bool probe_further) { int64_t max_probe_bitrate_bps = max_bitrate_bps_ > 0 ? max_bitrate_bps_ : kDefaultMaxProbingBitrateBps; MS_DEBUG_DEV( "[max_bitrate_bps_:%lld, max_probe_bitrate_bps:%" PRIi64 "]", max_bitrate_bps_, max_probe_bitrate_bps); if (limit_probes_with_allocateable_rate_ && max_total_allocated_bitrate_ > 0) { // If a max allocated bitrate has been configured, allow probing up to 2x // that rate. This allows some overhead to account for bursty streams, // which otherwise would have to ramp up when the overshoot is already in // progress. // It also avoids minor quality reduction caused by probes often being // received at slightly less than the target probe bitrate. max_probe_bitrate_bps = std::min(max_probe_bitrate_bps, max_total_allocated_bitrate_ * 2); } std::vector pending_probes; for (int64_t bitrate : bitrates_to_probe) { //RTC_DCHECK_GT(bitrate, 0); if (bitrate > max_probe_bitrate_bps) { bitrate = max_probe_bitrate_bps; probe_further = false; } ProbeClusterConfig config; config.at_time = Timestamp::ms(now_ms); config.target_data_rate = DataRate::bps(rtc::dchecked_cast(bitrate)); config.target_duration = TimeDelta::ms(kMinProbeDurationMs); config.target_probe_count = kMinProbePacketsSent; config.id = next_probe_cluster_id_; next_probe_cluster_id_++; MaybeLogProbeClusterCreated(config); pending_probes.push_back(config); } time_last_probing_initiated_ms_ = now_ms; if (probe_further) { state_ = State::kWaitingForProbingResult; min_bitrate_to_probe_further_bps_ = (*(bitrates_to_probe.end() - 1)) * config_.further_probe_threshold; } else { state_ = State::kProbingComplete; min_bitrate_to_probe_further_bps_ = kExponentialProbingDisabled; } return pending_probes; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/probe_controller.h ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_CONTROLLER_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_CONTROLLER_H_ #include "api/transport/network_control.h" #include "api/transport/webrtc_key_value_config.h" #include "rtc_base/constructor_magic.h" #include "rtc_base/experiments/field_trial_parser.h" #include "rtc_base/system/unused.h" #include #include #include #include namespace webrtc { struct ProbeControllerConfig { explicit ProbeControllerConfig(const WebRtcKeyValueConfig* key_value_config); ProbeControllerConfig(const ProbeControllerConfig&); ProbeControllerConfig& operator=(const ProbeControllerConfig&) = default; ~ProbeControllerConfig(); // These parameters configure the initial probes. First we send one or two // probes of sizes p1 * start_bitrate_bps_ and p2 * start_bitrate_bps_. // Then whenever we get a bitrate estimate of at least further_probe_threshold // times the size of the last sent probe we'll send another one of size // step_size times the new estimate. FieldTrialParameter first_exponential_probe_scale; FieldTrialOptional second_exponential_probe_scale; FieldTrialParameter further_exponential_probe_scale; FieldTrialParameter further_probe_threshold; // Configures how often we send ALR probes and how big they are. FieldTrialParameter alr_probing_interval; FieldTrialParameter alr_probe_scale; // Configures the probes emitted by changed to the allocated bitrate. FieldTrialOptional first_allocation_probe_scale; FieldTrialOptional second_allocation_probe_scale; FieldTrialFlag allocation_allow_further_probing; }; // This class controls initiation of probing to estimate initial channel // capacity. There is also support for probing during a session when max // bitrate is adjusted by an application. class ProbeController { public: explicit ProbeController(const WebRtcKeyValueConfig* key_value_config); ~ProbeController(); RTC_WARN_UNUSED_RESULT std::vector SetBitrates( int64_t min_bitrate_bps, int64_t start_bitrate_bps, int64_t max_bitrate_bps, int64_t at_time_ms); // The total bitrate, as opposed to the max bitrate, is the sum of the // configured bitrates for all active streams. RTC_WARN_UNUSED_RESULT std::vector OnMaxTotalAllocatedBitrate(int64_t max_total_allocated_bitrate, int64_t at_time_ms); RTC_WARN_UNUSED_RESULT std::vector OnNetworkAvailability( NetworkAvailability msg); RTC_WARN_UNUSED_RESULT std::vector SetEstimatedBitrate( int64_t bitrate_bps, int64_t at_time_ms); void EnablePeriodicAlrProbing(bool enable); void SetAlrStartTimeMs(absl::optional alr_start_time); void SetAlrEndedTimeMs(int64_t alr_end_time); RTC_WARN_UNUSED_RESULT std::vector RequestProbe( int64_t at_time_ms); // Sets a new maximum probing bitrate, without generating a new probe cluster. void SetMaxBitrate(int64_t max_bitrate_bps); // Resets the ProbeController to a state equivalent to as if it was just // created EXCEPT for |enable_periodic_alr_probing_|. void Reset(int64_t at_time_ms); RTC_WARN_UNUSED_RESULT std::vector Process( int64_t at_time_ms); private: enum class State { // Initial state where no probing has been triggered yet. kInit, // Waiting for probing results to continue further probing. kWaitingForProbingResult, // Probing is complete. kProbingComplete, }; RTC_WARN_UNUSED_RESULT std::vector InitiateExponentialProbing(int64_t at_time_ms); RTC_WARN_UNUSED_RESULT std::vector InitiateProbing( int64_t now_ms, std::vector bitrates_to_probe, bool probe_further); bool network_available_; State state_; int64_t min_bitrate_to_probe_further_bps_; int64_t time_last_probing_initiated_ms_; int64_t estimated_bitrate_bps_; int64_t start_bitrate_bps_; int64_t max_bitrate_bps_; int64_t last_bwe_drop_probing_time_ms_; absl::optional alr_start_time_ms_; absl::optional alr_end_time_ms_; bool enable_periodic_alr_probing_; int64_t time_of_last_large_drop_ms_; int64_t bitrate_before_last_large_drop_bps_; int64_t max_total_allocated_bitrate_; const bool in_rapid_recovery_experiment_; const bool limit_probes_with_allocateable_rate_; const bool allocation_probing_only_in_alr_; // For WebRTC.BWE.MidCallProbing.* metric. bool mid_call_probing_waiting_for_result_; int64_t mid_call_probing_bitrate_bps_; int64_t mid_call_probing_succcess_threshold_; int32_t next_probe_cluster_id_ = 1; ProbeControllerConfig config_; RTC_DISALLOW_COPY_AND_ASSIGN(ProbeController); }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_PROBE_CONTROLLER_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.cc ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::TrendlineEstimator" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/goog_cc/trendline_estimator.h" #include #include #include #include "absl/strings/match.h" #include "absl/types/optional.h" #include "api/network_state_predictor.h" #include "rtc_base/numerics/safe_minmax.h" #include "api/transport/webrtc_key_value_config.h" #include "Logger.hpp" namespace webrtc { namespace { // Parameters for linear least squares fit of regression line to noisy data. constexpr double kDefaultTrendlineSmoothingCoeff = 0.9; constexpr double kDefaultTrendlineThresholdGain = 4.0; absl::optional LinearFitSlope( const std::deque& packets) { // RTC_DCHECK(packets.size() >= 2); // Compute the "center of mass". double sum_x = 0; double sum_y = 0; for (const auto& packet : packets) { sum_x += packet.arrival_time_ms; sum_y += packet.smoothed_delay_ms; } double x_avg = sum_x / packets.size(); double y_avg = sum_y / packets.size(); // Compute the slope k = \sum (x_i-x_avg)(y_i-y_avg) / \sum (x_i-x_avg)^2 double numerator = 0; double denominator = 0; for (const auto& packet : packets) { double x = packet.arrival_time_ms; double y = packet.smoothed_delay_ms; numerator += (x - x_avg) * (y - y_avg); denominator += (x - x_avg) * (x - x_avg); } if (denominator == 0) return absl::nullopt; return numerator / denominator; } absl::optional ComputeSlopeCap( const std::deque& packets, const TrendlineEstimatorSettings& settings) { // RTC_DCHECK(1 <= settings.beginning_packets && // settings.beginning_packets < packets.size()); // RTC_DCHECK(1 <= settings.end_packets && // settings.end_packets < packets.size()); // RTC_DCHECK(settings.beginning_packets + settings.end_packets <= // packets.size()); TrendlineEstimator::PacketTiming early = packets[0]; for (size_t i = 1; i < settings.beginning_packets; ++i) { if (packets[i].raw_delay_ms < early.raw_delay_ms) early = packets[i]; } size_t late_start = packets.size() - settings.end_packets; TrendlineEstimator::PacketTiming late = packets[late_start]; for (size_t i = late_start + 1; i < packets.size(); ++i) { if (packets[i].raw_delay_ms < late.raw_delay_ms) late = packets[i]; } if (late.arrival_time_ms - early.arrival_time_ms < 1) { return absl::nullopt; } return (late.raw_delay_ms - early.raw_delay_ms) / (late.arrival_time_ms - early.arrival_time_ms) + settings.cap_uncertainty; } constexpr double kMaxAdaptOffsetMs = 15.0; constexpr double kOverUsingTimeThreshold = 10; constexpr int kMinNumDeltas = 60; constexpr int kDeltaCounterMax = 1000; } // namespace TrendlineEstimator::TrendlineEstimator( NetworkStatePredictor* network_state_predictor) : smoothing_coef_(kDefaultTrendlineSmoothingCoeff), threshold_gain_(kDefaultTrendlineThresholdGain), num_of_deltas_(0), first_arrival_time_ms_(-1), accumulated_delay_(0), smoothed_delay_(0), delay_hist_(), k_up_(0.0087), k_down_(0.039), overusing_time_threshold_(kOverUsingTimeThreshold), threshold_(12.5), prev_modified_trend_(NAN), last_update_ms_(-1), prev_trend_(0.0), time_over_using_(-1), overuse_counter_(0), hypothesis_(BandwidthUsage::kBwNormal), hypothesis_predicted_(BandwidthUsage::kBwNormal), network_state_predictor_(network_state_predictor) { } TrendlineEstimator::~TrendlineEstimator() {} void TrendlineEstimator::UpdateTrendline(double recv_delta_ms, double send_delta_ms, int64_t send_time_ms, int64_t arrival_time_ms) { const double delta_ms = recv_delta_ms - send_delta_ms; ++num_of_deltas_; num_of_deltas_ = std::min(num_of_deltas_, kDeltaCounterMax); if (first_arrival_time_ms_ == -1) first_arrival_time_ms_ = arrival_time_ms; // Exponential backoff filter. accumulated_delay_ += delta_ms; // BWE_TEST_LOGGING_PLOT(1, "accumulated_delay_ms", arrival_time_ms, // accumulated_delay_); smoothed_delay_ = smoothing_coef_ * smoothed_delay_ + (1 - smoothing_coef_) * accumulated_delay_; // BWE_TEST_LOGGING_PLOT(1, "smoothed_delay_ms", arrival_time_ms, // smoothed_delay_); // Maintain packet window delay_hist_.emplace_back( static_cast(arrival_time_ms - first_arrival_time_ms_), smoothed_delay_, accumulated_delay_); if (settings_.enable_sort) { for (size_t i = delay_hist_.size() - 1; i > 0 && delay_hist_[i].arrival_time_ms < delay_hist_[i - 1].arrival_time_ms; --i) { std::swap(delay_hist_[i], delay_hist_[i - 1]); } } if (delay_hist_.size() > settings_.window_size) delay_hist_.pop_front(); // Simple linear regression. double trend = prev_trend_; if (delay_hist_.size() == settings_.window_size) { // Update trend_ if it is possible to fit a line to the data. The delay // trend can be seen as an estimate of (send_rate - capacity)/capacity. // 0 < trend < 1 -> the delay increases, queues are filling up // trend == 0 -> the delay does not change // trend < 0 -> the delay decreases, queues are being emptied trend = LinearFitSlope(delay_hist_).value_or(trend); if (settings_.enable_cap) { absl::optional cap = ComputeSlopeCap(delay_hist_, settings_); // We only use the cap to filter out overuse detections, not // to detect additional underuses. if (trend >= 0 && cap.has_value() && trend > cap.value()) { trend = cap.value(); } } } // BWE_TEST_LOGGING_PLOT(1, "trendline_slope", arrival_time_ms, trend); Detect(trend, send_delta_ms, arrival_time_ms); } void TrendlineEstimator::Update(double recv_delta_ms, double send_delta_ms, int64_t send_time_ms, int64_t arrival_time_ms, bool calculated_deltas) { if (calculated_deltas) { UpdateTrendline(recv_delta_ms, send_delta_ms, send_time_ms, arrival_time_ms); } if (network_state_predictor_) { hypothesis_predicted_ = network_state_predictor_->Update( send_time_ms, arrival_time_ms, hypothesis_); } } BandwidthUsage TrendlineEstimator::State() const { return network_state_predictor_ ? hypothesis_predicted_ : hypothesis_; } void TrendlineEstimator::Detect(double trend, double ts_delta, int64_t now_ms) { if (num_of_deltas_ < 2) { hypothesis_ = BandwidthUsage::kBwNormal; return; } const double modified_trend = std::min(num_of_deltas_, kMinNumDeltas) * trend * threshold_gain_; prev_modified_trend_ = modified_trend; // BWE_TEST_LOGGING_PLOT(1, "T", now_ms, modified_trend); // BWE_TEST_LOGGING_PLOT(1, "threshold", now_ms, threshold_); if (modified_trend > threshold_) { if (time_over_using_ == -1) { // Initialize the timer. Assume that we've been // over-using half of the time since the previous // sample. time_over_using_ = ts_delta / 2; } else { // Increment timer time_over_using_ += ts_delta; } overuse_counter_++; if (time_over_using_ > overusing_time_threshold_ && overuse_counter_ > 1) { if (trend >= prev_trend_) { time_over_using_ = 0; overuse_counter_ = 0; hypothesis_ = BandwidthUsage::kBwOverusing; MS_DEBUG_DEV("hypothesis_: BandwidthUsage::kBwOverusing"); #if MS_LOG_DEV_LEVEL == 3 for (auto& packetTiming : delay_hist_) { MS_DEBUG_DEV( "packetTiming [arrival_time_ms:%f, smoothed_delay_ms:%f, raw_delay_ms:%f", packetTiming.arrival_time_ms, packetTiming.smoothed_delay_ms, packetTiming.raw_delay_ms ); } #endif } } } else if (modified_trend < -threshold_) { time_over_using_ = -1; overuse_counter_ = 0; hypothesis_ = BandwidthUsage::kBwUnderusing; MS_DEBUG_DEV("---- BandwidthUsage::kBwUnderusing ---"); } else { time_over_using_ = -1; overuse_counter_ = 0; hypothesis_ = BandwidthUsage::kBwNormal; MS_DEBUG_DEV("---- BandwidthUsage::kBwNormal ---"); } prev_trend_ = trend; UpdateThreshold(modified_trend, now_ms); } void TrendlineEstimator::UpdateThreshold(double modified_trend, int64_t now_ms) { if (last_update_ms_ == -1) last_update_ms_ = now_ms; if (fabs(modified_trend) > threshold_ + kMaxAdaptOffsetMs) { // Avoid adapting the threshold to big latency spikes, caused e.g., // by a sudden capacity drop. last_update_ms_ = now_ms; return; } const double k = fabs(modified_trend) < threshold_ ? k_down_ : k_up_; const int64_t kMaxTimeDeltaMs = 100; int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs); threshold_ += k * (fabs(modified_trend) - threshold_) * time_delta_ms; threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f); last_update_ms_ = now_ms; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.h ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_GOOG_CC_TRENDLINE_ESTIMATOR_H_ #define MODULES_CONGESTION_CONTROLLER_GOOG_CC_TRENDLINE_ESTIMATOR_H_ #include #include #include #include #include #include "api/network_state_predictor.h" #include "api/transport/webrtc_key_value_config.h" #include "modules/congestion_controller/goog_cc/delay_increase_detector_interface.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "rtc_base/constructor_magic.h" namespace webrtc { struct TrendlineEstimatorSettings { static constexpr unsigned kDefaultTrendlineWindowSize = 20; // Sort the packets in the window. Should be redundant, // but then almost no cost. bool enable_sort = false; // Cap the trendline slope based on the minimum delay seen // in the beginning_packets and end_packets respectively. bool enable_cap = false; unsigned beginning_packets = 7; unsigned end_packets = 7; double cap_uncertainty = 0.0; // Size (in packets) of the window. unsigned window_size = kDefaultTrendlineWindowSize; }; class TrendlineEstimator : public DelayIncreaseDetectorInterface { public: TrendlineEstimator(NetworkStatePredictor* network_state_predictor); ~TrendlineEstimator() override; TrendlineEstimator(const TrendlineEstimator&) = delete; TrendlineEstimator& operator=(const TrendlineEstimator&) = delete; // Update the estimator with a new sample. The deltas should represent deltas // between timestamp groups as defined by the InterArrival class. void Update(double recv_delta_ms, double send_delta_ms, int64_t send_time_ms, int64_t arrival_time_ms, bool calculated_deltas) override; void UpdateTrendline(double recv_delta_ms, double send_delta_ms, int64_t send_time_ms, int64_t arrival_time_ms); BandwidthUsage State() const override; struct PacketTiming { PacketTiming(double arrival_time_ms, double smoothed_delay_ms, double raw_delay_ms) : arrival_time_ms(arrival_time_ms), smoothed_delay_ms(smoothed_delay_ms), raw_delay_ms(raw_delay_ms) {} double arrival_time_ms; double smoothed_delay_ms; double raw_delay_ms; }; private: friend class GoogCcStatePrinter; void Detect(double trend, double ts_delta, int64_t now_ms); void UpdateThreshold(double modified_offset, int64_t now_ms); // Parameters. TrendlineEstimatorSettings settings_; const double smoothing_coef_; const double threshold_gain_; // Used by the existing threshold. int num_of_deltas_; // Keep the arrival times small by using the change from the first packet. int64_t first_arrival_time_ms_; // Exponential backoff filtering. double accumulated_delay_; double smoothed_delay_; // Linear least squares regression. std::deque delay_hist_; const double k_up_; const double k_down_; double overusing_time_threshold_; double threshold_; double prev_modified_trend_; int64_t last_update_ms_; double prev_trend_; double time_over_using_; int overuse_counter_; BandwidthUsage hypothesis_; BandwidthUsage hypothesis_predicted_; NetworkStatePredictor* network_state_predictor_; }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_GOOG_CC_TRENDLINE_ESTIMATOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/control_handler.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::CongestionControlHandler" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/rtp/control_handler.h" #include "api/units/data_rate.h" #include "rtc_base/numerics/safe_conversions.h" #include "rtc_base/numerics/safe_minmax.h" #include "system_wrappers/source/field_trial.h" #include "Logger.hpp" #include #include namespace webrtc { void CongestionControlHandler::SetTargetRate( TargetTransferRate new_target_rate) { last_incoming_ = new_target_rate; } void CongestionControlHandler::SetNetworkAvailability(bool network_available) { network_available_ = network_available; } absl::optional CongestionControlHandler::GetUpdate() { if (!last_incoming_.has_value()) return absl::nullopt; TargetTransferRate new_outgoing = *last_incoming_; DataRate log_target_rate = new_outgoing.target_rate; bool pause_encoding = false; if (!network_available_) pause_encoding = true; if (pause_encoding) new_outgoing.target_rate = DataRate::Zero(); if (!last_reported_ || last_reported_->target_rate != new_outgoing.target_rate || (!new_outgoing.target_rate.IsZero() && (last_reported_->network_estimate.loss_rate_ratio != new_outgoing.network_estimate.loss_rate_ratio || last_reported_->network_estimate.round_trip_time != new_outgoing.network_estimate.round_trip_time))) { if (encoder_paused_in_last_report_ != pause_encoding) MS_DEBUG_TAG(bwe, "Bitrate estimate state changed, BWE: %s", ToString(log_target_rate).c_str()); encoder_paused_in_last_report_ = pause_encoding; last_reported_ = new_outgoing; return new_outgoing; } return absl::nullopt; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/control_handler.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_RTP_CONTROL_HANDLER_H_ #define MODULES_CONGESTION_CONTROLLER_RTP_CONTROL_HANDLER_H_ #include "api/transport/network_types.h" #include "api/units/data_size.h" #include "api/units/time_delta.h" #include "rtc_base/constructor_magic.h" #include #include namespace webrtc { // This is used to observe the network controller state and route calls to // the proper handler. It also keeps cached values for safe asynchronous use. // This makes sure that things running on the worker queue can't access state // in RtpTransportControllerSend, which would risk causing data race on // destruction unless members are properly ordered. class CongestionControlHandler { public: CongestionControlHandler() = default; ~CongestionControlHandler() = default; void SetTargetRate(TargetTransferRate new_target_rate); void SetNetworkAvailability(bool network_available); absl::optional GetUpdate(); private: absl::optional last_incoming_; absl::optional last_reported_; bool network_available_ = true; bool encoder_paused_in_last_report_ = false; RTC_DISALLOW_COPY_AND_ASSIGN(CongestionControlHandler); }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_RTP_CONTROL_HANDLER_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/send_time_history.cc ================================================ /* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::SendTimeHistory" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/rtp/send_time_history.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "Logger.hpp" #include #include namespace webrtc { SendTimeHistory::SendTimeHistory(int64_t packet_age_limit_ms) : packet_age_limit_ms_(packet_age_limit_ms) {} SendTimeHistory::~SendTimeHistory() {} void SendTimeHistory::RemoveOld(int64_t at_time_ms) { while (!history_.empty() && at_time_ms - history_.begin()->second.creation_time_ms > packet_age_limit_ms_) { // TODO(sprang): Warn if erasing (too many) old items? RemovePacketBytes(history_.begin()->second); history_.erase(history_.begin()); } } void SendTimeHistory::AddNewPacket(PacketFeedback packet) { packet.long_sequence_number = seq_num_unwrapper_.Unwrap(packet.sequence_number); history_.insert(std::make_pair(packet.long_sequence_number, packet)); if (packet.send_time_ms >= 0) { AddPacketBytes(packet); last_send_time_ms_ = std::max(last_send_time_ms_, packet.send_time_ms); } } void SendTimeHistory::AddUntracked(size_t packet_size, int64_t send_time_ms) { if (send_time_ms < last_send_time_ms_) { MS_WARN_TAG(bwe, "ignoring untracked data for out of order packet"); } pending_untracked_size_ += packet_size; last_untracked_send_time_ms_ = std::max(last_untracked_send_time_ms_, send_time_ms); } SendTimeHistory::Status SendTimeHistory::OnSentPacket(uint16_t sequence_number, int64_t send_time_ms) { int64_t unwrapped_seq_num = seq_num_unwrapper_.Unwrap(sequence_number); auto it = history_.find(unwrapped_seq_num); if (it == history_.end()) return Status::kNotAdded; bool packet_retransmit = it->second.send_time_ms >= 0; it->second.send_time_ms = send_time_ms; last_send_time_ms_ = std::max(last_send_time_ms_, send_time_ms); if (!packet_retransmit) AddPacketBytes(it->second); if (pending_untracked_size_ > 0) { if (send_time_ms < last_untracked_send_time_ms_) { MS_WARN_TAG(bwe, "appending acknowledged data for out of order packet." " (Diff:%" PRIi64 " ms)", last_untracked_send_time_ms_ - send_time_ms); } it->second.unacknowledged_data += pending_untracked_size_; pending_untracked_size_ = 0; } return packet_retransmit ? Status::kDuplicate : Status::kOk; } absl::optional SendTimeHistory::GetPacket( uint16_t sequence_number) const { int64_t unwrapped_seq_num = seq_num_unwrapper_.UnwrapWithoutUpdate(sequence_number); absl::optional optional_feedback; auto it = history_.find(unwrapped_seq_num); if (it != history_.end()) optional_feedback.emplace(it->second); return optional_feedback; } bool SendTimeHistory::GetFeedback(PacketFeedback* packet_feedback, bool remove) { // RTC_DCHECK(packet_feedback); int64_t unwrapped_seq_num = seq_num_unwrapper_.Unwrap(packet_feedback->sequence_number); UpdateAckedSeqNum(unwrapped_seq_num); // RTC_DCHECK_GE(*last_ack_seq_num_, 0); auto it = history_.find(unwrapped_seq_num); if (it == history_.end()) return false; // Save arrival_time not to overwrite it. int64_t arrival_time_ms = packet_feedback->arrival_time_ms; *packet_feedback = it->second; packet_feedback->arrival_time_ms = arrival_time_ms; if (remove) history_.erase(it); return true; } DataSize SendTimeHistory::GetOutstandingData(uint16_t local_net_id, uint16_t remote_net_id) const { auto it = in_flight_bytes_.find({local_net_id, remote_net_id}); if (it != in_flight_bytes_.end()) { return DataSize::bytes(it->second); } else { return DataSize::Zero(); } } absl::optional SendTimeHistory::GetFirstUnackedSendTime() const { if (!last_ack_seq_num_) return absl::nullopt; auto it = history_.find(*last_ack_seq_num_); if (it == history_.end() || it->second.send_time_ms == PacketFeedback::kNoSendTime) return absl::nullopt; return it->second.send_time_ms; } void SendTimeHistory::AddPacketBytes(const PacketFeedback& packet) { if (packet.send_time_ms < 0 || packet.payload_size == 0 || (last_ack_seq_num_ && *last_ack_seq_num_ >= packet.long_sequence_number)) return; auto it = in_flight_bytes_.find({packet.local_net_id, packet.remote_net_id}); if (it != in_flight_bytes_.end()) { it->second += packet.payload_size; } else { in_flight_bytes_[{packet.local_net_id, packet.remote_net_id}] = packet.payload_size; } } void SendTimeHistory::RemovePacketBytes(const PacketFeedback& packet) { if (packet.send_time_ms < 0 || packet.payload_size == 0 || (last_ack_seq_num_ && *last_ack_seq_num_ >= packet.long_sequence_number)) return; auto it = in_flight_bytes_.find({packet.local_net_id, packet.remote_net_id}); if (it != in_flight_bytes_.end()) { it->second -= packet.payload_size; if (it->second == 0) in_flight_bytes_.erase(it); } } void SendTimeHistory::UpdateAckedSeqNum(int64_t acked_seq_num) { if (last_ack_seq_num_ && *last_ack_seq_num_ >= acked_seq_num) return; auto unacked_it = history_.begin(); if (last_ack_seq_num_) unacked_it = history_.lower_bound(*last_ack_seq_num_); auto newly_acked_end = history_.upper_bound(acked_seq_num); for (; unacked_it != newly_acked_end; ++unacked_it) { RemovePacketBytes(unacked_it->second); } last_ack_seq_num_.emplace(acked_seq_num); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/send_time_history.h ================================================ /* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_ #define MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_ #include "api/units/data_size.h" #include "rtc_base/constructor_magic.h" #include "modules/include/module_common_types_public.h" #include #include #include namespace webrtc { struct PacketFeedback; class SendTimeHistory { public: enum class Status { kNotAdded, kOk, kDuplicate }; explicit SendTimeHistory(int64_t packet_age_limit_ms); ~SendTimeHistory(); // Cleanup old entries, then add new packet info with provided parameters. void RemoveOld(int64_t at_time_ms); void AddNewPacket(PacketFeedback packet); void AddUntracked(size_t packet_size, int64_t send_time_ms); // Updates packet info identified by |sequence_number| with |send_time_ms|. // Returns a PacketSendState indicating if the packet was not found, sent, // or if it was previously already marked as sent. Status OnSentPacket(uint16_t sequence_number, int64_t send_time_ms); // Retrieves packet info identified by |sequence_number|. absl::optional GetPacket(uint16_t sequence_number) const; // Look up PacketFeedback for a sent packet, based on the sequence number, and // populate all fields except for arrival_time. The packet parameter must // thus be non-null and have the sequence_number field set. bool GetFeedback(PacketFeedback* packet_feedback, bool remove); DataSize GetOutstandingData(uint16_t local_net_id, uint16_t remote_net_id) const; absl::optional GetFirstUnackedSendTime() const; private: using RemoteAndLocalNetworkId = std::pair; void AddPacketBytes(const PacketFeedback& packet); void RemovePacketBytes(const PacketFeedback& packet); void UpdateAckedSeqNum(int64_t acked_seq_num); const int64_t packet_age_limit_ms_; size_t pending_untracked_size_ = 0; int64_t last_send_time_ms_ = -1; int64_t last_untracked_send_time_ms_ = -1; SequenceNumberUnwrapper seq_num_unwrapper_; std::map history_; absl::optional last_ack_seq_num_; std::map in_flight_bytes_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(SendTimeHistory); }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_RTP_SEND_TIME_HISTORY_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc ================================================ /* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::TransportFeedbackAdapter" // #define MS_LOG_DEV_LEVEL 3 #include "modules/congestion_controller/rtp/transport_feedback_adapter.h" #include "api/units/timestamp.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "system_wrappers/source/field_trial.h" #include "mediasoup_helpers.h" #include "Logger.hpp" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include #include #include #include namespace webrtc { namespace { PacketResult NetworkPacketFeedbackFromRtpPacketFeedback( const webrtc::PacketFeedback& pf) { PacketResult feedback; if (pf.arrival_time_ms == webrtc::PacketFeedback::kNotReceived) { feedback.receive_time = Timestamp::PlusInfinity(); } else { feedback.receive_time = Timestamp::ms(pf.arrival_time_ms); } feedback.sent_packet.sequence_number = pf.long_sequence_number; feedback.sent_packet.send_time = Timestamp::ms(pf.send_time_ms); feedback.sent_packet.size = DataSize::bytes(pf.payload_size); feedback.sent_packet.pacing_info = pf.pacing_info; feedback.sent_packet.prior_unacked_data = DataSize::bytes(pf.unacknowledged_data); return feedback; } } // namespace const int64_t kNoTimestamp = -1; const int64_t kSendTimeHistoryWindowMs = 60000; TransportFeedbackAdapter::TransportFeedbackAdapter() : allow_duplicates_(field_trial::IsEnabled( "WebRTC-TransportFeedbackAdapter-AllowDuplicates")), send_time_history_(kSendTimeHistoryWindowMs), current_offset_ms_(kNoTimestamp), last_timestamp_us_(kNoTimestamp), local_net_id_(0), remote_net_id_(0) {} TransportFeedbackAdapter::~TransportFeedbackAdapter() { } void TransportFeedbackAdapter::AddPacket(const RtpPacketSendInfo& packet_info, size_t overhead_bytes, Timestamp creation_time) { { PacketFeedback packet_feedback( creation_time.ms(), packet_info.transport_sequence_number, packet_info.length + overhead_bytes, local_net_id_, remote_net_id_, packet_info.pacing_info); if (packet_info.has_rtp_sequence_number) { packet_feedback.ssrc = packet_info.ssrc; packet_feedback.rtp_sequence_number = packet_info.rtp_sequence_number; } // MS_NOTE: TODO remove. // MS_DUMP("packet_feedback.arrival_time_ms: %" PRIi64, packet_feedback.arrival_time_ms); // MS_DUMP("packet_feedback.send_time_ms: %" PRIi64, packet_feedback.send_time_ms); // MS_DUMP("packet_feedback.sequence_number: %" PRIu16, packet_feedback.sequence_number); // MS_DUMP("packet_feedback.long_sequence_number: %" PRIi64, packet_feedback.long_sequence_number); // MS_DUMP("packet_feedback.payload_size: %zu", packet_feedback.payload_size); // MS_DUMP("packet_feedback.unacknowledged_data: %zu", packet_feedback.unacknowledged_data); // MS_DUMP("packet_feedback.local_net_id: %" PRIu16, packet_feedback.local_net_id); // MS_DUMP("packet_feedback.remote_net_id: %" PRIu16, packet_feedback.remote_net_id); // MS_DUMP("packet_feedback.ssrc: %" PRIu32, packet_feedback.ssrc.value()); // MS_DUMP("packet_feedback.rtp_sequence_number: %" PRIu16, packet_feedback.rtp_sequence_number); send_time_history_.RemoveOld(creation_time.ms()); send_time_history_.AddNewPacket(std::move(packet_feedback)); } { for (auto* observer : observers_) { observer->OnPacketAdded(packet_info.ssrc, packet_info.transport_sequence_number); } } } absl::optional TransportFeedbackAdapter::ProcessSentPacket( const rtc::SentPacket& sent_packet) { // TODO(srte): Only use one way to indicate that packet feedback is used. if (sent_packet.info.included_in_feedback || sent_packet.packet_id != -1) { SendTimeHistory::Status send_status = send_time_history_.OnSentPacket( sent_packet.packet_id, sent_packet.send_time_ms); absl::optional packet; if (allow_duplicates_ || send_status != SendTimeHistory::Status::kDuplicate) { packet = send_time_history_.GetPacket(sent_packet.packet_id); } if (packet) { SentPacket msg; msg.size = DataSize::bytes(packet->payload_size); msg.send_time = Timestamp::ms(packet->send_time_ms); msg.sequence_number = packet->long_sequence_number; msg.prior_unacked_data = DataSize::bytes(packet->unacknowledged_data); msg.data_in_flight = send_time_history_.GetOutstandingData(local_net_id_, remote_net_id_); return msg; } } else if (sent_packet.info.included_in_allocation) { send_time_history_.AddUntracked(sent_packet.info.packet_size_bytes, sent_packet.send_time_ms); } return absl::nullopt; } absl::optional TransportFeedbackAdapter::ProcessTransportFeedback( const RTC::RTCP::FeedbackRtpTransportPacket& feedback, Timestamp feedback_receive_time) { DataSize prior_in_flight = GetOutstandingData(); last_packet_feedback_vector_ = GetPacketFeedbackVector(feedback, feedback_receive_time); { for (auto* observer : observers_) { observer->OnPacketFeedbackVector(last_packet_feedback_vector_); } } std::vector feedback_vector = last_packet_feedback_vector_; if (feedback_vector.empty()) return absl::nullopt; TransportPacketsFeedback msg; for (const PacketFeedback& rtp_feedback : feedback_vector) { if (rtp_feedback.send_time_ms != PacketFeedback::kNoSendTime) { auto feedback = NetworkPacketFeedbackFromRtpPacketFeedback(rtp_feedback); MS_DEBUG_DEV("feedback received for RTP packet: [seq_num: %" PRIi64 ", send_time:%" PRIi64 ", size: %lld, feedback.receive_time:%" PRIi64, feedback.sent_packet.sequence_number, feedback.sent_packet.send_time.ms(), feedback.sent_packet.size.bytes(), feedback.receive_time.ms()); msg.packet_feedbacks.push_back(feedback); } else if (rtp_feedback.arrival_time_ms == PacketFeedback::kNotReceived) { MS_DEBUG_DEV("--- rtp_feedback.arrival_time_ms == PacketFeedback::kNotReceived ---"); msg.sendless_arrival_times.push_back(Timestamp::PlusInfinity()); } else { msg.sendless_arrival_times.push_back( Timestamp::ms(rtp_feedback.arrival_time_ms)); } } { absl::optional first_unacked_send_time_ms = send_time_history_.GetFirstUnackedSendTime(); if (first_unacked_send_time_ms) msg.first_unacked_send_time = Timestamp::ms(*first_unacked_send_time_ms); } msg.feedback_time = feedback_receive_time; msg.prior_in_flight = prior_in_flight; msg.data_in_flight = GetOutstandingData(); MS_DEBUG_DEV("prior_in_flight:%lld, data_in_flight:%lld", msg.prior_in_flight.bytes(), msg.data_in_flight.bytes()); return msg; } DataSize TransportFeedbackAdapter::GetOutstandingData() const { return send_time_history_.GetOutstandingData(local_net_id_, remote_net_id_); } std::vector TransportFeedbackAdapter::GetPacketFeedbackVector( const RTC::RTCP::FeedbackRtpTransportPacket& feedback, Timestamp feedback_time) { // Add timestamp deltas to a local time base selected on first packet arrival. // This won't be the true time base, but makes it easier to manually inspect // time stamps. if (last_timestamp_us_ == kNoTimestamp) { current_offset_ms_ = feedback_time.ms(); } else { current_offset_ms_ += mediasoup_helpers::FeedbackRtpTransport::GetBaseDeltaUs(&feedback, last_timestamp_us_) / 1000; } last_timestamp_us_ = mediasoup_helpers::FeedbackRtpTransport::GetBaseTimeUs(&feedback); std::vector packet_feedback_vector; if (feedback.GetPacketStatusCount() == 0) { MS_WARN_DEV("empty transport feedback packet received"); return packet_feedback_vector; } packet_feedback_vector.reserve(feedback.GetPacketStatusCount()); { size_t failed_lookups = 0; int64_t offset_us = 0; int64_t timestamp_ms = 0; uint16_t seq_num = feedback.GetBaseSequenceNumber(); for (const auto& packet : mediasoup_helpers::FeedbackRtpTransport::GetReceivedPackets(&feedback)) { // Insert into the vector those unreceived packets which precede this // iteration's received packet. for (; seq_num != packet.sequence_number(); ++seq_num) { PacketFeedback packet_feedback(PacketFeedback::kNotReceived, seq_num); // Note: Element not removed from history because it might be reported // as received by another feedback. if (!send_time_history_.GetFeedback(&packet_feedback, false)) ++failed_lookups; if (packet_feedback.local_net_id == local_net_id_ && packet_feedback.remote_net_id == remote_net_id_) { packet_feedback_vector.push_back(packet_feedback); } } // Handle this iteration's received packet. offset_us += packet.delta_us(); timestamp_ms = current_offset_ms_ + (offset_us / 1000); PacketFeedback packet_feedback(timestamp_ms, packet.sequence_number()); if (!send_time_history_.GetFeedback(&packet_feedback, true)) ++failed_lookups; if (packet_feedback.local_net_id == local_net_id_ && packet_feedback.remote_net_id == remote_net_id_) { packet_feedback_vector.push_back(packet_feedback); } ++seq_num; } if (failed_lookups > 0) { MS_WARN_DEV("failed to lookup send time for %zu" " packet%s, send time history too small?", failed_lookups, (failed_lookups > 1 ? "s" : "")); } } return packet_feedback_vector; } std::vector TransportFeedbackAdapter::GetTransportFeedbackVector() const { return last_packet_feedback_vector_; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.h ================================================ /* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_ #define MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_ #include "api/transport/network_types.h" #include "modules/congestion_controller/rtp/send_time_history.h" #include "rtc_base/network/sent_packet.h" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include #include namespace webrtc { class PacketFeedbackObserver; struct RtpPacketSendInfo; class TransportFeedbackAdapter { public: TransportFeedbackAdapter(); virtual ~TransportFeedbackAdapter(); void AddPacket(const RtpPacketSendInfo& packet_info, size_t overhead_bytes, Timestamp creation_time); absl::optional ProcessSentPacket( const rtc::SentPacket& sent_packet); absl::optional ProcessTransportFeedback( const RTC::RTCP::FeedbackRtpTransportPacket& feedback, Timestamp feedback_time); std::vector GetTransportFeedbackVector() const; DataSize GetOutstandingData() const; private: void OnTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket& feedback); std::vector GetPacketFeedbackVector( const RTC::RTCP::FeedbackRtpTransportPacket& feedback, Timestamp feedback_time); const bool allow_duplicates_; SendTimeHistory send_time_history_; int64_t current_offset_ms_; int64_t last_timestamp_us_; std::vector last_packet_feedback_vector_; // MS_NOTE: local_net_id_ and remote_net_id_ are not set. uint16_t local_net_id_; uint16_t remote_net_id_; std::vector observers_; }; } // namespace webrtc #endif // MODULES_CONGESTION_CONTROLLER_RTP_TRANSPORT_FEEDBACK_ADAPTER_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/include/module_common_types_public.h ================================================ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_ #define MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_ #include #include namespace webrtc { template inline bool IsNewer(U value, U prev_value) { static_assert(!std::numeric_limits::is_signed, "U must be unsigned"); // kBreakpoint is the half-way mark for the type U. For instance, for a // uint16_t it will be 0x8000, and for a uint32_t, it will be 0x8000000. constexpr U kBreakpoint = (std::numeric_limits::max() >> 1) + 1; // Distinguish between elements that are exactly kBreakpoint apart. // If t1>t2 and |t1-t2| = kBreakpoint: IsNewer(t1,t2)=true, // IsNewer(t2,t1)=false // rather than having IsNewer(t1,t2) = IsNewer(t2,t1) = false. if (value - prev_value == kBreakpoint) { return value > prev_value; } return value != prev_value && static_cast(value - prev_value) < kBreakpoint; } // Utility class to unwrap a number to a larger type. The numbers will never be // unwrapped to a negative value. template class Unwrapper { static_assert(!std::numeric_limits::is_signed, "U must be unsigned"); static_assert(std::numeric_limits::max() <= std::numeric_limits::max(), "U must not be wider than 32 bits"); public: // Get the unwrapped value, but don't update the internal state. int64_t UnwrapWithoutUpdate(U value) const { if (!last_value_) return value; constexpr int64_t kMaxPlusOne = static_cast(std::numeric_limits::max()) + 1; U cropped_last = static_cast(*last_value_); int64_t delta = value - cropped_last; if (IsNewer(value, cropped_last)) { if (delta < 0) delta += kMaxPlusOne; // Wrap forwards. } else if (delta > 0 && (*last_value_ + delta - kMaxPlusOne) >= 0) { // If value is older but delta is positive, this is a backwards // wrap-around. However, don't wrap backwards past 0 (unwrapped). delta -= kMaxPlusOne; } return *last_value_ + delta; } // Only update the internal state to the specified last (unwrapped) value. void UpdateLast(int64_t last_value) { last_value_ = last_value; } // Unwrap the value and update the internal state. int64_t Unwrap(U value) { int64_t unwrapped = UnwrapWithoutUpdate(value); UpdateLast(unwrapped); return unwrapped; } private: absl::optional last_value_; }; using SequenceNumberUnwrapper = Unwrapper; using TimestampUnwrapper = Unwrapper; // NB: Doesn't fulfill strict weak ordering requirements. // Mustn't be used as std::map Compare function. inline bool IsNewerSequenceNumber(uint16_t sequence_number, uint16_t prev_sequence_number) { return IsNewer(sequence_number, prev_sequence_number); } // NB: Doesn't fulfill strict weak ordering requirements. // Mustn't be used as std::map Compare function. inline bool IsNewerTimestamp(uint32_t timestamp, uint32_t prev_timestamp) { return IsNewer(timestamp, prev_timestamp); } inline uint16_t LatestSequenceNumber(uint16_t sequence_number1, uint16_t sequence_number2) { return IsNewerSequenceNumber(sequence_number1, sequence_number2) ? sequence_number1 : sequence_number2; } inline uint32_t LatestTimestamp(uint32_t timestamp1, uint32_t timestamp2) { return IsNewerTimestamp(timestamp1, timestamp2) ? timestamp1 : timestamp2; } } // namespace webrtc #endif // MODULES_INCLUDE_MODULE_COMMON_TYPES_PUBLIC_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/pacing/bitrate_prober.cc ================================================ /* * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::BitrateProber" // #define MS_LOG_DEV_LEVEL 3 #include "modules/pacing/bitrate_prober.h" #include "Logger.hpp" #include #include // TODO: Remove. #define TODO_PRINT_PROBING_STATE() \ switch (probing_state_) \ { \ case ProbingState::kDisabled: \ MS_DUMP("--- probing_state_:kDisabled, clusters_.size():%zu", clusters_.size()); \ break; \ case ProbingState::kInactive: \ MS_DUMP("--- probing_state_:kInactive, clusters_.size():%zu", clusters_.size()); \ break; \ case ProbingState::kActive: \ MS_DUMP("--- probing_state_:kActive, clusters_.size():%zu", clusters_.size()); \ break; \ case ProbingState::kSuspended: \ MS_DUMP("--- probing_state_:kSuspended, clusters_.size():%zu", clusters_.size()); \ break; \ } #undef TODO_PRINT_PROBING_STATE #define TODO_PRINT_PROBING_STATE() {} namespace webrtc { namespace { // The min probe packet size is scaled with the bitrate we're probing at. // This defines the max min probe packet size, meaning that on high bitrates // we have a min probe packet size of 200 bytes. constexpr size_t kMinProbePacketSize = 200; constexpr int64_t kProbeClusterTimeoutMs = 5000; } // namespace BitrateProberConfig::BitrateProberConfig( const WebRtcKeyValueConfig* key_value_config) : min_probe_packets_sent("min_probe_packets_sent", 5), min_probe_delta("min_probe_delta", TimeDelta::ms(1)), min_probe_duration("min_probe_duration", TimeDelta::ms(15)), max_probe_delay("max_probe_delay", TimeDelta::ms(3)) { ParseFieldTrial({&min_probe_packets_sent, &min_probe_delta, &min_probe_duration, &max_probe_delay}, key_value_config->Lookup("WebRTC-Bwe-ProbingConfiguration")); ParseFieldTrial({&min_probe_packets_sent, &min_probe_delta, &min_probe_duration, &max_probe_delay}, key_value_config->Lookup("WebRTC-Bwe-ProbingBehavior")); } BitrateProber::~BitrateProber() { // RTC_HISTOGRAM_COUNTS_1000("WebRTC.BWE.Probing.TotalProbeClustersRequested", // total_probe_count_); // RTC_HISTOGRAM_COUNTS_1000("WebRTC.BWE.Probing.TotalFailedProbeClusters", // total_failed_probe_count_); } BitrateProber::BitrateProber(const WebRtcKeyValueConfig& field_trials) : probing_state_(ProbingState::kDisabled), next_probe_time_ms_(-1), total_probe_count_(0), total_failed_probe_count_(0), config_(&field_trials) { SetEnabled(true); // TODO: Remove. TODO_PRINT_PROBING_STATE(); } void BitrateProber::SetEnabled(bool enable) { if (enable) { if (probing_state_ == ProbingState::kDisabled) { probing_state_ = ProbingState::kInactive; MS_DEBUG_TAG(bwe, "Bandwidth probing enabled, set to inactive"); } } else { probing_state_ = ProbingState::kDisabled; MS_DEBUG_TAG(bwe, "Bandwidth probing disabled"); } // TODO: Remove. TODO_PRINT_PROBING_STATE(); } bool BitrateProber::IsProbing() const { return probing_state_ == ProbingState::kActive; } void BitrateProber::OnIncomingPacket(size_t packet_size) { // Don't initialize probing unless we have something large enough to start // probing. if (probing_state_ == ProbingState::kInactive && !clusters_.empty() && packet_size >= std::min(RecommendedMinProbeSize(), kMinProbePacketSize)) { // Send next probe right away. next_probe_time_ms_ = -1; probing_state_ = ProbingState::kActive; } // TODO: Remove. TODO_PRINT_PROBING_STATE(); } void BitrateProber::CreateProbeCluster(int bitrate_bps, int64_t now_ms, int cluster_id) { // RTC_DCHECK(probing_state_ != ProbingState::kDisabled); // RTC_DCHECK_GT(bitrate_bps, 0); if (probing_state_ == ProbingState::kDisabled) { MS_ERROR("probing disabled"); return; } if (bitrate_bps <= 0) { MS_ERROR("bitrate must be > 0"); return; } total_probe_count_++; while (!clusters_.empty() && now_ms - clusters_.front().time_created_ms > kProbeClusterTimeoutMs) { clusters_.pop(); total_failed_probe_count_++; } ProbeCluster cluster; cluster.time_created_ms = now_ms; cluster.pace_info.probe_cluster_min_probes = config_.min_probe_packets_sent; cluster.pace_info.probe_cluster_min_bytes = static_cast(static_cast(bitrate_bps) * config_.min_probe_duration->ms() / 8000); // RTC_DCHECK_GE(cluster.pace_info.probe_cluster_min_bytes, 0); if (cluster.pace_info.probe_cluster_min_bytes < 0) { MS_ERROR("cluster min bytes must be >= 0"); return; } cluster.pace_info.send_bitrate_bps = bitrate_bps; cluster.pace_info.probe_cluster_id = cluster_id; clusters_.push(cluster); MS_DEBUG_DEV("probe cluster [bitrate:%d, min bytes:%d, min probes:%d]", cluster.pace_info.send_bitrate_bps, cluster.pace_info.probe_cluster_min_bytes, cluster.pace_info.probe_cluster_min_probes); // If we are already probing, continue to do so. Otherwise set it to // kInactive and wait for OnIncomingPacket to start the probing. if (probing_state_ != ProbingState::kActive) probing_state_ = ProbingState::kInactive; // TODO (ibc): We need to send probation even if there is no real packets, so add // this code (taken from `OnIncomingPacket()` above) also here. if (probing_state_ == ProbingState::kInactive && !clusters_.empty()) { // Send next probe right away. next_probe_time_ms_ = -1; probing_state_ = ProbingState::kActive; } // TODO: Remove. TODO_PRINT_PROBING_STATE(); } int BitrateProber::TimeUntilNextProbe(int64_t now_ms) { // TODO: Remove. TODO_PRINT_PROBING_STATE(); // Probing is not active or probing is already complete. if (probing_state_ != ProbingState::kActive || clusters_.empty()) return -1; int time_until_probe_ms = 0; if (next_probe_time_ms_ >= 0) { time_until_probe_ms = next_probe_time_ms_ - now_ms; if (time_until_probe_ms < -config_.max_probe_delay->ms()) { MS_WARN_TAG(bwe, "probe delay too high [next_ms:%" PRIi64 ", now_ms:%" PRIi64 "]", next_probe_time_ms_, now_ms); return -1; } } return std::max(time_until_probe_ms, 0); } absl::optional BitrateProber::CurrentCluster() const { // RTC_DCHECK(!clusters_.empty()); // RTC_DCHECK(probing_state_ == ProbingState::kActive); if (clusters_.empty() || probing_state_ != ProbingState::kActive) { return absl::nullopt; } return clusters_.front().pace_info; } // Probe size is recommended based on the probe bitrate required. We choose // a minimum of twice |kMinProbeDeltaMs| interval to allow scheduling to be // feasible. size_t BitrateProber::RecommendedMinProbeSize() const { // RTC_DCHECK(!clusters_.empty()); if (clusters_.empty()) { return 0; } return clusters_.front().pace_info.send_bitrate_bps * 2 * config_.min_probe_delta->ms() / (8 * 1000); } void BitrateProber::ProbeSent(int64_t now_ms, size_t bytes) { // RTC_DCHECK(probing_state_ == ProbingState::kActive); // RTC_DCHECK_GT(bytes, 0); if (probing_state_ != ProbingState::kActive) { MS_ERROR("probing not active"); return; } if (bytes <= 0) { MS_ERROR("bytes must be > 0"); return; } if (!clusters_.empty()) { ProbeCluster* cluster = &clusters_.front(); if (cluster->sent_probes == 0) { // RTC_DCHECK_EQ(cluster->time_started_ms, -1); if (cluster->time_started_ms != -1) { MS_ERROR("cluster started time must be -1"); return; } cluster->time_started_ms = now_ms; } cluster->sent_bytes += static_cast(bytes); cluster->sent_probes += 1; next_probe_time_ms_ = GetNextProbeTime(*cluster); if (cluster->sent_bytes >= cluster->pace_info.probe_cluster_min_bytes && cluster->sent_probes >= cluster->pace_info.probe_cluster_min_probes) { // RTC_HISTOGRAM_COUNTS_100000("WebRTC.BWE.Probing.ProbeClusterSizeInBytes", // cluster->sent_bytes); // RTC_HISTOGRAM_COUNTS_100("WebRTC.BWE.Probing.ProbesPerCluster", // cluster->sent_probes); // RTC_HISTOGRAM_COUNTS_10000("WebRTC.BWE.Probing.TimePerProbeCluster", // now_ms - cluster->time_started_ms); clusters_.pop(); } if (clusters_.empty()) probing_state_ = ProbingState::kSuspended; // TODO: Remove. TODO_PRINT_PROBING_STATE(); } } int64_t BitrateProber::GetNextProbeTime(const ProbeCluster& cluster) { // RTC_CHECK_GT(cluster.pace_info.send_bitrate_bps, 0); // RTC_CHECK_GE(cluster.time_started_ms, 0); MS_ASSERT(cluster.pace_info.send_bitrate_bps > 0, "cluster.pace_info.send_bitrate_bps must be > 0"); MS_ASSERT(cluster.time_started_ms > 0, "cluster.time_started_ms must be > 0"); // Compute the time delta from the cluster start to ensure probe bitrate stays // close to the target bitrate. Result is in milliseconds. int64_t delta_ms = (8000ll * cluster.sent_bytes + cluster.pace_info.send_bitrate_bps / 2) / cluster.pace_info.send_bitrate_bps; return cluster.time_started_ms + delta_ms; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/pacing/bitrate_prober.h ================================================ /* * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_PACING_BITRATE_PROBER_H_ #define MODULES_PACING_BITRATE_PROBER_H_ #include "api/transport/field_trial_based_config.h" #include "api/transport/network_types.h" #include "rtc_base/experiments/field_trial_parser.h" #include #include #include namespace webrtc { struct BitrateProberConfig { explicit BitrateProberConfig(const WebRtcKeyValueConfig* key_value_config); BitrateProberConfig(const BitrateProberConfig&) = default; BitrateProberConfig& operator=(const BitrateProberConfig&) = default; ~BitrateProberConfig() = default; // The minimum number probing packets used. FieldTrialParameter min_probe_packets_sent; // A minimum interval between probes to allow scheduling to be feasible. FieldTrialParameter min_probe_delta; // The minimum probing duration. FieldTrialParameter min_probe_duration; // Maximum amount of time each probe can be delayed. Probe cluster is reset // and retried from the start when this limit is reached. FieldTrialParameter max_probe_delay; }; // Note that this class isn't thread-safe by itself and therefore relies // on being protected by the caller. class BitrateProber { public: explicit BitrateProber(const WebRtcKeyValueConfig& field_trials); ~BitrateProber(); void SetEnabled(bool enable); // Returns true if the prober is in a probing session, i.e., it currently // wants packets to be sent out according to the time returned by // TimeUntilNextProbe(). bool IsProbing() const; // Initializes a new probing session if the prober is allowed to probe. Does // not initialize the prober unless the packet size is large enough to probe // with. void OnIncomingPacket(size_t packet_size); // Create a cluster used to probe for |bitrate_bps| with |num_probes| number // of probes. void CreateProbeCluster(int bitrate_bps, int64_t now_ms, int cluster_id); // Returns the number of milliseconds until the next probe should be sent to // get accurate probing. int TimeUntilNextProbe(int64_t now_ms); // Information about the current probing cluster. absl::optional CurrentCluster() const; // Returns the minimum number of bytes that the prober recommends for // the next probe. size_t RecommendedMinProbeSize() const; // Called to report to the prober that a probe has been sent. In case of // multiple packets per probe, this call would be made at the end of sending // the last packet in probe. |probe_size| is the total size of all packets // in probe. void ProbeSent(int64_t now_ms, size_t probe_size); private: enum class ProbingState { // Probing will not be triggered in this state at all times. kDisabled, // Probing is enabled and ready to trigger on the first packet arrival. kInactive, // Probe cluster is filled with the set of data rates to be probed and // probes are being sent. kActive, // Probing is enabled, but currently suspended until an explicit trigger // to start probing again. kSuspended, }; // A probe cluster consists of a set of probes. Each probe in turn can be // divided into a number of packets to accommodate the MTU on the network. struct ProbeCluster { PacedPacketInfo pace_info; int sent_probes = 0; int sent_bytes = 0; int64_t time_created_ms = -1; int64_t time_started_ms = -1; int retries = 0; }; int64_t GetNextProbeTime(const ProbeCluster& cluster); ProbingState probing_state_; // Probe bitrate per packet. These are used to compute the delta relative to // the previous probe packet based on the size and time when that packet was // sent. std::queue clusters_; // Time the next probe should be sent when in kActive state. int64_t next_probe_time_ms_; int total_probe_count_; int total_failed_probe_count_; BitrateProberConfig config_; }; } // namespace webrtc #endif // MODULES_PACING_BITRATE_PROBER_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/pacing/interval_budget.cc ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/pacing/interval_budget.h" #include "rtc_base/numerics/safe_conversions.h" #include namespace webrtc { namespace { constexpr int64_t kWindowMs = 500; } IntervalBudget::IntervalBudget(int initial_target_rate_kbps) : IntervalBudget(initial_target_rate_kbps, false) {} IntervalBudget::IntervalBudget(int initial_target_rate_kbps, bool can_build_up_underuse) : bytes_remaining_(0), can_build_up_underuse_(can_build_up_underuse) { set_target_rate_kbps(initial_target_rate_kbps); } void IntervalBudget::set_target_rate_kbps(int target_rate_kbps) { target_rate_kbps_ = target_rate_kbps; max_bytes_in_budget_ = (kWindowMs * target_rate_kbps_) / 8; bytes_remaining_ = std::min(std::max(-max_bytes_in_budget_, bytes_remaining_), max_bytes_in_budget_); } void IntervalBudget::IncreaseBudget(int64_t delta_time_ms) { int64_t bytes = target_rate_kbps_ * delta_time_ms / 8; if (bytes_remaining_ < 0 || can_build_up_underuse_) { // We overused last interval, compensate this interval. bytes_remaining_ = std::min(bytes_remaining_ + bytes, max_bytes_in_budget_); } else { // If we underused last interval we can't use it this interval. bytes_remaining_ = std::min(bytes, max_bytes_in_budget_); } } void IntervalBudget::UseBudget(size_t bytes) { bytes_remaining_ = std::max(bytes_remaining_ - static_cast(bytes), -max_bytes_in_budget_); } size_t IntervalBudget::bytes_remaining() const { return rtc::saturated_cast(std::max(0, bytes_remaining_)); } double IntervalBudget::budget_ratio() const { if (max_bytes_in_budget_ == 0) return 0.0; return static_cast(bytes_remaining_) / max_bytes_in_budget_; } int IntervalBudget::target_rate_kbps() const { return target_rate_kbps_; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/pacing/interval_budget.h ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_PACING_INTERVAL_BUDGET_H_ #define MODULES_PACING_INTERVAL_BUDGET_H_ #include #include namespace webrtc { // TODO(tschumim): Reflector IntervalBudget so that we can set a under- and // over-use budget in ms. class IntervalBudget { public: explicit IntervalBudget(int initial_target_rate_kbps); IntervalBudget(int initial_target_rate_kbps, bool can_build_up_underuse); void set_target_rate_kbps(int target_rate_kbps); // TODO(tschumim): Unify IncreaseBudget and UseBudget to one function. void IncreaseBudget(int64_t delta_time_ms); void UseBudget(size_t bytes); size_t bytes_remaining() const; double budget_ratio() const; int target_rate_kbps() const; private: int target_rate_kbps_; int64_t max_bytes_in_budget_; int64_t bytes_remaining_; bool can_build_up_underuse_; }; } // namespace webrtc #endif // MODULES_PACING_INTERVAL_BUDGET_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/pacing/paced_sender.cc ================================================ /* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::PacedSender" // #define MS_LOG_DEV_LEVEL 3 #include "modules/pacing/paced_sender.h" #include "modules/pacing/bitrate_prober.h" #include "modules/pacing/interval_budget.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "system_wrappers/source/field_trial.h" // webrtc::field_trial. #include "DepLibUV.hpp" #include "Logger.hpp" #include "RTC/RTP/Packet.hpp" #include #include #include namespace webrtc { namespace { // Time limit in milliseconds between packet bursts. const int64_t kDefaultMinPacketLimitMs = 5; const int64_t kCongestedPacketIntervalMs = 500; const int64_t kPausedProcessIntervalMs = kCongestedPacketIntervalMs; const int64_t kMaxElapsedTimeMs = 2000; // Upper cap on process interval, in case process has not been called in a long // time. const int64_t kMaxIntervalTimeMs = 30; } // namespace const float PacedSender::kDefaultPaceMultiplier = 2.5f; PacedSender::PacedSender(PacketRouter* packet_router, const WebRtcKeyValueConfig* field_trials) : packet_router_(packet_router), fallback_field_trials_( !field_trials ? absl::make_unique() : nullptr), field_trials_(field_trials ? field_trials : fallback_field_trials_.get()), min_packet_limit_ms_("", kDefaultMinPacketLimitMs), paused_(false), media_budget_(0), padding_budget_(0), prober_(*field_trials_), probing_send_failure_(false), pacing_bitrate_kbps_(0), time_last_process_us_(DepLibUV::GetTimeUsInt64()), first_sent_packet_ms_(-1), packet_counter_(0), account_for_audio_(false) { ParseFieldTrial({&min_packet_limit_ms_}, webrtc::field_trial::FindFullName("WebRTC-Pacer-MinPacketLimitMs")); UpdateBudgetWithElapsedTime(min_packet_limit_ms_); } void PacedSender::CreateProbeCluster(int bitrate_bps, int cluster_id) { // TODO: REMOVE // MS_DEBUG_DEV("---- bitrate_bps:%d, cluster_id:%d", bitrate_bps, cluster_id); prober_.CreateProbeCluster(bitrate_bps, DepLibUV::GetTimeMsInt64(), cluster_id); } void PacedSender::Pause() { if (!paused_) MS_DEBUG_DEV("paused"); paused_ = true; } void PacedSender::Resume() { if (paused_) MS_DEBUG_DEV("resumed"); paused_ = false; } void PacedSender::SetCongestionWindow(int64_t congestion_window_bytes) { congestion_window_bytes_ = congestion_window_bytes; } void PacedSender::UpdateOutstandingData(int64_t outstanding_bytes) { outstanding_bytes_ = outstanding_bytes; } bool PacedSender::Congested() const { if (congestion_window_bytes_ == kNoCongestionWindow) return false; return outstanding_bytes_ >= congestion_window_bytes_; } void PacedSender::SetProbingEnabled(bool enabled) { // RTC_CHECK_EQ(0, packet_counter_); if (packet_counter_ != 0) { MS_ERROR("packet counter must be 0"); return; } prober_.SetEnabled(enabled); } void PacedSender::SetPacingRates(uint32_t pacing_rate_bps, uint32_t padding_rate_bps) { // RTC_DCHECK(pacing_rate_bps > 0); if (pacing_rate_bps == 0) { MS_ERROR("pacing rate must be > 0"); return; } pacing_bitrate_kbps_ = pacing_rate_bps / 1000; padding_budget_.set_target_rate_kbps(padding_rate_bps / 1000); // TODO: REMOVE // MS_DEBUG_DEV("[pacer_updated pacing_kbps:%" PRIu32 ", padding_budget_kbps:%" PRIu32 "]", // pacing_bitrate_kbps_, // padding_rate_bps / 1000); } void PacedSender::InsertPacket(size_t bytes) { // RTC_DCHECK(pacing_bitrate_kbps_ > 0) // << "SetPacingRate must be called before InsertPacket."; if (pacing_bitrate_kbps_ <= 0) { MS_ERROR("SetPacingRates() must be called before InsertPacket()"); return; } prober_.OnIncomingPacket(bytes); packet_counter_++; // MS_NOTE: Since we don't send media packets within ::Process(), // we use this callback to acknowledge sent packets. OnPacketSent(bytes); } void PacedSender::SetAccountForAudioPackets(bool account_for_audio) { account_for_audio_ = account_for_audio; } int64_t PacedSender::TimeUntilNextProcess() { int64_t elapsed_time_us = DepLibUV::GetTimeUsInt64() - time_last_process_us_; int64_t elapsed_time_ms = (elapsed_time_us + 500) / 1000; // When paused we wake up every 500 ms to send a padding packet to ensure // we won't get stuck in the paused state due to no feedback being received. if (paused_) return std::max(kPausedProcessIntervalMs - elapsed_time_ms, 0); if (prober_.IsProbing()) { int64_t ret = prober_.TimeUntilNextProbe(DepLibUV::GetTimeMsInt64()); if (ret > 0 || (ret == 0 && !probing_send_failure_)) return ret; } return std::max(min_packet_limit_ms_ - elapsed_time_ms, 0); } int64_t PacedSender::UpdateTimeAndGetElapsedMs(int64_t now_us) { int64_t elapsed_time_ms = (now_us - time_last_process_us_ + 500) / 1000; time_last_process_us_ = now_us; if (elapsed_time_ms > kMaxElapsedTimeMs) { MS_WARN_TAG(bwe, "elapsed time (%" PRIi64 " ms) longer than expected," " limiting to %" PRIi64 " ms", elapsed_time_ms, kMaxElapsedTimeMs); elapsed_time_ms = kMaxElapsedTimeMs; } return elapsed_time_ms; } void PacedSender::Process() { int64_t now_us = DepLibUV::GetTimeUsInt64(); int64_t elapsed_time_ms = UpdateTimeAndGetElapsedMs(now_us); if (paused_) return; if (elapsed_time_ms > 0) { int target_bitrate_kbps = pacing_bitrate_kbps_; media_budget_.set_target_rate_kbps(target_bitrate_kbps); UpdateBudgetWithElapsedTime(elapsed_time_ms); } if (!prober_.IsProbing()) return; PacedPacketInfo pacing_info; absl::optional recommended_probe_size; pacing_info = prober_.CurrentCluster().value_or(PacedPacketInfo()); recommended_probe_size = prober_.RecommendedMinProbeSize(); size_t bytes_sent = 0; // MS_NOTE: Let's not use a useless vector. RTC::RTP::Packet* padding_packet{ nullptr }; // Check if we should send padding. while (true) { size_t padding_bytes_to_add = PaddingBytesToAdd(recommended_probe_size, bytes_sent); if (padding_bytes_to_add == 0) break; // TODO: REMOVE // MS_DEBUG_DEV( // "[recommended_probe_size:%zu, padding_bytes_to_add:%zu]", // *recommended_probe_size, padding_bytes_to_add); padding_packet = packet_router_->GeneratePadding(padding_bytes_to_add); // TODO: REMOVE. // MS_DEBUG_DEV("sending padding packet [size:%zu]", padding_packet->GetSize()); packet_router_->SendPacket(padding_packet, pacing_info); bytes_sent += padding_packet->GetLength(); if (recommended_probe_size && bytes_sent > *recommended_probe_size) break; } if (bytes_sent != 0) { auto now = DepLibUV::GetTimeUsInt64(); OnPaddingSent(now, bytes_sent); prober_.ProbeSent((now + 500) / 1000, bytes_sent); } } size_t PacedSender::PaddingBytesToAdd( absl::optional recommended_probe_size, size_t bytes_sent) { // Don't add padding if congested, even if requested for probing. if (Congested()) { return 0; } // MS_NOTE: This does not apply to mediasoup. // We can not send padding unless a normal packet has first been sent. If we // do, timestamps get messed up. // if (packet_counter_ == 0) { // return 0; // } if (recommended_probe_size) { if (*recommended_probe_size > bytes_sent) { return *recommended_probe_size - bytes_sent; } return 0; } return padding_budget_.bytes_remaining(); } void PacedSender::OnPacketSent(size_t size) { if (first_sent_packet_ms_ == -1) first_sent_packet_ms_ = DepLibUV::GetTimeMsInt64(); // Update media bytes sent. UpdateBudgetWithBytesSent(size); } PacedPacketInfo PacedSender::GetPacingInfo() { PacedPacketInfo pacing_info; if (prober_.IsProbing()) { pacing_info = prober_.CurrentCluster().value_or(PacedPacketInfo());; } return pacing_info; } void PacedSender::OnPaddingSent(int64_t now, size_t bytes_sent) { if (bytes_sent > 0) { UpdateBudgetWithBytesSent(bytes_sent); } } void PacedSender::UpdateBudgetWithElapsedTime(int64_t delta_time_ms) { delta_time_ms = std::min(kMaxIntervalTimeMs, delta_time_ms); media_budget_.IncreaseBudget(delta_time_ms); padding_budget_.IncreaseBudget(delta_time_ms); } void PacedSender::UpdateBudgetWithBytesSent(size_t bytes_sent) { outstanding_bytes_ += bytes_sent; media_budget_.UseBudget(bytes_sent); padding_budget_.UseBudget(bytes_sent); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/pacing/paced_sender.h ================================================ /* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_PACING_PACED_SENDER_H_ #define MODULES_PACING_PACED_SENDER_H_ #include "api/transport/field_trial_based_config.h" #include "api/transport/network_types.h" #include "api/transport/webrtc_key_value_config.h" #include "modules/pacing/bitrate_prober.h" #include "modules/pacing/interval_budget.h" #include "modules/pacing/packet_router.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "rtc_base/experiments/field_trial_parser.h" #include #include #include #include #include namespace webrtc { class PacedSender { public: static constexpr int64_t kNoCongestionWindow = -1; // Pacing-rate relative to our target send rate. // Multiplicative factor that is applied to the target bitrate to calculate // the number of bytes that can be transmitted per interval. // Increasing this factor will result in lower delays in cases of bitrate // overshoots from the encoder. static const float kDefaultPaceMultiplier; PacedSender(PacketRouter* packet_router, const WebRtcKeyValueConfig* field_trials = nullptr); virtual ~PacedSender() = default; virtual void CreateProbeCluster(int bitrate_bps, int cluster_id); // Temporarily pause all sending. void Pause(); // Resume sending packets. void Resume(); void SetCongestionWindow(int64_t congestion_window_bytes); void UpdateOutstandingData(int64_t outstanding_bytes); // Enable bitrate probing. Enabled by default, mostly here to simplify // testing. Must be called before any packets are being sent to have an // effect. void SetProbingEnabled(bool enabled); // Sets the pacing rates. Must be called once before packets can be sent. void SetPacingRates(uint32_t pacing_rate_bps, uint32_t padding_rate_bps); // Adds the packet information to the queue and calls TimeToSendPacket // when it's time to send. // MS_NOTE: defined in "modules/rtp_rtcp/include/rtp_packet_sender.h" void InsertPacket(size_t bytes); // Currently audio traffic is not accounted by pacer and passed through. // With the introduction of audio BWE audio traffic will be accounted for // the pacer budget calculation. The audio traffic still will be injected // at high priority. void SetAccountForAudioPackets(bool account_for_audio); // Returns the number of milliseconds until the module want a worker thread // to call Process. int64_t TimeUntilNextProcess(); // Process any pending packets in the queue(s). void Process(); void OnPacketSent(size_t size); PacedPacketInfo GetPacingInfo(); private: int64_t UpdateTimeAndGetElapsedMs(int64_t now_us); // Updates the number of bytes that can be sent for the next time interval. void UpdateBudgetWithElapsedTime(int64_t delta_time_in_ms); void UpdateBudgetWithBytesSent(size_t bytes); size_t PaddingBytesToAdd(absl::optional recommended_probe_size, size_t bytes_sent); void OnPaddingSent(int64_t now_us, size_t bytes_sent); bool Congested() const; PacketRouter* const packet_router_; const std::unique_ptr fallback_field_trials_; const WebRtcKeyValueConfig* field_trials_; FieldTrialParameter min_packet_limit_ms_; bool paused_; // This is the media budget, keeping track of how many bits of media // we can pace out during the current interval. IntervalBudget media_budget_; // This is the padding budget, keeping track of how many bits of padding we're // allowed to send out during the current interval. This budget will be // utilized when there's no media to send. IntervalBudget padding_budget_; BitrateProber prober_; bool probing_send_failure_; uint32_t pacing_bitrate_kbps_; int64_t time_last_process_us_; int64_t first_sent_packet_ms_; uint64_t packet_counter_; int64_t congestion_window_bytes_ = kNoCongestionWindow; int64_t outstanding_bytes_ = 0; bool account_for_audio_; }; } // namespace webrtc #endif // MODULES_PACING_PACED_SENDER_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/pacing/packet_router.h ================================================ /* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_PACING_PACKET_ROUTER_H_ #define MODULES_PACING_PACKET_ROUTER_H_ #include "api/transport/network_types.h" #include "rtc_base/constructor_magic.h" #include "RTC/RTP/Packet.hpp" #include #include namespace webrtc { // PacketRouter keeps track of rtp send modules to support the pacer. // In addition, it handles feedback messages, which are sent on a send // module if possible (sender report), otherwise on receive module // (receiver report). For the latter case, we also keep track of the // receive modules. class PacketRouter { public: PacketRouter() = default; virtual ~PacketRouter() = default; virtual void SendPacket(RTC::RTP::Packet* packet, const PacedPacketInfo& cluster_info) = 0; // MS_NOTE: Changed to return a single RTP::Packet pointer (maybe nullptr). virtual RTC::RTP::Packet* GeneratePadding(size_t target_size_bytes) = 0; }; } // namespace webrtc #endif // MODULES_PACING_PACKET_ROUTER_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc ================================================ /* * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::AimdRateControl" // #define MS_LOG_DEV_LEVEL 3 #include "modules/remote_bitrate_estimator/aimd_rate_control.h" #include "api/transport/network_types.h" #include "api/units/data_rate.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "modules/remote_bitrate_estimator/overuse_detector.h" #include "rtc_base/experiments/field_trial_parser.h" #include "rtc_base/numerics/safe_minmax.h" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include #include #include #include #include namespace webrtc { namespace { constexpr TimeDelta kDefaultRtt = TimeDelta::Millis<200>(); constexpr double kDefaultBackoffFactor = 0.85; constexpr char kBweBackOffFactorExperiment[] = "WebRTC-BweBackOffFactor"; bool IsEnabled(const WebRtcKeyValueConfig& field_trials, absl::string_view key) { return field_trials.Lookup(key).find("Enabled") == 0; } double ReadBackoffFactor(const WebRtcKeyValueConfig& key_value_config) { std::string experiment_string = key_value_config.Lookup(kBweBackOffFactorExperiment); double backoff_factor; int parsed_values = sscanf(experiment_string.c_str(), "Enabled-%lf", &backoff_factor); if (parsed_values == 1) { if (backoff_factor >= 1.0) { MS_WARN_TAG(bwe, "Back-off factor must be less than 1."); } else if (backoff_factor <= 0.0) { MS_WARN_TAG(bwe, "Back-off factor must be greater than 0."); } else { return backoff_factor; } } MS_WARN_TAG(bwe, "Failed to parse parameters for AimdRateControl experiment from field trial string. Using default."); return kDefaultBackoffFactor; } } // namespace AimdRateControl::AimdRateControl(const WebRtcKeyValueConfig* key_value_config) : AimdRateControl(key_value_config, /* send_side =*/false) {} AimdRateControl::AimdRateControl(const WebRtcKeyValueConfig* key_value_config, bool send_side) : min_configured_bitrate_(congestion_controller::GetMinBitrate()), max_configured_bitrate_(DataRate::kbps(30000)), current_bitrate_(max_configured_bitrate_), latest_estimated_throughput_(current_bitrate_), link_capacity_(), rate_control_state_(kRcHold), time_last_bitrate_change_(Timestamp::MinusInfinity()), time_last_bitrate_decrease_(Timestamp::MinusInfinity()), time_first_throughput_estimate_(Timestamp::MinusInfinity()), bitrate_is_initialized_(false), beta_(IsEnabled(*key_value_config, kBweBackOffFactorExperiment) ? ReadBackoffFactor(*key_value_config) : kDefaultBackoffFactor), in_alr_(false), rtt_(kDefaultRtt), send_side_(send_side), in_experiment_(!AdaptiveThresholdExperimentIsDisabled(*key_value_config)), no_bitrate_increase_in_alr_( IsEnabled(*key_value_config, "WebRTC-DontIncreaseDelayBasedBweInAlr")), smoothing_experiment_(false), estimate_bounded_backoff_( IsEnabled(*key_value_config, "WebRTC-Bwe-EstimateBoundedBackoff")), estimate_bounded_increase_( IsEnabled(*key_value_config, "WebRTC-Bwe-EstimateBoundedIncrease")), initial_backoff_interval_("initial_backoff_interval"), low_throughput_threshold_("low_throughput", DataRate::Zero()), capacity_deviation_ratio_threshold_("cap_thr", 0.2), capacity_limit_deviation_factor_("cap_lim", 1) { // E.g // WebRTC-BweAimdRateControlConfig/initial_backoff_interval:100ms, // low_throughput:50kbps/ ParseFieldTrial({&initial_backoff_interval_, &low_throughput_threshold_}, key_value_config->Lookup("WebRTC-BweAimdRateControlConfig")); if (initial_backoff_interval_) { MS_DEBUG_TAG(bwe, "Using aimd rate control with initial back-off interval: %s", ToString(*initial_backoff_interval_).c_str()); } MS_DEBUG_TAG(bwe, "Using aimd rate control with back off factor: %f ", beta_); ParseFieldTrial( {&capacity_deviation_ratio_threshold_, &capacity_limit_deviation_factor_}, key_value_config->Lookup("WebRTC-Bwe-AimdRateControl-NetworkState")); } AimdRateControl::~AimdRateControl() {} void AimdRateControl::SetStartBitrate(DataRate start_bitrate) { current_bitrate_ = start_bitrate; latest_estimated_throughput_ = current_bitrate_; bitrate_is_initialized_ = true; } void AimdRateControl::SetMinBitrate(DataRate min_bitrate) { MS_DEBUG_DEV("[min_bitrate:%" PRIi64 "]", min_bitrate.bps()); min_configured_bitrate_ = min_bitrate; current_bitrate_ = std::max(min_bitrate, current_bitrate_); } bool AimdRateControl::ValidEstimate() const { return bitrate_is_initialized_; } TimeDelta AimdRateControl::GetFeedbackInterval() const { // Estimate how often we can send RTCP if we allocate up to 5% of bandwidth // to feedback. const DataSize kRtcpSize = DataSize::bytes(80); const DataRate rtcp_bitrate = current_bitrate_ * 0.05; const TimeDelta interval = kRtcpSize / rtcp_bitrate; const TimeDelta kMinFeedbackInterval = TimeDelta::ms(200); const TimeDelta kMaxFeedbackInterval = TimeDelta::ms(1000); return interval.Clamped(kMinFeedbackInterval, kMaxFeedbackInterval); } bool AimdRateControl::TimeToReduceFurther(Timestamp at_time, DataRate estimated_throughput) const { const TimeDelta bitrate_reduction_interval = rtt_.Clamped(TimeDelta::ms(10), TimeDelta::ms(200)); if (at_time - time_last_bitrate_change_ >= bitrate_reduction_interval) { return true; } if (ValidEstimate()) { // TODO(terelius/holmer): Investigate consequences of increasing // the threshold to 0.95 * LatestEstimate(). const DataRate threshold = 0.5 * LatestEstimate(); return estimated_throughput < threshold; } return false; } bool AimdRateControl::InitialTimeToReduceFurther(Timestamp at_time) const { if (!initial_backoff_interval_) { return ValidEstimate() && TimeToReduceFurther(at_time, LatestEstimate() / 2 - DataRate::bps(1)); } // TODO(terelius): We could use the RTT (clamped to suitable limits) instead // of a fixed bitrate_reduction_interval. if (time_last_bitrate_decrease_.IsInfinite() || at_time - time_last_bitrate_decrease_ >= *initial_backoff_interval_) { return true; } return false; } DataRate AimdRateControl::LatestEstimate() const { return current_bitrate_; } void AimdRateControl::SetRtt(TimeDelta rtt) { rtt_ = rtt; } DataRate AimdRateControl::Update(const RateControlInput* input, Timestamp at_time) { // RTC_CHECK(input); // Set the initial bit rate value to what we're receiving the first half // second. // TODO(bugs.webrtc.org/9379): The comment above doesn't match to the code. if (!bitrate_is_initialized_) { const TimeDelta kInitializationTime = TimeDelta::seconds(5); // RTC_DCHECK_LE(kBitrateWindowMs, kInitializationTime.ms()); if (time_first_throughput_estimate_.IsInfinite()) { if (input->estimated_throughput) time_first_throughput_estimate_ = at_time; } else if (at_time - time_first_throughput_estimate_ > kInitializationTime && input->estimated_throughput) { current_bitrate_ = *input->estimated_throughput; bitrate_is_initialized_ = true; } } current_bitrate_ = ChangeBitrate(current_bitrate_, *input, at_time); return current_bitrate_; } void AimdRateControl::SetInApplicationLimitedRegion(bool in_alr) { in_alr_ = in_alr; } void AimdRateControl::SetEstimate(DataRate bitrate, Timestamp at_time) { bitrate_is_initialized_ = true; DataRate prev_bitrate = current_bitrate_; current_bitrate_ = ClampBitrate(bitrate, bitrate); time_last_bitrate_change_ = at_time; if (current_bitrate_ < prev_bitrate) { time_last_bitrate_decrease_ = at_time; } } void AimdRateControl::SetNetworkStateEstimate( const absl::optional& estimate) { network_estimate_ = estimate; } double AimdRateControl::GetNearMaxIncreaseRateBpsPerSecond() const { // RTC_DCHECK(!current_bitrate_.IsZero()); const TimeDelta kFrameInterval = TimeDelta::seconds(1) / 30; DataSize frame_size = current_bitrate_ * kFrameInterval; const DataSize kPacketSize = DataSize::bytes(1200); double packets_per_frame = std::ceil(frame_size / kPacketSize); DataSize avg_packet_size = frame_size / packets_per_frame; // Approximate the over-use estimator delay to 100 ms. TimeDelta response_time = rtt_ + TimeDelta::ms(100); if (in_experiment_) response_time = response_time * 2; double increase_rate_bps_per_second = (avg_packet_size / response_time).bps(); double kMinIncreaseRateBpsPerSecond = 4000; return std::max(kMinIncreaseRateBpsPerSecond, increase_rate_bps_per_second); } TimeDelta AimdRateControl::GetExpectedBandwidthPeriod() const { const TimeDelta kMinPeriod = smoothing_experiment_ ? TimeDelta::ms(500) : TimeDelta::seconds(2); const TimeDelta kDefaultPeriod = TimeDelta::seconds(3); const TimeDelta kMaxPeriod = TimeDelta::seconds(50); double increase_rate_bps_per_second = GetNearMaxIncreaseRateBpsPerSecond(); if (!last_decrease_) return smoothing_experiment_ ? kMinPeriod : kDefaultPeriod; double time_to_recover_decrease_seconds = last_decrease_->bps() / increase_rate_bps_per_second; TimeDelta period = TimeDelta::seconds(time_to_recover_decrease_seconds); return period.Clamped(kMinPeriod, kMaxPeriod); } DataRate AimdRateControl::ChangeBitrate(DataRate new_bitrate, const RateControlInput& input, Timestamp at_time) { DataRate estimated_throughput = input.estimated_throughput.value_or(latest_estimated_throughput_); if (input.estimated_throughput) latest_estimated_throughput_ = *input.estimated_throughput; // An over-use should always trigger us to reduce the bitrate, even though // we have not yet established our first estimate. By acting on the over-use, // we will end up with a valid estimate. if (!bitrate_is_initialized_ && input.bw_state != BandwidthUsage::kBwOverusing) return current_bitrate_; ChangeState(input, at_time); switch (rate_control_state_) { case kRcHold: break; case kRcIncrease: if (estimated_throughput > link_capacity_.UpperBound()) link_capacity_.Reset(); // Do not increase the delay based estimate in alr since the estimator // will not be able to get transport feedback necessary to detect if // the new estimate is correct. if (!(send_side_ && in_alr_ && no_bitrate_increase_in_alr_)) { if (link_capacity_.has_estimate()) { // The link_capacity estimate is reset if the measured throughput // is too far from the estimate. We can therefore assume that our // target rate is reasonably close to link capacity and use additive // increase. DataRate additive_increase = AdditiveRateIncrease(at_time, time_last_bitrate_change_); new_bitrate += additive_increase; } else { // If we don't have an estimate of the link capacity, use faster ramp // up to discover the capacity. DataRate multiplicative_increase = MultiplicativeRateIncrease( at_time, time_last_bitrate_change_, new_bitrate); new_bitrate += multiplicative_increase; } } time_last_bitrate_change_ = at_time; break; case kRcDecrease: // TODO(srte): Remove when |estimate_bounded_backoff_| has been validated. if (network_estimate_ && capacity_deviation_ratio_threshold_ && !estimate_bounded_backoff_) { estimated_throughput = std::max(estimated_throughput, network_estimate_->link_capacity_lower); } if (estimated_throughput > low_throughput_threshold_) { // Set bit rate to something slightly lower than the measured throughput // to get rid of any self-induced delay. new_bitrate = estimated_throughput * beta_; if (new_bitrate > current_bitrate_) { // Avoid increasing the rate when over-using. if (link_capacity_.has_estimate()) { new_bitrate = beta_ * link_capacity_.estimate(); } } if (estimate_bounded_backoff_ && network_estimate_) { new_bitrate = std::max( new_bitrate, network_estimate_->link_capacity_lower * beta_); } } else { new_bitrate = estimated_throughput; if (link_capacity_.has_estimate()) { new_bitrate = std::max(new_bitrate, link_capacity_.estimate()); } new_bitrate = std::min(new_bitrate, low_throughput_threshold_.Get()); } new_bitrate = std::min(new_bitrate, current_bitrate_); if (bitrate_is_initialized_ && estimated_throughput < current_bitrate_) { constexpr double kDegradationFactor = 0.9; if (smoothing_experiment_ && new_bitrate < kDegradationFactor * beta_ * current_bitrate_) { // If bitrate decreases more than a normal back off after overuse, it // indicates a real network degradation. We do not let such a decrease // to determine the bandwidth estimation period. last_decrease_ = absl::nullopt; } else { last_decrease_ = current_bitrate_ - new_bitrate; } } if (estimated_throughput < link_capacity_.LowerBound()) { // The current throughput is far from the estimated link capacity. Clear // the estimate to allow an immediate update in OnOveruseDetected. link_capacity_.Reset(); } bitrate_is_initialized_ = true; link_capacity_.OnOveruseDetected(estimated_throughput); // Stay on hold until the pipes are cleared. rate_control_state_ = kRcHold; time_last_bitrate_change_ = at_time; time_last_bitrate_decrease_ = at_time; break; default: MS_THROW_ERROR("unknown rate control state"); } return ClampBitrate(new_bitrate, estimated_throughput); } DataRate AimdRateControl::ClampBitrate(DataRate new_bitrate, DataRate estimated_throughput) const { // Allow the estimate to increase as long as alr is not detected to ensure // that there is no BWE values that can make the estimate stuck at a too // low bitrate. If an encoder can not produce the bitrate necessary to // fully use the capacity, alr will sooner or later trigger. if (!(send_side_ && no_bitrate_increase_in_alr_)) { // Don't change the bit rate if the send side is too far off. // We allow a bit more lag at very low rates to not too easily get stuck if // the encoder produces uneven outputs. const DataRate max_bitrate = 1.5 * estimated_throughput + DataRate::kbps(10); if (new_bitrate > current_bitrate_ && new_bitrate > max_bitrate) { new_bitrate = std::max(current_bitrate_, max_bitrate); } } if (network_estimate_ && (estimate_bounded_increase_ || capacity_limit_deviation_factor_)) { DataRate upper_bound = network_estimate_->link_capacity_upper; new_bitrate = std::min(new_bitrate, upper_bound); } new_bitrate = std::max(new_bitrate, min_configured_bitrate_); return new_bitrate; } DataRate AimdRateControl::MultiplicativeRateIncrease( Timestamp at_time, Timestamp last_time, DataRate current_bitrate) const { double alpha = 1.08; if (last_time.IsFinite()) { auto time_since_last_update = at_time - last_time; alpha = pow(alpha, std::min(time_since_last_update.seconds(), 1.0)); } DataRate multiplicative_increase = std::max(current_bitrate * (alpha - 1.0), DataRate::bps(1000)); return multiplicative_increase; } DataRate AimdRateControl::AdditiveRateIncrease(Timestamp at_time, Timestamp last_time) const { double time_period_seconds = (at_time - last_time).seconds(); double data_rate_increase_bps = GetNearMaxIncreaseRateBpsPerSecond() * time_period_seconds; return DataRate::bps(data_rate_increase_bps); } void AimdRateControl::ChangeState(const RateControlInput& input, Timestamp at_time) { switch (input.bw_state) { case BandwidthUsage::kBwNormal: if (rate_control_state_ == kRcHold) { time_last_bitrate_change_ = at_time; rate_control_state_ = kRcIncrease; } break; case BandwidthUsage::kBwOverusing: if (rate_control_state_ != kRcDecrease) { rate_control_state_ = kRcDecrease; } break; case BandwidthUsage::kBwUnderusing: rate_control_state_ = kRcHold; break; default: MS_THROW_ERROR("unknown input.bw_state"); } } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.h ================================================ /* * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_ #define MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_ #include "api/transport/network_types.h" #include "api/transport/webrtc_key_value_config.h" #include "api/units/data_rate.h" #include "api/units/timestamp.h" #include "modules/congestion_controller/goog_cc/link_capacity_estimator.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "rtc_base/experiments/field_trial_parser.h" #include #include namespace webrtc { // A rate control implementation based on additive increases of // bitrate when no over-use is detected and multiplicative decreases when // over-uses are detected. When we think the available bandwidth has changes or // is unknown, we will switch to a "slow-start mode" where we increase // multiplicatively. class AimdRateControl { public: explicit AimdRateControl(const WebRtcKeyValueConfig* key_value_config); AimdRateControl(const WebRtcKeyValueConfig* key_value_config, bool send_side); ~AimdRateControl(); // Returns true if the target bitrate has been initialized. This happens // either if it has been explicitly set via SetStartBitrate/SetEstimate, or if // we have measured a throughput. bool ValidEstimate() const; void SetStartBitrate(DataRate start_bitrate); void SetMinBitrate(DataRate min_bitrate); TimeDelta GetFeedbackInterval() const; // Returns true if the bitrate estimate hasn't been changed for more than // an RTT, or if the estimated_throughput is less than half of the current // estimate. Should be used to decide if we should reduce the rate further // when over-using. bool TimeToReduceFurther(Timestamp at_time, DataRate estimated_throughput) const; // As above. To be used if overusing before we have measured a throughput. bool InitialTimeToReduceFurther(Timestamp at_time) const; DataRate LatestEstimate() const; void SetRtt(TimeDelta rtt); DataRate Update(const RateControlInput* input, Timestamp at_time); void SetInApplicationLimitedRegion(bool in_alr); void SetEstimate(DataRate bitrate, Timestamp at_time); void SetNetworkStateEstimate( const absl::optional& estimate); // Returns the increase rate when used bandwidth is near the link capacity. double GetNearMaxIncreaseRateBpsPerSecond() const; // Returns the expected time between overuse signals (assuming steady state). TimeDelta GetExpectedBandwidthPeriod() const; private: friend class GoogCcStatePrinter; // Update the target bitrate based on, among other things, the current rate // control state, the current target bitrate and the estimated throughput. // When in the "increase" state the bitrate will be increased either // additively or multiplicatively depending on the rate control region. When // in the "decrease" state the bitrate will be decreased to slightly below the // current throughput. When in the "hold" state the bitrate will be kept // constant to allow built up queues to drain. DataRate ChangeBitrate(DataRate current_bitrate, const RateControlInput& input, Timestamp at_time); // Clamps new_bitrate to within the configured min bitrate and a linear // function of the throughput, so that the new bitrate can't grow too // large compared to the bitrate actually being received by the other end. DataRate ClampBitrate(DataRate new_bitrate, DataRate estimated_throughput) const; DataRate MultiplicativeRateIncrease(Timestamp at_time, Timestamp last_ms, DataRate current_bitrate) const; DataRate AdditiveRateIncrease(Timestamp at_time, Timestamp last_time) const; void UpdateChangePeriod(Timestamp at_time); void ChangeState(const RateControlInput& input, Timestamp at_time); DataRate min_configured_bitrate_; DataRate max_configured_bitrate_; DataRate current_bitrate_; DataRate latest_estimated_throughput_; LinkCapacityEstimator link_capacity_; absl::optional network_estimate_; RateControlState rate_control_state_; Timestamp time_last_bitrate_change_; Timestamp time_last_bitrate_decrease_; Timestamp time_first_throughput_estimate_; bool bitrate_is_initialized_; double beta_; bool in_alr_; TimeDelta rtt_; const bool send_side_; const bool in_experiment_; // Allow the delay based estimate to only increase as long as application // limited region (alr) is not detected. const bool no_bitrate_increase_in_alr_; const bool smoothing_experiment_; // Use estimated link capacity lower bound if it is higher than the // acknowledged rate when backing off due to overuse. const bool estimate_bounded_backoff_; // Use estimated link capacity upper bound as upper limit for increasing // bitrate over the acknowledged rate. const bool estimate_bounded_increase_; absl::optional last_decrease_; FieldTrialOptional initial_backoff_interval_; FieldTrialParameter low_throughput_threshold_; // Deprecated, enable |estimate_bounded_backoff_| instead. FieldTrialOptional capacity_deviation_ratio_threshold_; // Deprecated, enable |estimate_bounded_increase_| instead. FieldTrialOptional capacity_limit_deviation_factor_; }; } // namespace webrtc #endif // MODULES_REMOTE_BITRATE_ESTIMATOR_AIMD_RATE_CONTROL_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/bwe_defines.cc ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/remote_bitrate_estimator/include/bwe_defines.h" namespace webrtc { const char kBweTypeHistogram[] = "WebRTC.BWE.Types"; namespace congestion_controller { int GetMinBitrateBps() { constexpr int kMinBitrateBps = 5000; return kMinBitrateBps; } DataRate GetMinBitrate() { return DataRate::bps(GetMinBitrateBps()); } } // namespace congestion_controller RateControlInput::RateControlInput( BandwidthUsage bw_state, const absl::optional& estimated_throughput) : bw_state(bw_state), estimated_throughput(estimated_throughput) {} RateControlInput::~RateControlInput() = default; } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/include/bwe_defines.h ================================================ /* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_ #define MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_ #include "api/network_state_predictor.h" #include "api/units/data_rate.h" #include #include #define BWE_MAX(a, b) ((a) > (b) ? (a) : (b)) #define BWE_MIN(a, b) ((a) < (b) ? (a) : (b)) namespace webrtc { namespace congestion_controller { int GetMinBitrateBps(); DataRate GetMinBitrate(); } // namespace congestion_controller static const int64_t kBitrateWindowMs = 1000; extern const char kBweTypeHistogram[]; enum BweNames { kReceiverNoExtension = 0, kReceiverTOffset = 1, kReceiverAbsSendTime = 2, kSendSideTransportSeqNum = 3, kBweNamesMax = 4 }; enum RateControlState { kRcHold, kRcIncrease, kRcDecrease }; struct RateControlInput { RateControlInput(BandwidthUsage bw_state, const absl::optional& estimated_throughput); ~RateControlInput(); BandwidthUsage bw_state; absl::optional estimated_throughput; }; } // namespace webrtc #endif // MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_BWE_DEFINES_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h ================================================ /* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ // This class estimates the incoming available bandwidth. #ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_ #define MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_ #include #include #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "RTC/RTP/Packet.hpp" namespace webrtc { namespace rtcp { class TransportFeedback; } // namespace rtcp class RemoteBitrateEstimator; // RemoteBitrateObserver is used to signal changes in bitrate estimates for // the incoming streams. class RemoteBitrateObserver { public: // Called when a receive channel group has a new bitrate estimate for the // incoming streams. virtual void OnRembServerAvailableBitrate( const RemoteBitrateEstimator* remoteBitrateEstimator, const std::vector& ssrcs, uint32_t availableBitrate) = 0; virtual ~RemoteBitrateObserver() {} }; class TransportFeedbackSenderInterface { public: virtual ~TransportFeedbackSenderInterface() = default; virtual bool SendTransportFeedback(rtcp::TransportFeedback* packet) = 0; }; // TODO(holmer): Remove when all implementations have been updated. struct ReceiveBandwidthEstimatorStats {}; class RemoteBitrateEstimator { public: using Listener = RemoteBitrateObserver; public: virtual ~RemoteBitrateEstimator() {} // Called for each incoming packet. Updates the incoming payload bitrate // estimate and the over-use detector. If an over-use is detected the // remote bitrate estimate will be updated. Note that |payload_size| is the // packet size excluding headers. // Note that |arrival_time_ms| can be of an arbitrary time base. virtual void IncomingPacket( int64_t arrival_time_ms, size_t payload_size, const RTC::RTP::Packet& packet, uint32_t send_time_24bits) = 0; // Removes all data for |ssrc|. virtual void RemoveStream(uint32_t ssrc) = 0; // Returns true if a valid estimate exists and sets |bitrate_bps| to the // estimated payload bitrate in bits per second. |ssrcs| is the list of ssrcs // currently being received and of which the bitrate estimate is based upon. virtual bool LatestEstimate(std::vector* ssrcs, uint32_t* bitrate_bps) const = 0; // TODO(holmer): Remove when all implementations have been updated. virtual bool GetStats(ReceiveBandwidthEstimatorStats* output) const; virtual void SetMinBitrate(int min_bitrate_bps) = 0; // MS_NOTE: added method. public: uint32_t GetAvailableBitrate() const; protected: static const int64_t kProcessIntervalMs = 500; static const int64_t kStreamTimeOutMs = 2000; uint32_t available_bitrate = 0; }; inline bool RemoteBitrateEstimator::GetStats( ReceiveBandwidthEstimatorStats* output) const { return false; } inline uint32_t RemoteBitrateEstimator::GetAvailableBitrate() const { return this->available_bitrate; } } // namespace webrtc #endif // MODULES_REMOTE_BITRATE_ESTIMATOR_INCLUDE_REMOTE_BITRATE_ESTIMATOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/inter_arrival.cc ================================================ /* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::InterArrival" // #define MS_LOG_DEV_LEVEL 3 #include "modules/remote_bitrate_estimator/inter_arrival.h" #include "modules/include/module_common_types_public.h" #include "Logger.hpp" namespace webrtc { static const int kBurstDeltaThresholdMs = 5; static const int kMaxBurstDurationMs = 100; InterArrival::InterArrival(uint32_t timestamp_group_length_ticks, double timestamp_to_ms_coeff, bool enable_burst_grouping) : kTimestampGroupLengthTicks(timestamp_group_length_ticks), current_timestamp_group_(), prev_timestamp_group_(), timestamp_to_ms_coeff_(timestamp_to_ms_coeff), burst_grouping_(enable_burst_grouping), num_consecutive_reordered_packets_(0) {} bool InterArrival::ComputeDeltas(uint32_t timestamp, int64_t arrival_time_ms, int64_t system_time_ms, size_t packet_size, uint32_t* timestamp_delta, // send_delta. int64_t* arrival_time_delta_ms, // recv_delta. int* packet_size_delta) { if (timestamp_delta == nullptr) { MS_ERROR("timestamp_delta is null"); return false; } if (arrival_time_delta_ms == nullptr) { MS_ERROR("arrival_time_delta_ms is null"); return false; } if (packet_size_delta == nullptr) { MS_ERROR("packet_size_delta is null"); return false; } // Ignore packets with invalid arrival time. if (arrival_time_ms < 0) { MS_WARN_TAG(bwe, "invalid arrival time %" PRIi64, arrival_time_ms); return false; } bool calculated_deltas = false; if (current_timestamp_group_.IsFirstPacket()) { // We don't have enough data to update the filter, so we store it until we // have two frames of data to process. current_timestamp_group_.timestamp = timestamp; current_timestamp_group_.first_timestamp = timestamp; current_timestamp_group_.first_arrival_ms = arrival_time_ms; } else if (!PacketInOrder(timestamp)) { return false; } else if (NewTimestampGroup(arrival_time_ms, timestamp)) { // First packet of a later frame, the previous frame sample is ready. if (prev_timestamp_group_.complete_time_ms >= 0) { *timestamp_delta = current_timestamp_group_.timestamp - prev_timestamp_group_.timestamp; *arrival_time_delta_ms = current_timestamp_group_.complete_time_ms - prev_timestamp_group_.complete_time_ms; MS_DEBUG_DEV("timestamp previous/current [%" PRIu32 "/%" PRIu32"] complete time previous/current [%" PRIi64 "/%" PRIi64 "]", prev_timestamp_group_.timestamp, current_timestamp_group_.timestamp, prev_timestamp_group_.complete_time_ms, current_timestamp_group_.complete_time_ms); // Check system time differences to see if we have an unproportional jump // in arrival time. In that case reset the inter-arrival computations. int64_t system_time_delta_ms = current_timestamp_group_.last_system_time_ms - prev_timestamp_group_.last_system_time_ms; if (*arrival_time_delta_ms - system_time_delta_ms >= kArrivalTimeOffsetThresholdMs) { MS_WARN_TAG(bwe, "the arrival time clock offset has changed (diff = %" PRIi64 "ms, resetting", *arrival_time_delta_ms - system_time_delta_ms); Reset(); return false; } if (*arrival_time_delta_ms < 0) { // The group of packets has been reordered since receiving its local // arrival timestamp. ++num_consecutive_reordered_packets_; if (num_consecutive_reordered_packets_ >= kReorderedResetThreshold) { MS_WARN_TAG(bwe, "packets are being reordered on the path from the " "socket to the bandwidth estimator. Ignoring this " "packet for bandwidth estimation, resetting"); Reset(); } return false; } else { num_consecutive_reordered_packets_ = 0; } if (*arrival_time_delta_ms < 0) { MS_ERROR("arrival_time_delta_ms is < 0"); return false; } *packet_size_delta = static_cast(current_timestamp_group_.size) - static_cast(prev_timestamp_group_.size); calculated_deltas = true; } prev_timestamp_group_ = current_timestamp_group_; // The new timestamp is now the current frame. current_timestamp_group_.first_timestamp = timestamp; current_timestamp_group_.timestamp = timestamp; current_timestamp_group_.first_arrival_ms = arrival_time_ms; current_timestamp_group_.size = 0; MS_DEBUG_DEV("new timestamp group: first_timestamp:%" PRIu32 ", first_arrival_ms:%" PRIi64, current_timestamp_group_.first_timestamp, current_timestamp_group_.first_arrival_ms); } else { current_timestamp_group_.timestamp = LatestTimestamp(current_timestamp_group_.timestamp, timestamp); } // Accumulate the frame size. current_timestamp_group_.size += packet_size; current_timestamp_group_.complete_time_ms = arrival_time_ms; current_timestamp_group_.last_system_time_ms = system_time_ms; return calculated_deltas; } bool InterArrival::PacketInOrder(uint32_t timestamp) { if (current_timestamp_group_.IsFirstPacket()) { return true; } else { // Assume that a diff which is bigger than half the timestamp interval // (32 bits) must be due to reordering. This code is almost identical to // that in IsNewerTimestamp() in module_common_types.h. uint32_t timestamp_diff = timestamp - current_timestamp_group_.first_timestamp; const static uint32_t int_middle = 0x80000000; if (timestamp_diff == int_middle) { return timestamp > current_timestamp_group_.first_timestamp; } return timestamp_diff < int_middle; } } // Assumes that |timestamp| is not reordered compared to // |current_timestamp_group_|. bool InterArrival::NewTimestampGroup(int64_t arrival_time_ms, uint32_t timestamp) const { if (current_timestamp_group_.IsFirstPacket()) { return false; } else if (BelongsToBurst(arrival_time_ms, timestamp)) { return false; } else { uint32_t timestamp_diff = timestamp - current_timestamp_group_.first_timestamp; return timestamp_diff > kTimestampGroupLengthTicks; } } bool InterArrival::BelongsToBurst(int64_t arrival_time_ms, uint32_t timestamp) const { if (!burst_grouping_) { return false; } if (current_timestamp_group_.complete_time_ms < 0) { MS_ERROR("current_timestamp_group_.complete_time_ms < 0 [current_timestamp_group_.complete_time_ms:%" PRIi64 "]", current_timestamp_group_.complete_time_ms); return false; } int64_t arrival_time_delta_ms = arrival_time_ms - current_timestamp_group_.complete_time_ms; uint32_t timestamp_diff = timestamp - current_timestamp_group_.timestamp; int64_t ts_delta_ms = timestamp_to_ms_coeff_ * timestamp_diff + 0.5; if (ts_delta_ms == 0) return true; int propagation_delta_ms = arrival_time_delta_ms - ts_delta_ms; if (propagation_delta_ms < 0 && arrival_time_delta_ms <= kBurstDeltaThresholdMs && arrival_time_ms - current_timestamp_group_.first_arrival_ms < kMaxBurstDurationMs) return true; return false; } void InterArrival::Reset() { num_consecutive_reordered_packets_ = 0; current_timestamp_group_ = TimestampGroup(); prev_timestamp_group_ = TimestampGroup(); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/inter_arrival.h ================================================ /* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_ #define MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_ #include "rtc_base/constructor_magic.h" #include #include namespace webrtc { // Helper class to compute the inter-arrival time delta and the size delta // between two timestamp groups. A timestamp is a 32 bit unsigned number with // a client defined rate. class InterArrival { public: // After this many packet groups received out of order InterArrival will // reset, assuming that clocks have made a jump. static constexpr int kReorderedResetThreshold = 3; static constexpr int64_t kArrivalTimeOffsetThresholdMs = 3000; // A timestamp group is defined as all packets with a timestamp which are at // most timestamp_group_length_ticks older than the first timestamp in that // group. InterArrival(uint32_t timestamp_group_length_ticks, double timestamp_to_ms_coeff, bool enable_burst_grouping); // This function returns true if a delta was computed, or false if the current // group is still incomplete or if only one group has been completed. // |timestamp| is the timestamp. // |arrival_time_ms| is the local time at which the packet arrived. // |packet_size| is the size of the packet. // |timestamp_delta| (output) is the computed timestamp delta. // |arrival_time_delta_ms| (output) is the computed arrival-time delta. // |packet_size_delta| (output) is the computed size delta. bool ComputeDeltas(uint32_t timestamp, int64_t arrival_time_ms, int64_t system_time_ms, size_t packet_size, uint32_t* timestamp_delta, int64_t* arrival_time_delta_ms, int* packet_size_delta); private: struct TimestampGroup { TimestampGroup() : size(0), first_timestamp(0), timestamp(0), first_arrival_ms(-1), complete_time_ms(-1) {} bool IsFirstPacket() const { return complete_time_ms == -1; } size_t size; uint32_t first_timestamp; uint32_t timestamp; int64_t first_arrival_ms; int64_t complete_time_ms; int64_t last_system_time_ms; }; // Returns true if the packet with timestamp |timestamp| arrived in order. bool PacketInOrder(uint32_t timestamp); // Returns true if the last packet was the end of the current batch and the // packet with |timestamp| is the first of a new batch. bool NewTimestampGroup(int64_t arrival_time_ms, uint32_t timestamp) const; bool BelongsToBurst(int64_t arrival_time_ms, uint32_t timestamp) const; void Reset(); const uint32_t kTimestampGroupLengthTicks; TimestampGroup current_timestamp_group_; TimestampGroup prev_timestamp_group_; double timestamp_to_ms_coeff_; bool burst_grouping_; int num_consecutive_reordered_packets_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(InterArrival); }; } // namespace webrtc #endif // MODULES_REMOTE_BITRATE_ESTIMATOR_INTER_ARRIVAL_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/overuse_detector.cc ================================================ /* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::OveruseDetector" // #define MS_LOG_DEV_LEVEL 3 #include "modules/remote_bitrate_estimator/overuse_detector.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "rtc_base/numerics/safe_minmax.h" #include "Logger.hpp" #include #include #include #include namespace webrtc { const char kAdaptiveThresholdExperiment[] = "WebRTC-AdaptiveBweThreshold"; const char kEnabledPrefix[] = "Enabled"; const size_t kEnabledPrefixLength = sizeof(kEnabledPrefix) - 1; const char kDisabledPrefix[] = "Disabled"; const size_t kDisabledPrefixLength = sizeof(kDisabledPrefix) - 1; const double kMaxAdaptOffsetMs = 15.0; const double kOverUsingTimeThreshold = 10; const int kMaxNumDeltas = 60; bool AdaptiveThresholdExperimentIsDisabled( const WebRtcKeyValueConfig& key_value_config) { std::string experiment_string = key_value_config.Lookup(kAdaptiveThresholdExperiment); const size_t kMinExperimentLength = kDisabledPrefixLength; if (experiment_string.length() < kMinExperimentLength) return false; return experiment_string.substr(0, kDisabledPrefixLength) == kDisabledPrefix; } // Gets thresholds from the experiment name following the format // "WebRTC-AdaptiveBweThreshold/Enabled-0.5,0.002/". bool ReadExperimentConstants(const WebRtcKeyValueConfig& key_value_config, double* k_up, double* k_down) { std::string experiment_string = key_value_config.Lookup(kAdaptiveThresholdExperiment); const size_t kMinExperimentLength = kEnabledPrefixLength + 3; if (experiment_string.length() < kMinExperimentLength || experiment_string.substr(0, kEnabledPrefixLength) != kEnabledPrefix) return false; return sscanf(experiment_string.substr(kEnabledPrefixLength + 1).c_str(), "%lf,%lf", k_up, k_down) == 2; } OveruseDetector::OveruseDetector(const WebRtcKeyValueConfig* key_value_config) // Experiment is on by default, but can be disabled with finch by setting // the field trial string to "WebRTC-AdaptiveBweThreshold/Disabled/". : in_experiment_(!AdaptiveThresholdExperimentIsDisabled(*key_value_config)), k_up_(0.0087), k_down_(0.039), overusing_time_threshold_(100), threshold_(12.5), last_update_ms_(-1), prev_offset_(0.0), time_over_using_(-1), overuse_counter_(0), hypothesis_(BandwidthUsage::kBwNormal) { if (!AdaptiveThresholdExperimentIsDisabled(*key_value_config)) InitializeExperiment(*key_value_config); } OveruseDetector::~OveruseDetector() {} BandwidthUsage OveruseDetector::State() const { return hypothesis_; } BandwidthUsage OveruseDetector::Detect(double offset, double ts_delta, int num_of_deltas, int64_t now_ms) { if (num_of_deltas < 2) { return BandwidthUsage::kBwNormal; } const double T = std::min(num_of_deltas, kMaxNumDeltas) * offset; // BWE_TEST_LOGGING_PLOT(1, "T", now_ms, T); // BWE_TEST_LOGGING_PLOT(1, "threshold", now_ms, threshold_); if (T > threshold_) { if (time_over_using_ == -1) { // Initialize the timer. Assume that we've been // over-using half of the time since the previous // sample. time_over_using_ = ts_delta / 2; } else { // Increment timer time_over_using_ += ts_delta; } overuse_counter_++; if (time_over_using_ > overusing_time_threshold_ && overuse_counter_ > 1) { if (offset >= prev_offset_) { time_over_using_ = 0; overuse_counter_ = 0; MS_DEBUG_DEV("hypothesis_: BandwidthUsage::kBwOverusing"); hypothesis_ = BandwidthUsage::kBwOverusing; } } } else if (T < -threshold_) { time_over_using_ = -1; overuse_counter_ = 0; hypothesis_ = BandwidthUsage::kBwUnderusing; } else { time_over_using_ = -1; overuse_counter_ = 0; hypothesis_ = BandwidthUsage::kBwNormal; } prev_offset_ = offset; UpdateThreshold(T, now_ms); return hypothesis_; } void OveruseDetector::UpdateThreshold(double modified_offset, int64_t now_ms) { if (!in_experiment_) return; if (last_update_ms_ == -1) last_update_ms_ = now_ms; if (fabs(modified_offset) > threshold_ + kMaxAdaptOffsetMs) { // Avoid adapting the threshold to big latency spikes, caused e.g., // by a sudden capacity drop. last_update_ms_ = now_ms; return; } const double k = fabs(modified_offset) < threshold_ ? k_down_ : k_up_; const int64_t kMaxTimeDeltaMs = 100; int64_t time_delta_ms = std::min(now_ms - last_update_ms_, kMaxTimeDeltaMs); threshold_ += k * (fabs(modified_offset) - threshold_) * time_delta_ms; threshold_ = rtc::SafeClamp(threshold_, 6.f, 600.f); last_update_ms_ = now_ms; } void OveruseDetector::InitializeExperiment( const WebRtcKeyValueConfig& key_value_config) { // RTC_DCHECK(in_experiment_); double k_up = 0.0; double k_down = 0.0; overusing_time_threshold_ = kOverUsingTimeThreshold; if (ReadExperimentConstants(key_value_config, &k_up, &k_down)) { k_up_ = k_up; k_down_ = k_down; } } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/overuse_detector.h ================================================ /* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_ #define MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_ #include "api/transport/webrtc_key_value_config.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "rtc_base/constructor_magic.h" #include namespace webrtc { bool AdaptiveThresholdExperimentIsDisabled( const WebRtcKeyValueConfig& key_value_config); class OveruseDetector { public: explicit OveruseDetector(const WebRtcKeyValueConfig* key_value_config); virtual ~OveruseDetector(); // Update the detection state based on the estimated inter-arrival time delta // offset. |timestamp_delta| is the delta between the last timestamp which the // estimated offset is based on and the last timestamp on which the last // offset was based on, representing the time between detector updates. // |num_of_deltas| is the number of deltas the offset estimate is based on. // Returns the state after the detection update. BandwidthUsage Detect(double offset, double timestamp_delta, int num_of_deltas, int64_t now_ms); // Returns the current detector state. BandwidthUsage State() const; private: void UpdateThreshold(double modified_offset, int64_t now_ms); void InitializeExperiment(const WebRtcKeyValueConfig& key_value_config); bool in_experiment_; double k_up_; double k_down_; double overusing_time_threshold_; double threshold_; int64_t last_update_ms_; double prev_offset_; double time_over_using_; int overuse_counter_; BandwidthUsage hypothesis_; RTC_DISALLOW_COPY_AND_ASSIGN(OveruseDetector); }; } // namespace webrtc #endif // MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_DETECTOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.cc ================================================ /* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::OveruseEstimator" // #define MS_LOG_DEV_LEVEL 3 #include "modules/remote_bitrate_estimator/overuse_estimator.h" #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "Logger.hpp" #include #include #include namespace webrtc { enum { kMinFramePeriodHistoryLength = 60 }; enum { kDeltaCounterMax = 1000 }; OveruseEstimator::OveruseEstimator(const OverUseDetectorOptions& options) : options_(options), num_of_deltas_(0), slope_(options_.initial_slope), offset_(options_.initial_offset), prev_offset_(options_.initial_offset), E_(), process_noise_(), avg_noise_(options_.initial_avg_noise), var_noise_(options_.initial_var_noise), ts_delta_hist_() { memcpy(E_, options_.initial_e, sizeof(E_)); memcpy(process_noise_, options_.initial_process_noise, sizeof(process_noise_)); } OveruseEstimator::~OveruseEstimator() { ts_delta_hist_.clear(); } void OveruseEstimator::Update(int64_t t_delta, double ts_delta, int size_delta, BandwidthUsage current_hypothesis, int64_t now_ms) { const double min_frame_period = UpdateMinFramePeriod(ts_delta); const double t_ts_delta = t_delta - ts_delta; double fs_delta = size_delta; ++num_of_deltas_; if (num_of_deltas_ > kDeltaCounterMax) { num_of_deltas_ = kDeltaCounterMax; } // Update the Kalman filter. E_[0][0] += process_noise_[0]; E_[1][1] += process_noise_[1]; if ((current_hypothesis == BandwidthUsage::kBwOverusing && offset_ < prev_offset_) || (current_hypothesis == BandwidthUsage::kBwUnderusing && offset_ > prev_offset_)) { E_[1][1] += 10 * process_noise_[1]; } const double h[2] = {fs_delta, 1.0}; const double Eh[2] = {E_[0][0] * h[0] + E_[0][1] * h[1], E_[1][0] * h[0] + E_[1][1] * h[1]}; const double residual = t_ts_delta - slope_ * h[0] - offset_; const bool in_stable_state = (current_hypothesis == BandwidthUsage::kBwNormal); const double max_residual = 3.0 * sqrt(var_noise_); // We try to filter out very late frames. For instance periodic key // frames doesn't fit the Gaussian model well. if (fabs(residual) < max_residual) { UpdateNoiseEstimate(residual, min_frame_period, in_stable_state); } else { UpdateNoiseEstimate(residual < 0 ? -max_residual : max_residual, min_frame_period, in_stable_state); } const double denom = var_noise_ + h[0] * Eh[0] + h[1] * Eh[1]; const double K[2] = {Eh[0] / denom, Eh[1] / denom}; const double IKh[2][2] = {{1.0 - K[0] * h[0], -K[0] * h[1]}, {-K[1] * h[0], 1.0 - K[1] * h[1]}}; const double e00 = E_[0][0]; const double e01 = E_[0][1]; // Update state. E_[0][0] = e00 * IKh[0][0] + E_[1][0] * IKh[0][1]; E_[0][1] = e01 * IKh[0][0] + E_[1][1] * IKh[0][1]; E_[1][0] = e00 * IKh[1][0] + E_[1][0] * IKh[1][1]; E_[1][1] = e01 * IKh[1][0] + E_[1][1] * IKh[1][1]; // The covariance matrix must be positive semi-definite. bool positive_semi_definite = E_[0][0] + E_[1][1] >= 0 && E_[0][0] * E_[1][1] - E_[0][1] * E_[1][0] >= 0 && E_[0][0] >= 0; if (!positive_semi_definite) { MS_ERROR("the over-use estimator's covariance matrix is no longer semi-definite"); } slope_ = slope_ + K[0] * residual; prev_offset_ = offset_; offset_ = offset_ + K[1] * residual; } double OveruseEstimator::UpdateMinFramePeriod(double ts_delta) { double min_frame_period = ts_delta; if (ts_delta_hist_.size() >= kMinFramePeriodHistoryLength) { ts_delta_hist_.pop_front(); } for (const double old_ts_delta : ts_delta_hist_) { min_frame_period = std::min(old_ts_delta, min_frame_period); } ts_delta_hist_.push_back(ts_delta); return min_frame_period; } void OveruseEstimator::UpdateNoiseEstimate(double residual, double ts_delta, bool stable_state) { if (!stable_state) { return; } // Faster filter during startup to faster adapt to the jitter level // of the network. |alpha| is tuned for 30 frames per second, but is scaled // according to |ts_delta|. double alpha = 0.01; if (num_of_deltas_ > 10 * 30) { alpha = 0.002; } // Only update the noise estimate if we're not over-using. |beta| is a // function of alpha and the time delta since the previous update. const double beta = pow(1 - alpha, ts_delta * 30.0 / 1000.0); avg_noise_ = beta * avg_noise_ + (1 - beta) * residual; var_noise_ = beta * var_noise_ + (1 - beta) * (avg_noise_ - residual) * (avg_noise_ - residual); if (var_noise_ < 1) { var_noise_ = 1; } } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.h ================================================ /* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_ #define MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_ #include "modules/remote_bitrate_estimator/include/bwe_defines.h" #include "rtc_base/constructor_magic.h" #include #include namespace webrtc { // Bandwidth over-use detector options. These are used to drive // experimentation with bandwidth estimation parameters. // TODO(terelius): This is only used in overuse_estimator.cc, and only in the // default constructed state. Can we move the relevant variables into that // class and delete this? struct OverUseDetectorOptions { OverUseDetectorOptions() = default; double initial_slope = 8.0 / 512.0; double initial_offset = 0; double initial_e[2][2] = {{100.0, 0.0}, {0.0, 1e-1}}; double initial_process_noise[2] = {1e-13, 1e-3}; double initial_avg_noise = 0.0; double initial_var_noise = 50.0; }; class OveruseEstimator { public: explicit OveruseEstimator(const OverUseDetectorOptions& options); ~OveruseEstimator(); // Update the estimator with a new sample. The deltas should represent deltas // between timestamp groups as defined by the InterArrival class. // |current_hypothesis| should be the hypothesis of the over-use detector at // this time. void Update(int64_t t_delta, double ts_delta, int size_delta, BandwidthUsage current_hypothesis, int64_t now_ms); // Returns the estimated noise/jitter variance in ms^2. double var_noise() const { return var_noise_; } // Returns the estimated inter-arrival time delta offset in ms. double offset() const { return offset_; } // Returns the number of deltas which the current over-use estimator state is // based on. unsigned int num_of_deltas() const { return num_of_deltas_; } private: double UpdateMinFramePeriod(double ts_delta); void UpdateNoiseEstimate(double residual, double ts_delta, bool stable_state); // Must be first member variable. Cannot be const because we need to be // copyable. OverUseDetectorOptions options_; uint16_t num_of_deltas_; double slope_; double offset_; double prev_offset_; double E_[2][2]; double process_noise_[2]; double avg_noise_; double var_noise_; std::deque ts_delta_hist_; RTC_DISALLOW_COPY_AND_ASSIGN(OveruseEstimator); }; } // namespace webrtc #endif // MODULES_REMOTE_BITRATE_ESTIMATOR_OVERUSE_ESTIMATOR_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc ================================================ /* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::RemoteBitrateEstimatorAbsSendTime" // #define MS_LOG_DEV_LEVEL 3 #include "modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h" #include "api/transport/field_trial_based_config.h" #include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" #include "rtc_base/constructor_magic.h" #include "Logger.hpp" #include "DepLibUV.hpp" #include "RTC/RTP/Packet.hpp" #include #include namespace webrtc { namespace { absl::optional OptionalRateFromOptionalBps( absl::optional bitrate_bps) { if (bitrate_bps) { return DataRate::bps(*bitrate_bps); } else { return absl::nullopt; } } } // namespace enum { // MS_NOTE: kAbsSendTimeFraction taken from RTPHeaderExtension::kAbsSendTimeFraction. kAbsSendTimeFraction = 18, kTimestampGroupLengthMs = 5, kAbsSendTimeInterArrivalUpshift = 8, kInterArrivalShift = kAbsSendTimeFraction + kAbsSendTimeInterArrivalUpshift, kInitialProbingIntervalMs = 2000, kMinClusterSize = 4, kMaxProbePackets = 15, kExpectedNumberOfProbes = 3 }; static const double kTimestampToMs = 1000.0 / static_cast(1 << kInterArrivalShift); template std::vector Keys(const std::map& map) { std::vector keys; keys.reserve(map.size()); for (typename std::map::const_iterator it = map.begin(); it != map.end(); ++it) { keys.push_back(it->first); } return keys; } RemoteBitrateEstimatorAbsSendTime::~RemoteBitrateEstimatorAbsSendTime() = default; bool RemoteBitrateEstimatorAbsSendTime::IsWithinClusterBounds( int send_delta_ms, const Cluster& cluster_aggregate) { if (cluster_aggregate.count == 0) return true; float cluster_mean = cluster_aggregate.send_mean_ms / static_cast(cluster_aggregate.count); return fabs(static_cast(send_delta_ms) - cluster_mean) < 2.5f; } void RemoteBitrateEstimatorAbsSendTime::AddCluster(std::list* clusters, Cluster* cluster) { cluster->send_mean_ms /= static_cast(cluster->count); cluster->recv_mean_ms /= static_cast(cluster->count); cluster->mean_size /= cluster->count; clusters->push_back(*cluster); } RemoteBitrateEstimatorAbsSendTime::RemoteBitrateEstimatorAbsSendTime( RemoteBitrateObserver* observer) : observer_(observer), inter_arrival_(), estimator_(), detector_(&field_trials_), incoming_bitrate_(kBitrateWindowMs, 8000), incoming_bitrate_initialized_(false), total_probes_received_(0), first_packet_time_ms_(-1), last_update_ms_(-1), uma_recorded_(false), remote_rate_(&field_trials_) { MS_DEBUG_TAG(bwe, "RemoteBitrateEstimatorAbsSendTime: Instantiating."); } void RemoteBitrateEstimatorAbsSendTime::ComputeClusters( std::list* clusters) const { Cluster current; int64_t prev_send_time = -1; int64_t prev_recv_time = -1; for (std::list::const_iterator it = probes_.begin(); it != probes_.end(); ++it) { if (prev_send_time >= 0) { int send_delta_ms = it->send_time_ms - prev_send_time; int recv_delta_ms = it->recv_time_ms - prev_recv_time; if (send_delta_ms >= 1 && recv_delta_ms >= 1) { ++current.num_above_min_delta; } if (!IsWithinClusterBounds(send_delta_ms, current)) { if (current.count >= kMinClusterSize && current.send_mean_ms > 0.0f && current.recv_mean_ms > 0.0f) { AddCluster(clusters, ¤t); } current = Cluster(); } current.send_mean_ms += send_delta_ms; current.recv_mean_ms += recv_delta_ms; current.mean_size += it->payload_size; ++current.count; } prev_send_time = it->send_time_ms; prev_recv_time = it->recv_time_ms; } if (current.count >= kMinClusterSize && current.send_mean_ms > 0.0f && current.recv_mean_ms > 0.0f) { AddCluster(clusters, ¤t); } } std::list::const_iterator RemoteBitrateEstimatorAbsSendTime::FindBestProbe( const std::list& clusters) const { int highest_probe_bitrate_bps = 0; std::list::const_iterator best_it = clusters.end(); for (std::list::const_iterator it = clusters.begin(); it != clusters.end(); ++it) { if (it->send_mean_ms == 0 || it->recv_mean_ms == 0) continue; if (it->num_above_min_delta > it->count / 2 && (it->recv_mean_ms - it->send_mean_ms <= 2.0f && it->send_mean_ms - it->recv_mean_ms <= 5.0f)) { int probe_bitrate_bps = std::min(it->GetSendBitrateBps(), it->GetRecvBitrateBps()); if (probe_bitrate_bps > highest_probe_bitrate_bps) { highest_probe_bitrate_bps = probe_bitrate_bps; best_it = it; } } else { // MS_NOTE: avoid 'unused variable' compiler warning. #if MS_LOG_DEV_LEVEL == 3 int send_bitrate_bps = it->mean_size * 8 * 1000 / it->send_mean_ms; int recv_bitrate_bps = it->mean_size * 8 * 1000 / it->recv_mean_ms; MS_DEBUG_DEV( "probe failed, sent at %d bps, received at %d bps [mean " "send delta:%fms, mean recv delta:%fms, num probes:%d]", send_bitrate_bps, recv_bitrate_bps, it->send_mean_ms, it->recv_mean_ms, it->count); #endif break; } } return best_it; } RemoteBitrateEstimatorAbsSendTime::ProbeResult RemoteBitrateEstimatorAbsSendTime::ProcessClusters(int64_t now_ms) { std::list clusters; ComputeClusters(&clusters); if (clusters.empty()) { // If we reach the max number of probe packets and still have no clusters, // we will remove the oldest one. if (probes_.size() >= kMaxProbePackets) probes_.pop_front(); return ProbeResult::kNoUpdate; } std::list::const_iterator best_it = FindBestProbe(clusters); if (best_it != clusters.end()) { int probe_bitrate_bps = std::min(best_it->GetSendBitrateBps(), best_it->GetRecvBitrateBps()); // Make sure that a probe sent on a lower bitrate than our estimate can't // reduce the estimate. if (IsBitrateImproving(probe_bitrate_bps)) { MS_DEBUG_DEV( "probe successful, sent at %d bps, received at %d bps " "mean send delta:%fms, mean recv delta:%f ms, " "num probes:%d", best_it->GetSendBitrateBps(), best_it->GetRecvBitrateBps(), best_it->send_mean_ms, best_it->recv_mean_ms, best_it->count); remote_rate_.SetEstimate(DataRate::bps(probe_bitrate_bps), Timestamp::ms(now_ms)); return ProbeResult::kBitrateUpdated; } } // Not probing and received non-probe packet, or finished with current set // of probes. if (clusters.size() >= kExpectedNumberOfProbes) probes_.clear(); return ProbeResult::kNoUpdate; } bool RemoteBitrateEstimatorAbsSendTime::IsBitrateImproving( int new_bitrate_bps) const { bool initial_probe = !remote_rate_.ValidEstimate() && new_bitrate_bps > 0; bool bitrate_above_estimate = remote_rate_.ValidEstimate() && new_bitrate_bps > remote_rate_.LatestEstimate().bps(); return initial_probe || bitrate_above_estimate; } void RemoteBitrateEstimatorAbsSendTime::IncomingPacket( int64_t arrival_time_ms, size_t payload_size, const RTC::RTP::Packet& packet, const uint32_t send_time_24bits) { MS_TRACE(); IncomingPacketInfo(arrival_time_ms, send_time_24bits, payload_size, packet.GetSsrc()); } void RemoteBitrateEstimatorAbsSendTime::IncomingPacketInfo( int64_t arrival_time_ms, uint32_t send_time_24bits, size_t payload_size, uint32_t ssrc) { // RTC_CHECK(send_time_24bits < (1ul << 24)); if (send_time_24bits >= (1ul << 24)) { MS_ERROR("invalid sendTime24bits value"); return; } if (!uma_recorded_) { uma_recorded_ = true; } // Shift up send time to use the full 32 bits that inter_arrival works with, // so wrapping works properly. uint32_t timestamp = send_time_24bits << kAbsSendTimeInterArrivalUpshift; int64_t send_time_ms = static_cast(timestamp) * kTimestampToMs; int64_t now_ms = DepLibUV::GetTimeMsInt64(); // TODO(holmer): SSRCs are only needed for REMB, should be broken out from // here. // Check if incoming bitrate estimate is valid, and if it needs to be reset. absl::optional incoming_bitrate = incoming_bitrate_.Rate(arrival_time_ms); if (incoming_bitrate) { incoming_bitrate_initialized_ = true; } else if (incoming_bitrate_initialized_) { // Incoming bitrate had a previous valid value, but now not enough data // point are left within the current window. Reset incoming bitrate // estimator so that the window size will only contain new data points. incoming_bitrate_.Reset(); incoming_bitrate_initialized_ = false; } incoming_bitrate_.Update(payload_size, arrival_time_ms); if (first_packet_time_ms_ == -1) first_packet_time_ms_ = now_ms; uint32_t ts_delta = 0; int64_t t_delta = 0; int size_delta = 0; bool update_estimate = false; uint32_t target_bitrate_bps = 0; std::vector ssrcs; { TimeoutStreams(now_ms); ssrcs_[ssrc] = now_ms; // For now only try to detect probes while we don't have a valid estimate. // We currently assume that only packets larger than 200 bytes are paced by // the sender. const size_t kMinProbePacketSize = 200; if (payload_size > kMinProbePacketSize && (!remote_rate_.ValidEstimate() || now_ms - first_packet_time_ms_ < kInitialProbingIntervalMs)) { #if MS_LOG_DEV_LEVEL == 3 // TODO(holmer): Use a map instead to get correct order? if (total_probes_received_ < kMaxProbePackets) { int send_delta_ms = -1; int recv_delta_ms = -1; if (!probes_.empty()) { send_delta_ms = send_time_ms - probes_.back().send_time_ms; recv_delta_ms = arrival_time_ms - probes_.back().recv_time_ms; } MS_DEBUG_DEV( "probe packet received [send time:%" PRId64 "ms, recv " "time:%" PRId64 "ms, send delta:%dms, recv delta:%d ms]", send_time_ms, arrival_time_ms, send_delta_ms, recv_delta_ms); } #endif probes_.push_back(Probe(send_time_ms, arrival_time_ms, payload_size)); ++total_probes_received_; // Make sure that a probe which updated the bitrate immediately has an // effect by calling the OnRembServerAvailableBitrate callback. if (ProcessClusters(now_ms) == ProbeResult::kBitrateUpdated) update_estimate = true; } if (inter_arrival_->ComputeDeltas(timestamp, arrival_time_ms, now_ms, payload_size, &ts_delta, &t_delta, &size_delta)) { double ts_delta_ms = (1000.0 * ts_delta) / (1 << kInterArrivalShift); estimator_->Update(t_delta, ts_delta_ms, size_delta, detector_.State(), arrival_time_ms); detector_.Detect(estimator_->offset(), ts_delta_ms, estimator_->num_of_deltas(), arrival_time_ms); } if (!update_estimate) { // Check if it's time for a periodic update or if we should update because // of an over-use. if (last_update_ms_ == -1 || now_ms - last_update_ms_ > remote_rate_.GetFeedbackInterval().ms()) { update_estimate = true; } else if (detector_.State() == BandwidthUsage::kBwOverusing) { absl::optional incoming_rate = incoming_bitrate_.Rate(arrival_time_ms); if (incoming_rate && remote_rate_.TimeToReduceFurther(Timestamp::ms(now_ms), DataRate::bps(*incoming_rate))) { update_estimate = true; } } } if (update_estimate) { // The first overuse should immediately trigger a new estimate. // We also have to update the estimate immediately if we are overusing // and the target bitrate is too high compared to what we are receiving. const RateControlInput input( detector_.State(), OptionalRateFromOptionalBps(incoming_bitrate_.Rate(arrival_time_ms))); target_bitrate_bps = remote_rate_.Update(&input, Timestamp::ms(now_ms)).bps(); update_estimate = remote_rate_.ValidEstimate(); ssrcs = Keys(ssrcs_); } } if (update_estimate) { last_update_ms_ = now_ms; available_bitrate = target_bitrate_bps; observer_->OnRembServerAvailableBitrate(this, ssrcs, target_bitrate_bps); } } void RemoteBitrateEstimatorAbsSendTime::TimeoutStreams(int64_t now_ms) { for (Ssrcs::iterator it = ssrcs_.begin(); it != ssrcs_.end();) { if ((now_ms - it->second) > kStreamTimeOutMs) { ssrcs_.erase(it++); } else { ++it; } } if (ssrcs_.empty()) { // We can't update the estimate if we don't have any active streams. inter_arrival_.reset( new InterArrival((kTimestampGroupLengthMs << kInterArrivalShift) / 1000, kTimestampToMs, true)); estimator_.reset(new OveruseEstimator(OverUseDetectorOptions())); // We deliberately don't reset the first_packet_time_ms_ here for now since // we only probe for bandwidth in the beginning of a call right now. } } void RemoteBitrateEstimatorAbsSendTime::RemoveStream(uint32_t ssrc) { ssrcs_.erase(ssrc); } bool RemoteBitrateEstimatorAbsSendTime::LatestEstimate( std::vector* ssrcs, uint32_t* bitrate_bps) const { // Currently accessed from both the process thread (see // ModuleRtpRtcpImpl::Process()) and the configuration thread (see // Call::GetStats()). Should in the future only be accessed from a single // thread. if (!remote_rate_.ValidEstimate()) { return false; } *ssrcs = Keys(ssrcs_); if (ssrcs_.empty()) { *bitrate_bps = 0; } else { *bitrate_bps = remote_rate_.LatestEstimate().bps(); } return true; } void RemoteBitrateEstimatorAbsSendTime::SetMinBitrate(int min_bitrate_bps) { // Called from both the configuration thread and the network thread. Shouldn't // be called from the network thread in the future. remote_rate_.SetMinBitrate(DataRate::bps(min_bitrate_bps)); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h ================================================ /* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_ #define MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_ #include "api/transport/field_trial_based_config.h" #include "modules/remote_bitrate_estimator/aimd_rate_control.h" #include "modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h" #include "modules/remote_bitrate_estimator/inter_arrival.h" #include "modules/remote_bitrate_estimator/overuse_detector.h" #include "modules/remote_bitrate_estimator/overuse_estimator.h" #include "rtc_base/constructor_magic.h" #include "rtc_base/rate_statistics.h" #include "RTC/RTP/Packet.hpp" #include #include #include #include #include #include namespace webrtc { struct Probe { Probe(int64_t send_time_ms, int64_t recv_time_ms, size_t payload_size) : send_time_ms(send_time_ms), recv_time_ms(recv_time_ms), payload_size(payload_size) {} int64_t send_time_ms; int64_t recv_time_ms; size_t payload_size; }; struct Cluster { Cluster() : send_mean_ms(0.0f), recv_mean_ms(0.0f), mean_size(0), count(0), num_above_min_delta(0) {} int GetSendBitrateBps() const { assert(send_mean_ms > 0.0f); return mean_size * 8 * 1000 / send_mean_ms; } int GetRecvBitrateBps() const { assert(recv_mean_ms > 0.0f); return mean_size * 8 * 1000 / recv_mean_ms; } float send_mean_ms; float recv_mean_ms; // TODO(holmer): Add some variance metric as well? size_t mean_size; int count; int num_above_min_delta; }; class RemoteBitrateEstimatorAbsSendTime : public RemoteBitrateEstimator { public: RemoteBitrateEstimatorAbsSendTime(RemoteBitrateObserver* observer); ~RemoteBitrateEstimatorAbsSendTime(); void IncomingPacket( int64_t arrivalTimeMs, size_t payloadSize, const RTC::RTP::Packet& packet, uint32_t absSendTime) override; // This class relies on Process() being called periodically (at least once // every other second) for streams to be timed out properly. Therefore it // shouldn't be detached from the ProcessThread except if it's about to be // deleted. // void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override; void RemoveStream(uint32_t ssrc) override; bool LatestEstimate(std::vector* ssrcs, uint32_t* bitrate_bps) const override; void SetMinBitrate(int min_bitrate_bps) override; private: typedef std::map Ssrcs; enum class ProbeResult { kBitrateUpdated, kNoUpdate }; static bool IsWithinClusterBounds(int send_delta_ms, const Cluster& cluster_aggregate); static void AddCluster(std::list* clusters, Cluster* cluster); void IncomingPacketInfo(int64_t arrival_time_ms, uint32_t send_time_24bits, size_t payload_size, uint32_t ssrc); void ComputeClusters(std::list* clusters) const; std::list::const_iterator FindBestProbe( const std::list& clusters) const; // Returns true if a probe which changed the estimate was detected. ProbeResult ProcessClusters(int64_t now_ms); bool IsBitrateImproving(int probe_bitrate_bps) const; void TimeoutStreams(int64_t now_ms); const FieldTrialBasedConfig field_trials_; RemoteBitrateObserver* const observer_; std::unique_ptr inter_arrival_; std::unique_ptr estimator_; OveruseDetector detector_; RateStatistics incoming_bitrate_; bool incoming_bitrate_initialized_; std::vector recent_propagation_delta_ms_; std::vector recent_update_time_ms_; std::list probes_; size_t total_probes_received_; int64_t first_packet_time_ms_; int64_t last_update_ms_; bool uma_recorded_; Ssrcs ssrcs_; AimdRateControl remote_rate_; RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(RemoteBitrateEstimatorAbsSendTime); }; } // namespace webrtc #endif // MODULES_REMOTE_BITRATE_ESTIMATOR_REMOTE_BITRATE_ESTIMATOR_ABS_SEND_TIME_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc ================================================ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include #include namespace webrtc { PacketFeedback::PacketFeedback(int64_t arrival_time_ms, uint16_t sequence_number) : PacketFeedback(-1, arrival_time_ms, kNoSendTime, sequence_number, 0, 0, 0, PacedPacketInfo()) {} PacketFeedback::PacketFeedback(int64_t arrival_time_ms, int64_t send_time_ms, uint16_t sequence_number, size_t payload_size, const PacedPacketInfo& pacing_info) : PacketFeedback(-1, arrival_time_ms, send_time_ms, sequence_number, payload_size, 0, 0, pacing_info) {} PacketFeedback::PacketFeedback(int64_t creation_time_ms, uint16_t sequence_number, size_t payload_size, uint16_t local_net_id, uint16_t remote_net_id, const PacedPacketInfo& pacing_info) : PacketFeedback(creation_time_ms, kNotReceived, kNoSendTime, sequence_number, payload_size, local_net_id, remote_net_id, pacing_info) {} PacketFeedback::PacketFeedback(int64_t creation_time_ms, int64_t arrival_time_ms, int64_t send_time_ms, uint16_t sequence_number, size_t payload_size, uint16_t local_net_id, uint16_t remote_net_id, const PacedPacketInfo& pacing_info) : creation_time_ms(creation_time_ms), arrival_time_ms(arrival_time_ms), send_time_ms(send_time_ms), sequence_number(sequence_number), long_sequence_number(0), payload_size(payload_size), unacknowledged_data(0), local_net_id(local_net_id), remote_net_id(remote_net_id), pacing_info(pacing_info), ssrc(0), rtp_sequence_number(0) {} PacketFeedback::PacketFeedback(const PacketFeedback&) = default; PacketFeedback& PacketFeedback::operator=(const PacketFeedback&) = default; PacketFeedback::~PacketFeedback() = default; bool PacketFeedback::operator==(const PacketFeedback& rhs) const { return arrival_time_ms == rhs.arrival_time_ms && send_time_ms == rhs.send_time_ms && sequence_number == rhs.sequence_number && payload_size == rhs.payload_size && pacing_info == rhs.pacing_info; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h ================================================ /* * Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_DEFINES_H_ #define MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_DEFINES_H_ #include "api/transport/network_types.h" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include #include #include #include namespace webrtc { namespace rtcp { class TransportFeedback; } struct RTCPReportBlock { RTCPReportBlock() : sender_ssrc(0), source_ssrc(0), fraction_lost(0), packets_lost(0), extended_highest_sequence_number(0), jitter(0), last_sender_report_timestamp(0), delay_since_last_sender_report(0) {} RTCPReportBlock(uint32_t sender_ssrc, uint32_t source_ssrc, uint8_t fraction_lost, int32_t packets_lost, uint32_t extended_highest_sequence_number, uint32_t jitter, uint32_t last_sender_report_timestamp, uint32_t delay_since_last_sender_report) : sender_ssrc(sender_ssrc), source_ssrc(source_ssrc), fraction_lost(fraction_lost), packets_lost(packets_lost), extended_highest_sequence_number(extended_highest_sequence_number), jitter(jitter), last_sender_report_timestamp(last_sender_report_timestamp), delay_since_last_sender_report(delay_since_last_sender_report) {} // Fields as described by RFC 3550 6.4.2. uint32_t sender_ssrc; // SSRC of sender of this report. uint32_t source_ssrc; // SSRC of the RTP packet sender. uint8_t fraction_lost; int32_t packets_lost; // 24 bits valid. uint32_t extended_highest_sequence_number; uint32_t jitter; uint32_t last_sender_report_timestamp; uint32_t delay_since_last_sender_report; }; typedef std::list ReportBlockList; class RtcpBandwidthObserver { public: // REMB or TMMBR virtual void OnReceivedEstimatedBitrate(uint32_t bitrate) = 0; virtual void OnReceivedRtcpReceiverReport( const ReportBlockList& report_blocks, int64_t rtt, int64_t now_ms) = 0; virtual ~RtcpBandwidthObserver() {} }; struct PacketFeedback { PacketFeedback(int64_t arrival_time_ms, uint16_t sequence_number); PacketFeedback(int64_t arrival_time_ms, int64_t send_time_ms, uint16_t sequence_number, size_t payload_size, const PacedPacketInfo& pacing_info); PacketFeedback(int64_t creation_time_ms, uint16_t sequence_number, size_t payload_size, uint16_t local_net_id, uint16_t remote_net_id, const PacedPacketInfo& pacing_info); PacketFeedback(int64_t creation_time_ms, int64_t arrival_time_ms, int64_t send_time_ms, uint16_t sequence_number, size_t payload_size, uint16_t local_net_id, uint16_t remote_net_id, const PacedPacketInfo& pacing_info); PacketFeedback(const PacketFeedback&); PacketFeedback& operator=(const PacketFeedback&); ~PacketFeedback(); static constexpr int kNotAProbe = -1; static constexpr int64_t kNotReceived = -1; static constexpr int64_t kNoSendTime = -1; // NOTE! The variable |creation_time_ms| is not used when testing equality. // This is due to |creation_time_ms| only being used by SendTimeHistory // for book-keeping, and is of no interest outside that class. // TODO(philipel): Remove |creation_time_ms| from PacketFeedback when cleaning // up SendTimeHistory. bool operator==(const PacketFeedback& rhs) const; // Time corresponding to when this object was created. int64_t creation_time_ms; // Time corresponding to when the packet was received. Timestamped with the // receiver's clock. For unreceived packet, the sentinel value kNotReceived // is used. int64_t arrival_time_ms; // Time corresponding to when the packet was sent, timestamped with the // sender's clock. int64_t send_time_ms; // Packet identifier, incremented with 1 for every packet generated by the // sender. uint16_t sequence_number; // Session unique packet identifier, incremented with 1 for every packet // generated by the sender. int64_t long_sequence_number; // Size of the packet excluding RTP headers. size_t payload_size; // Size of preceeding packets that are not part of feedback. size_t unacknowledged_data; // The network route ids that this packet is associated with. uint16_t local_net_id; uint16_t remote_net_id; // Pacing information about this packet. PacedPacketInfo pacing_info; // The SSRC and RTP sequence number of the packet this feedback refers to. absl::optional ssrc; uint16_t rtp_sequence_number; }; struct RtpPacketSendInfo { public: RtpPacketSendInfo() = default; uint16_t transport_sequence_number = 0; uint32_t ssrc = 0; uint16_t rtp_sequence_number = 0; // Get rid of this flag when all code paths populate |rtp_sequence_number|. bool has_rtp_sequence_number = false; size_t length = 0; PacedPacketInfo pacing_info; }; class NetworkStateEstimateObserver { public: virtual void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) = 0; virtual ~NetworkStateEstimateObserver() = default; }; class TransportFeedbackObserver { public: TransportFeedbackObserver() {} virtual ~TransportFeedbackObserver() {} virtual void OnAddPacket(const RtpPacketSendInfo& packet_info) = 0; virtual void OnTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket& feedback) = 0; }; class PacketFeedbackObserver { public: virtual ~PacketFeedbackObserver() = default; virtual void OnPacketAdded(uint32_t ssrc, uint16_t seq_num) = 0; virtual void OnPacketFeedbackVector( const std::vector& packet_feedback_vector) = 0; }; // Callback, used to notify an observer whenever the send-side delay is updated. class SendSideDelayObserver { public: virtual ~SendSideDelayObserver() {} virtual void SendSideDelayUpdated(int avg_delay_ms, int max_delay_ms, uint64_t total_delay_ms, uint32_t ssrc) = 0; }; // Callback, used to notify an observer whenever a packet is sent to the // transport. // TODO(asapersson): This class will remove the need for SendSideDelayObserver. // Remove SendSideDelayObserver once possible. class SendPacketObserver { public: virtual ~SendPacketObserver() {} virtual void OnSendPacket(uint16_t packet_id, int64_t capture_time_ms, uint32_t ssrc) = 0; }; // Status returned from TimeToSendPacket() family of callbacks. enum class RtpPacketSendResult { kSuccess, // Packet sent OK. kTransportUnavailable, // Network unavailable, try again later. kPacketNotFound // SSRC/sequence number does not map to an available packet. }; } // namespace webrtc #endif // MODULES_RTP_RTCP_INCLUDE_RTP_RTCP_DEFINES_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/modules/rtp_rtcp/source/rtp_packet/transport_feedback.h ================================================ /* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_ #define MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_ #include namespace webrtc { namespace rtcp { // Convert to multiples of 0.25ms. static constexpr int kDeltaScaleFactor = 250; class ReceivedPacket { public: ReceivedPacket(uint16_t sequence_number, int16_t delta_ticks) : sequence_number_(sequence_number), delta_ticks_(delta_ticks), received_(true) {} explicit ReceivedPacket(uint16_t sequence_number) : sequence_number_(sequence_number), received_(false) {} ReceivedPacket(const ReceivedPacket&) = default; ReceivedPacket& operator=(const ReceivedPacket&) = default; uint16_t sequence_number() const { return sequence_number_; } int16_t delta_ticks() const { return delta_ticks_; } int32_t delta_us() const { return delta_ticks_ * kDeltaScaleFactor; } bool received() const { return received_; } private: uint16_t sequence_number_; int16_t delta_ticks_; bool received_; }; } // namespace rtcp } // namespace webrtc #endif // MODULES_RTP_RTCP_SOURCE_RTCP_PACKET_TRANSPORT_FEEDBACK_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/constructor_magic.h ================================================ /* * Copyright 2004 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_CONSTRUCTOR_MAGIC_H_ #define RTC_BASE_CONSTRUCTOR_MAGIC_H_ // Put this in the declarations for a class to be unassignable. #define RTC_DISALLOW_ASSIGN(TypeName) \ TypeName& operator=(const TypeName&) = delete // A macro to disallow the copy constructor and operator= functions. This should // be used in the declarations for a class. #define RTC_DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&) = delete; \ RTC_DISALLOW_ASSIGN(TypeName) // A macro to disallow all the implicit constructors, namely the default // constructor, copy constructor and operator= functions. // // This should be used in the declarations for a class that wants to prevent // anyone from instantiating it. This is especially useful for classes // containing only static methods. #define RTC_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ TypeName() = delete; \ RTC_DISALLOW_COPY_AND_ASSIGN(TypeName) #endif // RTC_BASE_CONSTRUCTOR_MAGIC_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/alr_experiment.cc ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::AlrExperiment" // #define MS_LOG_DEV_LEVEL 3 #include "rtc_base/experiments/alr_experiment.h" #include "api/transport/field_trial_based_config.h" #include "Logger.hpp" #include #include #include namespace webrtc { const char AlrExperimentSettings::kScreenshareProbingBweExperimentName[] = "WebRTC-ProbingScreenshareBwe"; const char AlrExperimentSettings::kStrictPacingAndProbingExperimentName[] = "WebRTC-StrictPacingAndProbing"; const char kDefaultProbingScreenshareBweSettings[] = "1.0,2875,80,40,-60,3,3000"; bool AlrExperimentSettings::MaxOneFieldTrialEnabled() { return AlrExperimentSettings::MaxOneFieldTrialEnabled( FieldTrialBasedConfig()); } bool AlrExperimentSettings::MaxOneFieldTrialEnabled( const WebRtcKeyValueConfig& key_value_config) { return key_value_config.Lookup(kStrictPacingAndProbingExperimentName) .empty() || key_value_config.Lookup(kScreenshareProbingBweExperimentName).empty(); } absl::optional AlrExperimentSettings::CreateFromFieldTrial(const char* experiment_name) { return AlrExperimentSettings::CreateFromFieldTrial(FieldTrialBasedConfig(), experiment_name); } absl::optional AlrExperimentSettings::CreateFromFieldTrial( const WebRtcKeyValueConfig& key_value_config, const char* experiment_name) { absl::optional ret; std::string group_name = key_value_config.Lookup(experiment_name); const std::string kIgnoredSuffix = "_Dogfood"; std::string::size_type suffix_pos = group_name.rfind(kIgnoredSuffix); if (suffix_pos != std::string::npos && suffix_pos == group_name.length() - kIgnoredSuffix.length()) { group_name.resize(group_name.length() - kIgnoredSuffix.length()); } if (group_name.empty()) { if (experiment_name == kScreenshareProbingBweExperimentName) { // This experiment is now default-on with fixed settings. // TODO(sprang): Remove this kill-switch and clean up experiment code. group_name = kDefaultProbingScreenshareBweSettings; } else { return ret; } } AlrExperimentSettings settings; if (sscanf(group_name.c_str(), "%f,%" PRId64 ",%d,%d,%d,%d,%d", &settings.pacing_factor, &settings.max_paced_queue_time, &settings.alr_bandwidth_usage_percent, &settings.alr_start_budget_level_percent, &settings.alr_stop_budget_level_percent, &settings.group_id, &settings.alr_timeout) == 7) { ret.emplace(settings); MS_DEBUG_TAG(bwe, "Using ALR experiment settings: " "pacing factor: %f" ", max pacer queue length:%" PRIi64 ", ALR bandwidth usage percent: %d" ", ALR start budget level percent: %d" ", ALR end budget level percent: %d" ", ALR experiment group ID: %d" ", ALR timeout: %d", settings.pacing_factor, settings.max_paced_queue_time, settings.alr_bandwidth_usage_percent, settings.alr_start_budget_level_percent, settings.alr_stop_budget_level_percent, settings.group_id, settings.alr_timeout); } else { MS_DEBUG_TAG(bwe, "Failed to parse ALR experiment: %s", experiment_name); } return ret; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/alr_experiment.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_EXPERIMENTS_ALR_EXPERIMENT_H_ #define RTC_BASE_EXPERIMENTS_ALR_EXPERIMENT_H_ #include "api/transport/webrtc_key_value_config.h" #include #include namespace webrtc { struct AlrExperimentSettings { public: float pacing_factor; int64_t max_paced_queue_time; int alr_bandwidth_usage_percent; int alr_start_budget_level_percent; int alr_stop_budget_level_percent; int alr_timeout; // Will be sent to the receive side for stats slicing. // Can be 0..6, because it's sent as a 3 bits value and there's also // reserved value to indicate absence of experiment. int group_id; static const char kScreenshareProbingBweExperimentName[]; static const char kStrictPacingAndProbingExperimentName[]; static absl::optional CreateFromFieldTrial( const char* experiment_name); static absl::optional CreateFromFieldTrial( const WebRtcKeyValueConfig& key_value_config, const char* experiment_name); static bool MaxOneFieldTrialEnabled(); static bool MaxOneFieldTrialEnabled( const WebRtcKeyValueConfig& key_value_config); private: AlrExperimentSettings() = default; }; } // namespace webrtc #endif // RTC_BASE_EXPERIMENTS_ALR_EXPERIMENT_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/field_trial_parser.cc ================================================ /* * Copyright 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #define MS_CLASS "webrtc::FieldTrialParser" // #define MS_LOG_DEV_LEVEL 3 #include "rtc_base/experiments/field_trial_parser.h" #include "Logger.hpp" #include #include #include #include namespace webrtc { namespace { int FindOrEnd(std::string str, size_t start, char delimiter) { size_t pos = str.find(delimiter, start); pos = (pos == std::string::npos) ? str.length() : pos; return static_cast(pos); } } // namespace FieldTrialParameterInterface::FieldTrialParameterInterface(std::string key) : key_(key) {} FieldTrialParameterInterface::~FieldTrialParameterInterface() { // RTC_DCHECK(used_) << "Field trial parameter with key: '" << key_ // << "' never used."; } void ParseFieldTrial( std::initializer_list fields, std::string trial_string) { std::map field_map; FieldTrialParameterInterface* keyless_field = nullptr; for (FieldTrialParameterInterface* field : fields) { field->MarkAsUsed(); if (!field->sub_parameters_.empty()) { for (FieldTrialParameterInterface* sub_field : field->sub_parameters_) { // RTC_DCHECK(!sub_field->key_.empty()); sub_field->MarkAsUsed(); field_map[sub_field->key_] = sub_field; } continue; } if (field->key_.empty()) { // RTC_DCHECK(!keyless_field); keyless_field = field; } else { field_map[field->key_] = field; } } size_t i = 0; while (i < trial_string.length()) { int val_end = FindOrEnd(trial_string, i, ','); int colon_pos = FindOrEnd(trial_string, i, ':'); int key_end = std::min(val_end, colon_pos); int val_begin = key_end + 1; std::string key = trial_string.substr(i, key_end - i); absl::optional opt_value; if (val_end >= val_begin) opt_value = trial_string.substr(val_begin, val_end - val_begin); i = val_end + 1; auto field = field_map.find(key); if (field != field_map.end()) { if (!field->second->Parse(std::move(opt_value))) { MS_WARN_TAG(bwe, "Failed to read field with key: '%s' in trial: \"%s\"", key.c_str(), trial_string.c_str()); } } else if (!opt_value && keyless_field && !key.empty()) { if (!keyless_field->Parse(key)) { MS_WARN_TAG(bwe, "Failed to read empty key field with value: '%s' in trial: \"%s\"", key.c_str(), trial_string.c_str()); } } else { MS_DEBUG_TAG(bwe, "No field with key: '%s' (found in trial: \"%s\")", key.c_str(), trial_string.c_str()); } } for (FieldTrialParameterInterface* field : fields) { field->ParseDone(); } } template <> absl::optional ParseTypedParameter(std::string str) { if (str == "true" || str == "1") { return true; } else if (str == "false" || str == "0") { return false; } return absl::nullopt; } template <> absl::optional ParseTypedParameter(std::string str) { double value; char unit[2]{0, 0}; if (sscanf(str.c_str(), "%lf%1s", &value, unit) >= 1) { if (unit[0] == '%') return value / 100; return value; } else { return absl::nullopt; } } template <> absl::optional ParseTypedParameter(std::string str) { int value; if (sscanf(str.c_str(), "%i", &value) == 1) { return value; } else { return absl::nullopt; } } template <> absl::optional ParseTypedParameter(std::string str) { return std::move(str); } FieldTrialFlag::FieldTrialFlag(std::string key) : FieldTrialFlag(key, false) {} FieldTrialFlag::FieldTrialFlag(std::string key, bool default_value) : FieldTrialParameterInterface(key), value_(default_value) {} bool FieldTrialFlag::Get() const { return value_; } webrtc::FieldTrialFlag::operator bool() const { return value_; } bool FieldTrialFlag::Parse(absl::optional str_value) { // Only set the flag if there is no argument provided. if (str_value) { absl::optional opt_value = ParseTypedParameter(*str_value); if (!opt_value) return false; value_ = *opt_value; } else { value_ = true; } return true; } AbstractFieldTrialEnum::AbstractFieldTrialEnum( std::string key, int default_value, std::map mapping) : FieldTrialParameterInterface(key), value_(default_value), enum_mapping_(mapping) { for (auto& key_val : enum_mapping_) valid_values_.insert(key_val.second); } AbstractFieldTrialEnum::AbstractFieldTrialEnum(const AbstractFieldTrialEnum&) = default; AbstractFieldTrialEnum::~AbstractFieldTrialEnum() = default; bool AbstractFieldTrialEnum::Parse(absl::optional str_value) { if (str_value) { auto it = enum_mapping_.find(*str_value); if (it != enum_mapping_.end()) { value_ = it->second; return true; } absl::optional value = ParseTypedParameter(*str_value); if (value.has_value() && (valid_values_.find(*value) != valid_values_.end())) { value_ = *value; return true; } } return false; } template class FieldTrialParameter; template class FieldTrialParameter; template class FieldTrialParameter; template class FieldTrialParameter; template class FieldTrialConstrained; template class FieldTrialConstrained; template class FieldTrialOptional; template class FieldTrialOptional; template class FieldTrialOptional; template class FieldTrialOptional; } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/field_trial_parser.h ================================================ /* * Copyright 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_ #define RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_ #include #include #include #include #include #include #include // Field trial parser functionality. Provides funcitonality to parse field trial // argument strings in key:value format. Each parameter is described using // key:value, parameters are separated with a ,. Values can't include the comma // character, since there's no quote facility. For most types, white space is // ignored. Parameters are declared with a given type for which an // implementation of ParseTypedParameter should be provided. The // ParseTypedParameter implementation is given whatever is between the : and the // ,. If the key is provided without : a FieldTrialOptional will use nullopt. // Example string: "my_optional,my_int:3,my_string:hello" // For further description of usage and behavior, see the examples in the unit // tests. namespace webrtc { class FieldTrialParameterInterface { public: virtual ~FieldTrialParameterInterface(); std::string key() const { return key_; } protected: // Protected to allow implementations to provide assignment and copy. FieldTrialParameterInterface(const FieldTrialParameterInterface&) = default; FieldTrialParameterInterface& operator=(const FieldTrialParameterInterface&) = default; explicit FieldTrialParameterInterface(std::string key); friend void ParseFieldTrial( std::initializer_list fields, std::string raw_string); void MarkAsUsed() { used_ = true; } virtual bool Parse(absl::optional str_value) = 0; virtual void ParseDone() {} std::vector sub_parameters_; private: std::string key_; bool used_ = false; }; // ParseFieldTrial function parses the given string and fills the given fields // with extracted values if available. void ParseFieldTrial( std::initializer_list fields, std::string raw_string); // Specialize this in code file for custom types. Should return absl::nullopt if // the given string cannot be properly parsed. template absl::optional ParseTypedParameter(std::string); // This class uses the ParseTypedParameter function to implement a parameter // implementation with an enforced default value. template class FieldTrialParameter : public FieldTrialParameterInterface { public: FieldTrialParameter(std::string key, T default_value) : FieldTrialParameterInterface(key), value_(default_value) {} T Get() const { return value_; } operator T() const { return Get(); } const T* operator->() const { return &value_; } void SetForTest(T value) { value_ = value; } protected: bool Parse(absl::optional str_value) override { if (str_value) { absl::optional value = ParseTypedParameter(*str_value); if (value.has_value()) { value_ = value.value(); return true; } } return false; } private: T value_; }; // This class uses the ParseTypedParameter function to implement a parameter // implementation with an enforced default value and a range constraint. Values // outside the configured range will be ignored. template class FieldTrialConstrained : public FieldTrialParameterInterface { public: FieldTrialConstrained(std::string key, T default_value, absl::optional lower_limit, absl::optional upper_limit) : FieldTrialParameterInterface(key), value_(default_value), lower_limit_(lower_limit), upper_limit_(upper_limit) {} T Get() const { return value_; } operator T() const { return Get(); } const T* operator->() const { return &value_; } protected: bool Parse(absl::optional str_value) override { if (str_value) { absl::optional value = ParseTypedParameter(*str_value); if (value && (!lower_limit_ || *value >= *lower_limit_) && (!upper_limit_ || *value <= *upper_limit_)) { value_ = *value; return true; } } return false; } private: T value_; absl::optional lower_limit_; absl::optional upper_limit_; }; class AbstractFieldTrialEnum : public FieldTrialParameterInterface { public: AbstractFieldTrialEnum(std::string key, int default_value, std::map mapping); ~AbstractFieldTrialEnum() override; AbstractFieldTrialEnum(const AbstractFieldTrialEnum&); protected: bool Parse(absl::optional str_value) override; protected: int value_; std::map enum_mapping_; std::set valid_values_; }; // The FieldTrialEnum class can be used to quickly define a parser for a // specific enum. It handles values provided as integers and as strings if a // mapping is provided. template class FieldTrialEnum : public AbstractFieldTrialEnum { public: FieldTrialEnum(std::string key, T default_value, std::map mapping) : AbstractFieldTrialEnum(key, static_cast(default_value), ToIntMap(mapping)) {} T Get() const { return static_cast(value_); } operator T() const { return Get(); } private: static std::map ToIntMap(std::map mapping) { std::map res; for (const auto& it : mapping) res[it.first] = static_cast(it.second); return res; } }; // This class uses the ParseTypedParameter function to implement an optional // parameter implementation that can default to absl::nullopt. template class FieldTrialOptional : public FieldTrialParameterInterface { public: explicit FieldTrialOptional(std::string key) : FieldTrialParameterInterface(key) {} FieldTrialOptional(std::string key, absl::optional default_value) : FieldTrialParameterInterface(key), value_(default_value) {} absl::optional GetOptional() const { return value_; } const T& Value() const { return value_.value(); } const T& operator*() const { return value_.value(); } const T* operator->() const { return &value_.value(); } explicit operator bool() const { return value_.has_value(); } protected: bool Parse(absl::optional str_value) override { if (str_value) { absl::optional value = ParseTypedParameter(*str_value); if (!value.has_value()) return false; value_ = value.value(); } else { value_ = absl::nullopt; } return true; } private: absl::optional value_; }; // Equivalent to a FieldTrialParameter in the case that both key and value // are present. If key is missing, evaluates to false. If key is present, but no // explicit value is provided, the flag evaluates to true. class FieldTrialFlag : public FieldTrialParameterInterface { public: explicit FieldTrialFlag(std::string key); FieldTrialFlag(std::string key, bool default_value); bool Get() const; operator bool() const; protected: bool Parse(absl::optional str_value) override; private: bool value_; }; // Accepts true, false, else parsed with sscanf %i, true if != 0. extern template class FieldTrialParameter; // Interpreted using sscanf %lf. extern template class FieldTrialParameter; // Interpreted using sscanf %i. extern template class FieldTrialParameter; // Using the given value as is. extern template class FieldTrialParameter; extern template class FieldTrialConstrained; extern template class FieldTrialConstrained; extern template class FieldTrialOptional; extern template class FieldTrialOptional; extern template class FieldTrialOptional; extern template class FieldTrialOptional; } // namespace webrtc #endif // RTC_BASE_EXPERIMENTS_FIELD_TRIAL_PARSER_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/field_trial_units.cc ================================================ /* * Copyright 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "rtc_base/experiments/field_trial_units.h" #include #include #include #include // Large enough to fit "seconds", the longest supported unit name. #define RTC_TRIAL_UNIT_LENGTH_STR "7" #define RTC_TRIAL_UNIT_SIZE 8 namespace webrtc { namespace { struct ValueWithUnit { double value; std::string unit; }; absl::optional ParseValueWithUnit(std::string str) { if (str == "inf") { return ValueWithUnit{std::numeric_limits::infinity(), ""}; } else if (str == "-inf") { return ValueWithUnit{-std::numeric_limits::infinity(), ""}; } else { double double_val; char unit_char[RTC_TRIAL_UNIT_SIZE]; unit_char[0] = 0; if (sscanf(str.c_str(), "%lf%" RTC_TRIAL_UNIT_LENGTH_STR "s", &double_val, unit_char) >= 1) { return ValueWithUnit{double_val, unit_char}; } } return absl::nullopt; } } // namespace template <> absl::optional ParseTypedParameter(std::string str) { absl::optional result = ParseValueWithUnit(str); if (result) { if (result->unit.empty() || result->unit == "kbps") { return DataRate::kbps(result->value); } else if (result->unit == "bps") { return DataRate::bps(result->value); } } return absl::nullopt; } template <> absl::optional ParseTypedParameter(std::string str) { absl::optional result = ParseValueWithUnit(str); if (result) { if (result->unit.empty() || result->unit == "bytes") return DataSize::bytes(result->value); } return absl::nullopt; } template <> absl::optional ParseTypedParameter(std::string str) { absl::optional result = ParseValueWithUnit(str); if (result) { if (result->unit == "s" || result->unit == "seconds") { return TimeDelta::seconds(result->value); } else if (result->unit == "us") { return TimeDelta::us(result->value); } else if (result->unit.empty() || result->unit == "ms") { return TimeDelta::ms(result->value); } } return absl::nullopt; } template class FieldTrialParameter; template class FieldTrialParameter; template class FieldTrialParameter; template class FieldTrialConstrained; template class FieldTrialConstrained; template class FieldTrialConstrained; template class FieldTrialOptional; template class FieldTrialOptional; template class FieldTrialOptional; } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/field_trial_units.h ================================================ /* * Copyright 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_EXPERIMENTS_FIELD_TRIAL_UNITS_H_ #define RTC_BASE_EXPERIMENTS_FIELD_TRIAL_UNITS_H_ #include "api/units/data_rate.h" #include "api/units/data_size.h" #include "api/units/time_delta.h" #include "rtc_base/experiments/field_trial_parser.h" namespace webrtc { extern template class FieldTrialParameter; extern template class FieldTrialParameter; extern template class FieldTrialParameter; extern template class FieldTrialConstrained; extern template class FieldTrialConstrained; extern template class FieldTrialConstrained; extern template class FieldTrialOptional; extern template class FieldTrialOptional; extern template class FieldTrialOptional; } // namespace webrtc #endif // RTC_BASE_EXPERIMENTS_FIELD_TRIAL_UNITS_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/rate_control_settings.cc ================================================ /* * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "rtc_base/experiments/rate_control_settings.h" #include "api/transport/field_trial_based_config.h" #include "rtc_base/experiments/field_trial_parser.h" #include "rtc_base/numerics/safe_conversions.h" #include #include #include namespace webrtc { namespace { const int kDefaultAcceptedQueueMs = 250; const int kDefaultMinPushbackTargetBitrateBps = 30000; } // namespace RateControlSettings::RateControlSettings( const WebRtcKeyValueConfig* const key_value_config) : congestion_window_("QueueSize"), congestion_window_pushback_("MinBitrate"), pacing_factor_("pacing_factor"), alr_probing_("alr_probing", false), probe_max_allocation_("probe_max_allocation", true), bitrate_adjuster_("bitrate_adjuster", false), adjuster_use_headroom_("adjuster_use_headroom", false) { ParseFieldTrial({&congestion_window_, &congestion_window_pushback_}, key_value_config->Lookup("WebRTC-CongestionWindow")); ParseFieldTrial( {&pacing_factor_, &alr_probing_, &probe_max_allocation_, &bitrate_adjuster_, &adjuster_use_headroom_}, key_value_config->Lookup("WebRTC-VideoRateControl")); } RateControlSettings::~RateControlSettings() = default; RateControlSettings::RateControlSettings(RateControlSettings&&) = default; RateControlSettings RateControlSettings::ParseFromFieldTrials() { FieldTrialBasedConfig field_trial_config; return RateControlSettings(&field_trial_config); } RateControlSettings RateControlSettings::ParseFromKeyValueConfig( const WebRtcKeyValueConfig* const key_value_config) { FieldTrialBasedConfig field_trial_config; return RateControlSettings(key_value_config ? key_value_config : &field_trial_config); } bool RateControlSettings::UseCongestionWindow() const { return static_cast(congestion_window_); } int64_t RateControlSettings::GetCongestionWindowAdditionalTimeMs() const { return congestion_window_.GetOptional().value_or(kDefaultAcceptedQueueMs); } bool RateControlSettings::UseCongestionWindowPushback() const { return congestion_window_ && congestion_window_pushback_; } uint32_t RateControlSettings::CongestionWindowMinPushbackTargetBitrateBps() const { return congestion_window_pushback_.GetOptional().value_or( kDefaultMinPushbackTargetBitrateBps); } absl::optional RateControlSettings::GetPacingFactor() const { return pacing_factor_.GetOptional(); } bool RateControlSettings::UseAlrProbing() const { return alr_probing_.Get(); } bool RateControlSettings::TriggerProbeOnMaxAllocatedBitrateChange() const { return probe_max_allocation_.Get(); } bool RateControlSettings::UseEncoderBitrateAdjuster() const { return bitrate_adjuster_.Get(); } bool RateControlSettings::BitrateAdjusterCanUseNetworkHeadroom() const { return adjuster_use_headroom_.Get(); } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/experiments/rate_control_settings.h ================================================ /* * Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_EXPERIMENTS_RATE_CONTROL_SETTINGS_H_ #define RTC_BASE_EXPERIMENTS_RATE_CONTROL_SETTINGS_H_ #include "api/transport/webrtc_key_value_config.h" #include "rtc_base/experiments/field_trial_parser.h" // #include "rtc_base/experiments/field_trial_units.h" #include namespace webrtc { class RateControlSettings final { public: ~RateControlSettings(); RateControlSettings(RateControlSettings&&); static RateControlSettings ParseFromFieldTrials(); static RateControlSettings ParseFromKeyValueConfig( const WebRtcKeyValueConfig* const key_value_config); // When CongestionWindowPushback is enabled, the pacer is oblivious to // the congestion window. The relation between outstanding data and // the congestion window affects encoder allocations directly. bool UseCongestionWindow() const; int64_t GetCongestionWindowAdditionalTimeMs() const; bool UseCongestionWindowPushback() const; uint32_t CongestionWindowMinPushbackTargetBitrateBps() const; absl::optional GetPacingFactor() const; bool UseAlrProbing() const; bool TriggerProbeOnMaxAllocatedBitrateChange() const; bool UseEncoderBitrateAdjuster() const; bool BitrateAdjusterCanUseNetworkHeadroom() const; private: explicit RateControlSettings( const WebRtcKeyValueConfig* const key_value_config); double GetSimulcastScreenshareHysteresisFactor() const; FieldTrialOptional congestion_window_; FieldTrialOptional congestion_window_pushback_; FieldTrialOptional pacing_factor_; FieldTrialParameter alr_probing_; FieldTrialParameter probe_max_allocation_; FieldTrialParameter bitrate_adjuster_; FieldTrialParameter adjuster_use_headroom_; }; } // namespace webrtc #endif // RTC_BASE_EXPERIMENTS_RATE_CONTROL_SETTINGS_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/network/sent_packet.cc ================================================ /* * Copyright 2018 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "rtc_base/network/sent_packet.h" namespace rtc { PacketInfo::PacketInfo() = default; PacketInfo::PacketInfo(const PacketInfo& info) = default; PacketInfo::~PacketInfo() = default; SentPacket::SentPacket() = default; SentPacket::SentPacket(int64_t packet_id, int64_t send_time_ms) : packet_id(packet_id), send_time_ms(send_time_ms) {} SentPacket::SentPacket(int64_t packet_id, int64_t send_time_ms, const rtc::PacketInfo& info) : packet_id(packet_id), send_time_ms(send_time_ms), info(info) {} } // namespace rtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/network/sent_packet.h ================================================ /* * Copyright 2018 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_NETWORK_SENT_PACKET_H_ #define RTC_BASE_NETWORK_SENT_PACKET_H_ #include #include #include namespace rtc { enum class PacketType { kUnknown, kData, kIceConnectivityCheck, kIceConnectivityCheckResponse, kStunMessage, kTurnMessage, }; enum class PacketInfoProtocolType { kUnknown, kUdp, kTcp, kSsltcp, kTls, }; struct PacketInfo { PacketInfo(); PacketInfo(const PacketInfo& info); ~PacketInfo(); bool included_in_feedback = false; bool included_in_allocation = false; PacketType packet_type = PacketType::kUnknown; PacketInfoProtocolType protocol = PacketInfoProtocolType::kUnknown; // A unique id assigned by the network manager, and absl::nullopt if not set. absl::optional network_id; size_t packet_size_bytes = 0; size_t turn_overhead_bytes = 0; size_t ip_overhead_bytes = 0; }; struct SentPacket { SentPacket(); SentPacket(int64_t packet_id, int64_t send_time_ms); SentPacket(int64_t packet_id, int64_t send_time_ms, const rtc::PacketInfo& info); int64_t packet_id = -1; int64_t send_time_ms = -1; rtc::PacketInfo info; }; } // namespace rtc #endif // RTC_BASE_NETWORK_SENT_PACKET_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/percentile_filter.h ================================================ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_NUMERICS_PERCENTILE_FILTER_H_ #define RTC_BASE_NUMERICS_PERCENTILE_FILTER_H_ #include #include #include namespace webrtc { // Class to efficiently get the percentile value from a group of observations. // The percentile is the value below which a given percentage of the // observations fall. template class PercentileFilter { public: // Construct filter. |percentile| should be between 0 and 1. explicit PercentileFilter(float percentile); // Insert one observation. The complexity of this operation is logarithmic in // the size of the container. void Insert(const T& value); // Remove one observation or return false if |value| doesn't exist in the // container. The complexity of this operation is logarithmic in the size of // the container. bool Erase(const T& value); // Get the percentile value. The complexity of this operation is constant. T GetPercentileValue() const; // Removes all the stored observations. void Reset(); private: // Update iterator and index to point at target percentile value. void UpdatePercentileIterator(); const float percentile_; std::multiset set_; // Maintain iterator and index of current target percentile value. typename std::multiset::iterator percentile_it_; int64_t percentile_index_; }; template PercentileFilter::PercentileFilter(float percentile) : percentile_(percentile), percentile_it_(set_.begin()), percentile_index_(0) { // RTC_CHECK_GE(percentile, 0.0f); // RTC_CHECK_LE(percentile, 1.0f); } template void PercentileFilter::Insert(const T& value) { // Insert element at the upper bound. set_.insert(value); if (set_.size() == 1u) { // First element inserted - initialize percentile iterator and index. percentile_it_ = set_.begin(); percentile_index_ = 0; } else if (value < *percentile_it_) { // If new element is before us, increment |percentile_index_|. ++percentile_index_; } UpdatePercentileIterator(); } template bool PercentileFilter::Erase(const T& value) { typename std::multiset::const_iterator it = set_.lower_bound(value); // Ignore erase operation if the element is not present in the current set. if (it == set_.end() || *it != value) return false; if (it == percentile_it_) { // If same iterator, update to the following element. Index is not // affected. percentile_it_ = set_.erase(it); } else { set_.erase(it); // If erased element was before us, decrement |percentile_index_|. if (value <= *percentile_it_) --percentile_index_; } UpdatePercentileIterator(); return true; } template void PercentileFilter::UpdatePercentileIterator() { if (set_.empty()) return; const int64_t index = static_cast(percentile_ * (set_.size() - 1)); std::advance(percentile_it_, index - percentile_index_); percentile_index_ = index; } template T PercentileFilter::GetPercentileValue() const { return set_.empty() ? 0 : *percentile_it_; } template void PercentileFilter::Reset() { set_.clear(); percentile_it_ = set_.begin(); percentile_index_ = 0; } } // namespace webrtc #endif // RTC_BASE_NUMERICS_PERCENTILE_FILTER_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/safe_compare.h ================================================ /* * Copyright 2016 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ // This file defines six constexpr functions: // // rtc::SafeEq // == // rtc::SafeNe // != // rtc::SafeLt // < // rtc::SafeLe // <= // rtc::SafeGt // > // rtc::SafeGe // >= // // They each accept two arguments of arbitrary types, and in almost all cases, // they simply call the appropriate comparison operator. However, if both // arguments are integers, they don't compare them using C++'s quirky rules, // but instead adhere to the true mathematical definitions. It is as if the // arguments were first converted to infinite-range signed integers, and then // compared, although of course nothing expensive like that actually takes // place. In practice, for signed/signed and unsigned/unsigned comparisons and // some mixed-signed comparisons with a compile-time constant, the overhead is // zero; in the remaining cases, it is just a few machine instructions (no // branches). #ifndef RTC_BASE_NUMERICS_SAFE_COMPARE_H_ #define RTC_BASE_NUMERICS_SAFE_COMPARE_H_ #include "rtc_base/type_traits.h" #include #include #include #include namespace rtc { namespace safe_cmp_impl { template struct LargerIntImpl : std::false_type {}; template <> struct LargerIntImpl : std::true_type { using type = int16_t; }; template <> struct LargerIntImpl : std::true_type { using type = int32_t; }; template <> struct LargerIntImpl : std::true_type { using type = int64_t; }; // LargerInt::value is true iff there's a signed type that's larger // than T1 (and no larger than the larger of T2 and int*, for performance // reasons); and if there is such a type, LargerInt::type is an alias // for it. template struct LargerInt : LargerIntImpl {}; template constexpr typename std::make_unsigned::type MakeUnsigned(T a) { return static_cast::type>(a); } // Overload for when both T1 and T2 have the same signedness. template ::value == std::is_signed::value>::type* = nullptr> constexpr bool Cmp(T1 a, T2 b) { return Op::Op(a, b); } // Overload for signed - unsigned comparison that can be promoted to a bigger // signed type. template ::value && std::is_unsigned::value && LargerInt::value>::type* = nullptr> constexpr bool Cmp(T1 a, T2 b) { return Op::Op(a, static_cast::type>(b)); } // Overload for unsigned - signed comparison that can be promoted to a bigger // signed type. template ::value && std::is_signed::value && LargerInt::value>::type* = nullptr> constexpr bool Cmp(T1 a, T2 b) { return Op::Op(static_cast::type>(a), b); } // Overload for signed - unsigned comparison that can't be promoted to a bigger // signed type. template ::value && std::is_unsigned::value && !LargerInt::value>::type* = nullptr> constexpr bool Cmp(T1 a, T2 b) { return a < 0 ? Op::Op(-1, 0) : Op::Op(safe_cmp_impl::MakeUnsigned(a), b); } // Overload for unsigned - signed comparison that can't be promoted to a bigger // signed type. template ::value && std::is_signed::value && !LargerInt::value>::type* = nullptr> constexpr bool Cmp(T1 a, T2 b) { return b < 0 ? Op::Op(0, -1) : Op::Op(a, safe_cmp_impl::MakeUnsigned(b)); } #define RTC_SAFECMP_MAKE_OP(name, op) \ struct name { \ template \ static constexpr bool Op(T1 a, T2 b) { \ return a op b; \ } \ }; RTC_SAFECMP_MAKE_OP(EqOp, ==) RTC_SAFECMP_MAKE_OP(NeOp, !=) RTC_SAFECMP_MAKE_OP(LtOp, <) RTC_SAFECMP_MAKE_OP(LeOp, <=) RTC_SAFECMP_MAKE_OP(GtOp, >) RTC_SAFECMP_MAKE_OP(GeOp, >=) #undef RTC_SAFECMP_MAKE_OP } // namespace safe_cmp_impl #define RTC_SAFECMP_MAKE_FUN(name) \ template \ constexpr \ typename std::enable_if::value && IsIntlike::value, \ bool>::type Safe##name(T1 a, T2 b) { \ /* Unary plus here turns enums into real integral types. */ \ return safe_cmp_impl::Cmp(+a, +b); \ } \ template \ constexpr \ typename std::enable_if::value || !IsIntlike::value, \ bool>::type Safe##name(const T1& a, \ const T2& b) { \ return safe_cmp_impl::name##Op::Op(a, b); \ } RTC_SAFECMP_MAKE_FUN(Eq) RTC_SAFECMP_MAKE_FUN(Ne) RTC_SAFECMP_MAKE_FUN(Lt) RTC_SAFECMP_MAKE_FUN(Le) RTC_SAFECMP_MAKE_FUN(Gt) RTC_SAFECMP_MAKE_FUN(Ge) #undef RTC_SAFECMP_MAKE_FUN } // namespace rtc #endif // RTC_BASE_NUMERICS_SAFE_COMPARE_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/safe_conversions.h ================================================ /* * Copyright 2014 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ // Borrowed from Chromium's src/base/numerics/safe_conversions.h. #ifndef RTC_BASE_NUMERICS_SAFE_CONVERSIONS_H_ #define RTC_BASE_NUMERICS_SAFE_CONVERSIONS_H_ #include "rtc_base/numerics/safe_conversions_impl.h" #include namespace rtc { // Convenience function that returns true if the supplied value is in range // for the destination type. template inline bool IsValueInRangeForNumericType(Src value) { return internal::RangeCheck(value) == internal::TYPE_VALID; } // checked_cast<> and dchecked_cast<> are analogous to static_cast<> for // numeric types, except that they [D]CHECK that the specified numeric // conversion will not overflow or underflow. NaN source will always trigger // the [D]CHECK. template inline Dst checked_cast(Src value) { // RTC_CHECK(IsValueInRangeForNumericType(value)); return static_cast(value); } template inline Dst dchecked_cast(Src value) { // RTC_DCHECK(IsValueInRangeForNumericType(value)); return static_cast(value); } // saturated_cast<> is analogous to static_cast<> for numeric types, except // that the specified numeric conversion will saturate rather than overflow or // underflow. NaN assignment to an integral will trigger a RTC_CHECK condition. template inline Dst saturated_cast(Src value) { // Optimization for floating point values, which already saturate. if (std::numeric_limits::is_iec559) return static_cast(value); switch (internal::RangeCheck(value)) { case internal::TYPE_VALID: return static_cast(value); case internal::TYPE_UNDERFLOW: return std::numeric_limits::min(); case internal::TYPE_OVERFLOW: return std::numeric_limits::max(); // Should fail only on attempting to assign NaN to a saturated integer. case internal::TYPE_INVALID: // FATAL(); return std::numeric_limits::max(); } // FATAL(); return static_cast(value); } } // namespace rtc #endif // RTC_BASE_NUMERICS_SAFE_CONVERSIONS_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/safe_conversions_impl.h ================================================ /* * Copyright 2014 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ // Borrowed from Chromium's src/base/numerics/safe_conversions_impl.h. #ifndef RTC_BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_ #define RTC_BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_ #include #include namespace rtc { namespace internal { enum DstSign { DST_UNSIGNED, DST_SIGNED }; enum SrcSign { SRC_UNSIGNED, SRC_SIGNED }; enum DstRange { OVERLAPS_RANGE, CONTAINS_RANGE }; // Helper templates to statically determine if our destination type can contain // all values represented by the source type. template ::is_signed ? DST_SIGNED : DST_UNSIGNED, SrcSign IsSrcSigned = std::numeric_limits::is_signed ? SRC_SIGNED : SRC_UNSIGNED> struct StaticRangeCheck {}; template struct StaticRangeCheck { typedef std::numeric_limits DstLimits; typedef std::numeric_limits SrcLimits; // Compare based on max_exponent, which we must compute for integrals. static const size_t kDstMaxExponent = DstLimits::is_iec559 ? DstLimits::max_exponent : (sizeof(Dst) * 8 - 1); static const size_t kSrcMaxExponent = SrcLimits::is_iec559 ? SrcLimits::max_exponent : (sizeof(Src) * 8 - 1); static const DstRange value = kDstMaxExponent >= kSrcMaxExponent ? CONTAINS_RANGE : OVERLAPS_RANGE; }; template struct StaticRangeCheck { static const DstRange value = sizeof(Dst) >= sizeof(Src) ? CONTAINS_RANGE : OVERLAPS_RANGE; }; template struct StaticRangeCheck { typedef std::numeric_limits DstLimits; typedef std::numeric_limits SrcLimits; // Compare based on max_exponent, which we must compute for integrals. static const size_t kDstMaxExponent = DstLimits::is_iec559 ? DstLimits::max_exponent : (sizeof(Dst) * 8 - 1); static const size_t kSrcMaxExponent = sizeof(Src) * 8; static const DstRange value = kDstMaxExponent >= kSrcMaxExponent ? CONTAINS_RANGE : OVERLAPS_RANGE; }; template struct StaticRangeCheck { static const DstRange value = OVERLAPS_RANGE; }; enum RangeCheckResult { TYPE_VALID = 0, // Value can be represented by the destination type. TYPE_UNDERFLOW = 1, // Value would overflow. TYPE_OVERFLOW = 2, // Value would underflow. TYPE_INVALID = 3 // Source value is invalid (i.e. NaN). }; // This macro creates a RangeCheckResult from an upper and lower bound // check by taking advantage of the fact that only NaN can be out of range in // both directions at once. #define BASE_NUMERIC_RANGE_CHECK_RESULT(is_in_upper_bound, is_in_lower_bound) \ RangeCheckResult(((is_in_upper_bound) ? 0 : TYPE_OVERFLOW) | \ ((is_in_lower_bound) ? 0 : TYPE_UNDERFLOW)) template ::is_signed ? DST_SIGNED : DST_UNSIGNED, SrcSign IsSrcSigned = std::numeric_limits::is_signed ? SRC_SIGNED : SRC_UNSIGNED, DstRange IsSrcRangeContained = StaticRangeCheck::value> struct RangeCheckImpl {}; // The following templates are for ranges that must be verified at runtime. We // split it into checks based on signedness to avoid confusing casts and // compiler warnings on signed an unsigned comparisons. // Dst range always contains the result: nothing to check. template struct RangeCheckImpl { static RangeCheckResult Check(Src value) { return TYPE_VALID; } }; // Signed to signed narrowing. template struct RangeCheckImpl { static RangeCheckResult Check(Src value) { typedef std::numeric_limits DstLimits; return DstLimits::is_iec559 ? BASE_NUMERIC_RANGE_CHECK_RESULT( value <= static_cast(DstLimits::max()), value >= static_cast(DstLimits::max() * -1)) : BASE_NUMERIC_RANGE_CHECK_RESULT( value <= static_cast(DstLimits::max()), value >= static_cast(DstLimits::min())); } }; // Unsigned to unsigned narrowing. template struct RangeCheckImpl { static RangeCheckResult Check(Src value) { typedef std::numeric_limits DstLimits; return BASE_NUMERIC_RANGE_CHECK_RESULT( value <= static_cast(DstLimits::max()), true); } }; // Unsigned to signed. template struct RangeCheckImpl { static RangeCheckResult Check(Src value) { typedef std::numeric_limits DstLimits; return sizeof(Dst) > sizeof(Src) ? TYPE_VALID : BASE_NUMERIC_RANGE_CHECK_RESULT( value <= static_cast(DstLimits::max()), true); } }; // Signed to unsigned. template struct RangeCheckImpl { static RangeCheckResult Check(Src value) { typedef std::numeric_limits DstLimits; typedef std::numeric_limits SrcLimits; // Compare based on max_exponent, which we must compute for integrals. static const size_t kDstMaxExponent = sizeof(Dst) * 8; static const size_t kSrcMaxExponent = SrcLimits::is_iec559 ? SrcLimits::max_exponent : (sizeof(Src) * 8 - 1); return (kDstMaxExponent >= kSrcMaxExponent) ? BASE_NUMERIC_RANGE_CHECK_RESULT(true, value >= static_cast(0)) : BASE_NUMERIC_RANGE_CHECK_RESULT( value <= static_cast(DstLimits::max()), value >= static_cast(0)); } }; template inline RangeCheckResult RangeCheck(Src value) { static_assert(std::numeric_limits::is_specialized, "argument must be numeric"); static_assert(std::numeric_limits::is_specialized, "result must be numeric"); return RangeCheckImpl::Check(value); } } // namespace internal } // namespace rtc #endif // RTC_BASE_NUMERICS_SAFE_CONVERSIONS_IMPL_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/numerics/safe_minmax.h ================================================ /* * Copyright 2017 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ // Minimum and maximum // =================== // // rtc::SafeMin(x, y) // rtc::SafeMax(x, y) // // (These are both constexpr.) // // Accept two arguments of either any two integral or any two floating-point // types, and return the smaller and larger value, respectively, with no // truncation or wrap-around. If only one of the input types is statically // guaranteed to be able to represent the result, the return type is that type; // if either one would do, the result type is the smaller type. (One of these // two cases always applies.) // // * The case with one floating-point and one integral type is not allowed, // because the floating-point type will have greater range, but may not // have sufficient precision to represent the integer value exactly.) // // Clamp (a.k.a. constrain to a given interval) // ============================================ // // rtc::SafeClamp(x, a, b) // // Accepts three arguments of any mix of integral types or any mix of // floating-point types, and returns the value in the closed interval [a, b] // that is closest to x (that is, if x < a it returns a; if x > b it returns b; // and if a <= x <= b it returns x). As for SafeMin() and SafeMax(), there is // no truncation or wrap-around. The result type // // 1. is statically guaranteed to be able to represent the result; // // 2. is no larger than the largest of the three argument types; and // // 3. has the same signedness as the type of the first argument, if this is // possible without violating the First or Second Law. // // There is always at least one type that meets criteria 1 and 2. If more than // one type meets these criteria equally well, the result type is one of the // types that is smallest. Note that unlike SafeMin() and SafeMax(), // SafeClamp() will sometimes pick a return type that isn't the type of any of // its arguments. // // * In this context, a type A is smaller than a type B if it has a smaller // range; that is, if A::max() - A::min() < B::max() - B::min(). For // example, int8_t < int16_t == uint16_t < int32_t, and all integral types // are smaller than all floating-point types.) // // * As for SafeMin and SafeMax, mixing integer and floating-point arguments // is not allowed, because floating-point types have greater range than // integer types, but do not have sufficient precision to represent the // values of most integer types exactly. // // Requesting a specific return type // ================================= // // All three functions allow callers to explicitly specify the return type as a // template parameter, overriding the default return type. E.g. // // rtc::SafeMin(x, y) // returns an int // // If the requested type is statically guaranteed to be able to represent the // result, then everything's fine, and the return type is as requested. But if // the requested type is too small, a static_assert is triggered. #ifndef RTC_BASE_NUMERICS_SAFE_MINMAX_H_ #define RTC_BASE_NUMERICS_SAFE_MINMAX_H_ #include "rtc_base/numerics/safe_compare.h" #include "rtc_base/type_traits.h" #include #include namespace rtc { namespace safe_minmax_impl { // Make the range of a type available via something other than a constexpr // function, to work around MSVC limitations. See // https://blogs.msdn.microsoft.com/vcblog/2015/12/02/partial-support-for-expression-sfinae-in-vs-2015-update-1/ template struct Limits { static constexpr T lowest = std::numeric_limits::lowest(); static constexpr T max = std::numeric_limits::max(); }; template ::value> struct UnderlyingType; template struct UnderlyingType { using type = T; }; template struct UnderlyingType { using type = typename std::underlying_type::type; }; // Given two types T1 and T2, find types that can hold the smallest (in // ::min_t) and the largest (in ::max_t) of the two values. template ::value, bool int2 = IsIntlike::value> struct MType { static_assert(int1 == int2, "You may not mix integral and floating-point arguments"); }; // Specialization for when neither type is integral (and therefore presumably // floating-point). template struct MType { using min_t = typename std::common_type::type; static_assert(std::is_same::value || std::is_same::value, ""); using max_t = typename std::common_type::type; static_assert(std::is_same::value || std::is_same::value, ""); }; // Specialization for when both types are integral. template struct MType { // The type with the lowest minimum value. In case of a tie, the type with // the lowest maximum value. In case that too is a tie, the types have the // same range, and we arbitrarily pick T1. using min_t = typename std::conditional< SafeLt(Limits::lowest, Limits::lowest), T1, typename std::conditional< SafeGt(Limits::lowest, Limits::lowest), T2, typename std::conditional::max, Limits::max), T1, T2>::type>::type>::type; static_assert(std::is_same::value || std::is_same::value, ""); // The type with the highest maximum value. In case of a tie, the types have // the same range (because in C++, integer types with the same maximum also // have the same minimum). static_assert(SafeNe(Limits::max, Limits::max) || SafeEq(Limits::lowest, Limits::lowest), "integer types with the same max should have the same min"); using max_t = typename std:: conditional::max, Limits::max), T1, T2>::type; static_assert(std::is_same::value || std::is_same::value, ""); }; // A dummy type that we pass around at compile time but never actually use. // Declared but not defined. struct DefaultType; // ::type is A, except we fall back to B if A is DefaultType. We static_assert // that the chosen type can hold all values that B can hold. template struct TypeOr { using type = typename std:: conditional::value, B, A>::type; static_assert(SafeLe(Limits::lowest, Limits::lowest) && SafeGe(Limits::max, Limits::max), "The specified type isn't large enough"); static_assert(IsIntlike::value == IsIntlike::value && std::is_floating_point::value == std::is_floating_point::value, "float<->int conversions not allowed"); }; } // namespace safe_minmax_impl template < typename R = safe_minmax_impl::DefaultType, typename T1 = safe_minmax_impl::DefaultType, typename T2 = safe_minmax_impl::DefaultType, typename R2 = typename safe_minmax_impl::TypeOr< R, typename safe_minmax_impl::MType< typename safe_minmax_impl::UnderlyingType::type, typename safe_minmax_impl::UnderlyingType::type>::min_t>::type> constexpr R2 SafeMin(T1 a, T2 b) { static_assert(IsIntlike::value || std::is_floating_point::value, "The first argument must be integral or floating-point"); static_assert(IsIntlike::value || std::is_floating_point::value, "The second argument must be integral or floating-point"); return SafeLt(a, b) ? static_cast(a) : static_cast(b); } template < typename R = safe_minmax_impl::DefaultType, typename T1 = safe_minmax_impl::DefaultType, typename T2 = safe_minmax_impl::DefaultType, typename R2 = typename safe_minmax_impl::TypeOr< R, typename safe_minmax_impl::MType< typename safe_minmax_impl::UnderlyingType::type, typename safe_minmax_impl::UnderlyingType::type>::max_t>::type> constexpr R2 SafeMax(T1 a, T2 b) { static_assert(IsIntlike::value || std::is_floating_point::value, "The first argument must be integral or floating-point"); static_assert(IsIntlike::value || std::is_floating_point::value, "The second argument must be integral or floating-point"); return SafeGt(a, b) ? static_cast(a) : static_cast(b); } namespace safe_minmax_impl { // Given three types T, L, and H, let ::type be a suitable return value for // SafeClamp(T, L, H). See the docs at the top of this file for details. template ::value, bool int2 = IsIntlike::value, bool int3 = IsIntlike::value> struct ClampType { static_assert(int1 == int2 && int1 == int3, "You may not mix integral and floating-point arguments"); }; // Specialization for when all three types are floating-point. template struct ClampType { using type = typename std::common_type::type; }; // Specialization for when all three types are integral. template struct ClampType { private: // Range of the return value. The return type must be able to represent this // full range. static constexpr auto r_min = SafeMax(Limits::lowest, SafeMin(Limits::lowest, Limits::lowest)); static constexpr auto r_max = SafeMin(Limits::max, SafeMax(Limits::max, Limits::max)); // Is the given type an acceptable return type? (That is, can it represent // all possible return values, and is it no larger than the largest of the // input types?) template struct AcceptableType { private: static constexpr bool not_too_large = sizeof(A) <= sizeof(L) || sizeof(A) <= sizeof(H) || sizeof(A) <= sizeof(T); static constexpr bool range_contained = SafeLe(Limits::lowest, r_min) && SafeLe(r_max, Limits::max); public: static constexpr bool value = not_too_large && range_contained; }; using best_signed_type = typename std::conditional< AcceptableType::value, int8_t, typename std::conditional< AcceptableType::value, int16_t, typename std::conditional::value, int32_t, int64_t>::type>::type>::type; using best_unsigned_type = typename std::conditional< AcceptableType::value, uint8_t, typename std::conditional< AcceptableType::value, uint16_t, typename std::conditional::value, uint32_t, uint64_t>::type>::type>::type; public: // Pick the best type, preferring the same signedness as T but falling back // to the other one if necessary. using type = typename std::conditional< std::is_signed::value, typename std::conditional::value, best_signed_type, best_unsigned_type>::type, typename std::conditional::value, best_unsigned_type, best_signed_type>::type>::type; static_assert(AcceptableType::value, ""); }; } // namespace safe_minmax_impl template < typename R = safe_minmax_impl::DefaultType, typename T = safe_minmax_impl::DefaultType, typename L = safe_minmax_impl::DefaultType, typename H = safe_minmax_impl::DefaultType, typename R2 = typename safe_minmax_impl::TypeOr< R, typename safe_minmax_impl::ClampType< typename safe_minmax_impl::UnderlyingType::type, typename safe_minmax_impl::UnderlyingType::type, typename safe_minmax_impl::UnderlyingType::type>::type>::type> R2 SafeClamp(T x, L min, H max) { static_assert(IsIntlike::value || std::is_floating_point::value, "The first argument must be integral or floating-point"); static_assert(IsIntlike::value || std::is_floating_point::value, "The second argument must be integral or floating-point"); static_assert(IsIntlike::value || std::is_floating_point::value, "The third argument must be integral or floating-point"); // RTC_DCHECK_LE(min, max); return SafeLe(x, min) ? static_cast(min) : SafeGe(x, max) ? static_cast(max) : static_cast(x); } } // namespace rtc #endif // RTC_BASE_NUMERICS_SAFE_MINMAX_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/rate_statistics.cc ================================================ /* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "rtc_base/rate_statistics.h" #include #include namespace webrtc { RateStatistics::RateStatistics(int64_t window_size_ms, float scale) : buckets_(new Bucket[window_size_ms]()), accumulated_count_(0), num_samples_(0), oldest_time_(-window_size_ms), oldest_index_(0), scale_(scale), max_window_size_ms_(window_size_ms), current_window_size_ms_(max_window_size_ms_) {} RateStatistics::RateStatistics(const RateStatistics& other) : accumulated_count_(other.accumulated_count_), num_samples_(other.num_samples_), oldest_time_(other.oldest_time_), oldest_index_(other.oldest_index_), scale_(other.scale_), max_window_size_ms_(other.max_window_size_ms_), current_window_size_ms_(other.current_window_size_ms_) { buckets_ = absl::make_unique(other.max_window_size_ms_); std::copy(other.buckets_.get(), other.buckets_.get() + other.max_window_size_ms_, buckets_.get()); } RateStatistics::RateStatistics(RateStatistics&& other) = default; RateStatistics::~RateStatistics() {} void RateStatistics::Reset() { accumulated_count_ = 0; num_samples_ = 0; oldest_time_ = -max_window_size_ms_; oldest_index_ = 0; current_window_size_ms_ = max_window_size_ms_; for (int64_t i = 0; i < max_window_size_ms_; i++) buckets_[i] = Bucket(); } void RateStatistics::Update(size_t count, int64_t now_ms) { if (now_ms < oldest_time_) { // Too old data is ignored. return; } EraseOld(now_ms); // First ever sample, reset window to start now. if (!IsInitialized()) oldest_time_ = now_ms; uint32_t now_offset = static_cast(now_ms - oldest_time_); uint32_t index = oldest_index_ + now_offset; if (index >= max_window_size_ms_) index -= max_window_size_ms_; buckets_[index].sum += count; ++buckets_[index].samples; accumulated_count_ += count; ++num_samples_; } absl::optional RateStatistics::Rate(int64_t now_ms) const { // Yeah, this const_cast ain't pretty, but the alternative is to declare most // of the members as mutable... const_cast(this)->EraseOld(now_ms); // If window is a single bucket or there is only one sample in a data set that // has not grown to the full window size, treat this as rate unavailable. int64_t active_window_size = now_ms - oldest_time_ + 1; if (num_samples_ == 0 || active_window_size <= 1 || (num_samples_ <= 1 && active_window_size < current_window_size_ms_)) { return absl::nullopt; } float scale = scale_ / active_window_size; return static_cast(accumulated_count_ * scale + 0.5f); } void RateStatistics::EraseOld(int64_t now_ms) { if (!IsInitialized()) return; // New oldest time that is included in data set. int64_t new_oldest_time = now_ms - current_window_size_ms_ + 1; // New oldest time is older than the current one, no need to cull data. if (new_oldest_time <= oldest_time_) return; // Loop over buckets and remove too old data points. while (num_samples_ > 0 && oldest_time_ < new_oldest_time) { const Bucket& oldest_bucket = buckets_[oldest_index_]; accumulated_count_ -= oldest_bucket.sum; num_samples_ -= oldest_bucket.samples; buckets_[oldest_index_] = Bucket(); if (++oldest_index_ >= max_window_size_ms_) oldest_index_ = 0; ++oldest_time_; } oldest_time_ = new_oldest_time; } bool RateStatistics::SetWindowSize(int64_t window_size_ms, int64_t now_ms) { if (window_size_ms <= 0 || window_size_ms > max_window_size_ms_) return false; current_window_size_ms_ = window_size_ms; EraseOld(now_ms); return true; } bool RateStatistics::IsInitialized() const { return oldest_time_ != -max_window_size_ms_; } } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/rate_statistics.h ================================================ /* * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_RATE_STATISTICS_H_ #define RTC_BASE_RATE_STATISTICS_H_ #include #include #include #include namespace webrtc { class RateStatistics { public: static constexpr float kBpsScale = 8000.0f; // max_window_size_ms = Maximum window size in ms for the rate estimation. // Initial window size is set to this, but may be changed // to something lower by calling SetWindowSize(). // scale = coefficient to convert counts/ms to desired unit // ex: kBpsScale (8000) for bits/s if count represents bytes. RateStatistics(int64_t max_window_size_ms, float scale); RateStatistics(const RateStatistics& other); RateStatistics(RateStatistics&& other); ~RateStatistics(); // Reset instance to original state. void Reset(); // Update rate with a new data point, moving averaging window as needed. void Update(size_t count, int64_t now_ms); // Note that despite this being a const method, it still updates the internal // state (moves averaging window), but it doesn't make any alterations that // are observable from the other methods, as long as supplied timestamps are // from a monotonic clock. Ie, it doesn't matter if this call moves the // window, since any subsequent call to Update or Rate would still have moved // the window as much or more. absl::optional Rate(int64_t now_ms) const; // Update the size of the averaging window. The maximum allowed value for // window_size_ms is max_window_size_ms as supplied in the constructor. bool SetWindowSize(int64_t window_size_ms, int64_t now_ms); private: void EraseOld(int64_t now_ms); bool IsInitialized() const; // Counters are kept in buckets (circular buffer), with one bucket // per millisecond. struct Bucket { size_t sum; // Sum of all samples in this bucket. size_t samples; // Number of samples in this bucket. }; std::unique_ptr buckets_; // Total count recorded in buckets. size_t accumulated_count_; // The total number of samples in the buckets. size_t num_samples_; // Oldest time recorded in buckets. int64_t oldest_time_; // Bucket index of oldest counter recorded in buckets. uint32_t oldest_index_; // To convert counts/ms to desired units const float scale_; // The window sizes, in ms, over which the rate is calculated. const int64_t max_window_size_ms_; int64_t current_window_size_ms_; }; } // namespace webrtc #endif // RTC_BASE_RATE_STATISTICS_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/system/unused.h ================================================ /* * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_SYSTEM_UNUSED_H_ #define RTC_BASE_SYSTEM_UNUSED_H_ // Annotate a function indicating the caller must examine the return value. // Use like: // int foo() RTC_WARN_UNUSED_RESULT; // To explicitly ignore a result, cast to void. // TODO(kwiberg): Remove when we can use [[nodiscard]] from C++17. #if defined(__clang__) #define RTC_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) #elif defined(__GNUC__) // gcc has a __warn_unused_result__ attribute, but you can't quiet it by // casting to void, so we don't use it. #define RTC_WARN_UNUSED_RESULT #else #define RTC_WARN_UNUSED_RESULT #endif // Prevent the compiler from warning about an unused variable. For example: // int result = DoSomething(); // assert(result == 17); // RTC_UNUSED(result); // Note: In most cases it is better to remove the unused variable rather than // suppressing the compiler warning. #ifndef RTC_UNUSED #define RTC_UNUSED(x) static_cast(x) #endif // RTC_UNUSED #endif // RTC_BASE_SYSTEM_UNUSED_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/type_traits.h ================================================ /* * Copyright 2016 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_TYPE_TRAITS_H_ #define RTC_BASE_TYPE_TRAITS_H_ #include #include namespace rtc { // Determines if the given class has zero-argument .data() and .size() methods // whose return values are convertible to T* and size_t, respectively. template class HasDataAndSize { private: template < typename C, typename std::enable_if< std::is_convertible().data()), T*>::value && std::is_convertible().size()), std::size_t>::value>::type* = nullptr> static int Test(int); template static char Test(...); public: static constexpr bool value = std::is_same(0)), int>::value; }; namespace test_has_data_and_size { template struct Test1 { DR data(); SR size(); }; static_assert(HasDataAndSize, int>::value, ""); static_assert(HasDataAndSize, const int>::value, ""); static_assert(HasDataAndSize, const int>::value, ""); static_assert(!HasDataAndSize, int>::value, "implicit cast of const int* to int*"); static_assert(!HasDataAndSize, int>::value, "implicit cast of char* to int*"); struct Test2 { int* data; size_t size; }; static_assert(!HasDataAndSize::value, ".data and .size aren't functions"); struct Test3 { int* data(); }; static_assert(!HasDataAndSize::value, ".size() is missing"); class Test4 { int* data(); size_t size(); }; static_assert(!HasDataAndSize::value, ".data() and .size() are private"); } // namespace test_has_data_and_size namespace type_traits_impl { // Determines if the given type is an enum that converts implicitly to // an integral type. template struct IsIntEnum { private: // This overload is used if the type is an enum, and unary plus // compiles and turns it into an integral type. template ::value && std::is_integral())>::value>::type* = nullptr> static int Test(int); // Otherwise, this overload is used. template static char Test(...); public: static constexpr bool value = std::is_same::type>(0)), int>::value; }; } // namespace type_traits_impl // Determines if the given type is integral, or an enum that // converts implicitly to an integral type. template struct IsIntlike { private: using X = typename std::remove_reference::type; public: static constexpr bool value = std::is_integral::value || type_traits_impl::IsIntEnum::value; }; namespace test_enum_intlike { enum E1 { e1 }; enum { e2 }; enum class E3 { e3 }; struct S {}; static_assert(type_traits_impl::IsIntEnum::value, ""); static_assert(type_traits_impl::IsIntEnum::value, ""); static_assert(!type_traits_impl::IsIntEnum::value, ""); static_assert(!type_traits_impl::IsIntEnum::value, ""); static_assert(!type_traits_impl::IsIntEnum::value, ""); static_assert(!type_traits_impl::IsIntEnum::value, ""); static_assert(IsIntlike::value, ""); static_assert(IsIntlike::value, ""); static_assert(!IsIntlike::value, ""); static_assert(IsIntlike::value, ""); static_assert(!IsIntlike::value, ""); static_assert(!IsIntlike::value, ""); } // namespace test_enum_intlike } // namespace rtc #endif // RTC_BASE_TYPE_TRAITS_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/rtc_base/units/unit_base.h ================================================ /* * Copyright 2018 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_UNITS_UNIT_BASE_H_ #define RTC_BASE_UNITS_UNIT_BASE_H_ #include "rtc_base/numerics/safe_conversions.h" #include #include #include #include #include namespace webrtc { namespace rtc_units_impl { // UnitBase is a base class for implementing custom value types with a specific // unit. It provides type safety and commonly useful operations. The underlying // storage is always an int64_t, it's up to the unit implementation to choose // what scale it represents. // // It's used like: // class MyUnit: public UnitBase {...}; // // Unit_T is the subclass representing the specific unit. template class UnitBase { public: UnitBase() = delete; static constexpr Unit_T Zero() { return Unit_T(0); } static constexpr Unit_T PlusInfinity() { return Unit_T(PlusInfinityVal()); } static constexpr Unit_T MinusInfinity() { return Unit_T(MinusInfinityVal()); } constexpr bool IsZero() const { return value_ == 0; } constexpr bool IsFinite() const { return !IsInfinite(); } constexpr bool IsInfinite() const { return value_ == PlusInfinityVal() || value_ == MinusInfinityVal(); } constexpr bool IsPlusInfinity() const { return value_ == PlusInfinityVal(); } constexpr bool IsMinusInfinity() const { return value_ == MinusInfinityVal(); } constexpr bool operator==(const Unit_T& other) const { return value_ == other.value_; } constexpr bool operator!=(const Unit_T& other) const { return value_ != other.value_; } constexpr bool operator<=(const Unit_T& other) const { return value_ <= other.value_; } constexpr bool operator>=(const Unit_T& other) const { return value_ >= other.value_; } constexpr bool operator>(const Unit_T& other) const { return value_ > other.value_; } constexpr bool operator<(const Unit_T& other) const { return value_ < other.value_; } Unit_T RoundTo(const Unit_T& resolution) const { //RTC_DCHECK(IsFinite()); //RTC_DCHECK(resolution.IsFinite()); //RTC_DCHECK_GT(resolution.value_, 0); return Unit_T((value_ + resolution.value_ / 2) / resolution.value_) * resolution.value_; } Unit_T RoundUpTo(const Unit_T& resolution) const { //RTC_DCHECK(IsFinite()); //RTC_DCHECK(resolution.IsFinite()); //RTC_DCHECK_GT(resolution.value_, 0); return Unit_T((value_ + resolution.value_ - 1) / resolution.value_) * resolution.value_; } Unit_T RoundDownTo(const Unit_T& resolution) const { //RTC_DCHECK(IsFinite()); //RTC_DCHECK(resolution.IsFinite()); //RTC_DCHECK_GT(resolution.value_, 0); return Unit_T(value_ / resolution.value_) * resolution.value_; } protected: template static constexpr Unit_T FromStaticValue() { static_assert(value >= 0 || !Unit_T::one_sided, ""); static_assert(value > MinusInfinityVal(), ""); static_assert(value < PlusInfinityVal(), ""); return Unit_T(value); } template static constexpr Unit_T FromStaticFraction() { static_assert(fraction_value >= 0 || !Unit_T::one_sided, ""); static_assert(fraction_value > MinusInfinityVal() / Denominator, ""); static_assert(fraction_value < PlusInfinityVal() / Denominator, ""); return Unit_T(fraction_value * Denominator); } template < typename T, typename std::enable_if::value>::type* = nullptr> static Unit_T FromValue(T value) { // if (Unit_T::one_sided) //RTC_DCHECK_GE(value, 0); //RTC_DCHECK_GT(value, MinusInfinityVal()); //RTC_DCHECK_LT(value, PlusInfinityVal()); return Unit_T(rtc::dchecked_cast(value)); } template ::value>::type* = nullptr> static Unit_T FromValue(T value) { if (value == std::numeric_limits::infinity()) { return PlusInfinity(); } else if (value == -std::numeric_limits::infinity()) { return MinusInfinity(); } else { //RTC_DCHECK(!std::isnan(value)); return FromValue(rtc::dchecked_cast(value)); } } template < int64_t Denominator, typename T, typename std::enable_if::value>::type* = nullptr> static Unit_T FromFraction(T value) { // if (Unit_T::one_sided) //RTC_DCHECK_GE(value, 0); //RTC_DCHECK_GT(value, MinusInfinityVal() / Denominator); //RTC_DCHECK_LT(value, PlusInfinityVal() / Denominator); return Unit_T(rtc::dchecked_cast(value * Denominator)); } template ::value>::type* = nullptr> static Unit_T FromFraction(T value) { return FromValue(value * Denominator); } template typename std::enable_if::value, T>::type ToValue() const { //RTC_DCHECK(IsFinite()); return rtc::dchecked_cast(value_); } template constexpr typename std::enable_if::value, T>::type ToValue() const { return IsPlusInfinity() ? std::numeric_limits::infinity() : IsMinusInfinity() ? -std::numeric_limits::infinity() : value_; } template constexpr T ToValueOr(T fallback_value) const { return IsFinite() ? value_ : fallback_value; } template typename std::enable_if::value, T>::type ToFraction() const { //RTC_DCHECK(IsFinite()); if (Unit_T::one_sided) { return rtc::dchecked_cast( DivRoundPositiveToNearest(value_, Denominator)); } else { return rtc::dchecked_cast(DivRoundToNearest(value_, Denominator)); } } template constexpr typename std::enable_if::value, T>::type ToFraction() const { return ToValue() * (1 / static_cast(Denominator)); } template constexpr int64_t ToFractionOr(int64_t fallback_value) const { return IsFinite() ? Unit_T::one_sided ? DivRoundPositiveToNearest(value_, Denominator) : DivRoundToNearest(value_, Denominator) : fallback_value; } template typename std::enable_if::value, T>::type ToMultiple() const { //RTC_DCHECK_GE(ToValue(), std::numeric_limits::min() / Factor); //RTC_DCHECK_LE(ToValue(), std::numeric_limits::max() / Factor); return rtc::dchecked_cast(ToValue() * Factor); } template constexpr typename std::enable_if::value, T>::type ToMultiple() const { return ToValue() * Factor; } explicit constexpr UnitBase(int64_t value) : value_(value) {} private: template friend class RelativeUnit; static inline constexpr int64_t PlusInfinityVal() { return std::numeric_limits::max(); } static inline constexpr int64_t MinusInfinityVal() { return std::numeric_limits::min(); } Unit_T& AsSubClassRef() { return reinterpret_cast(*this); } constexpr const Unit_T& AsSubClassRef() const { return reinterpret_cast(*this); } // Assumes that n >= 0 and d > 0. static constexpr int64_t DivRoundPositiveToNearest(int64_t n, int64_t d) { return (n + d / 2) / d; } // Assumes that d > 0. static constexpr int64_t DivRoundToNearest(int64_t n, int64_t d) { return (n + (n >= 0 ? d / 2 : -d / 2)) / d; } int64_t value_; }; // Extends UnitBase to provide operations for relative units, that is, units // that have a meaningful relation between values such that a += b is a // sensible thing to do. For a,b <- same unit. template class RelativeUnit : public UnitBase { public: Unit_T Clamped(Unit_T min_value, Unit_T max_value) const { return std::max(min_value, std::min(UnitBase::AsSubClassRef(), max_value)); } void Clamp(Unit_T min_value, Unit_T max_value) { *this = Clamped(min_value, max_value); } Unit_T operator+(const Unit_T other) const { if (this->IsPlusInfinity() || other.IsPlusInfinity()) { //RTC_DCHECK(!this->IsMinusInfinity()); //RTC_DCHECK(!other.IsMinusInfinity()); return this->PlusInfinity(); } else if (this->IsMinusInfinity() || other.IsMinusInfinity()) { //RTC_DCHECK(!this->IsPlusInfinity()); //RTC_DCHECK(!other.IsPlusInfinity()); return this->MinusInfinity(); } return UnitBase::FromValue(this->ToValue() + other.ToValue()); } Unit_T operator-(const Unit_T other) const { if (this->IsPlusInfinity() || other.IsMinusInfinity()) { //RTC_DCHECK(!this->IsMinusInfinity()); //RTC_DCHECK(!other.IsPlusInfinity()); return this->PlusInfinity(); } else if (this->IsMinusInfinity() || other.IsPlusInfinity()) { //RTC_DCHECK(!this->IsPlusInfinity()); //RTC_DCHECK(!other.IsMinusInfinity()); return this->MinusInfinity(); } return UnitBase::FromValue(this->ToValue() - other.ToValue()); } Unit_T& operator+=(const Unit_T other) { *this = *this + other; return this->AsSubClassRef(); } Unit_T& operator-=(const Unit_T other) { *this = *this - other; return this->AsSubClassRef(); } constexpr double operator/(const Unit_T other) const { return UnitBase::template ToValue() / other.template ToValue(); } template typename std::enable_if::value, Unit_T>::type operator/( const T& scalar) const { return UnitBase::FromValue( std::round(UnitBase::template ToValue() / scalar)); } Unit_T operator*(const double scalar) const { return UnitBase::FromValue(std::round(this->ToValue() * scalar)); } Unit_T operator*(const int64_t scalar) const { return UnitBase::FromValue(this->ToValue() * scalar); } Unit_T operator*(const int32_t scalar) const { return UnitBase::FromValue(this->ToValue() * scalar); } protected: using UnitBase::UnitBase; }; template inline Unit_T operator*(const double scalar, const RelativeUnit other) { return other * scalar; } template inline Unit_T operator*(const int64_t scalar, const RelativeUnit other) { return other * scalar; } template inline Unit_T operator*(const int32_t& scalar, const RelativeUnit other) { return other * scalar; } } // namespace rtc_units_impl } // namespace webrtc #endif // RTC_BASE_UNITS_UNIT_BASE_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc/system_wrappers/source/field_trial.cc ================================================ // Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file in the root of the source // tree. An additional intellectual property rights grant can be found // in the file PATENTS. All contributing project authors may // be found in the AUTHORS file in the root of the source tree. // #define MS_CLASS "webrtc::FieldTrial" // #define MS_LOG_DEV_LEVEL 3 #include "system_wrappers/source/field_trial.h" #include "Logger.hpp" #include #include #include #include // Simple field trial implementation, which allows client to // specify desired flags in InitFieldTrialsFromString. namespace webrtc { namespace field_trial { static const char* trials_init_string = NULL; #ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT namespace { constexpr char kPersistentStringSeparator = '/'; // Validates the given field trial string. // E.g.: // "WebRTC-experimentFoo/Enabled/WebRTC-experimentBar/Enabled100kbps/" // Assigns the process to group "Enabled" on WebRTCExperimentFoo trial // and to group "Enabled100kbps" on WebRTCExperimentBar. // // E.g. invalid config: // "WebRTC-experiment1/Enabled" (note missing / separator at the end). bool FieldTrialsStringIsValid(const absl::string_view trials) { if (trials.empty()) return true; size_t next_item = 0; std::map field_trials; while (next_item < trials.length()) { size_t name_end = trials.find(kPersistentStringSeparator, next_item); if (name_end == trials.npos || next_item == name_end) return false; size_t group_name_end = trials.find(kPersistentStringSeparator, name_end + 1); if (group_name_end == trials.npos || name_end + 1 == group_name_end) return false; absl::string_view name = trials.substr(next_item, name_end - next_item); absl::string_view group_name = trials.substr(name_end + 1, group_name_end - name_end - 1); next_item = group_name_end + 1; // Fail if duplicate with different group name. if (field_trials.find(name) != field_trials.end() && field_trials.find(name)->second != group_name) { return false; } field_trials[name] = group_name; } return true; } } // namespace std::string FindFullName(const std::string& name) { if (trials_init_string == NULL) return std::string(); std::string trials_string(trials_init_string); if (trials_string.empty()) return std::string(); size_t next_item = 0; while (next_item < trials_string.length()) { // Find next name/value pair in field trial configuration string. size_t field_name_end = trials_string.find(kPersistentStringSeparator, next_item); if (field_name_end == trials_string.npos || field_name_end == next_item) break; size_t field_value_end = trials_string.find(kPersistentStringSeparator, field_name_end + 1); if (field_value_end == trials_string.npos || field_value_end == field_name_end + 1) break; std::string field_name(trials_string, next_item, field_name_end - next_item); std::string field_value(trials_string, field_name_end + 1, field_value_end - field_name_end - 1); next_item = field_value_end + 1; if (name == field_name) return field_value; } return std::string(); } #endif // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT // Optionally initialize field trial from a string. void InitFieldTrialsFromString(const char* trials_string) { MS_DEBUG_TAG(bwe, "Setting field trial string: %s", trials_string); #ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT if (trials_string) { // RTC_DCHECK(FieldTrialsStringIsValid(trials_string)) // << "Invalid field trials string:" << trials_string; MS_ASSERT( FieldTrialsStringIsValid(trials_string), "invalid field trials string: '%s'", trials_string); }; #endif // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT trials_init_string = trials_string; } const char* GetFieldTrialString() { return trials_init_string; } } // namespace field_trial } // namespace webrtc ================================================ FILE: worker/deps/libwebrtc/libwebrtc/system_wrappers/source/field_trial.h ================================================ // // Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. // // Use of this source code is governed by a BSD-style license // that can be found in the LICENSE file in the root of the source // tree. An additional intellectual property rights grant can be found // in the file PATENTS. All contributing project authors may // be found in the AUTHORS file in the root of the source tree. // #ifndef SYSTEM_WRAPPERS_INCLUDE_FIELD_TRIAL_H_ #define SYSTEM_WRAPPERS_INCLUDE_FIELD_TRIAL_H_ #include // Field trials allow webrtc clients (such as Chrome) to turn on feature code // in binaries out in the field and gather information with that. // // By default WebRTC provides an implementaion of field trials that can be // found in system_wrappers/source/field_trial.cc. If clients want to provide // a custom version, they will have to: // // 1. Compile WebRTC defining the preprocessor macro // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT (if GN is used this can be achieved // by setting the GN arg rtc_exclude_field_trial_default to true). // 2. Provide an implementation of: // std::string webrtc::field_trial::FindFullName(const std::string& trial). // // They are designed to wire up directly to chrome field trials and to speed up // developers by reducing the need to wire APIs to control whether a feature is // on/off. E.g. to experiment with a new method that could lead to a different // trade-off between CPU/bandwidth: // // 1 - Develop the feature with default behaviour off: // // if (FieldTrial::FindFullName("WebRTCExperimentMethod2") == "Enabled") // method2(); // else // method1(); // // 2 - Once the changes are rolled to chrome, the new code path can be // controlled as normal chrome field trials. // // 3 - Evaluate the new feature and clean the code paths. // // Notes: // - NOT every feature is a candidate to be controlled by this mechanism as // it may require negotation between involved parties (e.g. SDP). // // TODO(andresp): since chrome --force-fieldtrials does not marks the trial // as active it does not gets propaged to renderer process. For now one // needs to push a config with start_active:true or run a local finch // server. // // TODO(andresp): find out how to get bots to run tests with trials enabled. namespace webrtc { namespace field_trial { // Returns the group name chosen for the named trial, or the empty string // if the trial does not exists. // // Note: To keep things tidy append all the trial names with WebRTC. std::string FindFullName(const std::string& name); // Convenience method, returns true iff FindFullName(name) return a string that // starts with "Enabled". // TODO(tommi): Make sure all implementations support this. inline bool IsEnabled(const char* name) { return FindFullName(name).find("Enabled") == 0; } // Convenience method, returns true iff FindFullName(name) return a string that // starts with "Disabled". inline bool IsDisabled(const char* name) { return FindFullName(name).find("Disabled") == 0; } // Optionally initialize field trial from a string. // This method can be called at most once before any other call into webrtc. // E.g. before the peer connection factory is constructed. // Note: trials_string must never be destroyed. void InitFieldTrialsFromString(const char* trials_string); const char* GetFieldTrialString(); } // namespace field_trial } // namespace webrtc #endif // SYSTEM_WRAPPERS_INCLUDE_FIELD_TRIAL_H_ ================================================ FILE: worker/deps/libwebrtc/libwebrtc.gyp ================================================ { 'target_defaults': { 'dependencies': [ 'deps/abseil-cpp/abseil-cpp.gyp:abseil', '../libuv/uv.gyp:libuv', '../openssl/openssl.gyp:openssl' ], 'direct_dependent_settings': { 'include_dirs': [ '.', 'libwebrtc' ] }, 'sources': [ # C++ source files. 'libwebrtc/system_wrappers/source/field_trial.cc', 'libwebrtc/rtc_base/rate_statistics.cc', 'libwebrtc/rtc_base/experiments/field_trial_parser.cc', 'libwebrtc/rtc_base/experiments/alr_experiment.cc', 'libwebrtc/rtc_base/experiments/field_trial_units.cc', 'libwebrtc/rtc_base/experiments/rate_control_settings.cc', 'libwebrtc/rtc_base/network/sent_packet.cc', 'libwebrtc/call/rtp_transport_controller_send.cc', 'libwebrtc/api/transport/bitrate_settings.cc', 'libwebrtc/api/transport/field_trial_based_config.cc', 'libwebrtc/api/transport/network_types.cc', 'libwebrtc/api/transport/goog_cc_factory.cc', 'libwebrtc/api/units/timestamp.cc', 'libwebrtc/api/units/time_delta.cc', 'libwebrtc/api/units/data_rate.cc', 'libwebrtc/api/units/data_size.cc', 'libwebrtc/api/units/frequency.cc', 'libwebrtc/api/network_state_predictor.cc', 'libwebrtc/modules/pacing/interval_budget.cc', 'libwebrtc/modules/pacing/bitrate_prober.cc', 'libwebrtc/modules/pacing/paced_sender.cc', 'libwebrtc/modules/remote_bitrate_estimator/overuse_detector.cc', 'libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.cc', 'libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc', 'libwebrtc/modules/remote_bitrate_estimator/inter_arrival.cc', 'libwebrtc/modules/remote_bitrate_estimator/bwe_defines.cc', 'libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc', 'libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc', 'libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.cc', 'libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.cc', 'libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc', 'libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.cc', 'libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/alr_detector.cc', 'libwebrtc/modules/congestion_controller/goog_cc/probe_controller.cc', 'libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.cc', 'libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.cc', 'libwebrtc/modules/congestion_controller/rtp/send_time_history.cc', 'libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc', 'libwebrtc/modules/congestion_controller/rtp/control_handler.cc', # C++ include files. 'libwebrtc/system_wrappers/source/field_trial.h', 'libwebrtc/rtc_base/rate_statistics.h', 'libwebrtc/rtc_base/experiments/field_trial_parser.h', 'libwebrtc/rtc_base/experiments/field_trial_units.h', 'libwebrtc/rtc_base/experiments/alr_experiment.h', 'libwebrtc/rtc_base/experiments/rate_control_settings.h', 'libwebrtc/rtc_base/network/sent_packet.h', 'libwebrtc/rtc_base/units/unit_base.h', 'libwebrtc/rtc_base/constructor_magic.h', 'libwebrtc/rtc_base/numerics/safe_minmax.h', 'libwebrtc/rtc_base/numerics/safe_conversions.h', 'libwebrtc/rtc_base/numerics/safe_conversions_impl.h', 'libwebrtc/rtc_base/numerics/percentile_filter.h', 'libwebrtc/rtc_base/numerics/safe_compare.h', 'libwebrtc/rtc_base/system/unused.h', 'libwebrtc/rtc_base/type_traits.h', 'libwebrtc/call/rtp_transport_controller_send.h', 'libwebrtc/call/rtp_transport_controller_send_interface.h', 'libwebrtc/api/transport/webrtc_key_value_config.h', 'libwebrtc/api/transport/network_types.h', 'libwebrtc/api/transport/bitrate_settings.h', 'libwebrtc/api/transport/network_control.h', 'libwebrtc/api/transport/field_trial_based_config.h', 'libwebrtc/api/transport/goog_cc_factory.h', 'libwebrtc/api/bitrate_constraints.h', 'libwebrtc/api/units/frequency.h', 'libwebrtc/api/units/data_size.h', 'libwebrtc/api/units/time_delta.h', 'libwebrtc/api/units/data_rate.h', 'libwebrtc/api/units/timestamp.h', 'libwebrtc/api/network_state_predictor.h', 'libwebrtc/modules/include/module_common_types_public.h', 'libwebrtc/modules/pacing/interval_budget.h', 'libwebrtc/modules/pacing/paced_sender.h', 'libwebrtc/modules/pacing/packet_router.h', 'libwebrtc/modules/pacing/bitrate_prober.h', 'libwebrtc/modules/remote_bitrate_estimator/inter_arrival.h', 'libwebrtc/modules/remote_bitrate_estimator/overuse_detector.h', 'libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.h', 'libwebrtc/modules/remote_bitrate_estimator/bwe_defines.h', 'libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.h', 'libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.h', 'libwebrtc/modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h', 'libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.h', 'libwebrtc/modules/rtp_rtcp/source/rtp_packet/transport_feedback.h', 'libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.h', 'libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.h', 'libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.h', 'libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.h', 'libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.h', 'libwebrtc/modules/congestion_controller/goog_cc/probe_controller.h', 'libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.h', 'libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.h', 'libwebrtc/modules/congestion_controller/goog_cc/delay_increase_detector_interface.h', 'libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.h', 'libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.h', 'libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.h', 'libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.h', 'libwebrtc/modules/congestion_controller/goog_cc/alr_detector.h', 'libwebrtc/modules/congestion_controller/rtp/send_time_history.h', 'libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.h', 'libwebrtc/modules/congestion_controller/rtp/control_handler.h', 'libwebrtc/mediasoup_helpers.h' ], 'include_dirs': [ 'libwebrtc', '../../include', '../json/single_include' ], 'conditions': [ # Endianness. [ 'node_byteorder == "big"', { # Define Big Endian. 'defines': [ 'MS_BIG_ENDIAN' ] }, { # Define Little Endian. 'defines': [ 'MS_LITTLE_ENDIAN' ] }], # Platform-specifics. [ 'OS != "win"', { 'cflags': [ '-std=c++11' ] }], [ 'OS == "mac"', { 'xcode_settings': { 'OTHER_CPLUSPLUSFLAGS' : [ '-std=c++11' ] } }] ] }, 'targets': [ { 'target_name': 'libwebrtc', 'type': 'static_library' } ] } ================================================ FILE: worker/deps/libwebrtc/meson.build ================================================ libwebrtc_sources = [ 'libwebrtc/system_wrappers/source/field_trial.cc', 'libwebrtc/rtc_base/rate_statistics.cc', 'libwebrtc/rtc_base/experiments/field_trial_parser.cc', 'libwebrtc/rtc_base/experiments/alr_experiment.cc', 'libwebrtc/rtc_base/experiments/field_trial_units.cc', 'libwebrtc/rtc_base/experiments/rate_control_settings.cc', 'libwebrtc/rtc_base/network/sent_packet.cc', 'libwebrtc/call/rtp_transport_controller_send.cc', 'libwebrtc/api/transport/bitrate_settings.cc', 'libwebrtc/api/transport/field_trial_based_config.cc', 'libwebrtc/api/transport/network_types.cc', 'libwebrtc/api/transport/goog_cc_factory.cc', 'libwebrtc/api/units/timestamp.cc', 'libwebrtc/api/units/time_delta.cc', 'libwebrtc/api/units/data_rate.cc', 'libwebrtc/api/units/data_size.cc', 'libwebrtc/api/units/frequency.cc', 'libwebrtc/api/network_state_predictor.cc', 'libwebrtc/modules/pacing/interval_budget.cc', 'libwebrtc/modules/pacing/bitrate_prober.cc', 'libwebrtc/modules/pacing/paced_sender.cc', 'libwebrtc/modules/remote_bitrate_estimator/overuse_detector.cc', 'libwebrtc/modules/remote_bitrate_estimator/overuse_estimator.cc', 'libwebrtc/modules/remote_bitrate_estimator/aimd_rate_control.cc', 'libwebrtc/modules/remote_bitrate_estimator/inter_arrival.cc', 'libwebrtc/modules/remote_bitrate_estimator/bwe_defines.cc', 'libwebrtc/modules/remote_bitrate_estimator/remote_bitrate_estimator_abs_send_time.cc', 'libwebrtc/modules/rtp_rtcp/include/rtp_rtcp_defines.cc', 'libwebrtc/modules/bitrate_controller/send_side_bandwidth_estimation.cc', 'libwebrtc/modules/bitrate_controller/loss_based_bandwidth_estimation.cc', 'libwebrtc/modules/congestion_controller/goog_cc/goog_cc_network_control.cc', 'libwebrtc/modules/congestion_controller/goog_cc/probe_bitrate_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/congestion_window_pushback_controller.cc', 'libwebrtc/modules/congestion_controller/goog_cc/link_capacity_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/alr_detector.cc', 'libwebrtc/modules/congestion_controller/goog_cc/probe_controller.cc', 'libwebrtc/modules/congestion_controller/goog_cc/median_slope_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/bitrate_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/trendline_estimator.cc', 'libwebrtc/modules/congestion_controller/goog_cc/delay_based_bwe.cc', 'libwebrtc/modules/congestion_controller/goog_cc/acknowledged_bitrate_estimator.cc', 'libwebrtc/modules/congestion_controller/rtp/send_time_history.cc', 'libwebrtc/modules/congestion_controller/rtp/transport_feedback_adapter.cc', 'libwebrtc/modules/congestion_controller/rtp/control_handler.cc', ] abseil_cpp_proj = subproject( 'abseil-cpp', default_options: [ 'warning_level=0', ], ) local_include_directories = declare_dependency( include_directories: include_directories('libwebrtc') ) libwebrtc = library( 'libwebrtc', libwebrtc_sources, dependencies: [ local_include_directories, openssl_proj.get_variable('openssl_dep'), abseil_cpp_proj.get_variable('absl_strings_dep'), abseil_cpp_proj.get_variable('absl_types_dep'), flatbuffers_proj.get_variable('flatbuffers_dep'), libuv_proj.get_variable('libuv_dep'), ], include_directories: libwebrtc_include_directories, cpp_args: cpp_args, ) libwebrtc_dep = declare_dependency( dependencies: [ local_include_directories, abseil_cpp_proj.get_variable('absl_strings_dep'), abseil_cpp_proj.get_variable('absl_types_dep'), ], include_directories: include_directories('.'), link_with: libwebrtc, ) ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/README.md ================================================ # webrtc-fuzzer-corpora This repository contains [libFuzzer](http://libfuzzer.info) corpus files useful for WebRTC developers to test their implementations. The `corpora` folders contains subfolders with corpus files. Some corpus files are taken from the Chromium project (those into the [webrtc/test/fuzzer/](https://chromium.googlesource.com/external/webrtc/+/master/test/fuzzers/corpora/) folder), so credits to the Chromium guys. The `reports` folder contains some relevant fuzzer generated crash and memory leak reports contributed by the community. ## SHA1 hash and file duplicates It is desiderable to avoid duplicate files in this repository in order to not pollute the folders with samples that might lead to usless testing iterations (e.g. file with different names but same binary content). To help managing this kind of issues, the script `add_sha1.sh` can be used to append the sha1 hashsum to the corpora and results files. ``` $ ./add_sha1.sh . renamed './corpora/agc-corpus/agc-1' -> './corpora/agc-corpus/agc-1-b35a35f11d86e802f694f84c45e48da96158f73f' renamed './corpora/agc-corpus/agc-2' -> './corpora/agc-corpus/agc-2-f3d59a997d30890da3239b377bba3bc502d18f1e' ... renamed './corpora/rtcp-corpus/0.rtcp' -> './corpora/rtcp-corpus/0-01b13c2eb549daadeec1eb7eb0846e9a2f5729eb.rtcp' ``` Target folder can be specified as first input. File extensions will not be touched. The script will detect an already hashed file by looking for the sha1sum in the filename. In this cases the output will be like: ``` > ./add_sha1.sh . | grep OK ./corpora/pseudotcp-corpus/785b96587d0eb44dd5d75b7a886f37e2ac504511 -> OK ./corpora/rtcp-corpus/0-01b13c2eb549daadeec1eb7eb0846e9a2f5729eb.rtcp -> OK ./corpora/rtp-corpus/rtp-0-951641f47532884149e6ebe9a87386226d8bf4fe -> OK ``` This script will alse reveal duplicate files in the same subfolder by matching the file sha1sum with other files names: ``` > ./add_sha1.sh . | grep DUPLICATE ./corpora/rtp-corpus/rtp-4 -> DUPLICATE ./corpora/rtp-corpus/rtp-1-5a709d82364ddf4f9350104c83994dded1c9f91c ./corpora/sdp-corpus/4.sdp -> DUPLICATE ./corpora/sdp-corpus/2-60495c33fc8758c5ce44f896c5f508bc026842c2.sdp ./corpora/sdp-corpus/8.sdp -> DUPLICATE ./corpora/sdp-corpus/2-60495c33fc8758c5ce44f896c5f508bc026842c2.sdp ``` Anyway `add_sha1` will only detect duplicates in the same subfolder. Another way to quickly reveal file duplicates across all subfolders is the following command: ``` > find . \( ! -regex '.*/\..*' \) -type f -exec sha1sum {} + | sort | uniq -w40 -dD 131d620296fa9eb2c372c7fd71b8e58f8a10abd5 ./corpora/sdp-corpus/46-131d620296fa9eb2c372c7fd71b8e58f8a10abd5.sdp 131d620296fa9eb2c372c7fd71b8e58f8a10abd5 ./corpora/sdp-corpus/50.sdp 271138fdddb4f02313f25b47581f9f8f4a2eb201 ./corpora/sdp-corpus/12-271138fdddb4f02313f25b47581f9f8f4a2eb201.sdp 271138fdddb4f02313f25b47581f9f8f4a2eb201 ./corpora/sdp-corpus/15.sdp 271138fdddb4f02313f25b47581f9f8f4a2eb201 ./corpora/sdp-corpus/40.sdp ... 461a0e9201a7ea5ea6a43511571bdafce10b8185 ./corpora/rtcp-corpus/17-461a0e9201a7ea5ea6a43511571bdafce10b8185.rtcp 461a0e9201a7ea5ea6a43511571bdafce10b8185 ./reports/crashes/rtcp/crash-461a0e9201a7ea5ea6a43511571bdafce10b8185 ``` ## License MIT or BSD or ISC or something like that (**let me focus on just fuzzing things!!**). ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/add_sha1.sh ================================================ #!/bin/bash if [ -z $1 ]; then echo "Usage ./add_sha1.sh corpus_folder" exit 1 fi SAVEIFS=$IFS IFS=$(echo -en "\n\b") crpfiles=$(find $1 \( ! -regex '.*/\..*' \) -not -iname "*.md" -not -iname "*.sh" -not -iname "*README*" -not -iname "*LICENSE*" -not -iname "*COPYRIGHT*" -type f) for file in $crpfiles; do sha1=$(sha1sum $file | awk '{print $1}') if [[ $file != *"$sha1"* ]]; then basedir=$(dirname $file) basefile=$(basename $file) name="${basefile%.*}" ext="${basefile##*.}" duplicates=$(find $basedir -type f -name "*$sha1*") if [ ! -z $duplicates ]; then echo "$file -> DUPLICATE $duplicates"; continue; fi if [[ $name == "$ext" ]]; then ext=""; else ext=".$ext"; fi mv -vn "$file" "$basedir/$name-$sha1$ext" else echo "$file -> OK" fi done IFS=$SAVEIFS ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus/47.rtcp ================================================ € ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus/55.rtcp ================================================ ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/10.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 97 a=rtpmap:0 PCMU/8000 a=rtpmap:97 iLBC/8000 m=audio 49172 RTP/AVP 98 a=rtpmap:98 telephone-event/8000 a=sendonly ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/11.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=audio 49174 RTP/AVP 98 a=rtpmap:98 telephone-event/8000 a=recvonly ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/12.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=video 51372 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/13.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49174 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=video 49170 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/14.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 newhost.biloxi.example.com t=0 0 m=audio 49178 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=video 49188 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/15.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=video 51372 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/16.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 a=rtpmap:0 PCMU/8000 m=audio 51372 RTP/AVP 97 101 a=rtpmap:97 iLBC/8000 a=rtpmap:101 telephone-event/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/17.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 0 RTP/AVP 0 a=rtpmap:0 PCMU/8000 m=audio 49170 RTP/AVP 97 101 a=rtpmap:97 iLBC/8000 a=rtpmap:101 telephone-event/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/18.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 99 a=rtpmap:99 iLBC/8000 m=video 51372 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/19.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 99 a=rtpmap:99 iLBC/8000 m=video 51374 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/2.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 8 97 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 iLBC/8000 m=video 51372 RTP/AVP 31 32 a=rtpmap:31 H261/90000 a=rtpmap:32 MPV/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/20.sdp ================================================ v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 99 a=rtpmap:99 iLBC/8000 m=video 51372 RTP/AVP 31 32 a=rtpmap:31 H261/90000 a=rtpmap:32 MPV/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/21.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 99 a=rtpmap:99 iLBC/8000 m=video 51374 RTP/AVP 31 32 a=rtpmap:31 H261/90000 a=rtpmap:32 MPV/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/22.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 8 97 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 iLBC/8000 m=video 51372 RTP/AVP 31 32 a=rtpmap:31 H261/90000 a=rtpmap:32 MPV/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/23.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49174 RTP/AVP 0 a=rtpmap:0 PCMU/8000 m=video 49172 RTP/AVP 32 c=IN IP4 otherhost.biloxi.example.com a=rtpmap:32 MPV/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/24.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 97 a=rtpmap:0 PCMU/8000 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/25.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 placeholder.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 97 a=rtpmap:97 iLBC/8000 a=sendonly ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/26.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/27.sdp ================================================ v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49178 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/28.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 97 a=rtpmap:0 PCMU/8000 a=rtpmap:97 iLBC/8000 m=audio 49172 RTP/AVP 98 a=rtpmap:98 telephone-event/8000 a=sendonly ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/29.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=audio 49174 RTP/AVP 98 a=rtpmap:98 telephone-event/8000 a=recvonly ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/3.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49174 RTP/AVP 0 a=rtpmap:0 PCMU/8000 m=video 49170 RTP/AVP 32 a=rtpmap:32 MPV/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/30.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 97 a=rtpmap:97 iLBC/8000 a=sendonly m=audio 49174 RTP/AVP 98 a=rtpmap:98 telephone-event/8000 a=recvonly ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/31.sdp ================================================ v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 97 a=rtpmap:0 PCMU/8000 a=rtpmap:97 iLBC/8000 m=audio 49172 RTP/AVP 98 a=rtpmap:98 telephone-event/8000 a=sendonly ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/32.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 97 a=rtpmap:0 PCMU/8000 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/33.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/34.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=audio 48282 RTP/AVP 98 c=IN IP4 mediaserver.biloxi.example.com a=rtpmap:98 telephone-event/8000 a=recvonly ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/35.sdp ================================================ v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=audio 49172 RTP/AVP 98 c=IN IP4 host.atlanta.example.com a=rtpmap:98 telephone-event/8000 a=sendonly ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/36.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 a=rtpmap:0 PCMU/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/37.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 0 a=rtpmap:0 PCMU/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/38.sdp ================================================ v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 a=rtpmap:0 PCMU/8000 m=video 49172 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/39.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 0 a=rtpmap:0 PCMU/8000 m=video 49168 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/4.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 8 97 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 iLBC/8000 m=video 51372 RTP/AVP 31 32 a=rtpmap:31 H261/90000 a=rtpmap:32 MPV/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/40.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=video 51372 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/41.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49174 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=video 49170 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/42.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49174 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=video 0 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/43.sdp ================================================ v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 m=video 0 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/44.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/45.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/46.sdp ================================================ v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/47.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/48.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 0.0.0.0 t=0 0 m=audio 23442 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/49.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/5.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 0 8 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 m=video 0 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/50.sdp ================================================ v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/51.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/52.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/53.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 0.0.0.0 t=0 0 m=audio 9322 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/54.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/55.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 97 a=rtpmap:97 iLBC/8000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/6.sdp ================================================ v=0 o=alice 2890844526 2890844527 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 51372 RTP/AVP 0 a=rtpmap:0 PCMU/8000 m=video 0 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/7.sdp ================================================ v=0 o=bob 2808844564 2808844565 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 0 a=rtpmap:0 PCMU/8000 m=video 0 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/8.sdp ================================================ v=0 o=alice 2890844526 2890844526 IN IP4 host.atlanta.example.com s= c=IN IP4 host.atlanta.example.com t=0 0 m=audio 49170 RTP/AVP 0 8 97 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:97 iLBC/8000 m=video 51372 RTP/AVP 31 32 a=rtpmap:31 H261/90000 a=rtpmap:32 MPV/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/9.sdp ================================================ v=0 o=bob 2808844564 2808844564 IN IP4 host.biloxi.example.com s= c=IN IP4 host.biloxi.example.com t=0 0 m=audio 49172 RTP/AVP 99 a=rtpmap:99 iLBC/8000 m=video 51374 RTP/AVP 31 a=rtpmap:31 H261/90000 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/firefox-1.sdp ================================================ v=0 o=mozilla...THIS_IS_SDPARTA-46.0.1 5115930144083302970 0 IN IP4 0.0.0.0 s=- t=0 0 a=fingerprint:sha-256 24:67:5E:1B:9A:B9:CF:36:C5:30:8F:35:F7:B1:50:66:88:81:92:CB:29:BA:53:A5:02:C8:0A:A5:4E:9C:AE:D9 a=group:BUNDLE sdparta_0 sdparta_1 sdparta_2 a=ice-options:trickle a=msid-semantic:WMS * m=audio 9 UDP/TLS/RTP/SAVPF 109 9 0 8 c=IN IP4 0.0.0.0 a=sendrecv a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=fmtp:109 maxplaybackrate=48000;stereo=1 a=ice-pwd:b9f3911b591ae61e2d7f6af0531fd2a3 a=ice-ufrag:3edc9012 a=mid:sdparta_0 a=msid:{258e92fb-547c-40ca-92e9-efe0cedb4cba} {bd1fafff-bfd0-40d4-b0a3-2a87cff307ee} a=rtcp-mux a=rtpmap:109 opus/48000/2 a=rtpmap:9 G722/8000/1 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=setup:actpass a=ssrc:2121669360 cname:{387b0735-bde2-43a4-8484-7f5663b60f24} m=video 9 UDP/TLS/RTP/SAVPF 120 126 97 c=IN IP4 0.0.0.0 a=sendrecv a=fmtp:126 profile-level-id=42e01f;level-asymmetry-allowed=1;packetization-mode=1 a=fmtp:97 profile-level-id=42e01f;level-asymmetry-allowed=1 a=fmtp:120 max-fs=12288;max-fr=60 a=ice-pwd:b9f3911b591ae61e2d7f6af0531fd2a3 a=ice-ufrag:3edc9012 a=mid:sdparta_1 a=msid:{258e92fb-547c-40ca-92e9-efe0cedb4cba} {9e8f5867-a9aa-4489-8bd4-3a8a57a5e592} a=rtcp-fb:120 nack a=rtcp-fb:120 nack pli a=rtcp-fb:120 ccm fir a=rtcp-fb:126 nack a=rtcp-fb:126 nack pli a=rtcp-fb:126 ccm fir a=rtcp-fb:97 nack a=rtcp-fb:97 nack pli a=rtcp-fb:97 ccm fir a=rtcp-mux a=rtpmap:120 VP8/90000 a=rtpmap:126 H264/90000 a=rtpmap:97 H264/90000 a=setup:actpass a=ssrc:2158832026 cname:{387b0735-bde2-43a4-8484-7f5663b60f24} m=application 9 DTLS/SCTP 5000 c=IN IP4 0.0.0.0 a=sendrecv a=ice-pwd:b9f3911b591ae61e2d7f6af0531fd2a3 a=ice-ufrag:3edc9012 a=mid:sdparta_2 a=sctpmap:5000 webrtc-datachannel 256 a=setup:actpass a=ssrc:2670959794 cname:{387b0735-bde2-43a4-8484-7f5663b60f24} ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/firefox-2.sdp ================================================ v=0 o=mozilla...THIS_IS_SDPARTA-46.0.1 3068771576687940834 0 IN IP4 0.0.0.0 s=- t=0 0 a=fingerprint:sha-256 AD:87:B3:11:E4:E2:BA:EF:D2:3F:2E:AC:24:57:8E:DC:1F:67:41:29:44:C4:96:E3:62:90:CC:90:59:CA:2C:84 a=group:BUNDLE sdparta_0 sdparta_1 sdparta_2 a=ice-options:trickle a=msid-semantic:WMS * m=audio 9 UDP/TLS/RTP/SAVPF 109 c=IN IP4 0.0.0.0 a=recvonly a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=fmtp:109 maxplaybackrate=48000;stereo=1 a=ice-pwd:ff4c4dc6fe92e1f22d2c10352d8967d5 a=ice-ufrag:a539544b a=mid:sdparta_0 a=rtcp-mux a=rtpmap:109 opus/48000/2 a=setup:active a=ssrc:600995474 cname:{5b598a29-a81b-4ffe-a2c5-507778057e7a} m=video 9 UDP/TLS/RTP/SAVPF 120 c=IN IP4 0.0.0.0 a=recvonly a=fmtp:120 max-fs=12288;max-fr=60 a=ice-pwd:ff4c4dc6fe92e1f22d2c10352d8967d5 a=ice-ufrag:a539544b a=mid:sdparta_1 a=rtcp-fb:120 nack a=rtcp-fb:120 nack pli a=rtcp-fb:120 ccm fir a=rtcp-mux a=rtpmap:120 VP8/90000 a=setup:active a=ssrc:3480150809 cname:{5b598a29-a81b-4ffe-a2c5-507778057e7a} m=application 9 DTLS/SCTP 5000 c=IN IP4 0.0.0.0 a=sendrecv a=ice-pwd:ff4c4dc6fe92e1f22d2c10352d8967d5 a=ice-ufrag:a539544b a=mid:sdparta_2 a=sctpmap:5000 webrtc-datachannel 256 a=setup:active a=ssrc:3021788991 cname:{5b598a29-a81b-4ffe-a2c5-507778057e7a} ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/opera-1.sdp ================================================ v=0 o=- 1656229333038673902 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE audio video data a=msid-semantic: WMS Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:1Jyk4q3nLIL5NiMx a=ice-pwd:GL8/iarMqPIhImfnsG2dyXlH a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA a=setup:actpass a=mid:audio a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time a=sendrecv a=rtcp-mux a=rtpmap:111 opus/48000/2 a=rtcp-fb:111 transport-cc a=fmtp:111 minptime=10; useinbandfec=1 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=rtpmap:9 G722/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:106 CN/32000 a=rtpmap:105 CN/16000 a=rtpmap:13 CN/8000 a=rtpmap:126 telephone-event/8000 a=maxptime:60 a=ssrc:2233075910 cname:VhHMGYCjn4alR9zP a=ssrc:2233075910 msid:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 689d3496-0896-4d52-bce6-8e90512a368b a=ssrc:2233075910 mslabel:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 a=ssrc:2233075910 label:689d3496-0896-4d52-bce6-8e90512a368b m=video 9 UDP/TLS/RTP/SAVPF 100 101 116 117 96 97 98 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:1Jyk4q3nLIL5NiMx a=ice-pwd:GL8/iarMqPIhImfnsG2dyXlH a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA a=setup:actpass a=mid:video a=extmap:2 urn:ietf:params:rtp-hdrext:toffset a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time a=extmap:4 urn:3gpp:video-orientation a=sendrecv a=rtcp-mux a=rtpmap:100 VP8/90000 a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=rtcp-fb:100 goog-remb a=rtcp-fb:100 transport-cc a=rtpmap:101 VP9/90000 a=rtcp-fb:101 ccm fir a=rtcp-fb:101 nack a=rtcp-fb:101 nack pli a=rtcp-fb:101 goog-remb a=rtcp-fb:101 transport-cc a=rtpmap:116 red/90000 a=rtpmap:117 ulpfec/90000 a=rtpmap:96 rtx/90000 a=fmtp:96 apt=100 a=rtpmap:97 rtx/90000 a=fmtp:97 apt=101 a=rtpmap:98 rtx/90000 a=fmtp:98 apt=116 a=ssrc-group:FID 50498894 2399294607 a=ssrc:50498894 cname:VhHMGYCjn4alR9zP a=ssrc:50498894 msid:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 1aef96f4-fc4c-4f86-98f3-0fbf4f625f70 a=ssrc:50498894 mslabel:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 a=ssrc:50498894 label:1aef96f4-fc4c-4f86-98f3-0fbf4f625f70 a=ssrc:2399294607 cname:VhHMGYCjn4alR9zP a=ssrc:2399294607 msid:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 1aef96f4-fc4c-4f86-98f3-0fbf4f625f70 a=ssrc:2399294607 mslabel:Ppsa09YmDLBombOh5e8HqfqxEIPF69a46Hd4 a=ssrc:2399294607 label:1aef96f4-fc4c-4f86-98f3-0fbf4f625f70 m=application 9 DTLS/SCTP 5000 c=IN IP4 0.0.0.0 a=ice-ufrag:1Jyk4q3nLIL5NiMx a=ice-pwd:GL8/iarMqPIhImfnsG2dyXlH a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA a=setup:actpass a=mid:data a=sctpmap:5000 webrtc-datachannel 1024 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/opera-2.sdp ================================================ v=0 o=- 2013283641453412290 2 IN IP4 127.0.0.1 s=- t=0 0 a=group:BUNDLE audio video data a=msid-semantic: WMS m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104 9 0 8 106 105 13 126 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:YVa3KTlFDCwsfPOQ a=ice-pwd:ByUn1Od88VokVM0rtQ/bbeZa a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA a=setup:active a=mid:audio a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time a=recvonly a=rtcp-mux a=rtpmap:111 opus/48000/2 a=rtcp-fb:111 transport-cc a=fmtp:111 minptime=10; useinbandfec=1 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=rtpmap:9 G722/8000 a=rtpmap:0 PCMU/8000 a=rtpmap:8 PCMA/8000 a=rtpmap:106 CN/32000 a=rtpmap:105 CN/16000 a=rtpmap:13 CN/8000 a=rtpmap:126 telephone-event/8000 a=maxptime:60 m=video 9 UDP/TLS/RTP/SAVPF 100 101 116 117 96 97 98 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:YVa3KTlFDCwsfPOQ a=ice-pwd:ByUn1Od88VokVM0rtQ/bbeZa a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA a=setup:active a=mid:video a=extmap:2 urn:ietf:params:rtp-hdrext:toffset a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time a=extmap:4 urn:3gpp:video-orientation a=recvonly a=rtcp-mux a=rtpmap:100 VP8/90000 a=rtcp-fb:100 ccm fir a=rtcp-fb:100 nack a=rtcp-fb:100 nack pli a=rtcp-fb:100 goog-remb a=rtcp-fb:100 transport-cc a=rtpmap:101 VP9/90000 a=rtcp-fb:101 ccm fir a=rtcp-fb:101 nack a=rtcp-fb:101 nack pli a=rtcp-fb:101 goog-remb a=rtcp-fb:101 transport-cc a=rtpmap:116 red/90000 a=rtpmap:117 ulpfec/90000 a=rtpmap:96 rtx/90000 a=fmtp:96 apt=100 a=rtpmap:97 rtx/90000 a=fmtp:97 apt=101 a=rtpmap:98 rtx/90000 a=fmtp:98 apt=116 m=application 9 DTLS/SCTP 5000 c=IN IP4 0.0.0.0 b=AS:30 a=ice-ufrag:YVa3KTlFDCwsfPOQ a=ice-pwd:ByUn1Od88VokVM0rtQ/bbeZa a=fingerprint:sha-256 5A:16:96:94:B2:AC:60:27:64:C5:FE:46:6C:02:C0:CD:49:E3:E2:0B:5B:C9:D4:86:C4:B3:A4:F2:23:80:7A:DA a=setup:active a=mid:data a=sctpmap:5000 webrtc-datachannel 1024 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-1.sdp ================================================ v=0 o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 s=- t=0 0 a=msid-semantic: WMS local_stream_1 m=audio 2345 RTP/SAVPF 111 103 104 c=IN IP4 74.125.127.126 a=rtcp:2347 IN IP4 74.125.127.126 a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2 a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host generation 2 a=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host generation 2 a=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host generation 2 a=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx raddr 192.168.1.5 rport 2346 generation 2 a=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx raddr 192.168.1.5 rport 2348 generation 2 a=ice-ufrag:ufrag_voice a=ice-pwd:pwd_voice a=mid:audio_content_name a=sendrecv a=rtcp-mux a=rtcp-rsize a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params a=rtpmap:111 opus/48000/2 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=ssrc:1 cname:stream_1_cname a=ssrc:1 msid:local_stream_1 audio_track_id_1 a=ssrc:1 mslabel:local_stream_1 a=ssrc:1 label:audio_track_id_1 m=video 3457 RTP/SAVPF 120 c=IN IP4 74.125.224.39 a=rtcp:3456 IN IP4 74.125.224.39 a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host generation 2 a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host generation 2 a=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host generation 2 a=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host generation 2 a=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay generation 2 a=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay generation 2 a=ice-ufrag:ufrag_video a=ice-pwd:pwd_video a=mid:video_content_name a=sendrecv a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 a=rtpmap:120 VP8/90000 a=ssrc-group:FEC 2 3 a=ssrc:2 cname:stream_1_cname a=ssrc:2 msid:local_stream_1 video_track_id_1 a=ssrc:2 mslabel:local_stream_1 a=ssrc:2 label:video_track_id_1 a=ssrc:3 cname:stream_1_cname a=ssrc:3 msid:local_stream_1 video_track_id_1 a=ssrc:3 mslabel:local_stream_1 a=ssrc:3 label:video_track_id_1 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-2.sdp ================================================ v=0 o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 s=- t=0 0 a=msid-semantic: WMS local_stream_1 m=audio 9 RTP/SAVPF 111 103 104 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:ufrag_voice a=ice-pwd:pwd_voice a=mid:audio_content_name a=sendrecv a=rtcp-mux a=rtcp-rsize a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params a=rtpmap:111 opus/48000/2 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=ssrc:1 cname:stream_1_cname a=ssrc:1 msid:local_stream_1 audio_track_id_1 a=ssrc:1 mslabel:local_stream_1 a=ssrc:1 label:audio_track_id_1 m=video 9 RTP/SAVPF 120 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:ufrag_video a=ice-pwd:pwd_video a=mid:video_content_name a=sendrecv a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 a=rtpmap:120 VP8/90000 a=ssrc-group:FEC 2 3 a=ssrc:2 cname:stream_1_cname a=ssrc:2 msid:local_stream_1 video_track_id_1 a=ssrc:2 mslabel:local_stream_1 a=ssrc:2 label:video_track_id_1 a=ssrc:3 cname:stream_1_cname a=ssrc:3 msid:local_stream_1 video_track_id_1 a=ssrc:3 mslabel:local_stream_1 a=ssrc:3 label:video_track_id_1 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-3.sdp ================================================ m=application 9 RTP/SAVPF 101 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:ufrag_data a=ice-pwd:pwd_data a=mid:data_content_name a=sendrecv a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:FvLcvU2P3ZWmQxgPAgcDu7Zl9vftYElFOjEzhWs5 a=rtpmap:101 google-data/90000 a=ssrc:10 cname:data_channel_cname a=ssrc:10 msid:data_channel data_channeld0 a=ssrc:10 mslabel:data_channel a=ssrc:10 label:data_channeld0 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-4.sdp ================================================ v=0 o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 s=- t=0 0 a=msid-semantic: WMS m=audio 9 RTP/SAVPF 111 103 104 c=IN IP4 0.0.0.0 a=x-google-flag:conference m=video 9 RTP/SAVPF 120 c=IN IP4 0.0.0.0 a=x-google-flag:conference ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-5.sdp ================================================ v=0 o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 s=- t=0 0 a=msid-semantic: WMS local_stream ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-6.sdp ================================================ m=audio 9 RTP/SAVPF 111 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:ufrag_voice a=ice-pwd:pwd_voice a=mid:audio_content_name a=sendrecv a=rtpmap:111 opus/48000/2 a=ssrc:1 cname:stream_1_cname a=ssrc:1 msid:local_stream audio_track_id_1 a=ssrc:1 mslabel:local_stream a=ssrc:1 label:audio_track_id_1 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-7.sdp ================================================ m=video 9 RTP/SAVPF 120 c=IN IP4 0.0.0.0 a=rtcp:9 IN IP4 0.0.0.0 a=ice-ufrag:ufrag_video a=ice-pwd:pwd_video a=mid:video_content_name a=sendrecv a=rtpmap:120 VP8/90000 a=ssrc:2 cname:stream_1_cname a=ssrc:2 msid:local_stream video_track_id_1 a=ssrc:2 mslabel:local_stream a=ssrc:2 label:video_track_id_1 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-8.sdp ================================================ v=0 o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 s=- t=0 0 a=msid-semantic: WMS local_stream_1 local_stream_2 m=audio 2345 RTP/SAVPF 111 103 104 c=IN IP4 74.125.127.126 a=rtcp:2347 IN IP4 74.125.127.126 a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2 a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host generation 2 a=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host generation 2 a=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host generation 2 a=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx raddr 192.168.1.5 rport 2346 generation 2 a=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx raddr 192.168.1.5 rport 2348 generation 2 a=ice-ufrag:ufrag_voice a=ice-pwd:pwd_voice a=mid:audio_content_name a=sendrecv a=rtcp-mux a=rtcp-rsize a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params a=rtpmap:111 opus/48000/2 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=ssrc:1 cname:stream_1_cname a=ssrc:1 msid:local_stream_1 audio_track_id_1 a=ssrc:1 mslabel:local_stream_1 a=ssrc:1 label:audio_track_id_1 a=ssrc:4 cname:stream_2_cname a=ssrc:4 msid:local_stream_2 audio_track_id_2 a=ssrc:4 mslabel:local_stream_2 a=ssrc:4 label:audio_track_id_2 m=video 3457 RTP/SAVPF 120 c=IN IP4 74.125.224.39 a=rtcp:3456 IN IP4 74.125.224.39 a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host generation 2 a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host generation 2 a=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host generation 2 a=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host generation 2 a=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay generation 2 a=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay generation 2 a=ice-ufrag:ufrag_video a=ice-pwd:pwd_video a=mid:video_content_name a=sendrecv a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 a=rtpmap:120 VP8/90000 a=ssrc-group:FEC 2 3 a=ssrc:2 cname:stream_1_cname a=ssrc:2 msid:local_stream_1 video_track_id_1 a=ssrc:2 mslabel:local_stream_1 a=ssrc:2 label:video_track_id_1 a=ssrc:3 cname:stream_1_cname a=ssrc:3 msid:local_stream_1 video_track_id_1 a=ssrc:3 mslabel:local_stream_1 a=ssrc:3 label:video_track_id_1 a=ssrc:5 cname:stream_2_cname a=ssrc:5 msid:local_stream_2 video_track_id_2 a=ssrc:5 mslabel:local_stream_2 a=ssrc:5 label:video_track_id_2 a=ssrc:6 cname:stream_2_cname a=ssrc:6 msid:local_stream_2 video_track_id_3 a=ssrc:6 mslabel:local_stream_2 a=ssrc:6 label:video_track_id_3 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/corpora/sdp-corpus/unittest-9.sdp ================================================ v=0 o=- 18446744069414584320 18446462598732840960 IN IP4 127.0.0.1 s=- t=0 0 a=msid-semantic: WMS local_stream_1 local_stream_2 m=audio 2345 RTP/SAVPF 111 103 104 c=IN IP4 74.125.127.126 a=rtcp:2347 IN IP4 74.125.127.126 a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1234 typ host generation 2 a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1235 typ host generation 2 a=candidate:a0+B/2 1 udp 2130706432 ::1 1238 typ host generation 2 a=candidate:a0+B/2 2 udp 2130706432 ::1 1239 typ host generation 2 a=candidate:a0+B/3 1 udp 2130706432 74.125.127.126 2345 typ srflx raddr 192.168.1.5 rport 2346 generation 2 a=candidate:a0+B/3 2 udp 2130706432 74.125.127.126 2347 typ srflx raddr 192.168.1.5 rport 2348 generation 2 a=ice-ufrag:ufrag_voice a=ice-pwd:pwd_voice a=mid:audio_content_name a=msid:local_stream_1 audio_track_id_1 a=sendrecv a=rtcp-mux a=rtcp-rsize a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32 dummy_session_params a=rtpmap:111 opus/48000/2 a=rtpmap:103 ISAC/16000 a=rtpmap:104 ISAC/32000 a=ssrc:1 cname:stream_1_cname a=ssrc:1 msid:local_stream_1 audio_track_id_1 a=ssrc:1 mslabel:local_stream_1 a=ssrc:1 label:audio_track_id_1 a=ssrc:4 cname:stream_2_cname a=ssrc:4 msid:local_stream_2 audio_track_id_2 a=ssrc:4 mslabel:local_stream_2 a=ssrc:4 label:audio_track_id_2 m=video 3457 RTP/SAVPF 120 c=IN IP4 74.125.224.39 a=rtcp:3456 IN IP4 74.125.224.39 a=candidate:a0+B/1 2 udp 2130706432 192.168.1.5 1236 typ host generation 2 a=candidate:a0+B/1 1 udp 2130706432 192.168.1.5 1237 typ host generation 2 a=candidate:a0+B/2 2 udp 2130706432 ::1 1240 typ host generation 2 a=candidate:a0+B/2 1 udp 2130706432 ::1 1241 typ host generation 2 a=candidate:a0+B/4 2 udp 2130706432 74.125.224.39 3456 typ relay generation 2 a=candidate:a0+B/4 1 udp 2130706432 74.125.224.39 3457 typ relay generation 2 a=ice-ufrag:ufrag_video a=ice-pwd:pwd_video a=mid:video_content_name a=msid:local_stream_1 video_track_id_1 a=sendrecv a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32 a=rtpmap:120 VP8/90000 a=ssrc-group:FEC 2 3 a=ssrc:2 cname:stream_1_cname a=ssrc:2 msid:local_stream_1 video_track_id_1 a=ssrc:2 mslabel:local_stream_1 a=ssrc:2 label:video_track_id_1 a=ssrc:3 cname:stream_1_cname a=ssrc:3 msid:local_stream_1 video_track_id_1 a=ssrc:3 mslabel:local_stream_1 a=ssrc:3 label:video_track_id_1 a=ssrc:5 cname:stream_2_cname a=ssrc:5 msid:local_stream_2 video_track_id_2 a=ssrc:5 mslabel:local_stream_2 a=ssrc:5 label:video_track_id_2 a=ssrc:6 cname:stream_2_cname a=ssrc:6 msid:local_stream_2 video_track_id_3 a=ssrc:6 mslabel:local_stream_2 a=ssrc:6 label:video_track_id_3 ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/reports/crashes/.placeholder ================================================ ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/reports/crashes/rtp/crash-7e2d460edd5d5d7f5548922f10489f468d1638bf ================================================ (print_fusDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/reports/memory-leaks/.placeholder ================================================ ================================================ FILE: worker/deps/webrtc-fuzzer-corpora/reports/timeouts/.placeholder ================================================ ================================================ FILE: worker/fbs/activeSpeakerObserver.fbs ================================================ namespace FBS.ActiveSpeakerObserver; table ActiveSpeakerObserverOptions { interval: uint16; } // Notifications from Worker. table DominantSpeakerNotification { producer_id: string (required); } ================================================ FILE: worker/fbs/audioLevelObserver.fbs ================================================ namespace FBS.AudioLevelObserver; table AudioLevelObserverOptions { max_entries: uint16; threshold: int8; interval: uint16; } // Notifications from Worker. table Volume { producer_id: string (required); volume: int8; } table VolumesNotification { volumes: [Volume] (required); } ================================================ FILE: worker/fbs/common.fbs ================================================ namespace FBS.Common; table StringString { key: string (required); value: string (required); } table StringUint8 { key: string (required); value: uint8; } table Uint16String { key: uint16; value: string (required); } table Uint32String { key: uint32; value: string (required); } table StringStringArray { key: string (required); values: [string] (required); } // NOTE (windows): IN|OUT are macros defined in windef.h. enum TraceDirection: uint8 { DIRECTION_IN = 0, DIRECTION_OUT } ================================================ FILE: worker/fbs/consumer.fbs ================================================ include "common.fbs"; include "rtpPacket.fbs"; include "rtpParameters.fbs"; include "rtpStream.fbs"; namespace FBS.Consumer; table ConsumerLayers { spatial_layer: uint8; temporal_layer: uint8 = null; } table ConsumerScore { score: uint8; producer_score: uint8; producer_scores: [uint8] (required); } table SetPreferredLayersRequest { preferred_layers: ConsumerLayers (required); } table SetPreferredLayersResponse { preferred_layers: ConsumerLayers; } table SetPriorityRequest { priority: uint8; } table SetPriorityResponse { priority: uint8; } enum TraceEventType: uint8 { KEYFRAME = 0, FIR, NACK, PLI, RTP, } table EnableTraceEventRequest { events: [TraceEventType] (required); } table DumpResponse { data: ConsumerDump (required); } table BaseConsumerDump { id: string (required); type: FBS.RtpParameters.Type; producer_id: string (required); kind: FBS.RtpParameters.MediaKind; rtp_parameters: FBS.RtpParameters.RtpParameters (required); consumable_rtp_encodings: [FBS.RtpParameters.RtpEncodingParameters] (required); supported_codec_payload_types: [uint8] (required); trace_event_types: [TraceEventType] (required); paused: bool; producer_paused: bool; priority: uint8; } table ConsumerDump { base: BaseConsumerDump (required); rtp_streams: [FBS.RtpStream.Dump] (required); preferred_spatial_layer: int16 = null; target_spatial_layer: int16 = null; current_spatial_layer: int16 = null; preferred_temporal_layer: int16 = null; target_temporal_layer: int16 = null; current_temporal_layer: int16 = null; } table GetStatsResponse { stats: [FBS.RtpStream.Stats] (required); } // Notifications from Worker. table LayersChangeNotification { layers: ConsumerLayers; } table RtpNotification { data: [ubyte] (required); } table ScoreNotification { score: ConsumerScore (required); } union TraceInfo { KeyFrameTraceInfo, FirTraceInfo, PliTraceInfo, RtpTraceInfo, } table KeyFrameTraceInfo { rtp_packet: FBS.RtpPacket.Dump (required); is_rtx: bool; } table FirTraceInfo { ssrc: uint32; } table PliTraceInfo { ssrc: uint32; } table RtpTraceInfo { rtp_packet: FBS.RtpPacket.Dump (required); is_rtx: bool; } table TraceNotification { type: TraceEventType; timestamp: uint64; direction: FBS.Common.TraceDirection; info: TraceInfo; } ================================================ FILE: worker/fbs/dataConsumer.fbs ================================================ include "common.fbs"; include "dataProducer.fbs"; include "sctpParameters.fbs"; namespace FBS.DataConsumer; table GetBufferedAmountResponse { buffered_amount: uint32; } table SetBufferedAmountLowThresholdRequest { threshold: uint32; } table DumpResponse { id: string (required); data_producer_id: string (required); type: FBS.DataProducer.Type; sctp_stream_parameters: FBS.SctpParameters.SctpStreamParameters; label: string (required); protocol: string (required); buffered_amount_low_threshold: uint32; paused: bool; data_producer_paused: bool; subchannels: [uint16] (required); } table GetStatsResponse { timestamp: uint64; label: string (required); protocol: string (required); messages_sent: uint64; bytes_sent: uint64; buffered_amount: uint32; } table SendRequest { ppid: uint32; data: [uint8] (required); } table SetSubchannelsRequest { subchannels: [uint16] (required); } table SetSubchannelsResponse { subchannels: [uint16] (required); } table AddSubchannelRequest { subchannel: uint16; } table AddSubchannelResponse { subchannels: [uint16] (required); } table RemoveSubchannelRequest { subchannel: uint16; } table RemoveSubchannelResponse { subchannels: [uint16] (required); } // Notifications from Worker. table BufferedAmountLowNotification { buffered_amount: uint32; } table MessageNotification { ppid: uint32; data: [uint8] (required); } ================================================ FILE: worker/fbs/dataProducer.fbs ================================================ include "sctpParameters.fbs"; namespace FBS.DataProducer; enum Type: uint8 { SCTP, DIRECT } table DumpResponse { id: string (required); type: Type; sctp_stream_parameters: FBS.SctpParameters.SctpStreamParameters; label: string (required); protocol: string (required); paused: bool; } table GetStatsResponse { timestamp: uint64; label: string (required); protocol: string (required); messages_received: uint64; bytes_received: uint64; buffered_amount: uint32; } table SendNotification { ppid: uint32; data: [uint8] (required); subchannels: [uint16]; required_subchannel: uint16 = null; } ================================================ FILE: worker/fbs/directTransport.fbs ================================================ include "transport.fbs"; namespace FBS.DirectTransport; table DirectTransportOptions { base: FBS.Transport.Options (required); } table DumpResponse { base: FBS.Transport.Dump (required); } table GetStatsResponse { base: FBS.Transport.Stats (required); } // Notifications from Worker. table RtcpNotification { data: [uint8] (required); } ================================================ FILE: worker/fbs/liburing.fbs ================================================ namespace FBS.LibUring; table Dump { sqe_process_count: uint64; sqe_miss_count: uint64; user_data_miss_count: uint64; } ================================================ FILE: worker/fbs/log.fbs ================================================ namespace FBS.Log; table Log { data: string (required); } ================================================ FILE: worker/fbs/meson.build ================================================ flatbuffers_schemas = [ 'activeSpeakerObserver.fbs', 'audioLevelObserver.fbs', 'common.fbs', 'consumer.fbs', 'dataConsumer.fbs', 'dataProducer.fbs', 'directTransport.fbs', 'liburing.fbs', 'log.fbs', 'message.fbs', 'notification.fbs', 'pipeTransport.fbs', 'plainTransport.fbs', 'producer.fbs', 'request.fbs', 'response.fbs', 'router.fbs', 'rtpObserver.fbs', 'rtpPacket.fbs', 'rtpParameters.fbs', 'rtpStream.fbs', 'rtxStream.fbs', 'sctpAssociation.fbs', 'sctpParameters.fbs', 'srtpParameters.fbs', 'transport.fbs', 'webRtcServer.fbs', 'webRtcTransport.fbs', 'worker.fbs', ] # Directory from which worker code will include the header files. flatbuffers_cpp_out_dir = 'FBS' flatc = find_program('flatc') flatbuffers_generator = custom_target('flatbuffers-generator', output: flatbuffers_cpp_out_dir, input: flatbuffers_schemas, command : [ flatc, '--cpp', '--cpp-field-case-style', 'lower', '--reflect-names', '--scoped-enums', '--filename-suffix', '', '-o', '@OUTPUT@', '@INPUT@' ], build_by_default: true, ) flatbuffers_generator_dep = declare_dependency( include_directories: '.', ) ================================================ FILE: worker/fbs/message.fbs ================================================ include "log.fbs"; include "notification.fbs"; include "request.fbs"; include "response.fbs"; namespace FBS.Message; union Body { Request: FBS.Request.Request, Response: FBS.Response.Response, Notification: FBS.Notification.Notification, Log: FBS.Log.Log, } table Message { data: Body (required); } root_type Message; ================================================ FILE: worker/fbs/notification.fbs ================================================ include "transport.fbs"; include "webRtcTransport.fbs"; include "plainTransport.fbs"; include "directTransport.fbs"; include "producer.fbs"; include "dataProducer.fbs"; include "dataConsumer.fbs"; include "activeSpeakerObserver.fbs"; include "audioLevelObserver.fbs"; namespace FBS.Notification; enum Event: uint8 { // Notifications to worker. WORKER_CLOSE = 0, TRANSPORT_SEND_RTCP, PRODUCER_SEND, DATAPRODUCER_SEND, // Notifications from worker. WORKER_RUNNING, TRANSPORT_SCTP_STATE_CHANGE, TRANSPORT_TRACE, WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE, WEBRTCTRANSPORT_ICE_STATE_CHANGE, WEBRTCTRANSPORT_DTLS_STATE_CHANGE, PLAINTRANSPORT_TUPLE, PLAINTRANSPORT_RTCP_TUPLE, DIRECTTRANSPORT_RTCP, PRODUCER_SCORE, PRODUCER_TRACE, PRODUCER_VIDEO_ORIENTATION_CHANGE, CONSUMER_PRODUCER_PAUSE, CONSUMER_PRODUCER_RESUME, CONSUMER_PRODUCER_CLOSE, CONSUMER_LAYERS_CHANGE, CONSUMER_RTP, CONSUMER_SCORE, CONSUMER_TRACE, DATACONSUMER_BUFFERED_AMOUNT_LOW, DATACONSUMER_SCTP_SENDBUFFER_FULL, DATACONSUMER_DATAPRODUCER_PAUSE, DATACONSUMER_DATAPRODUCER_RESUME, DATACONSUMER_DATAPRODUCER_CLOSE, DATACONSUMER_MESSAGE, ACTIVESPEAKEROBSERVER_DOMINANT_SPEAKER, AUDIOLEVELOBSERVER_SILENCE, AUDIOLEVELOBSERVER_VOLUMES, } union Body { // Notifications to worker. Transport_SendRtcpNotification: FBS.Transport.SendRtcpNotification, Transport_SctpStateChangeNotification: FBS.Transport.SctpStateChangeNotification, Producer_SendNotification: FBS.Producer.SendNotification, DataProducer_SendNotification: FBS.DataProducer.SendNotification, // Notifications from worker. Transport_TraceNotification: FBS.Transport.TraceNotification, WebRtcTransport_IceSelectedTupleChangeNotification: FBS.WebRtcTransport.IceSelectedTupleChangeNotification, WebRtcTransport_IceStateChangeNotification: FBS.WebRtcTransport.IceStateChangeNotification, WebRtcTransport_DtlsStateChangeNotification: FBS.WebRtcTransport.DtlsStateChangeNotification, PlainTransport_TupleNotification: FBS.PlainTransport.TupleNotification, PlainTransport_RtcpTupleNotification: FBS.PlainTransport.RtcpTupleNotification, DirectTransport_RtcpNotification: FBS.DirectTransport.RtcpNotification, Producer_ScoreNotification: FBS.Producer.ScoreNotification, Producer_TraceNotification: FBS.Producer.TraceNotification, Producer_VideoOrientationChangeNotification: FBS.Producer.VideoOrientationChangeNotification, Consumer_LayersChangeNotification: FBS.Consumer.LayersChangeNotification, Consumer_RtpNotification: FBS.Consumer.RtpNotification, Consumer_ScoreNotification: FBS.Consumer.ScoreNotification, Consumer_TraceNotification: FBS.Consumer.TraceNotification, DataConsumer_MessageNotification: FBS.DataConsumer.MessageNotification, DataConsumer_BufferedAmountLowNotification: FBS.DataConsumer.BufferedAmountLowNotification, ActiveSpeakerObserver_DominantSpeakerNotification: FBS.ActiveSpeakerObserver.DominantSpeakerNotification, AudioLevelObserver_VolumesNotification: FBS.AudioLevelObserver.VolumesNotification, } table Notification { handler_id: string (required); event: Event; body: Body; } ================================================ FILE: worker/fbs/pipeTransport.fbs ================================================ include "transport.fbs"; include "srtpParameters.fbs"; namespace FBS.PipeTransport; table PipeTransportOptions { base: FBS.Transport.Options (required); listen_info: FBS.Transport.ListenInfo (required); enable_rtx: bool; enable_srtp: bool; } table ConnectRequest { ip: string (required); port: uint16 = null; srtp_parameters: FBS.SrtpParameters.SrtpParameters; } table ConnectResponse { tuple: FBS.Transport.Tuple (required); } table DumpResponse { base: FBS.Transport.Dump (required); tuple: FBS.Transport.Tuple (required); rtx: bool; srtp_parameters: FBS.SrtpParameters.SrtpParameters; } table GetStatsResponse { base: FBS.Transport.Stats (required); tuple: FBS.Transport.Tuple (required); } ================================================ FILE: worker/fbs/plainTransport.fbs ================================================ include "transport.fbs"; include "sctpParameters.fbs"; include "srtpParameters.fbs"; namespace FBS.PlainTransport; table PlainTransportOptions { base: FBS.Transport.Options (required); listen_info: FBS.Transport.ListenInfo (required); rtcp_listen_info: FBS.Transport.ListenInfo; rtcp_mux: bool; comedia: bool; enable_srtp: bool; srtp_crypto_suite: FBS.SrtpParameters.SrtpCryptoSuite = null; } table ConnectRequest { ip: string; port: uint16 = null; rtcp_port: uint16 = null; srtp_parameters: FBS.SrtpParameters.SrtpParameters; } table ConnectResponse { tuple: FBS.Transport.Tuple (required); rtcp_tuple: FBS.Transport.Tuple; srtp_parameters: FBS.SrtpParameters.SrtpParameters; } table DumpResponse { base: FBS.Transport.Dump (required); rtcp_mux: bool; comedia: bool; tuple: FBS.Transport.Tuple (required); rtcp_tuple: FBS.Transport.Tuple; srtp_parameters: FBS.SrtpParameters.SrtpParameters; } table GetStatsResponse { base: FBS.Transport.Stats (required); rtcp_mux: bool; comedia: bool; tuple: FBS.Transport.Tuple (required); rtcp_tuple: FBS.Transport.Tuple; } // Notifications from Worker. table TupleNotification { tuple: FBS.Transport.Tuple (required); } table RtcpTupleNotification { tuple: FBS.Transport.Tuple (required); } ================================================ FILE: worker/fbs/producer.fbs ================================================ include "common.fbs"; include "rtpPacket.fbs"; include "rtpParameters.fbs"; include "rtpStream.fbs"; namespace FBS.Producer; enum TraceEventType: uint8 { KEYFRAME = 0, FIR, NACK, PLI, RTP, SR, } table EnableTraceEventRequest { events: [TraceEventType] (required); } table DumpResponse { id: string (required); kind: FBS.RtpParameters.MediaKind; type: FBS.RtpParameters.Type; rtp_parameters: FBS.RtpParameters.RtpParameters (required); rtp_mapping: FBS.RtpParameters.RtpMapping (required); rtp_streams: [FBS.RtpStream.Dump] (required); trace_event_types: [TraceEventType] (required); paused: bool; } table GetStatsResponse { stats: [FBS.RtpStream.Stats] (required); } table SendNotification { data: [uint8] (required); } // Notifications from Worker. table Score { encoding_idx: uint32; ssrc: uint32; rid: string; score: uint8; } table ScoreNotification { scores: [Score] (required); } table VideoOrientationChangeNotification { camera: bool; flip: bool; rotation: uint16; } union TraceInfo { KeyFrameTraceInfo, FirTraceInfo, PliTraceInfo, RtpTraceInfo, SrTraceInfo, } table KeyFrameTraceInfo { rtp_packet: FBS.RtpPacket.Dump (required); is_rtx: bool; } table FirTraceInfo { ssrc: uint32; } table PliTraceInfo { ssrc: uint32; } table RtpTraceInfo { rtp_packet: FBS.RtpPacket.Dump (required); is_rtx: bool; } table SrTraceInfo { ssrc: uint32; ntp_sec: uint32; ntp_frac: uint32; rtp_ts: uint32; packet_count: uint32; octet_count: uint32; } table TraceNotification { type: TraceEventType; timestamp: uint64; direction: FBS.Common.TraceDirection; info: TraceInfo; } ================================================ FILE: worker/fbs/request.fbs ================================================ include "worker.fbs"; include "router.fbs"; include "transport.fbs"; include "producer.fbs"; include "consumer.fbs"; include "dataConsumer.fbs"; include "rtpObserver.fbs"; namespace FBS.Request; enum Method: uint8 { WORKER_DUMP = 0, WORKER_GET_RESOURCE_USAGE, WORKER_UPDATE_SETTINGS, WORKER_CREATE_WEBRTCSERVER, WORKER_CREATE_ROUTER, WORKER_WEBRTCSERVER_CLOSE, WORKER_CLOSE_ROUTER, WEBRTCSERVER_DUMP, ROUTER_DUMP, ROUTER_CREATE_WEBRTCTRANSPORT, ROUTER_CREATE_WEBRTCTRANSPORT_WITH_SERVER, ROUTER_CREATE_PLAINTRANSPORT, ROUTER_CREATE_PIPETRANSPORT, ROUTER_CREATE_DIRECTTRANSPORT, ROUTER_CLOSE_TRANSPORT, ROUTER_CREATE_ACTIVESPEAKEROBSERVER, ROUTER_CREATE_AUDIOLEVELOBSERVER, ROUTER_CLOSE_RTPOBSERVER, TRANSPORT_DUMP, TRANSPORT_GET_STATS, TRANSPORT_CONNECT, TRANSPORT_SET_MAX_INCOMING_BITRATE, TRANSPORT_SET_MAX_OUTGOING_BITRATE, TRANSPORT_SET_MIN_OUTGOING_BITRATE, TRANSPORT_RESTART_ICE, TRANSPORT_PRODUCE, TRANSPORT_PRODUCE_DATA, TRANSPORT_CONSUME, TRANSPORT_CONSUME_DATA, TRANSPORT_ENABLE_TRACE_EVENT, TRANSPORT_CLOSE_PRODUCER, TRANSPORT_CLOSE_CONSUMER, TRANSPORT_CLOSE_DATAPRODUCER, TRANSPORT_CLOSE_DATACONSUMER, PLAINTRANSPORT_CONNECT, PIPETRANSPORT_CONNECT, WEBRTCTRANSPORT_CONNECT, PRODUCER_DUMP, PRODUCER_GET_STATS, PRODUCER_PAUSE, PRODUCER_RESUME, PRODUCER_ENABLE_TRACE_EVENT, CONSUMER_DUMP, CONSUMER_GET_STATS, CONSUMER_PAUSE, CONSUMER_RESUME, CONSUMER_SET_PREFERRED_LAYERS, CONSUMER_SET_PRIORITY, CONSUMER_REQUEST_KEY_FRAME, CONSUMER_ENABLE_TRACE_EVENT, DATAPRODUCER_DUMP, DATAPRODUCER_GET_STATS, DATAPRODUCER_PAUSE, DATAPRODUCER_RESUME, DATACONSUMER_DUMP, DATACONSUMER_GET_STATS, DATACONSUMER_PAUSE, DATACONSUMER_RESUME, DATACONSUMER_GET_BUFFERED_AMOUNT, DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD, DATACONSUMER_SEND, DATACONSUMER_SET_SUBCHANNELS, DATACONSUMER_ADD_SUBCHANNEL, DATACONSUMER_REMOVE_SUBCHANNEL, RTPOBSERVER_PAUSE, RTPOBSERVER_RESUME, RTPOBSERVER_ADD_PRODUCER, RTPOBSERVER_REMOVE_PRODUCER, } union Body { Worker_UpdateSettingsRequest: FBS.Worker.UpdateSettingsRequest, Worker_CreateWebRtcServerRequest: FBS.Worker.CreateWebRtcServerRequest, Worker_CloseWebRtcServerRequest: FBS.Worker.CloseWebRtcServerRequest, Worker_CreateRouterRequest: FBS.Worker.CreateRouterRequest, Worker_CloseRouterRequest: FBS.Worker.CloseRouterRequest, Router_CreateWebRtcTransportRequest: FBS.Router.CreateWebRtcTransportRequest, Router_CreatePlainTransportRequest: FBS.Router.CreatePlainTransportRequest, Router_CreatePipeTransportRequest: FBS.Router.CreatePipeTransportRequest, Router_CreateDirectTransportRequest: FBS.Router.CreateDirectTransportRequest, Router_CreateActiveSpeakerObserverRequest: FBS.Router.CreateActiveSpeakerObserverRequest, Router_CreateAudioLevelObserverRequest: FBS.Router.CreateAudioLevelObserverRequest, Router_CloseTransportRequest: FBS.Router.CloseTransportRequest, Router_CloseRtpObserverRequest: FBS.Router.CloseRtpObserverRequest, Transport_SetMaxIncomingBitrateRequest: FBS.Transport.SetMaxIncomingBitrateRequest, Transport_SetMaxOutgoingBitrateRequest: FBS.Transport.SetMaxOutgoingBitrateRequest, Transport_SetMinOutgoingBitrateRequest: FBS.Transport.SetMinOutgoingBitrateRequest, Transport_ProduceRequest: FBS.Transport.ProduceRequest, Transport_ConsumeRequest: FBS.Transport.ConsumeRequest, Transport_ProduceDataRequest: FBS.Transport.ProduceDataRequest, Transport_ConsumeDataRequest: FBS.Transport.ConsumeDataRequest, Transport_EnableTraceEventRequest: FBS.Transport.EnableTraceEventRequest, Transport_CloseProducerRequest: FBS.Transport.CloseProducerRequest, Transport_CloseConsumerRequest: FBS.Transport.CloseConsumerRequest, Transport_CloseDataProducerRequest: FBS.Transport.CloseDataProducerRequest, Transport_CloseDataConsumerRequest: FBS.Transport.CloseDataConsumerRequest, PlainTransport_ConnectRequest: FBS.PlainTransport.ConnectRequest, PipeTransport_ConnectRequest: FBS.PipeTransport.ConnectRequest, WebRtcTransport_ConnectRequest: FBS.WebRtcTransport.ConnectRequest, Producer_EnableTraceEventRequest: FBS.Producer.EnableTraceEventRequest, Consumer_SetPreferredLayersRequest: FBS.Consumer.SetPreferredLayersRequest, Consumer_SetPriorityRequest: FBS.Consumer.SetPriorityRequest, Consumer_EnableTraceEventRequest: FBS.Consumer.EnableTraceEventRequest, DataConsumer_SetBufferedAmountLowThresholdRequest: FBS.DataConsumer.SetBufferedAmountLowThresholdRequest, DataConsumer_SendRequest: FBS.DataConsumer.SendRequest, DataConsumer_SetSubchannelsRequest: FBS.DataConsumer.SetSubchannelsRequest, DataConsumer_AddSubchannelRequest: FBS.DataConsumer.AddSubchannelRequest, DataConsumer_RemoveSubchannelRequest: FBS.DataConsumer.RemoveSubchannelRequest, RtpObserver_AddProducerRequest: FBS.RtpObserver.AddProducerRequest, RtpObserver_RemoveProducerRequest: FBS.RtpObserver.RemoveProducerRequest, } table Request { id: uint32; method: Method; handler_id: string (required); body: Body; } root_type Request; ================================================ FILE: worker/fbs/response.fbs ================================================ include "worker.fbs"; include "router.fbs"; include "webRtcServer.fbs"; include "transport.fbs"; include "producer.fbs"; include "consumer.fbs"; include "dataProducer.fbs"; include "dataConsumer.fbs"; namespace FBS.Response; union Body { Worker_DumpResponse: FBS.Worker.DumpResponse, Worker_ResourceUsageResponse: FBS.Worker.ResourceUsageResponse, WebRtcServer_DumpResponse: FBS.WebRtcServer.DumpResponse, Router_DumpResponse: FBS.Router.DumpResponse, Transport_ProduceResponse: FBS.Transport.ProduceResponse, Transport_ConsumeResponse: FBS.Transport.ConsumeResponse, Transport_RestartIceResponse: FBS.Transport.RestartIceResponse, PlainTransport_ConnectResponse: FBS.PlainTransport.ConnectResponse, PlainTransport_DumpResponse: FBS.PlainTransport.DumpResponse, PlainTransport_GetStatsResponse: FBS.PlainTransport.GetStatsResponse, PipeTransport_ConnectResponse: FBS.PipeTransport.ConnectResponse, PipeTransport_DumpResponse: FBS.PipeTransport.DumpResponse, PipeTransport_GetStatsResponse: FBS.PipeTransport.GetStatsResponse, DirectTransport_DumpResponse: FBS.DirectTransport.DumpResponse, DirectTransport_GetStatsResponse: FBS.DirectTransport.GetStatsResponse, WebRtcTransport_ConnectResponse: FBS.WebRtcTransport.ConnectResponse, WebRtcTransport_DumpResponse: FBS.WebRtcTransport.DumpResponse, WebRtcTransport_GetStatsResponse: FBS.WebRtcTransport.GetStatsResponse, Producer_DumpResponse: FBS.Producer.DumpResponse, Producer_GetStatsResponse: FBS.Producer.GetStatsResponse, Consumer_DumpResponse: FBS.Consumer.DumpResponse, Consumer_GetStatsResponse: FBS.Consumer.GetStatsResponse, Consumer_SetPreferredLayersResponse: FBS.Consumer.SetPreferredLayersResponse, Consumer_SetPriorityResponse: FBS.Consumer.SetPriorityResponse, DataProducer_DumpResponse: FBS.DataProducer.DumpResponse, DataProducer_GetStatsResponse: FBS.DataProducer.GetStatsResponse, DataConsumer_GetBufferedAmountResponse: FBS.DataConsumer.GetBufferedAmountResponse, DataConsumer_DumpResponse: FBS.DataConsumer.DumpResponse, DataConsumer_GetStatsResponse: FBS.DataConsumer.GetStatsResponse, DataConsumer_SetSubchannelsResponse: FBS.DataConsumer.SetSubchannelsResponse, DataConsumer_AddSubchannelResponse: FBS.DataConsumer.AddSubchannelResponse, DataConsumer_RemoveSubchannelResponse: FBS.DataConsumer.RemoveSubchannelResponse } table Response { id: uint32; accepted: bool; body: Body; error: string; reason: string; } ================================================ FILE: worker/fbs/router.fbs ================================================ include "common.fbs"; include "activeSpeakerObserver.fbs"; include "audioLevelObserver.fbs"; include "transport.fbs"; include "pipeTransport.fbs"; include "plainTransport.fbs"; include "webRtcTransport.fbs"; include "directTransport.fbs"; namespace FBS.Router; table DumpResponse { id: string (required); transport_ids: [string] (required); rtp_observer_ids: [string] (required); map_producer_id_consumer_ids: [FBS.Common.StringStringArray] (required); map_consumer_id_producer_id: [FBS.Common.StringString] (required); map_producer_id_observer_ids: [FBS.Common.StringStringArray] (required); map_data_producer_id_data_consumer_ids: [FBS.Common.StringStringArray] (required); map_data_consumer_id_data_producer_id: [FBS.Common.StringString] (required); } table CreatePipeTransportRequest { transport_id: string (required); options: FBS.PipeTransport.PipeTransportOptions (required); } table CreatePlainTransportRequest { transport_id: string (required); options: FBS.PlainTransport.PlainTransportOptions (required); } table CreateWebRtcTransportRequest { transport_id: string (required); options: FBS.WebRtcTransport.WebRtcTransportOptions (required); } table CreateDirectTransportRequest { transport_id: string (required); options: FBS.DirectTransport.DirectTransportOptions (required); } table CreateAudioLevelObserverRequest { rtp_observer_id: string (required); options: FBS.AudioLevelObserver.AudioLevelObserverOptions (required); } table CreateActiveSpeakerObserverRequest { rtp_observer_id: string (required); options: FBS.ActiveSpeakerObserver.ActiveSpeakerObserverOptions (required); } table CloseTransportRequest { transport_id: string (required); } table CloseRtpObserverRequest { rtp_observer_id: string (required); } ================================================ FILE: worker/fbs/rtpObserver.fbs ================================================ namespace FBS.RtpObserver; table AddProducerRequest { producer_id: string (required); } table RemoveProducerRequest { producer_id: string (required); } ================================================ FILE: worker/fbs/rtpPacket.fbs ================================================ include "common.fbs"; namespace FBS.RtpPacket; table Dump { payload_type: uint8; sequence_number: uint16; timestamp: uint32; marker: bool; ssrc: uint32; is_key_frame: bool; size: uint64; payload_size: uint64; spatial_layer: uint8; temporal_layer: uint8; mid: string; rid: string; rrid: string; wide_sequence_number: uint16 = null; } ================================================ FILE: worker/fbs/rtpParameters.fbs ================================================ namespace FBS.RtpParameters; enum MediaKind: uint8 { AUDIO, VIDEO } enum Type: uint8 { SIMPLE, SIMULCAST, SVC, PIPE } // Boolean is a uint8/byte type. table Boolean { value: uint8; } table Integer32 { value: int32; } table Integer32Array { value: [int32]; } table Double { value: double; } table String { value: string (required); } union Value { Boolean, Integer32, Double, String, Integer32Array, } table Parameter { name: string (required); value: Value (required); } table RtcpFeedback { // TODO: Create a specifc type. type: string (required); parameter: string; } table RtpCodecParameters { mime_type: string (required); payload_type: uint8; clock_rate: uint32; channels: uint8 = null; parameters: [Parameter]; rtcp_feedback: [RtcpFeedback]; } enum RtpHeaderExtensionUri: uint8 { Mid, RtpStreamId, RepairRtpStreamId, AbsSendTime, TransportWideCcDraft01, SsrcAudioLevel, DependencyDescriptor, VideoOrientation, TimeOffset, AbsCaptureTime, PlayoutDelay, MediasoupPacketId, } table RtpHeaderExtensionParameters { uri: RtpHeaderExtensionUri; id: uint8; encrypt: bool = false; parameters: [Parameter]; } table Rtx { ssrc: uint32; } table RtpEncodingParameters { ssrc: uint32 = null; rid: string; codec_payload_type: uint8 = null; rtx: Rtx; dtx: bool = false; scalability_mode: string; max_bitrate: uint32 = null; } table RtcpParameters { cname: string; reduced_size: bool = true; } table RtpParameters { mid: string; codecs: [RtpCodecParameters] (required); header_extensions: [RtpHeaderExtensionParameters] (required); encodings: [RtpEncodingParameters] (required); rtcp: RtcpParameters (required); msid: string; } table CodecMapping { payload_type: uint8; mapped_payload_type: uint8; } table EncodingMapping { rid: string; ssrc: uint32 = null; scalability_mode: string; mapped_ssrc: uint32; } table RtpMapping { codecs: [CodecMapping] (required); encodings: [EncodingMapping] (required); } ================================================ FILE: worker/fbs/rtpStream.fbs ================================================ include "rtpParameters.fbs"; include "rtxStream.fbs"; namespace FBS.RtpStream; table Params { encoding_idx: uint32; ssrc: uint32; payload_type: uint8; mime_type: string (required); clock_rate: uint32; rid: string; cname: string (required); rtx_ssrc: uint32 = null; rtx_payload_type: uint8 = null; use_nack: bool; use_pli: bool; use_fir: bool; use_in_band_fec: bool; use_dtx: bool; spatial_layers: uint8; temporal_layers: uint8; } table Dump { params: Params (required); score: uint8; rtx_stream: FBS.RtxStream.RtxDump; } table BitrateByLayer { layer: string (required); bitrate: uint32; } union StatsData { BaseStats, RecvStats, SendStats, } table Stats { data: StatsData (required); } table BaseStats { timestamp: uint64; ssrc: uint32; kind: FBS.RtpParameters.MediaKind; mime_type: string (required); packets_lost: int32; fraction_lost: uint8; jitter: uint32; packets_discarded: uint64; packets_retransmitted: uint64; packets_repaired: uint64; nack_count: uint64; nack_packet_count: uint64; pli_count: uint64; fir_count: uint64; rid: string; rtx_ssrc: uint32 = null; rtx_packets_discarded: uint64; round_trip_time: float; score: uint8; } table RecvStats { base: Stats (required); packet_count: uint64; byte_count: uint64; bitrate: uint32; bitrate_by_layer: [BitrateByLayer] (required); } table SendStats { base: Stats (required); packet_count: uint64; byte_count: uint64; bitrate: uint32; } ================================================ FILE: worker/fbs/rtxStream.fbs ================================================ namespace FBS.RtxStream; table Params { ssrc: uint32; payload_type: uint8; mime_type: string (required); clock_rate: uint32; rrid: string; cname: string (required); } // NOTE: Naming this Dump collides in TS generation for Producer.DumpResponse. table RtxDump { params: Params (required); } ================================================ FILE: worker/fbs/sctpAssociation.fbs ================================================ namespace FBS.SctpAssociation; enum SctpState: uint8 { NEW = 0, CONNECTING, CONNECTED, FAILED, CLOSED, } ================================================ FILE: worker/fbs/sctpParameters.fbs ================================================ namespace FBS.SctpParameters; table NumSctpStreams { os: uint16 = 1024; mis: uint16 = 1024; } table SctpParameters { // Port is always 5000. port: uint16 = 5000; os: uint16; mis: uint16; max_message_size: uint32; send_buffer_size: uint32; sctp_buffered_amount: uint32; is_data_channel: bool; } table SctpStreamParameters { stream_id: uint16; ordered: bool = null; max_packet_life_time: uint16 = null; max_retransmits: uint16 = null; } ================================================ FILE: worker/fbs/srtpParameters.fbs ================================================ namespace FBS.SrtpParameters; enum SrtpCryptoSuite: uint8 { AEAD_AES_256_GCM, AEAD_AES_128_GCM, AES_CM_128_HMAC_SHA1_80, AES_CM_128_HMAC_SHA1_32, } table SrtpParameters { crypto_suite: FBS.SrtpParameters.SrtpCryptoSuite; key_base64: string (required); } ================================================ FILE: worker/fbs/transport.fbs ================================================ include "common.fbs"; include "consumer.fbs"; include "dataProducer.fbs"; include "rtpParameters.fbs"; include "sctpAssociation.fbs"; include "sctpParameters.fbs"; include "srtpParameters.fbs"; namespace FBS.Transport; enum Protocol: uint8 { UDP = 1, TCP } table PortRange { min: uint16 = 0; max: uint16 = 0; } table SocketFlags { ipv6_only: bool = false; udp_reuse_port: bool = false; } table ListenInfo { protocol: Protocol = UDP; ip: string (required); announced_address: string; expose_internal_ip: bool; port: uint16 = 0; port_range: PortRange (required); flags: SocketFlags (required); send_buffer_size: uint32 = 0; recv_buffer_size: uint32 = 0; } table RestartIceResponse { username_fragment: string (required); password: string (required); ice_lite: bool; } table ProduceRequest { producer_id: string (required); kind: FBS.RtpParameters.MediaKind; rtp_parameters: FBS.RtpParameters.RtpParameters (required); rtp_mapping: FBS.RtpParameters.RtpMapping (required); paused: bool = false; key_frame_request_delay: uint32; enable_mediasoup_packet_id_header_extension: bool = false; } table ProduceResponse { type: FBS.RtpParameters.Type; } table ConsumeRequest { consumer_id: string (required); producer_id: string (required); kind: FBS.RtpParameters.MediaKind; rtp_parameters: FBS.RtpParameters.RtpParameters (required); type: FBS.RtpParameters.Type; consumable_rtp_encodings: [FBS.RtpParameters.RtpEncodingParameters] (required); paused: bool = false; preferred_layers: FBS.Consumer.ConsumerLayers; ignore_dtx: bool = false; } table ConsumeResponse { paused: bool; producer_paused: bool; score: FBS.Consumer.ConsumerScore (required); preferred_layers: FBS.Consumer.ConsumerLayers; } table ProduceDataRequest { data_producer_id: string (required); type: FBS.DataProducer.Type; sctp_stream_parameters: FBS.SctpParameters.SctpStreamParameters; label: string; protocol: string; paused: bool = false; } table ConsumeDataRequest { data_consumer_id: string (required); data_producer_id: string (required); type: FBS.DataProducer.Type; sctp_stream_parameters: FBS.SctpParameters.SctpStreamParameters; label: string; protocol: string; paused: bool = false; subchannels: [uint16]; } table Tuple { local_address: string (required); local_port: uint16; remote_ip: string; remote_port: uint16; protocol: Protocol = UDP; } table RtpListener { ssrc_table: [FBS.Common.Uint32String] (required); mid_table: [FBS.Common.StringString] (required); rid_table: [FBS.Common.StringString] (required); } table SctpListener { stream_id_table: [FBS.Common.Uint16String] (required); } table RecvRtpHeaderExtensions { mid: uint8 = null; rid: uint8 = null; rrid: uint8 = null; abs_send_time: uint8 = null; transport_wide_cc01: uint8 = null; } table Options { direct: bool = false; /// Only needed for DirectTransport. This value is handled by base Transport. max_message_size: uint32 = null; initial_available_outgoing_bitrate: uint32 = null; enable_sctp: bool = false; num_sctp_streams: FBS.SctpParameters.NumSctpStreams; max_sctp_message_size: uint32; sctp_send_buffer_size: uint32; is_data_channel: bool = false; } enum TraceEventType: uint8 { PROBATION = 0, BWE } table Dump { id: string (required); direct: bool = false; producer_ids: [string] (required); consumer_ids: [string] (required); map_ssrc_consumer_id: [FBS.Common.Uint32String] (required); map_rtx_ssrc_consumer_id: [FBS.Common.Uint32String] (required); data_producer_ids: [string] (required); data_consumer_ids: [string] (required); recv_rtp_header_extensions: RecvRtpHeaderExtensions (required); rtp_listener: RtpListener (required); max_message_size: uint32; sctp_parameters: FBS.SctpParameters.SctpParameters; sctp_state: FBS.SctpAssociation.SctpState = null; sctp_listener: SctpListener; trace_event_types: [TraceEventType] (required); } table Stats { transport_id: string (required); timestamp: uint64; sctp_state: FBS.SctpAssociation.SctpState = null; bytes_received: uint64; recv_bitrate: uint32; bytes_sent: uint64; send_bitrate: uint32; rtp_bytes_received: uint64; rtp_recv_bitrate: uint32; rtp_bytes_sent: uint64; rtp_send_bitrate: uint32; rtx_bytes_received: uint64; rtx_recv_bitrate: uint32; rtx_bytes_sent: uint64; rtx_send_bitrate: uint32; probation_bytes_sent: uint64; probation_send_bitrate: uint32; available_outgoing_bitrate: uint32 = null; available_incoming_bitrate: uint32 = null; max_incoming_bitrate: uint32 = null; max_outgoing_bitrate: uint32 = null; min_outgoing_bitrate: uint32 = null; rtp_packet_loss_received: float64 = null; rtp_packet_loss_sent: float64 = null; } table SetMaxIncomingBitrateRequest { max_incoming_bitrate: uint32; } table SetMaxOutgoingBitrateRequest { max_outgoing_bitrate: uint32; } table SetMinOutgoingBitrateRequest { min_outgoing_bitrate: uint32; } table EnableTraceEventRequest { events: [TraceEventType] (required); } table CloseProducerRequest { producer_id: string (required); } table CloseConsumerRequest { consumer_id: string (required); } table CloseDataProducerRequest { data_producer_id: string (required); } table CloseDataConsumerRequest { data_consumer_id: string (required); } // Notifications to Worker. table SendRtcpNotification { data: [uint8] (required); } // Notifications from Worker. table SctpStateChangeNotification { sctp_state: FBS.SctpAssociation.SctpState; } union TraceInfo { BweTraceInfo, } enum BweType: uint8 { TRANSPORT_CC = 0, REMB } table BweTraceInfo { bwe_type: BweType; desired_bitrate: uint32; effective_desired_bitrate: uint32; min_bitrate: uint32; max_bitrate: uint32; start_bitrate: uint32; max_padding_bitrate: uint32; available_bitrate: uint32; } table TraceNotification { type: TraceEventType; timestamp: uint64; direction: FBS.Common.TraceDirection; info: TraceInfo; } ================================================ FILE: worker/fbs/webRtcServer.fbs ================================================ include "transport.fbs"; namespace FBS.WebRtcServer; table IpPort { ip: string (required); port: uint16; } table IceUserNameFragment { local_ice_username_fragment: string (required); web_rtc_transport_id: string (required); } table TupleHash { tuple_hash: uint64; web_rtc_transport_id: string (required); } table DumpResponse { id: string (required); udp_sockets: [IpPort] (required); tcp_servers: [IpPort] (required); web_rtc_transport_ids: [string] (required); local_ice_username_fragments: [IceUserNameFragment] (required); tuple_hashes: [TupleHash] (required); } ================================================ FILE: worker/fbs/webRtcTransport.fbs ================================================ include "transport.fbs"; include "sctpParameters.fbs"; namespace FBS.WebRtcTransport; table ListenIndividual { listen_infos: [FBS.Transport.ListenInfo] (required); } table ListenServer { web_rtc_server_id: string (required); } union Listen { ListenIndividual, ListenServer, } table WebRtcTransportOptions { base: FBS.Transport.Options (required); listen: Listen (required); enable_udp: bool = true; enable_tcp: bool = true; prefer_udp: bool = false; prefer_tcp: bool = false; ice_consent_timeout: uint8 = 30; } enum FingerprintAlgorithm: uint8 { SHA1, SHA224, SHA256, SHA384, SHA512, } table Fingerprint { algorithm: FingerprintAlgorithm; value: string (required); } enum DtlsRole: uint8 { AUTO, CLIENT, SERVER } enum DtlsState: uint8 { NEW, CONNECTING, CONNECTED, FAILED, CLOSED } table DtlsParameters { fingerprints: [Fingerprint] (required); role: DtlsRole = AUTO; } table IceParameters { username_fragment: string (required); password: string (required); ice_lite: bool = true; } enum IceCandidateType: uint8 { HOST } enum IceCandidateTcpType: uint8 { PASSIVE } enum IceRole: uint8 { CONTROLLED, CONTROLLING } enum IceState: uint8 { NEW, CONNECTED, COMPLETED, DISCONNECTED, } table IceCandidate { foundation: string (required); priority: uint32; address: string (required); protocol: FBS.Transport.Protocol = UDP; port: uint16; type: IceCandidateType; tcp_type: IceCandidateTcpType = null; } table ConnectRequest { dtls_parameters: DtlsParameters (required); } table ConnectResponse { dtls_local_role: DtlsRole; } table DumpResponse { base: FBS.Transport.Dump (required); ice_role: IceRole; ice_parameters: IceParameters (required); ice_candidates: [IceCandidate] (required); ice_state: IceState; ice_selected_tuple: FBS.Transport.Tuple; dtls_parameters: DtlsParameters (required); dtls_state: DtlsState; } table GetStatsResponse { base: FBS.Transport.Stats (required); ice_role: IceRole; ice_state: IceState; ice_selected_tuple: FBS.Transport.Tuple; dtls_state: DtlsState; } // Notifications from Worker. table IceSelectedTupleChangeNotification { tuple: FBS.Transport.Tuple (required); } table IceStateChangeNotification { ice_state: IceState; } table DtlsStateChangeNotification { dtls_state: DtlsState; remote_cert: string; } ================================================ FILE: worker/fbs/worker.fbs ================================================ include "liburing.fbs"; include "transport.fbs"; namespace FBS.Worker; table ChannelMessageHandlers { channel_request_handlers: [string] (required); channel_notification_handlers: [string] (required); } table DumpResponse { pid: uint32; web_rtc_server_ids: [string] (required); router_ids: [string] (required); channel_message_handlers: ChannelMessageHandlers (required); liburing: FBS.LibUring.Dump; } table ResourceUsageResponse { ru_utime: uint64; ru_stime: uint64; ru_maxrss: uint64; ru_ixrss: uint64; ru_idrss: uint64; ru_isrss: uint64; ru_minflt: uint64; ru_majflt: uint64; ru_nswap: uint64; ru_inblock: uint64; ru_oublock: uint64; ru_msgsnd: uint64; ru_msgrcv: uint64; ru_nsignals: uint64; ru_nvcsw: uint64; ru_nivcsw: uint64; } table UpdateSettingsRequest { log_level: string; log_tags: [string]; } table CreateWebRtcServerRequest { web_rtc_server_id: string (required); listen_infos: [FBS.Transport.ListenInfo]; } table CloseWebRtcServerRequest { web_rtc_server_id: string (required); } table CreateRouterRequest { router_id: string (required); } table CloseRouterRequest { router_id: string (required); } ================================================ FILE: worker/fuzzer/include/FuzzerUtils.hpp ================================================ #ifndef MS_FUZZER_UTILS_HPP #define MS_FUZZER_UTILS_HPP #include "common.hpp" namespace FuzzerUtils { void Fuzz(const uint8_t* data, size_t len); } // namespace FuzzerUtils #endif ================================================ FILE: worker/fuzzer/include/RTC/FuzzerDtlsTransport.hpp ================================================ #ifndef MS_FUZZER_RTC_DTLS_TRANSPORT_HPP #define MS_FUZZER_RTC_DTLS_TRANSPORT_HPP #include "common.hpp" #include "RTC/DtlsTransport.hpp" namespace FuzzerRtcDtlsTransport { class DtlsTransportListener : public RTC::DtlsTransport::Listener { /* Pure virtual methods inherited from RTC::DtlsTransport::Listener. */ public: void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) override; void OnDtlsTransportConnected( const RTC::DtlsTransport* dtlsTransport, RTC::SrtpSession::CryptoSuite srtpCryptoSuite, uint8_t* srtpLocalKey, size_t srtpLocalKeyLen, uint8_t* srtpRemoteKey, size_t srtpRemoteKeyLen, std::string& remoteCert) override; void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) override; void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) override; void OnDtlsTransportSendData( const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) override; void OnDtlsTransportApplicationDataReceived( const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) override; }; void Fuzz(const uint8_t* data, size_t len); } // namespace FuzzerRtcDtlsTransport #endif ================================================ FILE: worker/fuzzer/include/RTC/FuzzerRateCalculator.hpp ================================================ #ifndef MS_FUZZER_RTC_RATE_CALCULATOR_HPP #define MS_FUZZER_RTC_RATE_CALCULATOR_HPP #include "common.hpp" namespace FuzzerRtcRateCalculator { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/FuzzerSeqManager.hpp ================================================ #ifndef MS_FUZZER_RTC_SEQ_MANAGER_HPP #define MS_FUZZER_RTC_SEQ_MANAGER_HPP #include "common.hpp" namespace FuzzerRtcSeqManager { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/FuzzerTrendCalculator.hpp ================================================ #ifndef MS_FUZZER_RTC_TREND_CALCULATOR_HPP #define MS_FUZZER_RTC_TREND_CALCULATOR_HPP #include "common.hpp" namespace FuzzerRtcTrendCalculator { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/ICE/FuzzerStunPacket.hpp ================================================ #ifndef MS_FUZZER_RTC_ICE_STUN_PACKET_HPP #define MS_FUZZER_RTC_ICE_STUN_PACKET_HPP #include "common.hpp" namespace FuzzerRtcIceStunPacket { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerBye.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_BYE #define MS_FUZZER_RTC_RTCP_BYE #include "common.hpp" #include "RTC/RTCP/Bye.hpp" namespace FuzzerRtcRtcpBye { void Fuzz(RTC::RTCP::ByePacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPs.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS #include "common.hpp" #include "RTC/RTCP/Packet.hpp" namespace FuzzerRtcRtcpFeedbackPs { void Fuzz(RTC::RTCP::Packet* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsAfb.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_AFB #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_AFB #include "common.hpp" #include "RTC/RTCP/FeedbackPsAfb.hpp" namespace FuzzerRtcRtcpFeedbackPsAfb { void Fuzz(RTC::RTCP::FeedbackPsAfbPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsFir.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_FIR #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_FIR #include "common.hpp" #include "RTC/RTCP/FeedbackPsFir.hpp" namespace FuzzerRtcRtcpFeedbackPsFir { void Fuzz(RTC::RTCP::FeedbackPsFirPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsLei.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_LEI #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_LEI #include "common.hpp" #include "RTC/RTCP/FeedbackPsLei.hpp" namespace FuzzerRtcRtcpFeedbackPsLei { void Fuzz(RTC::RTCP::FeedbackPsLeiPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsPli.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_PLI #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_PLI #include "common.hpp" #include "RTC/RTCP/FeedbackPsPli.hpp" namespace FuzzerRtcRtcpFeedbackPsPli { void Fuzz(RTC::RTCP::FeedbackPsPliPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsRemb.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_REMB #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_REMB #include "common.hpp" #include "RTC/RTCP/FeedbackPsRemb.hpp" namespace FuzzerRtcRtcpFeedbackPsRemb { void Fuzz(RTC::RTCP::FeedbackPsRembPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsRpsi.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_RPSI #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_RPSI #include "common.hpp" #include "RTC/RTCP/FeedbackPsRpsi.hpp" namespace FuzzerRtcRtcpFeedbackPsRpsi { void Fuzz(RTC::RTCP::FeedbackPsRpsiPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsSli.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_SLI #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_SLI #include "common.hpp" #include "RTC/RTCP/FeedbackPsSli.hpp" namespace FuzzerRtcRtcpFeedbackPsSli { void Fuzz(RTC::RTCP::FeedbackPsSliPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsTst.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_TST #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_TST #include "common.hpp" #include "RTC/RTCP/FeedbackPsTst.hpp" namespace FuzzerRtcRtcpFeedbackPsTstn { void Fuzz(RTC::RTCP::FeedbackPsTstnPacket* packet); } namespace FuzzerRtcRtcpFeedbackPsTstr { void Fuzz(RTC::RTCP::FeedbackPsTstrPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackPsVbcm.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_PS_VBCM #define MS_FUZZER_RTC_RTCP_FEEDBACK_PS_VBCM #include "common.hpp" #include "RTC/RTCP/FeedbackPsVbcm.hpp" namespace FuzzerRtcRtcpFeedbackPsVbcm { void Fuzz(RTC::RTCP::FeedbackPsVbcmPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtp.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP #define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP #include "common.hpp" #include "RTC/RTCP/FeedbackRtp.hpp" namespace FuzzerRtcRtcpFeedbackRtp { void Fuzz(RTC::RTCP::Packet* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpEcn.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_ECN #define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_ECN #include "common.hpp" #include "RTC/RTCP/FeedbackRtpEcn.hpp" namespace FuzzerRtcRtcpFeedbackRtpEcn { void Fuzz(RTC::RTCP::FeedbackRtpEcnPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpNack.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_NACK #define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_NACK #include "common.hpp" #include "RTC/RTCP/FeedbackRtpNack.hpp" namespace FuzzerRtcRtcpFeedbackRtpNack { void Fuzz(RTC::RTCP::FeedbackRtpNackPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpSrReq.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_SR_REQ #define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_SR_REQ #include "common.hpp" #include "RTC/RTCP/FeedbackRtpSrReq.hpp" namespace FuzzerRtcRtcpFeedbackRtpSrReq { void Fuzz(RTC::RTCP::FeedbackRtpSrReqPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpTllei.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TLLEI #define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TLLEI #include "common.hpp" #include "RTC/RTCP/FeedbackRtpTllei.hpp" namespace FuzzerRtcRtcpFeedbackRtpTllei { void Fuzz(RTC::RTCP::FeedbackRtpTlleiPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpTmmb.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TMMB #define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TMMB #include "common.hpp" #include "RTC/RTCP/FeedbackRtpTmmb.hpp" namespace FuzzerRtcRtcpFeedbackRtpTmmbn { void Fuzz(RTC::RTCP::FeedbackRtpTmmbnPacket* packet); } namespace FuzzerRtcRtcpFeedbackRtpTmmbr { void Fuzz(RTC::RTCP::FeedbackRtpTmmbrPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerFeedbackRtpTransport.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TRANSPORT #define MS_FUZZER_RTC_RTCP_FEEDBACK_RTP_TRANSPORT #include "common.hpp" #include "RTC/RTCP/FeedbackRtpTransport.hpp" namespace FuzzerRtcRtcpFeedbackRtpTransport { void Fuzz(RTC::RTCP::FeedbackRtpTransportPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerPacket.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_PACKET_HPP #define MS_FUZZER_RTC_RTCP_PACKET_HPP #include "common.hpp" namespace FuzzerRtcRtcpPacket { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerReceiverReport.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_RECEIVER_REPORT #define MS_FUZZER_RTC_RTCP_RECEIVER_REPORT #include "common.hpp" #include "RTC/RTCP/ReceiverReport.hpp" namespace FuzzerRtcRtcpReceiverReport { void Fuzz(RTC::RTCP::ReceiverReportPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerSdes.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_SDES #define MS_FUZZER_RTC_RTCP_SDES #include "common.hpp" #include "RTC/RTCP/Sdes.hpp" namespace FuzzerRtcRtcpSdes { void Fuzz(RTC::RTCP::SdesPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerSenderReport.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_SENDER_REPORT #define MS_FUZZER_RTC_RTCP_SENDER_REPORT #include "common.hpp" #include "RTC/RTCP/SenderReport.hpp" namespace FuzzerRtcRtcpSenderReport { void Fuzz(RTC::RTCP::SenderReportPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTCP/FuzzerXr.hpp ================================================ #ifndef MS_FUZZER_RTC_RTCP_XR_PACKET #define MS_FUZZER_RTC_RTCP_XR_PACKET #include "common.hpp" #include "RTC/RTCP/XR.hpp" namespace FuzzerRtcRtcpExtendedReport { void Fuzz(RTC::RTCP::ExtendedReportPacket* packet); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/Codecs/FuzzerAV1.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_CODECS_AV1_HPP #define MS_FUZZER_RTC_RTP_CODECS_AV1_HPP #include "common.hpp" namespace FuzzerRtcRtpCodecsAV1 { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/Codecs/FuzzerDependencyDescriptor.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_CODECS_DEPENDENCY_DESCRIPTOR_HPP #define MS_FUZZER_RTC_RTP_CODECS_DEPENDENCY_DESCRIPTOR_HPP #include "common.hpp" #include "RTC/RTP/Codecs/DependencyDescriptor.hpp" namespace FuzzerRtcRtpCodecsDependencyDescriptor { class DependencyDescriptorListener : public RTC::RTP::Codecs::DependencyDescriptor::Listener { public: void OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) override { } }; void Fuzz(const uint8_t* data, size_t len); } // namespace FuzzerRtcRtpCodecsDependencyDescriptor #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/Codecs/FuzzerH264.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_CODECS_H264_HPP #define MS_FUZZER_RTC_RTP_CODECS_H264_HPP #include "common.hpp" namespace FuzzerRtcRtpCodecsH264 { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/Codecs/FuzzerOpus.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_CODECS_OPUS_HPP #define MS_FUZZER_RTC_RTP_CODECS_OPUS_HPP #include "common.hpp" namespace FuzzerRtcRtpCodecsOpus { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/Codecs/FuzzerVP8.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_CODECS_VP8_HPP #define MS_FUZZER_RTC_RTP_CODECS_VP8_HPP #include "common.hpp" namespace FuzzerRtcRtpCodecsVP8 { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/Codecs/FuzzerVP9.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_CODECS_VP9_HPP #define MS_FUZZER_RTC_RTP_CODECS_VP9_HPP #include "common.hpp" namespace FuzzerRtcRtpCodecsVP9 { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/FuzzerPacket.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_PACKET_HPP #define MS_FUZZER_RTC_RTP_PACKET_HPP #include "common.hpp" namespace FuzzerRtcRtcPacket { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/FuzzerProbationGenerator.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_PROBATION_PACKET_HPP #define MS_FUZZER_RTC_RTP_PROBATION_PACKET_HPP #include "common.hpp" namespace FuzzerRtcRtpProbationGenerator { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/FuzzerRetransmissionBuffer.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_RETRANSMISSION_BUFFER_HPP #define MS_FUZZER_RTC_RTP_RETRANSMISSION_BUFFER_HPP #include "common.hpp" namespace FuzzerRtcRtpRetransmissionBuffer { void Fuzz(const uint8_t* data, size_t len); } #endif ================================================ FILE: worker/fuzzer/include/RTC/RTP/FuzzerRtpStreamSend.hpp ================================================ #ifndef MS_FUZZER_RTC_RTP_STREAM_SEND_HPP #define MS_FUZZER_RTC_RTP_STREAM_SEND_HPP #include "common.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtpStreamSend.hpp" namespace FuzzerRtcRtpStreamSend { class TestRtpStreamListener : public RTC::RTP::RtpStreamSend::Listener { public: void OnRtpStreamScore( RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override { } void OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet) override { } }; void Fuzz(const uint8_t* data, size_t len); } // namespace FuzzerRtcRtpStreamSend #endif ================================================ FILE: worker/fuzzer/include/RTC/SCTP/FuzzerStateCookie.hpp ================================================ #ifndef MS_FUZZER_RTC_SCTP_STATE_COOKIE_HPP #define MS_FUZZER_RTC_SCTP_STATE_COOKIE_HPP #include "common.hpp" namespace FuzzerRtcSctpStateCookie { void Fuzz(const uint8_t* data, size_t len); } // namespace FuzzerRtcSctpStateCookie #endif ================================================ FILE: worker/fuzzer/include/RTC/SCTP/packet/FuzzerPacket.hpp ================================================ #ifndef MS_FUZZER_RTC_SCTP_PACKET_HPP #define MS_FUZZER_RTC_SCTP_PACKET_HPP #include "common.hpp" namespace FuzzerRtcSctpPacket { void Fuzz(const uint8_t* data, size_t len); } // namespace FuzzerRtcSctpPacket #endif ================================================ FILE: worker/fuzzer/new-corpus/.placeholder ================================================ ================================================ FILE: worker/fuzzer/reports/.placeholder ================================================ ================================================ FILE: worker/fuzzer/reports/crash-7e7caf72377ad55d353719f28febb5238eadfc9e ================================================ 88t ================================================ FILE: worker/fuzzer/reports/crash-91572165de5ef12fe8415b150e40457eccca0362 ================================================ %345DEFG%8EFX ================================================ FILE: worker/fuzzer/reports/crash-ac5d03e5d918b7f714c0452a59ad9c0e1ca3e501 ================================================ ::a: ================================================ FILE: worker/fuzzer/reports/crash-b75c1208384621922270e954b4902442592c3ca9 ================================================ dtat ================================================ FILE: worker/fuzzer/reports/crash-da39a3ee5e6b4b0d3255bfef95601890afd80709 ================================================ ================================================ FILE: worker/fuzzer/reports/crash-dcfd05592934ab472c98a1813256aabb9bb43bfb ================================================ '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ================================================ FILE: worker/fuzzer/reports/crash-ddfab2c0dd845e8d3e8f8d27e1f4cb49d92d279a ================================================ ::1: ================================================ FILE: worker/fuzzer/src/FuzzerUtils.cpp ================================================ #include "FuzzerUtils.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include // std::memcpy() #include namespace { alignas(4) thread_local uint8_t DataBuffer[65536]; } void FuzzerUtils::Fuzz(const uint8_t* data, size_t len) { // NOTE: We need to copy given data into another buffer because we are gonna // write into it. std::memcpy(DataBuffer, data, len); /* IP class. */ std::string ip; ip = std::string(reinterpret_cast(DataBuffer), INET6_ADDRSTRLEN / 2); Utils::IP::GetFamily(ip); ip = std::string(reinterpret_cast(DataBuffer), INET6_ADDRSTRLEN); Utils::IP::GetFamily(ip); ip = std::string(reinterpret_cast(DataBuffer), INET6_ADDRSTRLEN * 2); Utils::IP::GetFamily(ip); // Protect with try/catch since throws are legit. try { auto ip = std::string(reinterpret_cast(DataBuffer), len); Utils::IP::NormalizeIp(ip); } catch (const MediaSoupError& error) // NOLINT(bugprone-empty-catch) { } /* Byte class. */ Utils::Byte::Get1Byte(DataBuffer, len); Utils::Byte::Get2Bytes(DataBuffer, len); Utils::Byte::Get3Bytes(DataBuffer, len); Utils::Byte::Get4Bytes(DataBuffer, len); Utils::Byte::Get8Bytes(DataBuffer, len); Utils::Byte::Set1Byte(DataBuffer, len, uint8_t{ 6u }); Utils::Byte::Set2Bytes(DataBuffer, len, uint16_t{ 66u }); Utils::Byte::Set3Bytes(DataBuffer, len, uint32_t{ 666u }); Utils::Byte::Set4Bytes(DataBuffer, len, uint32_t{ 666u }); Utils::Byte::Set8Bytes(DataBuffer, len, uint64_t{ 6666u }); Utils::Byte::PadTo4Bytes(static_cast(len)); Utils::Byte::PadTo4Bytes(static_cast(len)); Utils::Byte::PadTo4Bytes(static_cast(len)); Utils::Byte::PadTo4Bytes(static_cast(len)); Utils::Byte::PadTo4Bytes(len); Utils::Byte::PadTo8Bytes(static_cast(len)); Utils::Byte::PadTo8Bytes(static_cast(len)); Utils::Byte::PadTo8Bytes(static_cast(len)); Utils::Byte::PadTo8Bytes(static_cast(len)); Utils::Byte::PadTo8Bytes(len); /* Bits class. */ Utils::Bits::CountSetBits(static_cast(len)); /* Crypto class. */ Utils::Crypto::GetRandomUInt( static_cast(len), static_cast(len + 1000000)); Utils::Crypto::GetRandomUInt( static_cast(len), static_cast(len + 1000000)); Utils::Crypto::GetRandomUInt(len, len + 1000000); Utils::Crypto::GetRandomString(len); Utils::Crypto::GetCRC32(DataBuffer, len); /* String class. */ // Protect with try/catch since throws are legit. try { size_t outLen; Utils::String::Base64Encode(DataBuffer, len); Utils::String::Base64Decode(DataBuffer, len, outLen); } catch (const MediaSoupError& error) // NOLINT(bugprone-empty-catch) { } /* Time class. */ auto ntp = Utils::Time::TimeMs2Ntp(static_cast(len)); Utils::Time::Ntp2TimeMs(ntp); Utils::Time::TimeMsToAbsSendTime(static_cast(len)); } ================================================ FILE: worker/fuzzer/src/RTC/FuzzerDtlsTransport.cpp ================================================ #define MS_CLASS "FuzzerRtcDtlsTransport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/FuzzerDtlsTransport.hpp" #include "Logger.hpp" #include "Utils.hpp" #include "mocks/include/MockShared.hpp" namespace { // NOLINTNEXTLINE(readability-identifier-naming) thread_local mocks::MockShared shared(/*getTimeMs*/ []() { return 1000; }); // DtlsTransport instance. It's reset every time DTLS handshake fails or DTLS // is closed. // NOLINTNEXTLINE(readability-identifier-naming) thread_local RTC::DtlsTransport* dtlsTransportSingleton{ nullptr }; // DtlsTransport Listener instance. It's reset every time the DtlsTransport // singletonDTLS is reset. // NOLINTNEXTLINE(readability-identifier-naming) thread_local FuzzerRtcDtlsTransport::DtlsTransportListener* dtlsTransportListenerSingleton{ nullptr }; } // namespace void FuzzerRtcDtlsTransport::Fuzz(const uint8_t* data, size_t len) { if (!RTC::DtlsTransport::IsDtls(data, len)) { return; } if (!dtlsTransportSingleton) { MS_DEBUG_DEV("no DtlsTransport singleton, creating it"); delete dtlsTransportListenerSingleton; dtlsTransportListenerSingleton = new DtlsTransportListener(); dtlsTransportSingleton = new RTC::DtlsTransport(dtlsTransportListenerSingleton, std::addressof(shared)); RTC::DtlsTransport::Role localRole; RTC::DtlsTransport::Fingerprint dtlsRemoteFingerprint; // Local DTLS role must be 'server' or 'client'. Choose it based on // randomness of first given byte. if (data[0] / 2 == 0) { localRole = RTC::DtlsTransport::Role::SERVER; } else { localRole = RTC::DtlsTransport::Role::CLIENT; } // Remote DTLS fingerprint random generation. // NOTE: Use a random integer in range 1..5 since FingerprintAlgorithm enum // has 5 possible values starting with value 1. dtlsRemoteFingerprint.algorithm = static_cast( Utils::Crypto::GetRandomUInt(1u, 5u)); dtlsRemoteFingerprint.value = Utils::Crypto::GetRandomString(Utils::Crypto::GetRandomUInt(3u, 20u)); dtlsTransportSingleton->Run(localRole); dtlsTransportSingleton->SetRemoteFingerprint(dtlsRemoteFingerprint); } dtlsTransportSingleton->ProcessDtlsData(data, len); // DTLS may have failed or closed after ProcessDtlsData(). If so, unset it. if ( dtlsTransportSingleton->GetState() == RTC::DtlsTransport::DtlsState::FAILED || dtlsTransportSingleton->GetState() == RTC::DtlsTransport::DtlsState::CLOSED) { MS_DEBUG_DEV("DtlsTransport singleton state is 'failed' or 'closed', unsetting it"); delete dtlsTransportSingleton; dtlsTransportSingleton = nullptr; } else { dtlsTransportSingleton->SendApplicationData(data, len); } } void FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportConnecting( const RTC::DtlsTransport* /*dtlsTransport*/) { MS_DEBUG_DEV("DtlsTransport singleton connecting"); } void FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportConnected( const RTC::DtlsTransport* /*dtlsTransport*/, RTC::SrtpSession::CryptoSuite /*srtpCryptoSuite*/, uint8_t* /*srtpLocalKey*/, size_t /*srtpLocalKeyLen*/, uint8_t* /*srtpRemoteKey*/, size_t /*srtpRemoteKeyLen*/, std::string& /*remoteCert*/) { MS_DEBUG_DEV("DtlsTransport singleton connected"); } void FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportFailed( const RTC::DtlsTransport* /*dtlsTransport*/) { MS_DEBUG_DEV("DtlsTransport singleton failed"); } void FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportClosed( const RTC::DtlsTransport* /*dtlsTransport*/) { MS_DEBUG_DEV("DtlsTransport singleton closed"); } void FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportSendData( const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* /*data*/, size_t /*len*/) { MS_DEBUG_DEV("DtlsTransport singleton wants to send data"); } void FuzzerRtcDtlsTransport::DtlsTransportListener::OnDtlsTransportApplicationDataReceived( const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* /*data*/, size_t /*len*/) { MS_DEBUG_DEV("DtlsTransport singleton received application data"); } ================================================ FILE: worker/fuzzer/src/RTC/FuzzerRateCalculator.cpp ================================================ #include "RTC/FuzzerRateCalculator.hpp" #include "DepLibUV.hpp" #include "Utils.hpp" #include "RTC/Consts.hpp" #include "RTC/RateCalculator.hpp" namespace { // NOLINTBEGIN(readability-identifier-naming) RTC::RateCalculator rateCalculator; uint64_t nowMs; // NOLINTEND(readability-identifier-naming) int init() { nowMs = DepLibUV::GetTimeMs(); return 0; } } // namespace void FuzzerRtcRateCalculator::Fuzz(const uint8_t* data, size_t len) { // Trick to initialize our stuff just once. // NOLINTNEXTLINE(readability-identifier-naming) static thread_local const int unused = init(); // Avoid [-Wunused-variable]. (void)unused; // We need at least 2 bytes of |data|. if (len < 2) { return; } auto size = Utils::Crypto::GetRandomUInt(0u, static_cast(RTC::Consts::MtuSize)); nowMs += Utils::Crypto::GetRandomUInt(0u, 1000u); rateCalculator.Update(size, nowMs); // Only get rate from time to time. if (Utils::Byte::Get2Bytes(data, 0) % 100 == 0) { rateCalculator.GetRate(nowMs); } } ================================================ FILE: worker/fuzzer/src/RTC/FuzzerSeqManager.cpp ================================================ #include "RTC/FuzzerSeqManager.hpp" #include "Utils.hpp" #include "RTC/SeqManager.hpp" #include void FuzzerRtcSeqManager::Fuzz(const uint8_t* data, size_t len) { if (len < 10) { return; } RTC::SeqManager seqManager; uint16_t output; for (size_t count = 0; count < 7; count++) { seqManager.Input(Utils::Byte::Get2Bytes(data, count), output); seqManager.Drop(Utils::Byte::Get2Bytes(data, count + 2)); } } ================================================ FILE: worker/fuzzer/src/RTC/FuzzerTrendCalculator.cpp ================================================ #include "RTC/FuzzerTrendCalculator.hpp" #include "DepLibUV.hpp" #include "Utils.hpp" #include "RTC/TrendCalculator.hpp" void FuzzerRtcTrendCalculator::Fuzz(const uint8_t* data, size_t len) { RTC::TrendCalculator trend; auto nowMs = DepLibUV::GetTimeMs(); size_t offset{ 0u }; while (len >= 4u) { const auto value = Utils::Byte::Get4Bytes(data, offset); trend.Update(value, nowMs); trend.GetValue(); trend.Update(value, nowMs - 1000u); trend.GetValue(); len -= 4u; offset += 4; nowMs += 500u; } } ================================================ FILE: worker/fuzzer/src/RTC/ICE/FuzzerStunPacket.cpp ================================================ #include "RTC/ICE/FuzzerStunPacket.hpp" #include "RTC/ICE/StunPacket.hpp" #include namespace { constexpr size_t ResponseFactoryBufferLength{ 65536 }; alignas(4) thread_local uint8_t ResponseFactoryBuffer[ResponseFactoryBufferLength]; constexpr size_t SerializeBufferLength{ 65536 }; alignas(4) thread_local uint8_t SerializeBuffer[SerializeBufferLength]; constexpr size_t CloneBufferLength{ 65536 }; alignas(4) thread_local uint8_t CloneBuffer[CloneBufferLength]; } // namespace void FuzzerRtcIceStunPacket::Fuzz(const uint8_t* data, size_t len) { if (!RTC::ICE::StunPacket::IsStun(data, len)) { return; } auto* packet = RTC::ICE::StunPacket::Parse(data, len); if (!packet) { return; } struct sockaddr_storage xorMappedAddressStorage{}; std::string_view errorReasonPhrase; // packet->Dump(); packet->GetClass(); packet->GetMethod(); packet->GetTransactionId(); packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::USERNAME); packet->GetUsername(); packet->GetPriority(); packet->GetIceControlling(); packet->GetIceControlled(); packet->GetNomination(); packet->GetSoftware(); packet->GetXorMappedAddress(std::addressof(xorMappedAddressStorage)); packet->GetErrorCode(errorReasonPhrase); packet->CheckAuthentication("1234:1234", "aksjd"); packet->CheckAuthentication("foo"); try { packet->AddUsername("foo"); } catch (...) // NOLINT(bugprone-empty-catch) { } try { packet->AddPriority(123456); } catch (...) // NOLINT(bugprone-empty-catch) { } try { packet->AddIceControlling(98989232u); } catch (...) // NOLINT(bugprone-empty-catch) { } try { packet->AddIceControlled(87823823u); } catch (...) // NOLINT(bugprone-empty-catch) { } try { packet->AddUseCandidate(); } catch (...) // NOLINT(bugprone-empty-catch) { } try { packet->AddNomination(7623547u); } catch (...) // NOLINT(bugprone-empty-catch) { } try { packet->AddXorMappedAddress( reinterpret_cast(std::addressof(xorMappedAddressStorage))); } catch (...) // NOLINT(bugprone-empty-catch) { } try { packet->AddErrorCode(555, "ERROR å∫∂殀å∂ƒ∫"); } catch (...) // NOLINT(bugprone-empty-catch) { } try { packet->Protect("askjhdakjsd"); } catch (...) // NOLINT(bugprone-empty-catch) { } try { packet->Protect(); } catch (...) // NOLINT(bugprone-empty-catch) { } if (packet->GetClass() == RTC::ICE::StunPacket::Class::REQUEST) { auto* successResponse = packet->CreateSuccessResponse(ResponseFactoryBuffer, sizeof(ResponseFactoryBuffer)); auto* errorResponse = packet->CreateErrorResponse( ResponseFactoryBuffer, sizeof(ResponseFactoryBuffer), 444, "ERROR aljsh œœ∫∂å∫∂ zhx å∫∂å∫∂ !!!"); delete successResponse; delete errorResponse; } packet->Serialize(SerializeBuffer, sizeof(SerializeBuffer)); const auto* clonedPacket = packet->Clone(CloneBuffer, sizeof(CloneBuffer)); delete packet; delete clonedPacket; } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerBye.cpp ================================================ #include "RTC/RTCP/FuzzerBye.hpp" void FuzzerRtcRtcpBye::Fuzz(RTC::RTCP::ByePacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); packet->AddSsrc(1111); packet->SetReason("because!"); packet->GetReason(); } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPs.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPs.hpp" #include "RTC/RTCP/FuzzerFeedbackPsAfb.hpp" #include "RTC/RTCP/FuzzerFeedbackPsFir.hpp" #include "RTC/RTCP/FuzzerFeedbackPsLei.hpp" #include "RTC/RTCP/FuzzerFeedbackPsPli.hpp" #include "RTC/RTCP/FuzzerFeedbackPsRemb.hpp" #include "RTC/RTCP/FuzzerFeedbackPsRpsi.hpp" #include "RTC/RTCP/FuzzerFeedbackPsSli.hpp" #include "RTC/RTCP/FuzzerFeedbackPsTst.hpp" #include "RTC/RTCP/FuzzerFeedbackPsVbcm.hpp" void FuzzerRtcRtcpFeedbackPs::Fuzz(RTC::RTCP::Packet* packet) { auto* fbps = dynamic_cast(packet); fbps->GetMessageType(); fbps->GetSenderSsrc(); fbps->SetSenderSsrc(1111); fbps->GetMediaSsrc(); fbps->SetMediaSsrc(2222); switch (fbps->GetMessageType()) { case RTC::RTCP::FeedbackPs::MessageType::PLI: { auto* pli = dynamic_cast(fbps); FuzzerRtcRtcpFeedbackPsPli::Fuzz(pli); break; } case RTC::RTCP::FeedbackPs::MessageType::SLI: { auto* sli = dynamic_cast(fbps); FuzzerRtcRtcpFeedbackPsSli::Fuzz(sli); break; } case RTC::RTCP::FeedbackPs::MessageType::RPSI: { auto* rpsi = dynamic_cast(fbps); FuzzerRtcRtcpFeedbackPsRpsi::Fuzz(rpsi); break; } case RTC::RTCP::FeedbackPs::MessageType::FIR: { auto* fir = dynamic_cast(fbps); FuzzerRtcRtcpFeedbackPsFir::Fuzz(fir); break; } case RTC::RTCP::FeedbackPs::MessageType::TSTR: { auto* tstr = dynamic_cast(fbps); FuzzerRtcRtcpFeedbackPsTstr::Fuzz(tstr); break; } case RTC::RTCP::FeedbackPs::MessageType::TSTN: { auto* tstn = dynamic_cast(fbps); FuzzerRtcRtcpFeedbackPsTstn::Fuzz(tstn); break; } case RTC::RTCP::FeedbackPs::MessageType::VBCM: { auto* vbcm = dynamic_cast(fbps); FuzzerRtcRtcpFeedbackPsVbcm::Fuzz(vbcm); break; } case RTC::RTCP::FeedbackPs::MessageType::PSLEI: { auto* lei = dynamic_cast(fbps); FuzzerRtcRtcpFeedbackPsLei::Fuzz(lei); break; } case RTC::RTCP::FeedbackPs::MessageType::AFB: { auto* afb = dynamic_cast(fbps); FuzzerRtcRtcpFeedbackPsAfb::Fuzz(afb); break; } default: { } } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsAfb.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPsAfb.hpp" void FuzzerRtcRtcpFeedbackPsAfb::Fuzz(RTC::RTCP::FeedbackPsAfbPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetSize(); packet->GetApplication(); } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsFir.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPsFir.hpp" void FuzzerRtcRtcpFeedbackPsFir::Fuzz(RTC::RTCP::FeedbackPsFirPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetSsrc(); item->GetSequenceNumber(); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsLei.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPsLei.hpp" void FuzzerRtcRtcpFeedbackPsLei::Fuzz(RTC::RTCP::FeedbackPsLeiPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetSsrc(); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsPli.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPsPli.hpp" void FuzzerRtcRtcpFeedbackPsPli::Fuzz(RTC::RTCP::FeedbackPsPliPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsRemb.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPsRemb.hpp" void FuzzerRtcRtcpFeedbackPsRemb::Fuzz(RTC::RTCP::FeedbackPsRembPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); packet->IsCorrect(); packet->SetBitrate(1111); packet->SetSsrcs({ 2222, 3333, 4444 }); packet->GetBitrate(); packet->GetSsrcs(); } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsRpsi.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPsRpsi.hpp" void FuzzerRtcRtcpFeedbackPsRpsi::Fuzz(RTC::RTCP::FeedbackPsRpsiPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->IsCorrect(); item->GetPayloadType(); item->GetBitString(); item->GetLength(); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsSli.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPsSli.hpp" void FuzzerRtcRtcpFeedbackPsSli::Fuzz(RTC::RTCP::FeedbackPsSliPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetFirst(); item->SetFirst(1111); item->GetNumber(); item->SetNumber(2222); item->GetPictureId(); item->SetPictureId(255); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsTst.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPsTst.hpp" void FuzzerRtcRtcpFeedbackPsTstn::Fuzz(RTC::RTCP::FeedbackPsTstnPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetSsrc(); item->GetSequenceNumber(); item->GetIndex(); } } void FuzzerRtcRtcpFeedbackPsTstr::Fuzz(RTC::RTCP::FeedbackPsTstrPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetSsrc(); item->GetSequenceNumber(); item->GetIndex(); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackPsVbcm.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackPsVbcm.hpp" void FuzzerRtcRtcpFeedbackPsVbcm::Fuzz(RTC::RTCP::FeedbackPsVbcmPacket* packet) { // Triggers a crash in fuzzer. // TODO: Verify that there is buffer enough for the announce length. // packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); // Triggers a crash in fuzzer. // item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetSsrc(); item->GetSequenceNumber(); item->GetPayloadType(); item->GetLength(); item->GetValue(); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtp.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackRtp.hpp" #include "RTC/RTCP/FuzzerFeedbackRtpEcn.hpp" #include "RTC/RTCP/FuzzerFeedbackRtpNack.hpp" #include "RTC/RTCP/FuzzerFeedbackRtpSrReq.hpp" #include "RTC/RTCP/FuzzerFeedbackRtpTllei.hpp" #include "RTC/RTCP/FuzzerFeedbackRtpTmmb.hpp" #include "RTC/RTCP/FuzzerFeedbackRtpTransport.hpp" void FuzzerRtcRtcpFeedbackRtp::Fuzz(RTC::RTCP::Packet* packet) { auto* fbrtp = dynamic_cast(packet); fbrtp->GetMessageType(); fbrtp->GetSenderSsrc(); fbrtp->SetSenderSsrc(1111); fbrtp->GetMediaSsrc(); fbrtp->SetMediaSsrc(2222); switch (fbrtp->GetMessageType()) { case RTC::RTCP::FeedbackRtp::MessageType::NACK: { auto* nack = dynamic_cast(fbrtp); FuzzerRtcRtcpFeedbackRtpNack::Fuzz(nack); break; } case RTC::RTCP::FeedbackRtp::MessageType::TMMBR: { auto* tmmbr = dynamic_cast(fbrtp); FuzzerRtcRtcpFeedbackRtpTmmbr::Fuzz(tmmbr); break; } case RTC::RTCP::FeedbackRtp::MessageType::TMMBN: { auto* tmmbn = dynamic_cast(fbrtp); FuzzerRtcRtcpFeedbackRtpTmmbn::Fuzz(tmmbn); break; } case RTC::RTCP::FeedbackRtp::MessageType::SR_REQ: { auto* srReq = dynamic_cast(fbrtp); FuzzerRtcRtcpFeedbackRtpSrReq::Fuzz(srReq); break; } case RTC::RTCP::FeedbackRtp::MessageType::TLLEI: { auto* tllei = dynamic_cast(fbrtp); FuzzerRtcRtcpFeedbackRtpTllei::Fuzz(tllei); break; } case RTC::RTCP::FeedbackRtp::MessageType::ECN: { auto* ecn = dynamic_cast(fbrtp); FuzzerRtcRtcpFeedbackRtpEcn::Fuzz(ecn); break; } case RTC::RTCP::FeedbackRtp::MessageType::TCC: { auto* feedback = dynamic_cast(fbrtp); FuzzerRtcRtcpFeedbackRtpTransport::Fuzz(feedback); break; } default: { } } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpEcn.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackRtpEcn.hpp" void FuzzerRtcRtcpFeedbackRtpEcn::Fuzz(RTC::RTCP::FeedbackRtpEcnPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetSequenceNumber(); item->GetEct0Counter(); item->GetEct1Counter(); item->GetEcnCeCounter(); item->GetNotEctCounter(); item->GetLostPackets(); item->GetDuplicatedPackets(); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpNack.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackRtpNack.hpp" void FuzzerRtcRtcpFeedbackRtpNack::Fuzz(RTC::RTCP::FeedbackRtpNackPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetPacketId(); item->GetLostPacketBitmask(); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpSrReq.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackRtpSrReq.hpp" void FuzzerRtcRtcpFeedbackRtpSrReq::Fuzz(RTC::RTCP::FeedbackRtpSrReqPacket* packet) { // TODO } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTllei.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackRtpTllei.hpp" void FuzzerRtcRtcpFeedbackRtpTllei::Fuzz(RTC::RTCP::FeedbackRtpTlleiPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetPacketId(); item->GetLostPacketBitmask(); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTmmb.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackRtpTmmb.hpp" void FuzzerRtcRtcpFeedbackRtpTmmbn::Fuzz(RTC::RTCP::FeedbackRtpTmmbnPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetSsrc(); item->SetSsrc(1111); item->GetBitrate(); item->SetBitrate(2222); item->GetOverhead(); item->SetOverhead(3333); } } void FuzzerRtcRtcpFeedbackRtpTmmbr::Fuzz(RTC::RTCP::FeedbackRtpTmmbrPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddItem(Item* item); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& item = (*it); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetSsrc(); item->SetSsrc(1111); item->GetBitrate(); item->SetBitrate(2222); item->GetOverhead(); item->SetOverhead(3333); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTransport.cpp ================================================ #include "RTC/RTCP/FuzzerFeedbackRtpTransport.hpp" void FuzzerRtcRtcpFeedbackRtpTransport::Fuzz(RTC::RTCP::FeedbackRtpTransportPacket* packet) { packet->GetCount(); packet->GetSize(); packet->IsFull(); packet->IsSerializable(); packet->IsCorrect(); packet->GetBaseSequenceNumber(); packet->GetPacketStatusCount(); packet->GetReferenceTime(); packet->GetReferenceTimestamp(); packet->GetFeedbackPacketCount(); packet->GetLatestSequenceNumber(); packet->GetLatestTimestamp(); packet->GetPacketResults(); packet->GetPacketFractionLost(); packet->Serialize(RTC::RTCP::SerializationBuffer); } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerPacket.cpp ================================================ #include "RTC/RTCP/FuzzerPacket.hpp" #include "RTC/RTCP/FuzzerBye.hpp" #include "RTC/RTCP/FuzzerFeedbackPs.hpp" #include "RTC/RTCP/FuzzerFeedbackRtp.hpp" #include "RTC/RTCP/FuzzerReceiverReport.hpp" #include "RTC/RTCP/FuzzerSdes.hpp" #include "RTC/RTCP/FuzzerSenderReport.hpp" #include "RTC/RTCP/FuzzerXr.hpp" #include "RTC/RTCP/Packet.hpp" #include // std::memcpy() namespace { alignas(4) thread_local uint8_t DataBuffer[65536]; } // namespace void FuzzerRtcRtcpPacket::Fuzz(const uint8_t* data, size_t len) { if (!RTC::RTCP::Packet::IsRtcp(data, len)) { return; } // NOTE: We need to copy given data into another buffer because we are gonna // write into it. std::memcpy(DataBuffer, data, len); RTC::RTCP::Packet* packet = RTC::RTCP::Packet::Parse(DataBuffer, len); if (!packet) { return; } while (packet != nullptr) { auto* previousPacket = packet; switch (RTC::RTCP::Type(packet->GetType())) { case RTC::RTCP::Type::SR: { auto* sr = dynamic_cast(packet); FuzzerRtcRtcpSenderReport::Fuzz(sr); break; } case RTC::RTCP::Type::RR: { auto* rr = dynamic_cast(packet); FuzzerRtcRtcpReceiverReport::Fuzz(rr); break; } case RTC::RTCP::Type::SDES: { auto* sdes = dynamic_cast(packet); FuzzerRtcRtcpSdes::Fuzz(sdes); break; } case RTC::RTCP::Type::BYE: { auto* bye = dynamic_cast(packet); FuzzerRtcRtcpBye::Fuzz(bye); break; } case RTC::RTCP::Type::RTPFB: { FuzzerRtcRtcpFeedbackRtp::Fuzz(packet); break; } case RTC::RTCP::Type::PSFB: { FuzzerRtcRtcpFeedbackPs::Fuzz(packet); break; } case RTC::RTCP::Type::XR: { auto* xr = dynamic_cast(packet); FuzzerRtcRtcpExtendedReport::Fuzz(xr); break; } default: { } } packet = packet->GetNext(); delete previousPacket; } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerReceiverReport.cpp ================================================ #include "RTC/RTCP/FuzzerReceiverReport.hpp" #include "RTC/RTCP/Packet.hpp" void FuzzerRtcRtcpReceiverReport::Fuzz(RTC::RTCP::ReceiverReportPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); packet->GetSsrc(); packet->SetSsrc(1111); // TODO. // AddReport(ReceiverReport* report); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& report = (*it); report->Serialize(RTC::RTCP::SerializationBuffer); report->GetSize(); report->GetSsrc(); report->SetSsrc(1111); report->GetFractionLost(); report->SetFractionLost(64); report->GetTotalLost(); report->SetTotalLost(2222); report->GetLastSeq(); report->SetLastSeq(3333); report->GetJitter(); report->SetJitter(4444); report->GetLastSenderReport(); report->SetLastSenderReport(5555); report->GetDelaySinceLastSenderReport(); report->SetDelaySinceLastSenderReport(6666); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerSdes.cpp ================================================ #include "RTC/RTCP/FuzzerSdes.hpp" void FuzzerRtcRtcpSdes::Fuzz(RTC::RTCP::SdesPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); // TODO. // AddChunk(SdesChunk* chunk); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& chunk = (*it); chunk->Serialize(RTC::RTCP::SerializationBuffer); chunk->GetSize(); chunk->GetSsrc(); chunk->SetSsrc(1111); // TODO // AddItem(SdesItem* item); for (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2) { auto& item = (*it2); item->Serialize(RTC::RTCP::SerializationBuffer); item->GetSize(); item->GetType(); item->GetLength(); item->GetValue(); } } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerSenderReport.cpp ================================================ #include "RTC/RTCP/FuzzerSenderReport.hpp" #include "RTC/RTCP/Packet.hpp" void FuzzerRtcRtcpSenderReport::Fuzz(RTC::RTCP::SenderReportPacket* packet) { // A well formed packet must have a single report. if (packet->GetCount() == 1) { packet->Serialize(RTC::RTCP::SerializationBuffer); } packet->GetCount(); packet->GetSize(); // TODO. // AddReport(SenderReport* report); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& report = (*it); report->Serialize(RTC::RTCP::SerializationBuffer); report->GetSize(); report->GetSsrc(); report->SetSsrc(1111); report->GetNtpSec(); report->SetNtpSec(2222); report->GetNtpFrac(); report->SetNtpFrac(3333); report->GetRtpTs(); report->SetRtpTs(4444); report->GetPacketCount(); report->SetPacketCount(1024); report->GetOctetCount(); report->SetOctetCount(11223344); } } ================================================ FILE: worker/fuzzer/src/RTC/RTCP/FuzzerXr.cpp ================================================ #include "RTC/RTCP/FuzzerXr.hpp" void FuzzerRtcRtcpExtendedReport::Fuzz(RTC::RTCP::ExtendedReportPacket* packet) { packet->Serialize(RTC::RTCP::SerializationBuffer); packet->GetCount(); packet->GetSize(); packet->GetSsrc(); packet->SetSsrc(1111); for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& report = (*it); report->Serialize(RTC::RTCP::SerializationBuffer); report->GetSize(); report->GetType(); } } ================================================ FILE: worker/fuzzer/src/RTC/RTP/Codecs/FuzzerAV1.cpp ================================================ #include "RTC/RTP/Codecs/FuzzerAV1.hpp" #include "RTC/RTP/Codecs/AV1.hpp" class Listener : public RTC::RTP::Codecs::DependencyDescriptor::Listener { public: void OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) override { } }; void FuzzerRtcRtpCodecsAV1::Fuzz(const uint8_t* data, size_t len) { Listener listener; std::unique_ptr templateDependencyStructure; auto dependencyDescriptor = std::unique_ptr( RTC::RTP::Codecs::DependencyDescriptor::Parse( data, len, std::addressof(listener), templateDependencyStructure)); auto* descriptor = RTC::RTP::Codecs::AV1::Parse(dependencyDescriptor); if (!descriptor) { return; } delete descriptor; } ================================================ FILE: worker/fuzzer/src/RTC/RTP/Codecs/FuzzerDependencyDescriptor.cpp ================================================ #include "RTC/RTP/Codecs/FuzzerDependencyDescriptor.hpp" void FuzzerRtcRtpCodecsDependencyDescriptor::Fuzz(const uint8_t* data, size_t len) { std::unique_ptr templateDependencyStructure; DependencyDescriptorListener listener; auto* descriptor = RTC::RTP::Codecs::DependencyDescriptor::Parse( data, len, std::addressof(listener), templateDependencyStructure); if (!descriptor) { return; } delete descriptor; } ================================================ FILE: worker/fuzzer/src/RTC/RTP/Codecs/FuzzerH264.cpp ================================================ #include "RTC/RTP/Codecs/FuzzerH264.hpp" #include "RTC/RTP/Codecs/H264.hpp" void FuzzerRtcRtpCodecsH264::Fuzz(const uint8_t* data, size_t len) { RTC::RTP::Codecs::DependencyDescriptor* dependencyDescriptor{ nullptr }; const RTC::RTP::Codecs::H264::PayloadDescriptor* descriptor = RTC::RTP::Codecs::H264::Parse(data, len, dependencyDescriptor); if (!descriptor) { return; } delete descriptor; } ================================================ FILE: worker/fuzzer/src/RTC/RTP/Codecs/FuzzerOpus.cpp ================================================ #include "RTC/RTP/Codecs/FuzzerOpus.hpp" #include "RTC/RTP/Codecs/Opus.hpp" void FuzzerRtcRtpCodecsOpus::Fuzz(const uint8_t* data, size_t len) { const RTC::RTP::Codecs::Opus::PayloadDescriptor* descriptor = RTC::RTP::Codecs::Opus::Parse(data, len); if (!descriptor) { return; } delete descriptor; } ================================================ FILE: worker/fuzzer/src/RTC/RTP/Codecs/FuzzerVP8.cpp ================================================ #include "RTC/RTP/Codecs/FuzzerVP8.hpp" #include "RTC/RTP/Codecs/VP8.hpp" void FuzzerRtcRtpCodecsVP8::Fuzz(const uint8_t* data, size_t len) { const RTC::RTP::Codecs::VP8::PayloadDescriptor* descriptor = RTC::RTP::Codecs::VP8::Parse(data, len); if (!descriptor) { return; } delete descriptor; } ================================================ FILE: worker/fuzzer/src/RTC/RTP/Codecs/FuzzerVP9.cpp ================================================ #include "RTC/RTP/Codecs/FuzzerVP9.hpp" #include "RTC/RTP/Codecs/VP9.hpp" void FuzzerRtcRtpCodecsVP9::Fuzz(const uint8_t* data, size_t len) { const RTC::RTP::Codecs::VP9::PayloadDescriptor* descriptor = RTC::RTP::Codecs::VP9::Parse(data, len); if (!descriptor) { return; } delete descriptor; } ================================================ FILE: worker/fuzzer/src/RTC/RTP/FuzzerPacket.cpp ================================================ #include "RTC/RTP/FuzzerPacket.hpp" #include "RTC/RTP/HeaderExtensionIds.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RtpDictionaries.hpp" #include #include void FuzzerRtcRtcPacket::Fuzz(const uint8_t* data, size_t len) { if (!RTC::RTP::Packet::IsRtp(data, len)) { return; } std::unique_ptr packet{ RTC::RTP::Packet::Parse(data, len, len) }; if (!packet) { return; } // We need to serialize the Packet into a separate buffer because setters // below will try to write into packet memory. // // NOTE: Let's make the buffer bigger to test API that increases packet size. const std::unique_ptr buffer(new uint8_t[len + 512]); packet->Serialize(buffer.get(), len + 512); std::vector extensions; uint8_t extenLen; bool voice; uint8_t volume; bool camera; bool flip; uint16_t rotation; uint32_t absSendTime; uint16_t playoutDelayMinDelay; uint16_t playoutDelayMaxDelay; uint16_t wideSeqNumber; std::string mid; std::string rid; packet->GetBuffer(); packet->GetBufferLength(); packet->GetLength(); // packet->Dump(); packet->GetVersion(); packet->GetPayloadType(); packet->SetPayloadType(100); packet->HasMarker(); packet->SetMarker(true); packet->SetMarker(false); packet->GetSequenceNumber(); packet->SetSequenceNumber(12345); packet->GetTimestamp(); packet->SetTimestamp(8888); packet->GetSsrc(); packet->SetSsrc(666); packet->HasCsrcs(); packet->HasHeaderExtension(); packet->GetHeaderExtensionId(); packet->GetHeaderExtensionValue(); packet->GetHeaderExtensionValueLength(); packet->HasExtensions(); packet->HasOneByteExtensions(); packet->HasTwoBytesExtensions(); RTC::RTP::HeaderExtensionIds headerExtensionIds{}; headerExtensionIds.mid = 5; headerExtensionIds.rid = 6; headerExtensionIds.absSendTime = 3; headerExtensionIds.transportWideCc01 = 4; headerExtensionIds.ssrcAudioLevel = 1; headerExtensionIds.videoOrientation = 2; headerExtensionIds.playoutDelay = 8; packet->AssignExtensionIds(headerExtensionIds); packet->HasExtension(5); packet->GetExtensionValue(5, extenLen); packet->ReadMid(mid); packet->UpdateMid(mid); packet->HasExtension(6); packet->GetExtensionValue(6, extenLen); packet->ReadRid(rid); packet->HasExtension(3); packet->GetExtensionValue(3, extenLen); packet->ReadAbsSendTime(absSendTime); packet->UpdateAbsSendTime(12345678u); packet->HasExtension(4); packet->GetExtensionValue(4, extenLen); packet->ReadTransportWideCc01(wideSeqNumber); packet->UpdateTransportWideCc01(12345u); packet->HasExtension(1); packet->GetExtensionValue(1, extenLen); packet->ReadSsrcAudioLevel(volume, voice); packet->HasExtension(2); packet->GetExtensionValue(2, extenLen); packet->ReadVideoOrientation(camera, flip, rotation); packet->HasExtension(8); packet->GetExtensionValue(8, extenLen); packet->ReadPlayoutDelay(playoutDelayMinDelay, playoutDelayMaxDelay); packet->HasExtension(6); packet->HasExtension(7); packet->HasExtension(8); packet->HasExtension(9); packet->HasExtension(10); packet->HasExtension(11); packet->HasExtension(12); packet->HasExtension(13); packet->HasExtension(14); packet->HasExtension(15); uint8_t value1[] = { 0x01, 0x02, 0x03, 0x04 }; extensions.emplace_back( RTC::RtpHeaderExtensionUri::Type::MID, // type 1, // id 4, // len value1 // value ); uint8_t value2[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11 }; extensions.emplace_back( RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, // type 2, // id 11, // len value2 // value ); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions); extensions.clear(); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions); uint8_t value3[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 }; extensions.emplace_back( RTC::RtpHeaderExtensionUri::Type::MID, // type 14, // id 24, // len value3 // value ); extensions.emplace_back( RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, // type 15, // id 24, // len value3 // value ); extensions.emplace_back( RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID, // type 22, // id 24, // len value3 // value ); extensions.emplace_back( RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR, // type 100, // id 24, // len value3 // value ); // NOTE: Cannot use One-Byte Extensions because we are using big ids and // lengths. // packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions); packet->HasExtension(13); packet->GetExtensionValue(13, extenLen); packet->ReadAbsSendTime(absSendTime); packet->UpdateAbsSendTime(12345678); packet->HasExtension(14); packet->GetExtensionValue(14, extenLen); packet->ReadTransportWideCc01(wideSeqNumber); packet->UpdateTransportWideCc01(12345); packet->HasExtension(11); packet->GetExtensionValue(11, extenLen); packet->ReadSsrcAudioLevel(volume, voice); packet->HasExtension(12); packet->GetExtensionValue(12, extenLen); packet->ReadVideoOrientation(camera, flip, rotation); packet->HasExtension(15); packet->GetExtensionValue(15, extenLen); packet->ReadPlayoutDelay(playoutDelayMinDelay, playoutDelayMaxDelay); packet->HasPayload(); packet->GetPayload(); packet->GetPayloadLength(); packet->HasPadding(); packet->IsPaddedTo4Bytes(); packet->GetPaddingLength(); packet->SetPaddingLength(1); packet->SetPaddingLength(6); packet->SetPaddingLength(0); packet->IsPaddedTo4Bytes(); // clang-format off uint8_t payload[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA }; // clang-format on packet->SetPayload(payload, sizeof(payload)); packet->RtxEncode(1, 2, 3); packet->RtxDecode(4, 5); packet->PadTo4Bytes(); packet->ShiftPayload(4, 2); packet->ShiftPayload(4, -2); packet->ShiftPayload(3, 4); packet->ShiftPayload(3, -4); // These cannot be tested this way. // packet->SetPayloadDescriptorHandler(); // packet->ProcessPayload(); // packet->GetPayloadEncoder(); // packet->EncodePayload(); // packet->RestorePayload(); // packet->IsKeyFrame(); // packet->GetSpatialLayer(); // packet->GetTemporalLayer(); const std::unique_ptr buffer2(new uint8_t[len + 512]); packet.reset(packet->Clone(buffer2.get(), len + 512)); packet->RemoveHeaderExtension(); packet->SetPayloadLength(sizeof(payload) - 2); packet->RemovePayload(); } ================================================ FILE: worker/fuzzer/src/RTC/RTP/FuzzerProbationGenerator.cpp ================================================ #include "RTC/RTP/FuzzerProbationGenerator.hpp" #include "RTC/RTP/ProbationGenerator.hpp" void FuzzerRtcRtpProbationGenerator::Fuzz(const uint8_t* /*data*/, size_t len) { std::unique_ptr probationGenerator{ new RTC::RTP::ProbationGenerator() }; probationGenerator->GetNextPacket(len); } ================================================ FILE: worker/fuzzer/src/RTC/RTP/FuzzerRetransmissionBuffer.cpp ================================================ #include "RTC/RTP/FuzzerRetransmissionBuffer.hpp" #include "Utils.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RetransmissionBuffer.hpp" #include "RTC/RTP/SharedPacket.hpp" void FuzzerRtcRtpRetransmissionBuffer::Fuzz(const uint8_t* data, size_t len) { const uint16_t maxItems{ 2500u }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; // Trick to initialize our stuff just once (use static). static RTC::RTP::RetransmissionBuffer retransmissionBuffer( maxItems, maxRetransmissionDelayMs, clockRate); // clang-format off uint8_t buffer[] = { 0b10000000, 0b01111011, 0b01010010, 0b00001110, 0b01011011, 0b01101011, 0b11001010, 0b10110101, 0, 0, 0, 2 }; // clang-format on // Create base RtpPacket instance. auto* packet = RTC::RTP::Packet::Parse(buffer, 12); size_t offset{ 0u }; while (len >= 4u) { const RTC::RTP::SharedPacket sharedPacket; // Set 'random' sequence number and timestamp. packet->SetSequenceNumber(Utils::Byte::Get2Bytes(data, offset)); packet->SetTimestamp(Utils::Byte::Get4Bytes(data, offset)); retransmissionBuffer.Insert(packet, sharedPacket); len -= 4u; offset += 4; } delete packet; } ================================================ FILE: worker/fuzzer/src/RTC/RTP/FuzzerRtpStreamSend.cpp ================================================ #include "RTC/RTP/FuzzerRtpStreamSend.hpp" #include "Utils.hpp" #include "mocks/include/MockShared.hpp" #include "RTC/RTP/SharedPacket.hpp" namespace { // NOLINTNEXTLINE(readability-identifier-naming) thread_local mocks::MockShared shared(/*getTimeMs*/ []() { return 1000; }); } // namespace void FuzzerRtcRtpStreamSend::Fuzz(const uint8_t* data, size_t len) { // clang-format off uint8_t buffer[] = { 0b10000000, 0b01111011, 0b01010010, 0b00001110, 0b01011011, 0b01101011, 0b11001010, 0b10110101, 0, 0, 0, 2 }; // clang-format on // Create base RtpPacket instance. auto* packet = RTC::RTP::Packet::Parse(buffer, 12); // Create a RtpStreamSend instance. TestRtpStreamListener testRtpStreamListener; // Create RtpStreamSend instance. RTC::RTP::RtpStream::Params params; params.ssrc = 1111; params.clockRate = 90000; params.useNack = true; params.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; packet->SetSsrc(params.ssrc); std::string mid; auto* stream = new RTC::RTP::RtpStreamSend( std::addressof(testRtpStreamListener), std::addressof(shared), params, mid); size_t offset{ 0u }; while (len >= 4u) { const RTC::RTP::SharedPacket sharedPacket; // Set 'random' sequence number and timestamp. packet->SetSequenceNumber(Utils::Byte::Get2Bytes(data, offset)); packet->SetTimestamp(Utils::Byte::Get4Bytes(data, offset)); stream->ReceivePacket(packet, sharedPacket); len -= 4u; offset += 4; } delete stream; delete packet; } ================================================ FILE: worker/fuzzer/src/RTC/SCTP/association/FuzzerStateCookie.cpp ================================================ #include "RTC/SCTP/FuzzerStateCookie.hpp" #include "Utils.hpp" #include "RTC/SCTP/association/StateCookie.hpp" #include // std::memcpy() namespace { alignas(4) thread_local uint8_t DataBuffer[65536]; alignas(4) thread_local uint8_t StateCookieSerializeBuffer[65536]; alignas(4) thread_local uint8_t StateCookieCloneBuffer[65536]; } // namespace void FuzzerRtcSctpStateCookie::Fuzz(const uint8_t* data, size_t len) { // NOTE: We need to copy given data into another buffer because we are gonna // write into it. std::memcpy(DataBuffer, data, len); // We need to force `data` to be a StateCookie since it's too hard that // random data matches it. if (len > RTC::SCTP::StateCookie::StateCookieLength) { len = Utils::Crypto::GetRandomUInt( RTC::SCTP::StateCookie::StateCookieLength, RTC::SCTP::StateCookie::StateCookieLength + 10); if (len < RTC::SCTP::StateCookie::StateCookieLength + 5) { Utils::Byte::Set8Bytes(DataBuffer, 0, RTC::SCTP::StateCookie::Magic1); Utils::Byte::Set2Bytes( DataBuffer, RTC::SCTP::StateCookie::NegotiatedCapabilitiesOffset, RTC::SCTP::StateCookie::Magic2); } } RTC::SCTP::StateCookie* stateCookie = RTC::SCTP::StateCookie::Parse(DataBuffer, len); if (!stateCookie) { return; } stateCookie->GetLocalVerificationTag(); stateCookie->GetRemoteVerificationTag(); stateCookie->GetLocalInitialTsn(); stateCookie->GetRemoteInitialTsn(); stateCookie->GetRemoteAdvertisedReceiverWindowCredit(); stateCookie->GetTieTag(); stateCookie->GetNegotiatedCapabilities(); stateCookie->Serialize(StateCookieSerializeBuffer, len); stateCookie->GetLocalVerificationTag(); stateCookie->GetRemoteVerificationTag(); stateCookie->GetLocalInitialTsn(); stateCookie->GetRemoteInitialTsn(); stateCookie->GetRemoteAdvertisedReceiverWindowCredit(); stateCookie->GetTieTag(); stateCookie->GetNegotiatedCapabilities(); auto* clonedStateCookie = stateCookie->Clone(StateCookieCloneBuffer, len); delete stateCookie; clonedStateCookie->GetLocalVerificationTag(); clonedStateCookie->GetRemoteVerificationTag(); clonedStateCookie->GetLocalInitialTsn(); clonedStateCookie->GetRemoteInitialTsn(); clonedStateCookie->GetRemoteAdvertisedReceiverWindowCredit(); clonedStateCookie->GetTieTag(); clonedStateCookie->GetNegotiatedCapabilities(); clonedStateCookie->Serialize(StateCookieSerializeBuffer, len); delete clonedStateCookie; } ================================================ FILE: worker/fuzzer/src/RTC/SCTP/packet/FuzzerPacket.cpp ================================================ #include "RTC/SCTP/packet/FuzzerPacket.hpp" #include "RTC/SCTP/packet/Packet.hpp" namespace { thread_local uint8_t PacketSerializeBuffer[65536]; thread_local uint8_t PacketCloneBuffer[65536]; } // namespace void FuzzerRtcSctpPacket::Fuzz(const uint8_t* data, size_t len) { RTC::SCTP::Packet* packet = RTC::SCTP::Packet::Parse(data, len); if (!packet) { return; } packet->GetSourcePort(); packet->GetDestinationPort(); packet->GetVerificationTag(); packet->GetChecksum(); packet->ValidateCRC32cChecksum(); packet->HasChunks(); packet->GetChunksCount(); packet->GetChunkAt(0); packet->GetChunkAt(1); packet->GetChunkAt(2); packet->GetChunkAt(666); packet->Serialize(PacketSerializeBuffer, len); packet->GetSourcePort(); packet->SetSourcePort(12345); packet->GetDestinationPort(); packet->SetDestinationPort(54321); packet->GetVerificationTag(); packet->SetVerificationTag(12345678); packet->GetChecksum(); packet->ValidateCRC32cChecksum(); packet->SetChecksum(999999); packet->WriteCRC32cChecksum(); packet->ValidateCRC32cChecksum(); packet->HasChunks(); packet->GetChunksCount(); packet->GetChunkAt(0); packet->GetChunkAt(1); packet->GetChunkAt(2); packet->GetChunkAt(666); auto* clonedPacket = packet->Clone(PacketCloneBuffer, len); delete packet; clonedPacket->GetSourcePort(); clonedPacket->SetSourcePort(12345); clonedPacket->GetDestinationPort(); clonedPacket->SetDestinationPort(54321); clonedPacket->GetVerificationTag(); clonedPacket->SetVerificationTag(12345678); clonedPacket->GetChecksum(); clonedPacket->ValidateCRC32cChecksum(); clonedPacket->SetChecksum(999999); clonedPacket->WriteCRC32cChecksum(); clonedPacket->ValidateCRC32cChecksum(); clonedPacket->HasChunks(); clonedPacket->GetChunksCount(); clonedPacket->GetChunkAt(0); clonedPacket->GetChunkAt(1); clonedPacket->GetChunkAt(2); clonedPacket->GetChunkAt(666); clonedPacket->Serialize(PacketSerializeBuffer, len); delete clonedPacket; } ================================================ FILE: worker/fuzzer/src/fuzzer.cpp ================================================ #define MS_CLASS "fuzzer" #include "common.hpp" #include "DepLibSRTP.hpp" #include "DepLibUV.hpp" #include "DepLibWebRTC.hpp" #include "DepOpenSSL.hpp" // TODO: Remove once we only use built-in SCTP stack. #include "DepUsrSCTP.hpp" #include "FuzzerUtils.hpp" #include "Settings.hpp" #include "Utils.hpp" #include "RTC/DtlsTransport.hpp" #include "RTC/FuzzerDtlsTransport.hpp" #include "RTC/FuzzerRateCalculator.hpp" #include "RTC/FuzzerSeqManager.hpp" #include "RTC/FuzzerTrendCalculator.hpp" #include "RTC/ICE/FuzzerStunPacket.hpp" #include "RTC/RTCP/FuzzerPacket.hpp" #include "RTC/RTP/Codecs/FuzzerAV1.hpp" #include "RTC/RTP/Codecs/FuzzerDependencyDescriptor.hpp" #include "RTC/RTP/Codecs/FuzzerH264.hpp" #include "RTC/RTP/Codecs/FuzzerOpus.hpp" #include "RTC/RTP/Codecs/FuzzerVP8.hpp" #include "RTC/RTP/Codecs/FuzzerVP9.hpp" #include "RTC/RTP/FuzzerPacket.hpp" #include "RTC/RTP/FuzzerProbationGenerator.hpp" #include "RTC/RTP/FuzzerRetransmissionBuffer.hpp" #include "RTC/RTP/FuzzerRtpStreamSend.hpp" #include "RTC/SCTP/FuzzerStateCookie.hpp" #include "RTC/SCTP/packet/FuzzerPacket.hpp" #include // std::getenv() #include #include // std::istringstream() #include #include namespace { // NOLINTBEGIN(readability-identifier-naming) bool fuzzStun = false; bool fuzzDtls = false; bool fuzzSctp = false; bool fuzzRtp = false; bool fuzzRtcp = false; bool fuzzCodecs = false; bool fuzzUtils = false; // NOLINTEND(readability-identifier-naming) int init() { std::string logLevel{ "none" }; std::vector logTags = { "info" }; const auto* logLevelPtr = std::getenv("MS_FUZZ_LOG_LEVEL"); const auto* logTagsPtr = std::getenv("MS_FUZZ_LOG_TAGS"); // Get logLevel from ENV variable. if (logLevelPtr) { logLevel = std::string(logLevelPtr); } // Get logTags from ENV variable. if (logTagsPtr) { auto logTagsStr = std::string(logTagsPtr); std::istringstream iss(logTagsStr); std::string logTag; while (iss >> logTag) { logTags.push_back(logTag); } } Settings::SetLogLevel(logLevel); Settings::SetLogTags(logTags); Settings::PrintConfiguration(); // Select what to fuzz. if (std::getenv("MS_FUZZ_STUN")) { std::cout << "[fuzzer] STUN fuzzer enabled" << std::endl; fuzzStun = true; } if (std::getenv("MS_FUZZ_DTLS")) { std::cout << "[fuzzer] DTLS fuzzer enabled" << std::endl; fuzzDtls = true; } if (std::getenv("MS_FUZZ_SCTP")) { std::cout << "[fuzzer] SCTP fuzzer enabled" << std::endl; fuzzSctp = true; } if (std::getenv("MS_FUZZ_RTP")) { std::cout << "[fuzzer] RTP fuzzer enabled" << std::endl; fuzzRtp = true; } if (std::getenv("MS_FUZZ_RTCP")) { std::cout << "[fuzzer] RTCP fuzzer enabled" << std::endl; fuzzRtcp = true; } if (std::getenv("MS_FUZZ_CODECS")) { std::cout << "[fuzzer] codecs fuzzer enabled" << std::endl; fuzzCodecs = true; } if (std::getenv("MS_FUZZ_UTILS")) { std::cout << "[fuzzer] Utils fuzzer enabled" << std::endl; fuzzUtils = true; } if (!fuzzStun && !fuzzDtls && !fuzzSctp && !fuzzRtp && !fuzzRtcp && !fuzzCodecs && !fuzzUtils) { std::cout << "[fuzzer] all fuzzers enabled" << std::endl; fuzzStun = true; fuzzDtls = true; fuzzSctp = true; fuzzRtp = true; fuzzRtcp = true; fuzzCodecs = true; fuzzUtils = true; } // Initialize static stuff. DepLibUV::ClassInit(); DepOpenSSL::ClassInit(); DepLibSRTP::ClassInit(); // TODO: Remove once we only use built-in SCTP stack. if (!Settings::configuration.useBuiltInSctpStack) { DepUsrSCTP::ClassInit(); } DepLibWebRTC::ClassInit(); Utils::Crypto::ClassInit(); RTC::DtlsTransport::ClassInit(); return 0; } } // namespace extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t len) { // Trick to initialize our stuff just once. // NOLINTNEXTLINE(readability-identifier-naming) static thread_local const int unused = init(); // Avoid [-Wunused-variable]. (void)unused; if (fuzzStun) { FuzzerRtcIceStunPacket::Fuzz(data, len); } if (fuzzDtls) { FuzzerRtcDtlsTransport::Fuzz(data, len); } if (fuzzSctp) { FuzzerRtcSctpPacket::Fuzz(data, len); FuzzerRtcSctpStateCookie::Fuzz(data, len); } if (fuzzRtp) { FuzzerRtcRtcPacket::Fuzz(data, len); FuzzerRtcRtpStreamSend::Fuzz(data, len); FuzzerRtcRtpRetransmissionBuffer::Fuzz(data, len); FuzzerRtcRtpProbationGenerator::Fuzz(data, len); FuzzerRtcSeqManager::Fuzz(data, len); FuzzerRtcRateCalculator::Fuzz(data, len); } if (fuzzRtcp) { FuzzerRtcRtcpPacket::Fuzz(data, len); } if (fuzzCodecs) { FuzzerRtcRtpCodecsOpus::Fuzz(data, len); FuzzerRtcRtpCodecsVP8::Fuzz(data, len); FuzzerRtcRtpCodecsVP9::Fuzz(data, len); FuzzerRtcRtpCodecsH264::Fuzz(data, len); FuzzerRtcRtpCodecsAV1::Fuzz(data, len); FuzzerRtcRtpCodecsDependencyDescriptor::Fuzz(data, len); } if (fuzzUtils) { FuzzerUtils::Fuzz(data, len); FuzzerRtcTrendCalculator::Fuzz(data, len); } return 0; } ================================================ FILE: worker/include/Channel/ChannelMessageRegistrator.hpp ================================================ #ifndef MS_CHANNEL_MESSAGE_REGISTRATOR_HPP #define MS_CHANNEL_MESSAGE_REGISTRATOR_HPP #include "Channel/ChannelMessageRegistratorInterface.hpp" #include "Channel/ChannelSocket.hpp" #include #include namespace Channel { class ChannelMessageRegistrator : public Channel::ChannelMessageRegistratorInterface { public: explicit ChannelMessageRegistrator(); ~ChannelMessageRegistrator() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) override; void RegisterHandler( const std::string& id, ChannelSocket::RequestHandler* channelRequestHandler, ChannelSocket::NotificationHandler* channelNotificationHandler) override; void UnregisterHandler(const std::string& id) override; ChannelSocket::RequestHandler* GetChannelRequestHandler(const std::string& id) override; ChannelSocket::NotificationHandler* GetChannelNotificationHandler(const std::string& id) override; private: std::unordered_map mapChannelRequestHandlers; std::unordered_map mapChannelNotificationHandlers; }; } // namespace Channel #endif ================================================ FILE: worker/include/Channel/ChannelMessageRegistratorInterface.hpp ================================================ #ifndef MS_CHANNEL_MESSAGE_REGISTRATOR_INTERFACE_HPP #define MS_CHANNEL_MESSAGE_REGISTRATOR_INTERFACE_HPP // TODO: We should have a ChannelSocketInterface class instead. #include "Channel/ChannelSocket.hpp" #include namespace Channel { class ChannelMessageRegistratorInterface { public: virtual ~ChannelMessageRegistratorInterface() = default; public: virtual flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) = 0; virtual void RegisterHandler( const std::string& id, ChannelSocket::RequestHandler* channelRequestHandler, ChannelSocket::NotificationHandler* channelNotificationHandler) = 0; virtual void UnregisterHandler(const std::string& id) = 0; virtual ChannelSocket::RequestHandler* GetChannelRequestHandler(const std::string& id) = 0; virtual ChannelSocket::NotificationHandler* GetChannelNotificationHandler(const std::string& id) = 0; }; } // namespace Channel #endif ================================================ FILE: worker/include/Channel/ChannelNotification.hpp ================================================ #ifndef MS_CHANNEL_NOTIFICATION_HPP #define MS_CHANNEL_NOTIFICATION_HPP #include "FBS/notification.h" #include #include namespace Channel { class ChannelNotification { public: using Event = FBS::Notification::Event; private: static const absl::flat_hash_map Event2String; public: explicit ChannelNotification(const FBS::Notification::Notification* notification); ~ChannelNotification() = default; public: // Passed by argument. Event event; // Others. const char* eventCStr; std::string handlerId; const FBS::Notification::Notification* data{ nullptr }; }; } // namespace Channel #endif ================================================ FILE: worker/include/Channel/ChannelNotifier.hpp ================================================ #ifndef MS_CHANNEL_NOTIFIER_HPP #define MS_CHANNEL_NOTIFIER_HPP #include "Channel/ChannelSocket.hpp" #include namespace Channel { class ChannelNotifier { public: explicit ChannelNotifier(Channel::ChannelSocket* channel); public: flatbuffers::FlatBufferBuilder& GetBufferBuilder() { return this->bufferBuilder; } template void Emit( const std::string& targetId, FBS::Notification::Event event, FBS::Notification::Body type, flatbuffers::Offset& body) { auto& builder = this->bufferBuilder; auto notification = FBS::Notification::CreateNotificationDirect( builder, targetId.c_str(), event, type, body.Union()); auto message = FBS::Message::CreateMessage(builder, FBS::Message::Body::Notification, notification.Union()); builder.FinishSizePrefixed(message); this->channel->Send(builder.GetBufferPointer(), builder.GetSize()); builder.Clear(); } void Emit(const std::string& targetId, FBS::Notification::Event event); private: // Passed by argument. Channel::ChannelSocket* channel{ nullptr }; // Others. flatbuffers::FlatBufferBuilder bufferBuilder; }; } // namespace Channel #endif ================================================ FILE: worker/include/Channel/ChannelRequest.hpp ================================================ #ifndef MS_CHANNEL_REQUEST_HPP #define MS_CHANNEL_REQUEST_HPP #include "common.hpp" #include "FBS/message.h" #include "FBS/request.h" #include "FBS/response.h" #include #include #include namespace Channel { // Avoid cyclic #include problem by declaring classes instead of including // the corresponding header files. class ChannelSocket; class ChannelRequest { public: using Method = FBS::Request::Method; public: static thread_local flatbuffers::FlatBufferBuilder bufferBuilder; static const absl::flat_hash_map Method2String; public: ChannelRequest(Channel::ChannelSocket* channel, const FBS::Request::Request* request); ~ChannelRequest() = default; flatbuffers::FlatBufferBuilder& GetBufferBuilder() const { return ChannelRequest::bufferBuilder; } void Accept(); template void Accept(FBS::Response::Body type, flatbuffers::Offset& body) { assert(!this->replied); this->replied = true; auto& builder = ChannelRequest::bufferBuilder; auto response = FBS::Response::CreateResponse(builder, this->id, true, type, body.Union()); auto message = FBS::Message::CreateMessage(builder, FBS::Message::Body::Response, response.Union()); builder.FinishSizePrefixed(message); this->Send(builder.GetBufferPointer(), builder.GetSize()); builder.Clear(); } void Error(const char* reason = nullptr); void TypeError(const char* reason = nullptr); private: void Send(const uint8_t* buffer, size_t size) const; void SendResponse(const flatbuffers::Offset& response) const; public: // Passed by argument. Channel::ChannelSocket* channel{ nullptr }; const FBS::Request::Request* data{ nullptr }; // Others. uint32_t id{ 0u }; Method method; const char* methodCStr; std::string handlerId; bool replied{ false }; }; } // namespace Channel #endif ================================================ FILE: worker/include/Channel/ChannelSocket.hpp ================================================ #ifndef MS_CHANNEL_SOCKET_HPP #define MS_CHANNEL_SOCKET_HPP #include "common.hpp" #include "Channel/ChannelNotification.hpp" #include "Channel/ChannelRequest.hpp" #include "handles/UnixStreamSocketHandle.hpp" namespace Channel { class ConsumerSocket : public UnixStreamSocketHandle { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnConsumerSocketMessage( const ConsumerSocket* consumerSocket, char* msg, size_t msgLen) = 0; virtual void OnConsumerSocketClosed(const ConsumerSocket* consumerSocket) = 0; }; public: ConsumerSocket(int fd, size_t bufferSize, Listener* listener); ~ConsumerSocket() override; /* Pure virtual methods inherited from UnixStreamSocketHandle. */ public: void UserOnUnixStreamRead() override; void UserOnUnixStreamSocketClosed() override; private: // Passed by argument. Listener* listener{ nullptr }; }; class ProducerSocket : public UnixStreamSocketHandle { public: ProducerSocket(int fd, size_t bufferSize); /* Pure virtual methods inherited from UnixStreamSocketHandle. */ public: void UserOnUnixStreamRead() override { } void UserOnUnixStreamSocketClosed() override { } }; class ChannelSocket : public ConsumerSocket::Listener { public: class RequestHandler { public: virtual ~RequestHandler() = default; public: virtual void HandleRequest(Channel::ChannelRequest* request) = 0; }; class NotificationHandler { public: virtual ~NotificationHandler() = default; public: virtual void HandleNotification(Channel::ChannelNotification* notification) = 0; }; class Listener : public RequestHandler, public NotificationHandler { public: ~Listener() override = default; public: virtual void OnChannelClosed(Channel::ChannelSocket* channel) = 0; }; public: #if defined(MS_TEST) || defined(MS_FUZZER) explicit ChannelSocket(); #endif explicit ChannelSocket(int consumerFd, int producerFd); explicit ChannelSocket( ChannelReadFn channelReadFn, ChannelReadCtx channelReadCtx, ChannelWriteFn channelWriteFn, ChannelWriteCtx channelWriteCtx); ~ChannelSocket() override; public: void Close(); void SetListener(Listener* listener); void Send(const uint8_t* data, uint32_t dataLen); void SendLog(const char* data, uint32_t dataLen); bool CallbackRead(); private: void SendImpl(const uint8_t* payload, uint32_t payloadLen); /* Pure virtual methods inherited from ConsumerSocket::Listener. */ public: void OnConsumerSocketMessage(const ConsumerSocket* consumerSocket, char* msg, size_t msgLen) override; void OnConsumerSocketClosed(const ConsumerSocket* consumerSocket) override; private: // Passed by argument. Listener* listener{ nullptr }; // Others. bool closed{ false }; ConsumerSocket* consumerSocket{ nullptr }; ProducerSocket* producerSocket{ nullptr }; ChannelReadFn channelReadFn{ nullptr }; ChannelReadCtx channelReadCtx{ nullptr }; ChannelWriteFn channelWriteFn{ nullptr }; ChannelWriteCtx channelWriteCtx{ nullptr }; uv_async_t* uvReadHandle{ nullptr }; flatbuffers::FlatBufferBuilder bufferBuilder; }; } // namespace Channel #endif ================================================ FILE: worker/include/DepLibSRTP.hpp ================================================ #ifndef MS_DEP_LIBSRTP_HPP #define MS_DEP_LIBSRTP_HPP #include #include #include class DepLibSRTP { public: static void ClassInit(); static void ClassDestroy(); static bool IsError(srtp_err_status_t code) { return (code != srtp_err_status_ok); } static const std::string& GetErrorString(srtp_err_status_t code); private: static const std::unordered_map ErrorCode2String; }; #endif ================================================ FILE: worker/include/DepLibUV.hpp ================================================ #ifndef MS_DEP_LIBUV_HPP #define MS_DEP_LIBUV_HPP #include "common.hpp" #include class DepLibUV { public: static void ClassInit(); static void ClassDestroy(); static void PrintVersion(); static void RunLoop(); static uv_loop_t* GetLoop() { return DepLibUV::loop; } static uint64_t GetTimeMs() { return static_cast(uv_hrtime() / 1000000u); } static uint64_t GetTimeUs() { return static_cast(uv_hrtime() / 1000u); } static uint64_t GetTimeNs() { return uv_hrtime(); } // Used within libwebrtc dependency which uses int64_t values for time // representation. static int64_t GetTimeMsInt64() { return static_cast(DepLibUV::GetTimeMs()); } // Used within libwebrtc dependency which uses int64_t values for time // representation. static int64_t GetTimeUsInt64() { return static_cast(DepLibUV::GetTimeUs()); } private: static thread_local uv_loop_t* loop; }; #endif ================================================ FILE: worker/include/DepLibUring.hpp ================================================ #ifndef MS_DEP_LIBURING_HPP #define MS_DEP_LIBURING_HPP #include "FBS/liburing.h" #include #include #include #include class DepLibUring { public: using onSendCallback = const std::function; /* Struct for the user data field of SQE and CQE. */ struct UserData { // Pointer to send buffer. uint8_t* store{ nullptr }; // Frame len buffer for TCP. uint8_t frameLen[2] = { 0 }; // iovec for TCP, first item for framing, second item for payload. struct iovec iov[2]; // Send callback. onSendCallback* cb{ nullptr }; // Index in userDatas array. size_t idx{ 0 }; }; /* Number of submission queue entries (SQE). */ static constexpr size_t QueueDepth{ 1024 * 4 }; static constexpr size_t SendBufferSize{ 1500 }; using SendBuffer = uint8_t[SendBufferSize]; static void ClassInit(); static void ClassDestroy(); static bool CheckRuntimeSupport(); static bool IsEnabled(); static flatbuffers::Offset FillBuffer(flatbuffers::FlatBufferBuilder& builder); static void StartPollingCQEs(); static void StopPollingCQEs(); static uint8_t* GetSendBuffer(); static bool PrepareSend( int sockfd, const uint8_t* data, size_t len, const struct sockaddr* addr, onSendCallback* cb); static bool PrepareWrite( int sockfd, const uint8_t* data1, size_t len1, const uint8_t* data2, size_t len2, onSendCallback* cb); static void Submit(); static void SetActive(); static bool IsActive(); class LibUring; // Whether liburing is enabled or not after runtime checks. static thread_local bool enabled; static thread_local LibUring* liburing; public: // Singleton. class LibUring { public: LibUring(); ~LibUring(); flatbuffers::Offset FillBuffer(flatbuffers::FlatBufferBuilder& builder) const; void StartPollingCQEs(); void StopPollingCQEs(); uint8_t* GetSendBuffer(); bool PrepareSend( int sockfd, const uint8_t* data, size_t len, const struct sockaddr* addr, onSendCallback* cb); bool PrepareWrite( int sockfd, const uint8_t* data1, size_t len1, const uint8_t* data2, size_t len2, onSendCallback* cb); void Submit(); void SetActive() { this->active = true; } bool IsActive() const { return this->active; } bool IsZeroCopyEnabled() const { return this->zeroCopyEnabled; } io_uring* GetRing() { return std::addressof(this->ring); } int GetEventFd() const { return this->efd; } void ReleaseUserDataEntry(size_t idx) { this->availableUserDataEntries.push(idx); } private: void SetInactive() { this->active = false; } UserData* GetUserData(); bool IsDataInSendBuffers(const uint8_t* data) const { return data >= this->sendBuffers[0] && data <= this->sendBuffers[DepLibUring::QueueDepth - 1]; } private: // io_uring instance. io_uring ring; // Event file descriptor to watch for io_uring completions. int efd; // libuv handle used to poll io_uring completions. uv_poll_t* uvHandle{ nullptr }; // Whether we are currently sending RTP over io_uring. bool active{ false }; // Whether Zero Copy feature is enabled. bool zeroCopyEnabled{ true }; // Pre-allocated UserData's. UserData userDatas[QueueDepth]{}; // Indexes of available UserData entries. std::queue availableUserDataEntries; // Pre-allocated SendBuffer's. SendBuffer sendBuffers[QueueDepth]; // iovec structs to be registered for Zero Copy. struct iovec iovecs[QueueDepth]; // Submission queue entry process count. uint64_t sqeProcessCount{ 0u }; // Submission queue entry miss count. uint64_t sqeMissCount{ 0u }; // User data miss count. uint64_t userDataMissCount{ 0u }; }; }; #endif ================================================ FILE: worker/include/DepLibWebRTC.hpp ================================================ #ifndef MS_DEP_LIBWEBRTC_HPP #define MS_DEP_LIBWEBRTC_HPP class DepLibWebRTC { public: static void ClassInit(); static void ClassDestroy(); }; #endif ================================================ FILE: worker/include/DepOpenSSL.hpp ================================================ #ifndef MS_DEP_OPENSSL_HPP #define MS_DEP_OPENSSL_HPP class DepOpenSSL { public: static void ClassInit(); }; #endif ================================================ FILE: worker/include/DepUsrSCTP.hpp ================================================ #ifndef MS_DEP_USRSCTP_HPP #define MS_DEP_USRSCTP_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/SctpAssociation.hpp" #include "handles/TimerHandleInterface.hpp" #include class DepUsrSCTP { private: class Checker : public TimerHandleInterface::Listener { public: explicit Checker(SharedInterface* shared); ~Checker() override; public: void Start(); void Stop(); /* Pure virtual methods inherited from TimerHandleInterface::Listener. */ public: void OnTimer(TimerHandleInterface* timer) override; private: TimerHandleInterface* timer{ nullptr }; uint64_t lastCalledAtMs{ 0u }; }; public: static void ClassInit(); static void ClassDestroy(); static void CreateChecker(SharedInterface* shared); static void CloseChecker(); static uintptr_t GetNextSctpAssociationId(); static void RegisterSctpAssociation(RTC::SctpAssociation* sctpAssociation); static void DeregisterSctpAssociation(RTC::SctpAssociation* sctpAssociation); static RTC::SctpAssociation* RetrieveSctpAssociation(uintptr_t id); private: static thread_local Checker* checker; static uint64_t numSctpAssociations; static uintptr_t nextSctpAssociationId; static absl::flat_hash_map mapIdSctpAssociation; }; #endif ================================================ FILE: worker/include/LogLevel.hpp ================================================ #ifndef MS_LOG_LEVEL_HPP #define MS_LOG_LEVEL_HPP #include "common.hpp" enum class LogLevel : uint8_t { LOG_DEBUG = 3, LOG_WARN = 2, LOG_ERROR = 1, LOG_NONE = 0 }; #endif ================================================ FILE: worker/include/Logger.hpp ================================================ /** * Logger facility. * * This include file defines logging macros for source files (.cpp). Each * source file including Logger.hpp MUST define its own MS_CLASS macro. Include * files (.hpp) MUST NOT include Logger.hpp. * * All the logging macros use the same format as printf(). The XXX_STD() version * of a macro logs to stdoud/stderr instead of using the ChannelSocket instance. * However some macros such as MS_ABORT() and MS_ASSERT() always log to stderr. * * If the macro MS_LOG_STD is defined, all the macros log to stdout/stderr. * * If the macro MS_LOG_FILE_LINE is defined, all the logging macros print more * verbose information, including current file and line. * * MS_TRACE() * * Logs the current method/function if MS_LOG_TRACE macro is defined and the * current log level is "debug". * * MS_HAS_DEBUG_TAG(tag) * MS_HAS_WARN_TAG(tag) * * True if the current log level is satisfied and the given tag is enabled. * * MS_DEBUG_TAG(tag, ...) * MS_WARN_TAG(tag, ...) * * Logs if the current log level is satisfied and the given tag is enabled. * * Example: * MS_WARN_TAG(ice, "ICE failed"); * * MS_DEBUG_2TAGS(tag1, tag2, ...) * MS_WARN_2TAGS(tag1, tag2, ...) * * Logs if the current log level is satisfied and any of the given two tags * is enabled. * * Example: * MS_DEBUG_2TAGS(ice, dtls, "media connection established"); * * MS_DEBUG_DEV(...) * * Logs if the current source file defines the MS_LOG_DEV_LEVEL macro with * value 3. * * Example: * MS_DEBUG_DEV("foo:%" PRIu32, foo); * * MS_WARN_DEV(...) * * Logs if the current source file defines the MS_LOG_DEV_LEVEL macro with * value >= 2. * * Example: * MS_WARN_DEV("foo:%" PRIu32, foo); * * MS_DUMP(...) * * Logs always. Useful for temporal debugging. Do not use it for Dump() * methods, use MS_DUMP_CLEAN() instead. * * Example: * MS_DUMP("foo"); * * MS_DUMP_CLEAN(indentation, ...) * * Log always. Useful for Dump() methods in packets. It doesn't print the * class and method names. * `indentation` mandatory argument must be 0, 1, 2 or 3, and it affects the * output by adding indentation at the start of the string. * * MS_DUMP_DATA(const uint8_t* data, size_t len) * * Logs always. Prints the given data in hexadecimal format (Wireshark * friendly). * * MS_ERROR(...) * * Logs an error if the current log level is satisfied (or if the current * source file defines the MS_LOG_DEV_LEVEL macro with value >= 1). Must just * be used for internal errors that should not happen. * * MS_ABORT(...) * * Logs the given error to stderr and aborts the process. * * MS_ASSERT(condition, ...) * * If the condition is not satisfied, it calls MS_ABORT(). */ #ifndef MS_LOGGER_HPP #define MS_LOGGER_HPP #include "common.hpp" #include "LogLevel.hpp" #include "Settings.hpp" #include "Channel/ChannelSocket.hpp" #include // std::snprintf(), std::fprintf(), stdout, stderr #include // std::abort() #include // clang-format off // NOLINTBEGIN #define _MS_TAG_ENABLED(tag) Settings::configuration.logTags.tag #define _MS_TAG_ENABLED_2(tag1, tag2) (Settings::configuration.logTags.tag1 || Settings::configuration.logTags.tag2) // NOLINTEND #if !defined(MS_LOG_DEV_LEVEL) #define MS_LOG_DEV_LEVEL 0 #elif MS_LOG_DEV_LEVEL < 0 || MS_LOG_DEV_LEVEL > 3 #error "invalid MS_LOG_DEV_LEVEL macro value" #endif // Usage: // MS_DEBUG_DEV("Leading text "MS_UINT16_TO_BINARY_PATTERN, MS_UINT16_TO_BINARY(value)); #define MS_UINT16_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c" #define MS_UINT16_TO_BINARY(value) \ ((value & 0x8000) ? '1' : '0'), \ ((value & 0x4000) ? '1' : '0'), \ ((value & 0x2000) ? '1' : '0'), \ ((value & 0x1000) ? '1' : '0'), \ ((value & 0x800) ? '1' : '0'), \ ((value & 0x400) ? '1' : '0'), \ ((value & 0x200) ? '1' : '0'), \ ((value & 0x100) ? '1' : '0'), \ ((value & 0x80) ? '1' : '0'), \ ((value & 0x40) ? '1' : '0'), \ ((value & 0x20) ? '1' : '0'), \ ((value & 0x10) ? '1' : '0'), \ ((value & 0x08) ? '1' : '0'), \ ((value & 0x04) ? '1' : '0'), \ ((value & 0x02) ? '1' : '0'), \ ((value & 0x01) ? '1' : '0') // Usage: // MS_DEBUG_DEV("Leading text "MS_UINT8_TO_BINARY_PATTERN, MS_UINT8_TO_BINARY(value)); #define MS_UINT8_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c" #define MS_UINT8_TO_BINARY(value) \ ((value & 0x80) ? '1' : '0'), \ ((value & 0x40) ? '1' : '0'), \ ((value & 0x20) ? '1' : '0'), \ ((value & 0x10) ? '1' : '0'), \ ((value & 0x08) ? '1' : '0'), \ ((value & 0x04) ? '1' : '0'), \ ((value & 0x02) ? '1' : '0'), \ ((value & 0x01) ? '1' : '0') class Logger { public: static void ClassInit(Channel::ChannelSocket* channel); public: static const uint64_t Pid; static thread_local Channel::ChannelSocket* channel; static const size_t BufferSize {50000}; static thread_local char buffer[]; }; /* Logging macros. */ // NOLINTBEGIN #define _MS_LOG_SEPARATOR_CHAR_STD "\n" #ifdef MS_LOG_FILE_LINE #define _MS_LOG_STR "%s:%d | %s::%s()" #define _MS_LOG_STR_DESC _MS_LOG_STR " | " #define _MS_FILE (std::strchr(__FILE__, '/') ? std::strchr(__FILE__, '/') + 1 : __FILE__) #define _MS_LOG_ARG _MS_FILE, __LINE__, MS_CLASS, __FUNCTION__ #else #define _MS_LOG_STR "%s::%s()" #define _MS_LOG_STR_DESC _MS_LOG_STR " | " #define _MS_LOG_ARG MS_CLASS, __FUNCTION__ #endif // NOLINTEND #ifdef MS_LOG_TRACE #define MS_TRACE() \ do \ { \ if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG) \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "D(trace) " _MS_LOG_STR, _MS_LOG_ARG); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ } \ while (false) #define MS_TRACE_STD() \ do \ { \ if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG) \ { \ std::fprintf(stdout, "(trace) " _MS_LOG_STR _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG); \ std::fflush(stdout); \ } \ } \ while (false) #else #define MS_TRACE() {} #define MS_TRACE_STD() {} #endif #define MS_HAS_DEBUG_TAG(tag) \ (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED(tag)) #define MS_HAS_WARN_TAG(tag) \ (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED(tag)) #define MS_DEBUG_TAG(tag, desc, ...) \ do \ { \ if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED(tag)) \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "D" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ } \ while (false) #define MS_DEBUG_TAG_STD(tag, desc, ...) \ do \ { \ if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED(tag)) \ { \ std::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stdout); \ } \ } \ while (false) #define MS_WARN_TAG(tag, desc, ...) \ do \ { \ if (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED(tag)) \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "W" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ } \ while (false) #define MS_WARN_TAG_STD(tag, desc, ...) \ do \ { \ if (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED(tag)) \ { \ std::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stderr); \ } \ } \ while (false) #define MS_DEBUG_2TAGS(tag1, tag2, desc, ...) \ do \ { \ if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED_2(tag1, tag2)) \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "D" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ } \ while (false) #define MS_DEBUG_2TAGS_STD(tag1, tag2, desc, ...) \ do \ { \ if (Settings::configuration.logLevel == LogLevel::LOG_DEBUG && _MS_TAG_ENABLED_2(tag1, tag2)) \ { \ std::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stdout); \ } \ } \ while (false) #define MS_WARN_2TAGS(tag1, tag2, desc, ...) \ do \ { \ if (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED_2(tag1, tag2)) \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "W" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ } \ while (false) #define MS_WARN_2TAGS_STD(tag1, tag2, desc, ...) \ do \ { \ if (Settings::configuration.logLevel >= LogLevel::LOG_WARN && _MS_TAG_ENABLED_2(tag1, tag2)) \ { \ std::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stderr); \ } \ } \ while (false) #if MS_LOG_DEV_LEVEL == 3 #define MS_DEBUG_DEV(desc, ...) \ do \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "D" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ while (false) #define MS_DEBUG_DEV_STD(desc, ...) \ do \ { \ std::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stdout); \ } \ while (false) #else #define MS_DEBUG_DEV(desc, ...) {} #define MS_DEBUG_DEV_STD(desc, ...) {} #endif #if MS_LOG_DEV_LEVEL >= 2 #define MS_WARN_DEV(desc, ...) \ do \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "W" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ while (false) #define MS_WARN_DEV_STD(desc, ...) \ do \ { \ std::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stderr); \ } \ while (false) #else #define MS_WARN_DEV(desc, ...) {} #define MS_WARN_DEV_STD(desc, ...) {} #endif #define MS_DUMP(desc, ...) \ do \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "X" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ while (false) #define MS_DUMP_STD(desc, ...) \ do \ { \ std::fprintf(stdout, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stdout); \ } \ while (false) #define MS_DUMP_CLEAN(indentation, desc, ...) \ do \ { \ const int spaceCount = (indentation) * 2; \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "X%*s" desc, spaceCount, "", ##__VA_ARGS__); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ while (false) #define MS_DUMP_CLEAN_STD(indentation, desc, ...) \ do \ { \ const int spaceCount = (indentation) * 2; \ std::fprintf(stdout, "%*s" desc _MS_LOG_SEPARATOR_CHAR_STD, spaceCount, "", ##__VA_ARGS__); \ std::fflush(stdout); \ } \ while (false) #define MS_DUMP_DATA(data, len) \ do \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "X" _MS_LOG_STR, _MS_LOG_ARG); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ size_t bufferDataLen{ 0 }; \ for (size_t i{0}; i < len; ++i) \ { \ if (i % 4 == 0) \ { \ if (bufferDataLen != 0) \ { \ Logger::channel->SendLog(Logger::buffer, static_cast(bufferDataLen)); \ bufferDataLen = 0; \ } \ const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, "X%06X ", static_cast(i)); \ bufferDataLen += loggerWritten; \ } \ const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, "%02X ", static_cast(data[i])); \ bufferDataLen += loggerWritten; \ } \ if (bufferDataLen != 0) \ { \ Logger::channel->SendLog(Logger::buffer, static_cast(bufferDataLen)); \ } \ } \ while (false) #define MS_DUMP_DATA_STD(data, len) \ do \ { \ std::fprintf(stdout, _MS_LOG_STR _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG); \ size_t bufferDataLen{ 0 }; \ for (size_t i{0}; i < len; ++i) \ { \ if (i % 4 == 0) \ { \ if (bufferDataLen != 0) \ { \ Logger::buffer[bufferDataLen] = '\0'; \ std::fprintf(stdout, "%s", Logger::buffer); \ bufferDataLen = 0; \ } \ const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, "\n%06X ", static_cast(i)); \ bufferDataLen += loggerWritten; \ } \ const int loggerWritten = std::snprintf(Logger::buffer + bufferDataLen, Logger::BufferSize, "%02X ", static_cast(data[i])); \ bufferDataLen += loggerWritten; \ } \ if (bufferDataLen != 0) \ { \ Logger::buffer[bufferDataLen] = '\0'; \ std::fprintf(stdout, "%s", Logger::buffer); \ } \ std::fflush(stdout); \ } \ while (false) #define MS_ERROR(desc, ...) \ do \ { \ if (Settings::configuration.logLevel >= LogLevel::LOG_ERROR || MS_LOG_DEV_LEVEL >= 1) \ { \ const int loggerWritten = std::snprintf(Logger::buffer, Logger::BufferSize, "E" _MS_LOG_STR_DESC desc, _MS_LOG_ARG, ##__VA_ARGS__); \ Logger::channel->SendLog(Logger::buffer, static_cast(loggerWritten)); \ } \ } \ while (false) #define MS_ERROR_STD(desc, ...) \ do \ { \ if (Settings::configuration.logLevel >= LogLevel::LOG_ERROR || MS_LOG_DEV_LEVEL >= 1) \ { \ std::fprintf(stderr, _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stderr); \ } \ } \ while (false) #ifdef MS_EXECUTABLE #define MS_ABORT(desc, ...) \ do \ { \ std::fprintf(stderr, "(ABORT) " _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stderr); \ std::abort(); \ } \ while (false) #else #define MS_ABORT(desc, ...) \ do \ { \ std::fprintf(stderr, "(ABORT) " _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ std::fflush(stderr); \ char abortMessage[Logger::BufferSize]; \ std::snprintf(abortMessage, Logger::BufferSize, "(ABORT) " _MS_LOG_STR_DESC desc _MS_LOG_SEPARATOR_CHAR_STD, _MS_LOG_ARG, ##__VA_ARGS__); \ throw std::runtime_error(abortMessage); \ } \ while (false) #endif #define MS_ASSERT(condition, desc, ...) \ if (!(condition)) \ { \ MS_ABORT("failed assertion `%s`: " desc, #condition, ##__VA_ARGS__); \ } #ifdef MS_LOG_STD #undef MS_TRACE #define MS_TRACE MS_TRACE_STD #undef MS_DEBUG_TAG #define MS_DEBUG_TAG MS_DEBUG_TAG_STD #undef MS_WARN_TAG #define MS_WARN_TAG MS_WARN_TAG_STD #undef MS_DEBUG_2TAGS #define MS_DEBUG_2TAGS MS_DEBUG_2TAGS_STD #undef MS_WARN_2TAGS #define MS_WARN_2TAGS MS_WARN_2TAGS_STD #undef MS_DEBUG_DEV #define MS_DEBUG_DEV MS_DEBUG_DEV_STD #undef MS_WARN_DEV #define MS_WARN_DEV MS_WARN_DEV_STD #undef MS_DUMP #define MS_DUMP MS_DUMP_STD #undef MS_DUMP_CLEAN #define MS_DUMP_CLEAN MS_DUMP_CLEAN_STD #undef MS_DUMP_DATA #define MS_DUMP_DATA MS_DUMP_DATA_STD #undef MS_ERROR #define MS_ERROR MS_ERROR_STD #endif // clang-format on #endif ================================================ FILE: worker/include/MediaSoupErrors.hpp ================================================ #ifndef MS_MEDIASOUP_ERRORS_HPP #define MS_MEDIASOUP_ERRORS_HPP #include "Logger.hpp" #include // std::snprintf() #include class MediaSoupError : public std::runtime_error { public: explicit MediaSoupError(const char* description) : std::runtime_error(description) { } public: static const size_t BufferSize{ 2000 }; static thread_local char buffer[]; }; class MediaSoupTypeError : public MediaSoupError { public: explicit MediaSoupTypeError(const char* description) : MediaSoupError(description) { } }; // clang-format off #define MS_THROW_ERROR(desc, ...) \ do \ { \ MS_ERROR("throwing MediaSoupError: " desc, ##__VA_ARGS__); \ std::snprintf(MediaSoupError::buffer, MediaSoupError::BufferSize, desc, ##__VA_ARGS__); \ throw MediaSoupError(MediaSoupError::buffer); \ } while (false) #define MS_THROW_ERROR_STD(desc, ...) \ do \ { \ MS_ERROR_STD("throwing MediaSoupError: " desc, ##__VA_ARGS__); \ std::snprintf(MediaSoupError::buffer, MediaSoupError::BufferSize, desc, ##__VA_ARGS__); \ throw MediaSoupError(MediaSoupError::buffer); \ } while (false) #define MS_THROW_TYPE_ERROR(desc, ...) \ do \ { \ MS_ERROR("throwing MediaSoupTypeError: " desc, ##__VA_ARGS__); \ std::snprintf(MediaSoupError::buffer, MediaSoupError::BufferSize, desc, ##__VA_ARGS__); \ throw MediaSoupTypeError(MediaSoupError::buffer); \ } while (false) #define MS_THROW_TYPE_ERROR_STD(desc, ...) \ do \ { \ MS_ERROR_STD("throwing MediaSoupTypeError: " desc, ##__VA_ARGS__); \ std::snprintf(MediaSoupError::buffer, MediaSoupError::BufferSize, desc, ##__VA_ARGS__); \ throw MediaSoupTypeError(MediaSoupError::buffer); \ } while (false) // clang-format on #endif ================================================ FILE: worker/include/RTC/ActiveSpeakerObserver.hpp ================================================ #ifndef MS_RTC_ACTIVE_SPEAKER_OBSERVER_HPP #define MS_RTC_ACTIVE_SPEAKER_OBSERVER_HPP #include "SharedInterface.hpp" #include "RTC/RtpObserver.hpp" #include "handles/TimerHandleInterface.hpp" #include #include // Implementation of Dominant Speaker Identification for Multipoint // Videoconferencing by Ilana Volfin and Israel Cohen. This implementation uses // the RTP Audio Level extension from RFC-6464 for the input signal. This has // been ported from DominantSpeakerIdentification.java in Jitsi: // https://github.com/jitsi/jitsi-utils/blob/master/src/main/java/org/jitsi/utils/dsi/DominantSpeakerIdentification.java namespace RTC { class ActiveSpeakerObserver : public RTC::RtpObserver, public TimerHandleInterface::Listener { private: class Speaker { public: explicit Speaker(SharedInterface* shared); public: void EvalActivityScores(); double GetActivityScore(uint8_t interval) const; void LevelChanged(uint32_t level, uint64_t now); void LevelTimedOut(uint64_t now); private: bool ComputeImmediates(); bool ComputeLongs(); bool ComputeMediums(); void EvalImmediateActivityScore(); void EvalMediumActivityScore(); void EvalLongActivityScore(); void UpdateMinLevel(int8_t level); public: bool paused{ false }; double immediateActivityScore{ 0 }; double mediumActivityScore{ 0 }; double longActivityScore{ 0 }; uint64_t lastLevelChangeTime{ 0 }; private: uint8_t minLevel{ 0u }; uint8_t nextMinLevel{ 0u }; uint32_t nextMinLevelWindowLen{ 0u }; std::vector immediates; std::vector mediums; std::vector longs; std::vector levels; size_t nextLevelIndex{ 0u }; }; class ProducerSpeaker { public: ProducerSpeaker(SharedInterface* shared, RTC::Producer* producer); ~ProducerSpeaker(); public: RTC::Producer* producer; Speaker* speaker; }; private: static const uint8_t RelativeSpeachActivitiesLen{ 3u }; public: ActiveSpeakerObserver( SharedInterface* shared, const std::string& id, RTC::RtpObserver::Listener* listener, const FBS::ActiveSpeakerObserver::ActiveSpeakerObserverOptions* options); ~ActiveSpeakerObserver() override; public: void AddProducer(RTC::Producer* producer) override; void RemoveProducer(RTC::Producer* producer) override; void ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) override; void ProducerPaused(RTC::Producer* producer) override; void ProducerResumed(RTC::Producer* producer) override; private: void Paused() override; void Resumed() override; void Update(); bool CalculateActiveSpeaker(); void TimeoutIdleLevels(uint64_t now); /* Pure virtual methods inherited from TimerHandleInterface. */ protected: void OnTimer(TimerHandleInterface* timer) override; private: double relativeSpeachActivities[RelativeSpeachActivitiesLen]{}; std::string dominantId; TimerHandleInterface* periodicTimer{ nullptr }; uint16_t interval{ 300u }; // Map of ProducerSpeakers indexed by Producer id. absl::flat_hash_map mapProducerSpeakers; uint64_t lastLevelIdleTime{ 0u }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/AudioLevelObserver.hpp ================================================ #ifndef MS_RTC_AUDIO_LEVEL_OBSERVER_HPP #define MS_RTC_AUDIO_LEVEL_OBSERVER_HPP #include "SharedInterface.hpp" #include "RTC/RtpObserver.hpp" #include "handles/TimerHandleInterface.hpp" #include namespace RTC { class AudioLevelObserver : public RTC::RtpObserver, public TimerHandleInterface::Listener { private: struct DBovs { uint16_t totalSum{ 0u }; // Sum of dBvos (positive integer). size_t count{ 0u }; // Number of dBvos entries in totalSum. }; public: AudioLevelObserver( SharedInterface* shared, const std::string& id, RTC::RtpObserver::Listener* listener, const FBS::AudioLevelObserver::AudioLevelObserverOptions* options); ~AudioLevelObserver() override; public: void AddProducer(RTC::Producer* producer) override; void RemoveProducer(RTC::Producer* producer) override; void ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) override; void ProducerPaused(RTC::Producer* producer) override; void ProducerResumed(RTC::Producer* producer) override; private: void Paused() override; void Resumed() override; void Update(); void ResetMapProducerDBovs(); /* Pure virtual methods inherited from TimerHandleInterface. */ protected: void OnTimer(TimerHandleInterface* timer) override; private: // Passed by argument. uint16_t maxEntries{ 1u }; int8_t threshold{ -80 }; uint16_t interval{ 1000u }; // Allocated by this. TimerHandleInterface* periodicTimer{ nullptr }; // Others. absl::flat_hash_map mapProducerDBovs; bool silence{ true }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/BweType.hpp ================================================ #ifndef MS_RTC_BWE_TYPE_HPP #define MS_RTC_BWE_TYPE_HPP #include "common.hpp" namespace RTC { enum class BweType : uint8_t { TRANSPORT_CC = 1, REMB }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/Consts.hpp ================================================ #ifndef MS_RTC_CONSTS_HPP #define MS_RTC_CONSTS_HPP #include "common.hpp" namespace RTC { namespace Consts { /** * Max MTU size. */ constexpr size_t MtuSize{ 1500u }; /** * Maximum size for a RTCP compound packet. * IPv4|Ipv6 header size (20|40 bytes). IPv6 considered. * UDP|TCP header size (8|20 bytes). TCP considered. * SRTP Encryption (148 bytes): * - SRTP_MAX_TRAILER_LEN + 4 is the maximum number of octects that will * be added to an RTCP packet by srtp_protect_rtcp(). * - srtp.h: SRTP_MAX_TRAILER_LEN (SRTP_MAX_TAG_LEN + SRTP_MAX_MKI_LEN). */ constexpr size_t RtcpPacketMaxSize{ RTC::Consts::MtuSize - 40 - 20 - 148u }; /** * Max length for a 1 byte RTP header extension. */ constexpr uint8_t OneByteRtpExtensionMaxLength{ 16u }; /** * Max length for a 2 bytes RTP header extension. */ constexpr uint8_t TwoBytesRtpExtensionMaxLength{ 255u }; /** * MID RTP header extension max length (just used when setting/updating MID * extension). */ constexpr uint8_t MidRtpExtensionMaxLength{ 8u }; /** * Largest safe SCTP packet. Starting from the minimum guaranteed MTU value * of 1280 for IPv6 (which may not support fragmentation), take off 85 * bytes for DTLS/TURN/TCP/IP and ciphertext overhead. * * Additionally, it's possible that TURN adds an additional 4 bytes of * overhead after a channel has been established, so an additional 4 bytes * is subtracted. * * 1280 IPV6 MTU * -40 IPV6 header * -8 UDP * -24 GCM Cipher * -13 DTLS record header * -4 TURN ChannelData * = 1191 bytes. * * @remarks * Value copied from dcSCTP library. */ constexpr size_t MaxSafeMtuSizeForSctp{ 1191u }; } // namespace Consts } // namespace RTC #endif ================================================ FILE: worker/include/RTC/Consumer.hpp ================================================ #ifndef MS_RTC_CONSUMER_HPP #define MS_RTC_CONSUMER_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "Channel/ChannelRequest.hpp" #include "Channel/ChannelSocket.hpp" #include "FBS/consumer.h" #include "FBS/transport.h" #include "RTC/ConsumerTypes.hpp" #include "RTC/RTCP/CompoundPacket.hpp" #include "RTC/RTCP/FeedbackRtpNack.hpp" #include "RTC/RTCP/ReceiverReport.hpp" #include "RTC/RTP/HeaderExtensionIds.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" #include "RTC/RTP/RtpStreamSend.hpp" #include "RTC/RTP/SharedPacket.hpp" #include "RTC/RtpDictionaries.hpp" #include #include #include namespace RTC { class Consumer : public Channel::ChannelSocket::RequestHandler { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnConsumerSendRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) = 0; virtual void OnConsumerRetransmitRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) = 0; virtual void OnConsumerKeyFrameRequested(RTC::Consumer* consumer, uint32_t mappedSsrc) = 0; virtual void OnConsumerNeedBitrateChange(RTC::Consumer* consumer) = 0; virtual void OnConsumerNeedZeroBitrate(RTC::Consumer* consumer) = 0; virtual void OnConsumerProducerClosed(RTC::Consumer* consumer) = 0; }; private: struct TraceEventTypes { bool rtp{ false }; bool keyframe{ false }; bool nack{ false }; bool pli{ false }; bool fir{ false }; }; public: Consumer( SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, const FBS::Transport::ConsumeRequest* data, RTC::RtpParameters::Type type); ~Consumer() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; virtual flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder) = 0; virtual flatbuffers::Offset FillBufferScore( flatbuffers::FlatBufferBuilder& /*builder*/) const { return 0; }; RTC::Media::Kind GetKind() const { return this->kind; } const RTC::RtpParameters& GetRtpParameters() const { return this->rtpParameters; } const struct RTC::RTP::HeaderExtensionIds& GetRtpHeaderExtensionIds() const { return this->rtpHeaderExtensionIds; } RTC::RtpParameters::Type GetType() const { return this->type; } virtual RTC::ConsumerTypes::VideoLayers GetPreferredLayers() const { // By default return 1:1. RTC::ConsumerTypes::VideoLayers layers; return layers; } const std::vector& GetMediaSsrcs() const { return this->mediaSsrcs; } const std::vector& GetRtxSsrcs() const { return this->rtxSsrcs; } virtual bool IsActive() const { // The parent Consumer just checks whether Consumer and Producer are // not paused and the transport connected. // clang-format off return ( this->transportConnected && !this->paused && !this->producerPaused && !this->producerClosed ); // clang-format on } void TransportConnected(); void TransportDisconnected(); bool IsPaused() const { return this->paused; } bool IsProducerPaused() const { return this->producerPaused; } void ProducerPaused(); void ProducerResumed(); virtual void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; virtual void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; void ProducerRtpStreamScores(const std::vector* scores); virtual void ProducerRtpStreamScore( RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) = 0; virtual void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) = 0; void ProducerClosed(); void SetExternallyManagedBitrate() { this->externallyManagedBitrate = true; } virtual uint8_t GetBitratePriority() const = 0; virtual uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) = 0; virtual void ApplyLayers() = 0; virtual uint32_t GetDesiredBitrate() const = 0; virtual void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) = 0; virtual bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) = 0; virtual const std::vector& GetRtpStreams() const = 0; virtual void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) = 0; virtual void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) = 0; virtual void ReceiveKeyFrameRequest( RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) = 0; virtual void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) = 0; virtual void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) = 0; virtual uint32_t GetTransmissionRate(uint64_t nowMs) = 0; virtual float GetRtt() const = 0; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; protected: void EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx = false) const; void EmitTraceEventPliType(uint32_t ssrc) const; void EmitTraceEventFirType(uint32_t ssrc) const; void EmitTraceEventNackType() const; void EmitTraceEvent(flatbuffers::Offset& notification) const; private: virtual void UserOnTransportConnected() = 0; virtual void UserOnTransportDisconnected() = 0; virtual void UserOnPaused() = 0; virtual void UserOnResumed() = 0; public: // Passed by argument. std::string id; std::string producerId; protected: // Passed by argument. SharedInterface* shared{ nullptr }; RTC::Consumer::Listener* listener{ nullptr }; RTC::Media::Kind kind; RTC::RtpParameters rtpParameters; RTC::RtpParameters::Type type; std::vector consumableRtpEncodings; struct RTC::RTP::HeaderExtensionIds rtpHeaderExtensionIds; const std::vector* producerRtpStreamScores{ nullptr }; // Others. // Whether a payload type is supported or not is represented in the // corresponding position of the bitset. std::bitset<128u> supportedCodecPayloadTypes; uint64_t lastRtcpSentTime{ 0u }; uint16_t maxRtcpInterval{ 0u }; bool externallyManagedBitrate{ false }; uint8_t priority{ 1u }; struct TraceEventTypes traceEventTypes; private: // Others. std::vector mediaSsrcs; std::vector rtxSsrcs; bool transportConnected{ false }; bool paused{ false }; bool producerPaused{ false }; bool producerClosed{ false }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/ConsumerTypes.hpp ================================================ #ifndef MS_RTC_CONSUMER_TYPES_HPP #define MS_RTC_CONSUMER_TYPES_HPP #include "common.hpp" namespace RTC { namespace ConsumerTypes { struct VideoLayers { int16_t spatial{ -1 }; int16_t temporal{ -1 }; VideoLayers() = default; VideoLayers(int16_t spatial, int16_t temporal) : spatial(spatial), temporal(temporal) { } VideoLayers(const VideoLayers& other) = default; bool operator==(const VideoLayers& other) const { return spatial == other.spatial && temporal == other.temporal; } bool operator!=(const VideoLayers& other) const { return !(*this == other); } void Reset() { spatial = -1; temporal = -1; } }; } // namespace ConsumerTypes } // namespace RTC #endif ================================================ FILE: worker/include/RTC/DataConsumer.hpp ================================================ #ifndef MS_RTC_DATA_CONSUMER_HPP #define MS_RTC_DATA_CONSUMER_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "Channel/ChannelRequest.hpp" #include "Channel/ChannelSocket.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SctpDictionaries.hpp" #include #include namespace RTC { class DataConsumer : public Channel::ChannelSocket::RequestHandler { protected: using onQueuedCallback = const std::function; public: class Listener { public: virtual ~Listener() = default; public: // TODO: SCTP: Remove when we migrate to the new SCTP stack. virtual void OnDataConsumerSendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb) = 0; virtual void OnDataConsumerSendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) = 0; virtual void OnDataConsumerNeedBufferedAmount( RTC::DataConsumer* dataConsumer, uint32_t& bufferedAmount) = 0; virtual void OnDataConsumerDataProducerClosed(RTC::DataConsumer* dataConsumer) = 0; }; public: enum class Type : uint8_t { SCTP = 0, DIRECT }; public: DataConsumer( SharedInterface* shared, const std::string& id, const std::string& dataProducerId, RTC::DataConsumer::Listener* listener, const FBS::Transport::ConsumeDataRequest* data, size_t maxMessageSize); ~DataConsumer() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder) const; Type GetType() const { return this->type; } const RTC::SctpStreamParameters& GetSctpStreamParameters() const { return this->sctpStreamParameters; } bool IsActive() const { // It's active it DataConsumer and DataProducer are not paused and the transport // is connected. // clang-format off return ( this->transportConnected && (this->type == DataConsumer::Type::DIRECT || this->sctpAssociationConnected) && !this->paused && !this->dataProducerPaused && !this->dataProducerClosed ); // clang-format on } void TransportConnected(); void TransportDisconnected(); bool IsPaused() const { return this->paused; } bool IsDataProducerPaused() const { return this->dataProducerPaused; } void DataProducerPaused(); void DataProducerResumed(); void SctpAssociationConnected(); void SctpAssociationClosed(); void SetSctpAssociationBufferedAmount(uint32_t bufferedAmount); void SctpAssociationSendBufferFull(); void DataProducerClosed(); // TODO: SCTP: Remove when we migrate to the new SCTP stack. bool SendMessage( const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel, const onQueuedCallback* cb = nullptr); bool SendMessage( RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel, const onQueuedCallback* cb = nullptr); /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; public: // Passed by argument. std::string id; std::string dataProducerId; private: // Passed by argument. SharedInterface* shared{ nullptr }; RTC::DataConsumer::Listener* listener{ nullptr }; size_t maxMessageSize{ 0u }; // Others. Type type; RTC::SctpStreamParameters sctpStreamParameters; std::string label; std::string protocol; absl::flat_hash_set subchannels; bool transportConnected{ false }; bool sctpAssociationConnected{ false }; bool paused{ false }; bool dataProducerPaused{ false }; bool dataProducerClosed{ false }; size_t messagesSent{ 0u }; size_t bytesSent{ 0u }; uint32_t bufferedAmount{ 0u }; uint32_t bufferedAmountLowThreshold{ 0u }; bool forceTriggerBufferedAmountLow{ false }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/DataProducer.hpp ================================================ #ifndef MS_RTC_DATA_PRODUCER_HPP #define MS_RTC_DATA_PRODUCER_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "Channel/ChannelRequest.hpp" #include "Channel/ChannelSocket.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SctpDictionaries.hpp" #include #include namespace RTC { class DataProducer : public Channel::ChannelSocket::RequestHandler, public Channel::ChannelSocket::NotificationHandler { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnDataProducerReceiveData(RTC::DataProducer* producer, size_t len) = 0; // TODO: SCTP: Remove when we migrate to the new SCTP stack. virtual void OnDataProducerMessageReceived( RTC::DataProducer* dataProducer, const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel) = 0; virtual void OnDataProducerMessageReceived( RTC::DataProducer* dataProducer, RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel) = 0; virtual void OnDataProducerPaused(RTC::DataProducer* dataProducer) = 0; virtual void OnDataProducerResumed(RTC::DataProducer* dataProducer) = 0; }; public: enum class Type : uint8_t { SCTP = 0, DIRECT }; public: DataProducer( SharedInterface* shared, const std::string& id, size_t maxMessageSize, RTC::DataProducer::Listener* listener, const FBS::Transport::ProduceDataRequest* data); ~DataProducer() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder) const; Type GetType() const { return this->type; } const RTC::SctpStreamParameters& GetSctpStreamParameters() const { return this->sctpStreamParameters; } bool IsPaused() const { return this->paused; } // TODO: SCTP: Remove when we migrate to the new SCTP stack. void ReceiveMessage( const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel); void ReceiveMessage( RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel); /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; /* Methods inherited from Channel::ChannelSocket::NotificationHandler. */ public: void HandleNotification(Channel::ChannelNotification* notification) override; public: // Passed by argument. std::string id; private: // Passed by argument. SharedInterface* shared{ nullptr }; size_t maxMessageSize{ 0u }; RTC::DataProducer::Listener* listener{ nullptr }; // Others. Type type; RTC::SctpStreamParameters sctpStreamParameters; std::string label; std::string protocol; bool paused{ false }; size_t messagesReceived{ 0u }; size_t bytesReceived{ 0u }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/DirectTransport.hpp ================================================ #ifndef MS_RTC_DIRECT_TRANSPORT_HPP #define MS_RTC_DIRECT_TRANSPORT_HPP #include "SharedInterface.hpp" #include "RTC/Transport.hpp" namespace RTC { class DirectTransport : public RTC::Transport { public: DirectTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::DirectTransport::DirectTransportOptions* options); ~DirectTransport() override; public: flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; private: bool IsConnected() const override; void SendRtpPacket( RTC::Consumer* consumer, RTC::RTP::Packet* packet, RTC::Transport::onSendCallback* cb = nullptr) override; void SendRtcpPacket(RTC::RTCP::Packet* packet) override; void SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) override; // TODO: SCTP: Remove once we only use built-in SCTP stack. void SendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb = nullptr) override; void SendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb = nullptr) override; bool SendData(const uint8_t* data, size_t len) override; void RecvStreamClosed(uint32_t ssrc) override; void SendStreamClosed(uint32_t ssrc) override; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; /* Methods inherited from Channel::ChannelSocket::NotificationHandler. */ public: void HandleNotification(Channel::ChannelNotification* notification) override; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/DtlsTransport.hpp ================================================ #ifndef MS_RTC_DTLS_TRANSPORT_HPP #define MS_RTC_DTLS_TRANSPORT_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "FBS/webRtcTransport.h" #include "RTC/SrtpSession.hpp" #include "handles/TimerHandleInterface.hpp" #include #include #include #include #include #include namespace RTC { class DtlsTransport : public TimerHandleInterface::Listener { public: enum class DtlsState : uint8_t { NEW = 1, CONNECTING, CONNECTED, FAILED, CLOSED }; public: enum class Role : uint8_t { AUTO = 1, CLIENT, SERVER }; public: enum class FingerprintAlgorithm : uint8_t { SHA1 = 1, SHA224, SHA256, SHA384, SHA512 }; public: struct Fingerprint { FingerprintAlgorithm algorithm; std::string value; }; private: struct SrtpCryptoSuiteMapEntry { RTC::SrtpSession::CryptoSuite cryptoSuite; const char* name; }; public: class Listener { public: virtual ~Listener() = default; public: // DTLS is in the process of negotiating a secure connection. Incoming // media can flow through. // NOTE: The caller MUST NOT call any method during this callback. virtual void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) = 0; // DTLS has completed negotiation of a secure connection (including DTLS-SRTP // and remote fingerprint verification). Outgoing media can now flow through. // NOTE: The caller MUST NOT call any method during this callback. virtual void OnDtlsTransportConnected( const RTC::DtlsTransport* dtlsTransport, RTC::SrtpSession::CryptoSuite srtpCryptoSuite, uint8_t* srtpLocalKey, size_t srtpLocalKeyLen, uint8_t* srtpRemoteKey, size_t srtpRemoteKeyLen, std::string& remoteCert) = 0; // The DTLS connection has been closed as the result of an error (such as a // DTLS alert or a failure to validate the remote fingerprint). virtual void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) = 0; // The DTLS connection has been closed due to receipt of a close_notify alert. virtual void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) = 0; // Need to send DTLS data to the peer. virtual void OnDtlsTransportSendData( const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0; // DTLS application data received. virtual void OnDtlsTransportApplicationDataReceived( const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) = 0; }; public: static void ClassInit(); static void ClassDestroy(); static Role RoleFromFbs(FBS::WebRtcTransport::DtlsRole role); static FBS::WebRtcTransport::DtlsRole RoleToFbs(Role role); static FBS::WebRtcTransport::DtlsState StateToFbs(DtlsState state); static FingerprintAlgorithm AlgorithmFromFbs(FBS::WebRtcTransport::FingerprintAlgorithm algorithm); static FBS::WebRtcTransport::FingerprintAlgorithm AlgorithmToFbs(FingerprintAlgorithm algorithm); static bool IsDtls(const uint8_t* data, size_t len) { // clang-format off return ( // Minimum DTLS record length is 13 bytes. (len >= 13) && // @see RFC 7983. (data[0] > 19 && data[0] < 64) ); // clang-format on } static const std::vector& GetLocalFingerprints() { return DtlsTransport::localFingerprints; } private: static void GenerateCertificateAndPrivateKey(); static void ReadCertificateAndPrivateKeyFromFiles(); static void CreateSslCtx(); static void GenerateFingerprints(); private: static thread_local X509* certificate; static thread_local EVP_PKEY* privateKey; static thread_local SSL_CTX* sslCtx; static thread_local uint8_t sslReadBuffer[]; static const absl::flat_hash_map String2Role; static const absl::flat_hash_map String2FingerprintAlgorithm; static const absl::flat_hash_map FingerprintAlgorithm2String; static thread_local std::vector localFingerprints; static const std::vector SrtpCryptoSuites; public: explicit DtlsTransport(Listener* listener, SharedInterface* shared); ~DtlsTransport() override; public: void Dump(int indentation = 0) const; void Run(Role localRole); bool SetRemoteFingerprint(const Fingerprint& fingerprint); void ProcessDtlsData(const uint8_t* data, size_t len); DtlsState GetState() const { return this->state; } std::optional GetLocalRole() const { return this->localRole; } // Returns a boolean indicating whether the data could be sent. bool SendApplicationData(const uint8_t* data, size_t len); // This method must be public since it's called within an OpenSSL callback. void SendDtlsData(const uint8_t* data, size_t len); private: bool IsRunning() const { switch (this->state) { case DtlsState::NEW: { return false; } case DtlsState::CONNECTING: case DtlsState::CONNECTED: { return true; } case DtlsState::FAILED: case DtlsState::CLOSED: { return false; } } // Make GCC 4.9 happy. return false; } void Reset(); bool CheckStatus(int returnCode); bool SetTimeout(); bool ProcessHandshake(); bool CheckRemoteFingerprint(); void ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite); std::optional GetNegotiatedSrtpCryptoSuite(); /* Callbacks fired by OpenSSL events. */ public: void OnSslInfo(int where, int ret); /* Pure virtual methods inherited from TimerHandleInterface::Listener. */ public: void OnTimer(TimerHandleInterface* timer) override; private: // Passed by argument. Listener* listener{ nullptr }; SharedInterface* shared{ nullptr }; // Allocated by this. SSL* ssl{ nullptr }; BIO* sslBioFromNetwork{ nullptr }; // The BIO from which ssl reads. BIO* sslBioToNetwork{ nullptr }; // The BIO in which ssl writes. TimerHandleInterface* timer{ nullptr }; // Others. DtlsState state{ DtlsState::NEW }; std::optional localRole; std::optional remoteFingerprint; bool handshakeDone{ false }; bool handshakeDoneNow{ false }; std::string remoteCert; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/ICE/IceCandidate.hpp ================================================ #ifndef MS_RTC_ICE_ICE_CANDIDATE_HPP #define MS_RTC_ICE_ICE_CANDIDATE_HPP #include "common.hpp" #include "FBS/webRtcTransport.h" #include "RTC/TcpServer.hpp" #include "RTC/TransportTuple.hpp" #include "RTC/UdpSocket.hpp" #include #include namespace RTC { namespace ICE { class IceCandidate { using Protocol = TransportTuple::Protocol; public: enum class CandidateType : uint8_t { HOST = 1 }; public: enum class TcpCandidateType : uint8_t { PASSIVE = 1 }; public: static CandidateType CandidateTypeFromFbs(FBS::WebRtcTransport::IceCandidateType type); static FBS::WebRtcTransport::IceCandidateType CandidateTypeToFbs(CandidateType type); static TcpCandidateType TcpCandidateTypeFromFbs(FBS::WebRtcTransport::IceCandidateTcpType type); static FBS::WebRtcTransport::IceCandidateTcpType TcpCandidateTypeToFbs(TcpCandidateType type); public: IceCandidate(RTC::UdpSocket* udpSocket, uint32_t priority) : foundation("udpcandidate"), priority(priority), address(udpSocket->GetLocalIp()), protocol(Protocol::UDP), port(udpSocket->GetLocalPort()) { } IceCandidate(RTC::UdpSocket* udpSocket, uint32_t priority, std::string& announcedAddress) : foundation("udpcandidate"), priority(priority), address(announcedAddress), protocol(Protocol::UDP), port(udpSocket->GetLocalPort()) { } IceCandidate(RTC::TcpServer* tcpServer, uint32_t priority) : foundation("tcpcandidate"), priority(priority), address(tcpServer->GetLocalIp()), protocol(Protocol::TCP), port(tcpServer->GetLocalPort()) { } IceCandidate(RTC::TcpServer* tcpServer, uint32_t priority, std::string& announcedAddress) : foundation("tcpcandidate"), priority(priority), address(announcedAddress), protocol(Protocol::TCP), port(tcpServer->GetLocalPort()) { } flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; private: // Others. std::string foundation; uint32_t priority{ 0u }; std::string address; Protocol protocol; uint16_t port{ 0u }; CandidateType type{ CandidateType::HOST }; TcpCandidateType tcpType{ TcpCandidateType::PASSIVE }; }; } // namespace ICE } // namespace RTC #endif ================================================ FILE: worker/include/RTC/ICE/IceServer.hpp ================================================ #ifndef MS_RTC_ICE_ICE_SERVER_HPP #define MS_RTC_ICE_ICE_SERVER_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "FBS/webRtcTransport.h" #include "RTC/ICE/StunPacket.hpp" #include "RTC/TransportTuple.hpp" #include "handles/TimerHandleInterface.hpp" #include #include #include namespace RTC { namespace ICE { class IceServer : public TimerHandleInterface::Listener { public: enum class IceState : uint8_t { NEW = 1, CONNECTED, COMPLETED, DISCONNECTED, }; public: class Listener { public: virtual ~Listener() = default; public: /** * These callbacks are guaranteed to be called before ProcessStunPacket() * returns, so the given pointers are still usable. */ virtual void OnIceServerSendStunPacket( const RTC::ICE::IceServer* iceServer, const RTC::ICE::StunPacket* packet, RTC::TransportTuple* tuple) = 0; virtual void OnIceServerLocalUsernameFragmentAdded( const RTC::ICE::IceServer* iceServer, const std::string& usernameFragment) = 0; virtual void OnIceServerLocalUsernameFragmentRemoved( const RTC::ICE::IceServer* iceServer, const std::string& usernameFragment) = 0; virtual void OnIceServerTupleAdded( const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) = 0; virtual void OnIceServerTupleRemoved( const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) = 0; virtual void OnIceServerSelectedTuple( const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) = 0; virtual void OnIceServerConnected(const RTC::ICE::IceServer* iceServer) = 0; virtual void OnIceServerCompleted(const RTC::ICE::IceServer* iceServer) = 0; virtual void OnIceServerDisconnected(const RTC::ICE::IceServer* iceServer) = 0; }; public: static const std::string& IceStateToString(IceState iceState); static FBS::WebRtcTransport::IceState IceStateToFbs(IceState state); private: static std::unordered_map iceStateToString; public: IceServer( Listener* listener, SharedInterface* shared, const std::string& usernameFragment, const std::string& password, uint8_t consentTimeoutSec); ~IceServer() override; public: void Dump(int indentation = 0) const; void ProcessStunPacket(const RTC::ICE::StunPacket* packet, RTC::TransportTuple* tuple); const std::string& GetUsernameFragment() const { return this->usernameFragment; } const std::string& GetPassword() const { return this->password; } IceState GetState() const { return this->state; } RTC::TransportTuple* GetSelectedTuple() const { return this->selectedTuple; } void RestartIce(const std::string& usernameFragment, const std::string& password); bool IsValidTuple(const RTC::TransportTuple* tuple) const; void RemoveTuple(RTC::TransportTuple* tuple); /** * This should be just called in 'connected' or 'completed' state and the * given tuple must be an already valid tuple. */ void MayForceSelectedTuple(const RTC::TransportTuple* tuple); private: void ProcessStunRequest(const RTC::ICE::StunPacket* request, RTC::TransportTuple* tuple); void ProcessStunIndication(const RTC::ICE::StunPacket* indication); void ProcessStunResponse(const RTC::ICE::StunPacket* response); void HandleTuple( RTC::TransportTuple* tuple, bool hasUseCandidate, bool hasNomination, uint32_t nomination); /** * Store the given tuple and return its stored address. */ RTC::TransportTuple* AddTuple(RTC::TransportTuple* tuple); /** * If the given tuple exists return its stored address, nullptr otherwise. */ RTC::TransportTuple* HasTuple(const RTC::TransportTuple* tuple) const; /** * Set the given tuple as the selected tuple. * NOTE: The given tuple MUST be already stored within the list. */ void SetSelectedTuple(RTC::TransportTuple* storedTuple); bool IsConsentCheckSupported() const { return this->consentTimeoutMs != 0u; } bool IsConsentCheckRunning() const { return (this->consentCheckTimer && this->consentCheckTimer->IsActive()); } void StartConsentCheck(); void RestartConsentCheck(); void StopConsentCheck(); /* Pure virtual methods inherited from TimerHandleInterface::Listener. */ public: void OnTimer(TimerHandleInterface* timer) override; private: // Passed by argument. Listener* listener{ nullptr }; SharedInterface* shared{ nullptr }; std::string usernameFragment; std::string password; uint16_t consentTimeoutMs{ 30000u }; // Others. std::string oldUsernameFragment; std::string oldPassword; IceState state{ IceState::NEW }; uint32_t remoteNomination{ 0u }; std::list tuples; RTC::TransportTuple* selectedTuple{ nullptr }; TimerHandleInterface* consentCheckTimer{ nullptr }; bool isRemovingTuples{ false }; }; } // namespace ICE } // namespace RTC #endif ================================================ FILE: worker/include/RTC/ICE/StunPacket.hpp ================================================ #ifndef MS_RTC_ICE_STUN_PACKET_HPP #define MS_RTC_ICE_STUN_PACKET_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/Serializable.hpp" #include #include namespace RTC { namespace ICE { /** * STUN Packet. * * @see RFC 5389. * @see RFC 8445. */ class StunPacket : public Serializable { public: /** * STUN Message Header. * * @see RFC 5389 section 6. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |0 0| STUN Message Type | Message Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Magic Cookie | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | Transaction ID (96 bits) | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ /** * The STUN Message Type field is decomposed further into the following * structure: * * 0 1 * 2 3 4 5 6 7 8 9 0 1 2 3 4 5 * +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ * |M |M |M|M|M|C|M|M|M|C|M|M|M|M| * |11|10|9|8|7|1|6|5|4|0|3|2|1|0| * +--+--+-+-+-+-+-+-+-+-+-+-+-+-+ * * Here the bits in the message type field are shown as most significant * (M11) through least significant (M0). M11 through M0 represent a * 12-bit encoding of the method. C1 and C0 represent a 2-bit encoding * of the class. */ /** * STUN Attributes. * * After the STUN Message Header are zero or more Attributes. Each * Attribute MUST be TLV encoded, with a 16-bit type, 16-bit length, and * value. Each STUN Attribute MUST end on a 32-bit boundary. All fields * in an Attribute are transmitted most significant bit first. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type | Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Value (variable) .... * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ /** * STUN message class. */ enum class Class : uint8_t { REQUEST = 0, INDICATION = 1, SUCCESS_RESPONSE = 2, ERROR_RESPONSE = 3, UNSET = 255 }; /** * STUN message method. */ enum class Method : uint16_t { BINDING = 1, UNSET = 255 }; /** * STUN Attribute type. */ enum class AttributeType : uint16_t { MAPPED_ADDRESS = 0x0001, USERNAME = 0x0006, MESSAGE_INTEGRITY = 0x0008, ERROR_CODE = 0x0009, UNKNOWN_ATTRIBUTES = 0x000A, REALM = 0x0014, NONCE = 0x0015, XOR_MAPPED_ADDRESS = 0x0020, PRIORITY = 0x0024, USE_CANDIDATE = 0x0025, SOFTWARE = 0x8022, ALTERNATE_SERVER = 0x8023, FINGERPRINT = 0x8028, ICE_CONTROLLED = 0x8029, ICE_CONTROLLING = 0x802A, NOMINATION = 0xC001 }; // Authentication result. enum class AuthenticationResult : uint8_t { OK = 0, UNAUTHORIZED = 1, BAD_MESSAGE = 2 }; private: /** * @remarks * - This struct is NOT guaranteed to be aligned to any fixed number of * bytes because it contains a `size_t`, which is 4 or 8 bytes depending * on the architecture. Anyway we never cast any buffer to this struct. */ struct Attribute { Attribute(AttributeType type, uint16_t len, size_t offset) : type(type), len(len), offset(offset) {}; /** * Attribute type. */ AttributeType type; /** * Length of the value (not padded). */ uint16_t len; /** * Offset of the Attribute from the start of the Attributes. */ size_t offset; }; public: static const size_t FixedHeaderLength{ 20 }; static const size_t TransactionIdLength{ 12 }; static const size_t UsernameAttributeMaxLength{ 513 }; static const size_t XorMappedAddressIPv4Length{ 8 }; static const size_t XorMappedAddressIPv6Length{ 20 }; static const size_t SoftwareAttributeMaxLength{ 763 }; static const size_t MessageIntegrityAttributeLength{ 20 }; static const size_t FingerprintAttributeLength{ 4 }; /** * Whether given buffer could be a valid STUN Packet. */ static bool IsStun(const uint8_t* buffer, size_t bufferLength); /** * Parse a STUN Packet. * * @remarks * - `bufferLength` must be the exact length of the STUN Packet. */ static StunPacket* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a STUN Packet. * * @remarks * - `bufferLength` must be the exact length of the STUN Packet. * - If `transactionId` is not given then a random Transaction ID is * generated. */ static StunPacket* Factory( uint8_t* buffer, size_t bufferLength, StunPacket::Class klass, StunPacket::Method method, const uint8_t* transactionId); static StunPacket* Factory( uint8_t* buffer, size_t bufferLength, StunPacket::Class klass, StunPacket::Method method); private: static const uint8_t MagicCookie[]; private: /** * Constructor is private because we only want to create STUN Packet * instances via Parse() and Factory(). */ StunPacket(uint8_t* buffer, size_t bufferLength); public: ~StunPacket() override; void Dump(int indentation = 0) const final; StunPacket* Clone(uint8_t* buffer, size_t bufferLength) const final; StunPacket::Class GetClass() const { return this->klass; } StunPacket::Method GetMethod() const { return this->method; } const uint8_t* GetTransactionId() const { return GetTransactionIdPointer(); } void SetTransactionId(const uint8_t* transactionId); bool HasAttribute(StunPacket::AttributeType type) const { return this->attributes.find(type) != this->attributes.end(); } const std::string_view GetUsername() const { const auto* attribute = GetAttribute(StunPacket::AttributeType::USERNAME); if (!attribute) { return {}; } return std::string_view( reinterpret_cast(GetAttributeValue(attribute)), attribute->len); } void AddUsername(const std::string_view username); uint32_t GetPriority() const { const auto* attribute = GetAttribute(StunPacket::AttributeType::PRIORITY); if (!attribute) { return 0; } return Utils::Byte::Get4Bytes(GetAttributeValue(attribute), 0); } void AddPriority(uint32_t priority); uint64_t GetIceControlling() const { const auto* attribute = GetAttribute(StunPacket::AttributeType::ICE_CONTROLLING); if (!attribute) { return 0; } return Utils::Byte::Get8Bytes(GetAttributeValue(attribute), 0); } void AddIceControlling(uint64_t iceControlling); uint64_t GetIceControlled() const { const auto* attribute = GetAttribute(StunPacket::AttributeType::ICE_CONTROLLED); if (!attribute) { return 0; } return Utils::Byte::Get8Bytes(GetAttributeValue(attribute), 0); } void AddIceControlled(uint64_t iceControlled); void AddUseCandidate(); uint32_t GetNomination() const { const auto* attribute = GetAttribute(StunPacket::AttributeType::NOMINATION); if (!attribute) { return 0; } return Utils::Byte::Get4Bytes(GetAttributeValue(attribute), 0); } void AddNomination(uint32_t nomination); const std::string_view GetSoftware() const { const auto* attribute = GetAttribute(StunPacket::AttributeType::SOFTWARE); if (!attribute) { return {}; } return std::string_view( reinterpret_cast(GetAttributeValue(attribute)), attribute->len); } void AddSoftware(const std::string_view software); bool GetXorMappedAddress(struct sockaddr_storage* xorMappedAddressStorage) const; void AddXorMappedAddress(const struct sockaddr* xorMappedAddress); uint16_t GetErrorCode(std::string_view& reasonPhrase) const { const auto* attribute = GetAttribute(StunPacket::AttributeType::ERROR_CODE); if (!attribute) { return 0; } const auto* attributeValue = GetAttributeValue(attribute); const uint8_t errorClass = Utils::Byte::Get1Byte(attributeValue, 2) & 0b00000111; const uint8_t errorNumber = Utils::Byte::Get1Byte(attributeValue, 3); const auto errorCode = static_cast((errorClass * 100) + errorNumber); // Reason Phrase comes after the first 4 bytes (it could be zero // length). if (attribute->len > 4) { reasonPhrase = std::string_view( reinterpret_cast(GetAttributeValue(attribute) + 4), attribute->len - 4); } else { reasonPhrase = {}; } return errorCode; } void AddErrorCode(uint16_t errorCode, const std::string_view reasonPhrase); /** * Check authentication of the STUN request or notification. */ StunPacket::AuthenticationResult CheckAuthentication( const std::string_view usernameFragment1, const std::string_view& password) const; /** * Check authentication of the STUN success response or error response. */ StunPacket::AuthenticationResult CheckAuthentication(std::string_view password) const; /** * Whether the STUN Packet is protected, meaning that it has * MESSAGE-INTEGRITY and/or FINGERPRINT Attributes. */ bool IsProtected() const { return ( HasAttribute(StunPacket::AttributeType::MESSAGE_INTEGRITY) || HasAttribute(StunPacket::AttributeType::FINGERPRINT)); } /** * Adds MESSAGE-INTEGRITY and FINGERPRINT to the STUN Packet. * * @remarks * - MESSAGE-INTEGRITY is only added if given `password` is not empty. * - The application MUST NOT add more Attributes into the STUN Packet * and MUST NOT modify any field of the STUN Packet after calling * this method. * * @throw MediaSoupTypeError - If there is no enough space in the buffer. * @throw MediaSoupError - If the STUN Packet already has MESSAGE-INTEGRITY * or FINGERPRINT Attributes. */ void Protect(const std::string_view password); void Protect(); /** * Creates a STUN success response for the current STUN request. * * @throw MediaSoupError - If the STUN Packet is not a STUN request. */ StunPacket* CreateSuccessResponse(uint8_t* buffer, size_t bufferLength) const; /** * Creates a STUN error response for the current STUN request. It uses * given `errorCode` and `reasonPhrase` to add a ERROR-CODE Attribute. * * @throw MediaSoupError - If the STUN Packet is not a STUN request. */ StunPacket* CreateErrorResponse( uint8_t* buffer, size_t bufferLength, uint16_t errorCode, const std::string_view& reasonPhrase) const; private: uint8_t* GetFixedHeaderPointer() const { return const_cast(GetBuffer()); } uint16_t GetMessageLength() const { return Utils::Byte::Get2Bytes(GetFixedHeaderPointer(), 2); } void SetMessageLength(uint16_t msgLength) { Utils::Byte::Set2Bytes(GetFixedHeaderPointer(), 2, msgLength); } uint8_t* GetTransactionIdPointer() const { return GetFixedHeaderPointer() + 8; } uint8_t* GetAttributesPointer() const { return GetFixedHeaderPointer() + StunPacket::FixedHeaderLength; } size_t GetAttributesLength() const { return GetLength() - StunPacket::FixedHeaderLength; } /** * Validates whether the STUN Packet is valid. It also stores internal * offsets pointing to relevant STUN Attributes if `storeAttributes` is * `true`. */ #ifdef MS_TEST public: #endif bool Validate(bool storeAttributes); #ifdef MS_TEST private: #endif /** * Parses Attributes. Returns `true` if they are valid. It also stores * internal containers holding Attributes if `storeAttributes` is `true`. */ bool ParseAttributes(bool storeAttributes); /** * Stores the parsed Attribute data into the map of Attributes. * * @return `true` if the Attribute was stored and `false` if it couldn't * be stored because there was already an Attribute with same type in * the map. */ bool StoreParsedAttribute(StunPacket::AttributeType type, uint16_t len, size_t offset); /** * Stores a new Attribute data into the map of Attributes. * * @throw MediaSoupError - If there is not enough space in the buffer for * the new Attribute or if the Attribute couldn't be stored because * there was already an Attribute with same type in the map. */ void StoreNewAttribute(StunPacket::AttributeType type, const void* data, uint16_t len); const StunPacket::Attribute* GetAttribute(StunPacket::AttributeType type) const { const auto it = this->attributes.find(type); if (it != this->attributes.end()) { return std::addressof(it->second); } return nullptr; } const uint8_t* GetAttributeValue(const StunPacket::Attribute* attribute) const { return GetAttributesPointer() + attribute->offset + 4; } #ifdef MS_TEST public: #endif const uint8_t* GetMessageIntegrity() const { const auto* attribute = GetAttribute(StunPacket::AttributeType::MESSAGE_INTEGRITY); if (!attribute) { return nullptr; } return GetAttributeValue(attribute); } uint32_t GetFingerprint() const { const auto* attribute = GetAttribute(StunPacket::AttributeType::FINGERPRINT); if (!attribute) { return 0; } return Utils::Byte::Get4Bytes(GetAttributeValue(attribute), 0); } #ifdef MS_TEST private: #endif void AssertNotProtected() const; private: StunPacket::Class klass{ StunPacket::Class::UNSET }; StunPacket::Method method{ StunPacket::Method::UNSET }; // Map of STUN Attributes indexed by Attribute type. std::unordered_map attributes; }; } // namespace ICE } // namespace RTC #endif ================================================ FILE: worker/include/RTC/KeyFrameRequestManager.hpp ================================================ #ifndef MS_KEY_FRAME_REQUEST_MANAGER_HPP #define MS_KEY_FRAME_REQUEST_MANAGER_HPP #include "SharedInterface.hpp" #include "handles/TimerHandleInterface.hpp" #include namespace RTC { class PendingKeyFrameInfo : public TimerHandleInterface::Listener { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnKeyFrameRequestTimeout(PendingKeyFrameInfo* keyFrameRequestInfo) = 0; }; public: PendingKeyFrameInfo(Listener* listener, SharedInterface* shared, uint32_t ssrc); ~PendingKeyFrameInfo() override; uint32_t GetSsrc() const { return this->ssrc; } void SetRetryOnTimeout(bool notify) { this->retryOnTimeout = notify; } bool GetRetryOnTimeout() const { return this->retryOnTimeout; } void Restart() { this->timer->Restart(); } /* Pure virtual methods inherited from TimerHandleInterface::Listener. */ public: void OnTimer(TimerHandleInterface* timer) override; private: Listener* listener{ nullptr }; uint32_t ssrc; TimerHandleInterface* timer{ nullptr }; bool retryOnTimeout{ true }; }; class KeyFrameRequestDelayer : public TimerHandleInterface::Listener { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnKeyFrameDelayTimeout(KeyFrameRequestDelayer* keyFrameRequestDelayer) = 0; }; public: KeyFrameRequestDelayer(Listener* listener, SharedInterface* shared, uint32_t ssrc, uint32_t delay); ~KeyFrameRequestDelayer() override; uint32_t GetSsrc() const { return this->ssrc; } bool GetKeyFrameRequested() const { return this->keyFrameRequested; } void SetKeyFrameRequested(bool flag) { this->keyFrameRequested = flag; } /* Pure virtual methods inherited from TimerHandleInterface::Listener. */ public: void OnTimer(TimerHandleInterface* timer) override; private: Listener* listener{ nullptr }; uint32_t ssrc; TimerHandleInterface* timer{ nullptr }; bool keyFrameRequested{ false }; }; class KeyFrameRequestManager : public PendingKeyFrameInfo::Listener, public KeyFrameRequestDelayer::Listener { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnKeyFrameNeeded(KeyFrameRequestManager* keyFrameRequestManager, uint32_t ssrc) = 0; }; public: explicit KeyFrameRequestManager( Listener* listener, SharedInterface* shared, uint32_t keyFrameRequestDelay); ~KeyFrameRequestManager() override; void KeyFrameNeeded(uint32_t ssrc); void ForceKeyFrameNeeded(uint32_t ssrc); void KeyFrameReceived(uint32_t ssrc); /* Pure virtual methods inherited from PendingKeyFrameInfo::Listener. */ public: void OnKeyFrameRequestTimeout(PendingKeyFrameInfo* pendingKeyFrameInfo) override; /* Pure virtual methods inherited from PendingKeyFrameInfo::Listener. */ public: void OnKeyFrameDelayTimeout(KeyFrameRequestDelayer* keyFrameRequestDelayer) override; private: Listener* listener{ nullptr }; SharedInterface* shared{ nullptr }; uint32_t keyFrameRequestDelay{ 0u }; // 0 means disabled. absl::flat_hash_map mapSsrcPendingKeyFrameInfo; absl::flat_hash_map mapSsrcKeyFrameRequestDelayer; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/NackGenerator.hpp ================================================ #ifndef MS_RTC_NACK_GENERATOR_HPP #define MS_RTC_NACK_GENERATOR_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/SeqManager.hpp" #include "handles/TimerHandleInterface.hpp" #include #include #include namespace RTC { class NackGenerator : public TimerHandleInterface::Listener { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnNackGeneratorNackRequired(const std::vector& seqNumbers) = 0; virtual void OnNackGeneratorKeyFrameRequired() = 0; }; private: struct NackInfo { NackInfo() = default; explicit NackInfo(uint64_t createdAtMs, uint16_t seq, uint16_t sendAtSeq) : createdAtMs(createdAtMs), seq(seq), sendAtSeq(sendAtSeq) { } uint64_t createdAtMs{ 0u }; uint16_t seq{ 0u }; uint16_t sendAtSeq{ 0u }; uint64_t sentAtMs{ 0u }; uint8_t retries{ 0u }; }; enum class NackFilter : uint8_t { SEQ, TIME }; public: explicit NackGenerator(Listener* listener, SharedInterface* shared, unsigned int sendNackDelayMs); ~NackGenerator() override; bool ReceivePacket(const RTC::RTP::Packet* packet, bool isRecovered); size_t GetNackListLength() const { return this->nackList.size(); } void UpdateRtt(uint32_t rtt) { this->rtt = rtt; } void Reset(); private: void AddPacketsToNackList(uint16_t seqStart, uint16_t seqEnd); bool RemoveNackItemsUntilKeyFrame(); std::vector GetNackBatch(NackFilter filter); void MayRunTimer() const; /* Pure virtual methods inherited from TimerHandleInterface::Listener. */ public: void OnTimer(TimerHandleInterface* timer) override; private: // Passed by argument. Listener* listener{ nullptr }; SharedInterface* shared{ nullptr }; unsigned int sendNackDelayMs{ 0u }; // Allocated by this. TimerHandleInterface* timer{ nullptr }; // Others. std::map::SeqLowerThan> nackList; std::set::SeqLowerThan> keyFrameList; std::set::SeqLowerThan> recoveredList; bool started{ false }; uint16_t lastSeq{ 0u }; // Seq number of last valid packet. uint32_t rtt{ 0u }; // Round trip time (ms). }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/Parameters.hpp ================================================ #ifndef MS_RTC_PARAMETERS_HPP #define MS_RTC_PARAMETERS_HPP #include "common.hpp" #include "FBS/rtpParameters.h" #include #include #include namespace RTC { class Parameters { public: class Value { public: enum class Type : uint8_t { BOOLEAN = 1, INTEGER, DOUBLE, STRING, ARRAY_OF_INTEGERS }; public: explicit Value(bool booleanValue) : type(Type::BOOLEAN), booleanValue(booleanValue) { } explicit Value(int32_t integerValue) : type(Type::INTEGER), integerValue(integerValue) { } explicit Value(double doubleValue) : type(Type::DOUBLE), doubleValue(doubleValue) { } explicit Value(std::string& stringValue) : type(Type::STRING), stringValue(stringValue) { } explicit Value(std::string&& stringValue) : type(Type::STRING), stringValue(std::move(stringValue)) { } explicit Value(std::vector& arrayOfIntegers) : type(Type::ARRAY_OF_INTEGERS), arrayOfIntegers(arrayOfIntegers) { } public: Type type; bool booleanValue{ false }; int32_t integerValue{ 0 }; double doubleValue{ 0.0 }; std::string stringValue; std::vector arrayOfIntegers; }; public: std::vector> FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; void Set(const flatbuffers::Vector>* data); bool HasBoolean(const std::string& key) const; bool HasInteger(const std::string& key) const; bool HasPositiveInteger(const std::string& key) const; bool HasDouble(const std::string& key) const; bool HasString(const std::string& key) const; bool HasArrayOfIntegers(const std::string& key) const; bool IncludesInteger(const std::string& key, int32_t integer) const; bool GetBoolean(const std::string& key) const; int32_t GetInteger(const std::string& key) const; double GetDouble(const std::string& key) const; const std::string& GetString(const std::string& key) const; const std::vector& GetArrayOfIntegers(const std::string& key) const; private: absl::flat_hash_map mapKeyValues; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/PipeConsumer.hpp ================================================ #ifndef MS_RTC_PIPECONSUMER_HPP #define MS_RTC_PIPECONSUMER_HPP #include "RTC/Consumer.hpp" #include "RTC/SeqManager.hpp" #include namespace RTC { class PipeConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener { private: static void StorePacketInTargetLayerRetransmissionBuffer( std::map::SeqLowerThan>& targetLayerRetransmissionBuffer, RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket); public: PipeConsumer( SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, const FBS::Transport::ConsumeRequest* data); ~PipeConsumer() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder) override; flatbuffers::Offset FillBufferScore( flatbuffers::FlatBufferBuilder& builder) const override; void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerRtpStreamScore( RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; uint8_t GetBitratePriority() const override; uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) override; void ApplyLayers() override; uint32_t GetDesiredBitrate() const override; void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override; bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override; const std::vector& GetRtpStreams() const override { return this->rtpStreams; } void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override; void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override; void ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) override; void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) override; void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) override; uint32_t GetTransmissionRate(uint64_t nowMs) override; float GetRtt() const override; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; private: void UserOnTransportConnected() override; void UserOnTransportDisconnected() override; void UserOnPaused() override; void UserOnResumed() override; void CreateRtpStreams(); void RequestKeyFrame(); /* Pure virtual methods inherited from RtpStreamSend::Listener. */ public: void OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override; void OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) override; private: // Allocated by this. std::vector rtpStreams; // Others. absl::flat_hash_map mapMappedSsrcSsrc; absl::flat_hash_map mapSsrcRtpStream; bool keyFrameSupported{ false }; absl::flat_hash_map mapRtpStreamSyncRequired; absl::flat_hash_map> mapRtpStreamRtpSeqManager; // Buffers to store packets that arrive earlier than the first packet of the // video key frame. absl::flat_hash_map< RTC::RTP::RtpStreamSend*, std::map::SeqLowerThan>> mapRtpStreamTargetLayerRetransmissionBuffer; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/PipeTransport.hpp ================================================ #ifndef MS_RTC_PIPE_TRANSPORT_HPP #define MS_RTC_PIPE_TRANSPORT_HPP #include "SharedInterface.hpp" #include "FBS/pipeTransport.h" #include "RTC/SrtpSession.hpp" #include "RTC/Transport.hpp" #include "RTC/TransportTuple.hpp" #include "RTC/UdpSocket.hpp" namespace RTC { class PipeTransport : public RTC::Transport, public RTC::UdpSocket::Listener { private: static RTC::SrtpSession::CryptoSuite srtpCryptoSuite; static std::string srtpCryptoSuiteString; static size_t srtpMasterLength; public: PipeTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::PipeTransport::PipeTransportOptions* options); ~PipeTransport() override; public: flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; /* Methods inherited from Channel::ChannelSocket::NotificationHandler. */ public: void HandleNotification(Channel::ChannelNotification* notification) override; private: bool IsConnected() const override; bool HasSrtp() const; void SendRtpPacket( RTC::Consumer* consumer, RTC::RTP::Packet* packet, RTC::Transport::onSendCallback* cb = nullptr) override; void SendRtcpPacket(RTC::RTCP::Packet* packet) override; void SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) override; // TODO: SCTP: Remove once we only use built-in SCTP stack. void SendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb = nullptr) override; void SendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb = nullptr) override; bool SendData(const uint8_t* data, size_t len) override; void RecvStreamClosed(uint32_t ssrc) override; void SendStreamClosed(uint32_t ssrc) override; void OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen); void OnRtpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen); void OnRtcpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); void OnSctpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); /* Pure virtual methods inherited from RTC::UdpSocket::Listener. */ public: void OnUdpSocketPacketReceived( RTC::UdpSocket* socket, const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* remoteAddr) override; private: // Allocated by this. RTC::UdpSocket* udpSocket{ nullptr }; RTC::TransportTuple* tuple{ nullptr }; RTC::SrtpSession* srtpRecvSession{ nullptr }; RTC::SrtpSession* srtpSendSession{ nullptr }; // Others. ListenInfo listenInfo; struct sockaddr_storage remoteAddrStorage{}; bool rtx{ false }; std::string srtpKey; std::string srtpKeyBase64; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/PlainTransport.hpp ================================================ #ifndef MS_RTC_PLAIN_TRANSPORT_HPP #define MS_RTC_PLAIN_TRANSPORT_HPP #include "SharedInterface.hpp" #include "FBS/plainTransport.h" #include "RTC/SrtpSession.hpp" #include "RTC/Transport.hpp" #include "RTC/TransportTuple.hpp" #include "RTC/UdpSocket.hpp" #include namespace RTC { class PlainTransport : public RTC::Transport, public RTC::UdpSocket::Listener { public: PlainTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::PlainTransport::PlainTransportOptions* options); ~PlainTransport() override; public: flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; void HandleNotification(Channel::ChannelNotification* notification) override; private: bool IsConnected() const override; bool HasSrtp() const; bool IsSrtpReady() const; void SendRtpPacket( RTC::Consumer* consumer, RTC::RTP::Packet* packet, RTC::Transport::onSendCallback* cb = nullptr) override; void SendRtcpPacket(RTC::RTCP::Packet* packet) override; void SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) override; // TODO: SCTP: Remove once we only use built-in SCTP stack. void SendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb = nullptr) override; void SendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb = nullptr) override; bool SendData(const uint8_t* data, size_t len) override; void RecvStreamClosed(uint32_t ssrc) override; void SendStreamClosed(uint32_t ssrc) override; void OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen); void OnRtpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen); void OnRtcpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); void OnSctpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); void EmitTuple() const; void EmitRtcpTuple() const; /* Pure virtual methods inherited from RTC::UdpSocket::Listener. */ public: void OnUdpSocketPacketReceived( RTC::UdpSocket* socket, const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* remoteAddr) override; private: // Allocated by this. RTC::UdpSocket* udpSocket{ nullptr }; RTC::UdpSocket* rtcpUdpSocket{ nullptr }; RTC::TransportTuple* tuple{ nullptr }; RTC::TransportTuple* rtcpTuple{ nullptr }; RTC::SrtpSession* srtpRecvSession{ nullptr }; RTC::SrtpSession* srtpSendSession{ nullptr }; // Others. ListenInfo listenInfo; ListenInfo rtcpListenInfo; bool rtcpMux{ true }; bool comedia{ false }; struct sockaddr_storage remoteAddrStorage{}; struct sockaddr_storage rtcpRemoteAddrStorage{}; RTC::SrtpSession::CryptoSuite srtpCryptoSuite{ RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80 }; std::string srtpKey; size_t srtpMasterLength{ 0 }; std::string srtpKeyBase64; bool connectCalled{ false }; // Whether connect() was succesfully called. }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/PortManager.hpp ================================================ #ifndef MS_RTC_PORT_MANAGER_HPP #define MS_RTC_PORT_MANAGER_HPP #include "common.hpp" #include "RTC/Transport.hpp" #include #include #include #include namespace RTC { class PortManager { private: enum class Protocol : uint8_t { UDP = 1, TCP }; private: struct PortRange { explicit PortRange(uint16_t numPorts, uint16_t minPort) : ports(numPorts, false), minPort(minPort) { } std::vector ports; uint16_t minPort{ 0u }; uint16_t numUsedPorts{ 0u }; }; public: static uv_udp_t* BindUdp(std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags) { return reinterpret_cast(Bind(Protocol::UDP, ip, port, flags)); } static uv_udp_t* BindUdp( std::string& ip, uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, uint64_t& hash) { return reinterpret_cast(Bind(Protocol::UDP, ip, minPort, maxPort, flags, hash)); } static uv_tcp_t* BindTcp(std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags) { return reinterpret_cast(Bind(Protocol::TCP, ip, port, flags)); } static uv_tcp_t* BindTcp( std::string& ip, uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, uint64_t& hash) { return reinterpret_cast(Bind(Protocol::TCP, ip, minPort, maxPort, flags, hash)); } static void Unbind(uint64_t hash, uint16_t port); void Dump(int indentation = 0) const; private: static uv_handle_t* Bind( Protocol protocol, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags); static uv_handle_t* Bind( Protocol protocol, std::string& ip, uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, uint64_t& hash); static uint64_t GeneratePortRangeHash( Protocol protocol, sockaddr_storage* bindAddr, uint16_t minPort, uint16_t maxPort); static PortRange& GetOrCreatePortRange(uint64_t hash, uint16_t minPort, uint16_t maxPort); static uint8_t ConvertSocketFlags(RTC::Transport::SocketFlags& flags, Protocol protocol, int family); private: static thread_local absl::flat_hash_map mapPortRanges; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/Producer.hpp ================================================ #ifndef MS_RTC_PRODUCER_HPP #define MS_RTC_PRODUCER_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "Channel/ChannelRequest.hpp" #include "Channel/ChannelSocket.hpp" #include "RTC/KeyFrameRequestManager.hpp" #include "RTC/RTCP/CompoundPacket.hpp" #include "RTC/RTCP/Packet.hpp" #include "RTC/RTCP/SenderReport.hpp" #include "RTC/RTCP/XrDelaySinceLastRr.hpp" #include "RTC/RTP/HeaderExtensionIds.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" #include "RTC/RtpDictionaries.hpp" #include #include namespace RTC { class Producer : public RTC::RTP::RtpStreamRecv::Listener, public RTC::KeyFrameRequestManager::Listener, public Channel::ChannelSocket::RequestHandler, public Channel::ChannelSocket::NotificationHandler { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnProducerReceiveData(RTC::Producer* producer, size_t len) = 0; virtual void OnProducerReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) = 0; virtual void OnProducerPaused(RTC::Producer* producer) = 0; virtual void OnProducerResumed(RTC::Producer* producer) = 0; virtual void OnProducerNewRtpStream( RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; virtual void OnProducerRtpStreamScore( RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) = 0; virtual void OnProducerRtcpSenderReport( RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first) = 0; virtual void OnProducerRtpPacketReceived(RTC::Producer* producer, RTC::RTP::Packet* packet) = 0; virtual void OnProducerSendRtcpPacket(RTC::Producer* producer, RTC::RTCP::Packet* packet) = 0; virtual void OnProducerNeedWorstRemoteFractionLost( RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) = 0; }; private: struct RtpEncodingMapping { std::string rid; uint32_t ssrc{ 0 }; uint32_t mappedSsrc{ 0 }; }; private: struct RtpMapping { absl::flat_hash_map codecs; std::vector encodings; }; private: struct VideoOrientation { bool camera{ false }; bool flip{ false }; uint16_t rotation{ 0 }; }; public: enum class ReceiveRtpPacketResult : uint8_t { DISCARDED, MEDIA, RETRANSMISSION }; private: struct TraceEventTypes { bool rtp{ false }; bool keyframe{ false }; bool nack{ false }; bool pli{ false }; bool fir{ false }; bool sr{ false }; }; public: Producer( SharedInterface* shared, const std::string& id, RTC::Producer::Listener* listener, const FBS::Transport::ProduceRequest* data); ~Producer() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder); RTC::Media::Kind GetKind() const { return this->kind; } const RTC::RtpParameters& GetRtpParameters() const { return this->rtpParameters; } const struct RTC::RTP::HeaderExtensionIds& GetRtpHeaderExtensionIds() const { return this->rtpHeaderExtensionIds; } RTC::RtpParameters::Type GetType() const { return this->type; } bool IsPaused() const { return this->paused; } const absl::flat_hash_map& GetRtpStreams() { return this->mapRtpStreamMappedSsrc; } const std::vector* GetRtpStreamScores() const { return std::addressof(this->rtpStreamScores); } ReceiveRtpPacketResult ReceiveRtpPacket(RTC::RTP::Packet* packet); void ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report); void ReceiveRtcpXrDelaySinceLastRr(RTC::RTCP::DelaySinceLastRr::SsrcInfo* ssrcInfo); bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs); void RequestKeyFrame(uint32_t mappedSsrc); /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; /* Methods inherited from Channel::ChannelSocket::NotificationHandler. */ public: void HandleNotification(Channel::ChannelNotification* notification) override; private: RTC::RTP::RtpStreamRecv* GetRtpStream(const RTC::RTP::Packet* packet); RTC::RTP::RtpStreamRecv* CreateRtpStream( const RTC::RTP::Packet* packet, const RTC::RtpCodecParameters& mediaCodec, size_t encodingIdx); void NotifyNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream); bool MangleRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::RtpStreamRecv* rtpStream) const; void PostProcessRtpPacket(RTC::RTP::Packet* packet); void EmitScore() const; void EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx = false) const; void EmitTraceEventPliType(uint32_t ssrc) const; void EmitTraceEventFirType(uint32_t ssrc) const; void EmitTraceEventNackType() const; void EmitTraceEventSrType(RTC::RTCP::SenderReport* report) const; void EmitTraceEvent(flatbuffers::Offset& notification) const; /* Pure virtual methods inherited from RTC::RtpStreamRecv::Listener. */ public: void OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override; void OnRtpStreamSendRtcpPacket(RTC::RTP::RtpStreamRecv* rtpStream, RTC::RTCP::Packet* packet) override; void OnRtpStreamNeedWorstRemoteFractionLost( RTC::RTP::RtpStreamRecv* rtpStream, uint8_t& worstRemoteFractionLost) override; /* Pure virtual methods inherited from RTC::KeyFrameRequestManager::Listener. */ public: void OnKeyFrameNeeded(RTC::KeyFrameRequestManager* keyFrameRequestManager, uint32_t ssrc) override; public: // Passed by argument. std::string id; private: // Passed by argument. SharedInterface* shared{ nullptr }; RTC::Producer::Listener* listener{ nullptr }; // Allocated by this. absl::flat_hash_map mapSsrcRtpStream; RTC::KeyFrameRequestManager* keyFrameRequestManager{ nullptr }; // Others. RTC::Media::Kind kind; RTC::RtpParameters rtpParameters; RTC::RtpParameters::Type type; struct RtpMapping rtpMapping; std::vector rtpStreamByEncodingIdx; std::vector rtpStreamScores; absl::flat_hash_map mapRtxSsrcRtpStream; absl::flat_hash_map mapRtpStreamMappedSsrc; absl::flat_hash_map mapMappedSsrcSsrc; struct RTC::RTP::HeaderExtensionIds rtpHeaderExtensionIds; bool paused{ false }; bool enableMediasoupPacketIdHeaderExtension{ false }; RTC::RTP::Packet* currentRtpPacket{ nullptr }; // Timestamp when last RTCP was sent. uint64_t lastRtcpSentTime{ 0u }; uint16_t maxRtcpInterval{ 0u }; // Video orientation. bool videoOrientationDetected{ false }; struct VideoOrientation videoOrientation; struct TraceEventTypes traceEventTypes; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/Bye.hpp ================================================ #ifndef MS_RTC_RTCP_BYE_HPP #define MS_RTC_RTCP_BYE_HPP #include "common.hpp" #include "RTC/RTCP/Packet.hpp" #include #include namespace RTC { namespace RTCP { class ByePacket : public Packet { public: using Iterator = std::vector::iterator; public: static ByePacket* Parse(const uint8_t* data, size_t len); public: ByePacket() : Packet(Type::BYE) { } explicit ByePacket(CommonHeader* commonHeader) : Packet(commonHeader) { } ~ByePacket() override = default; void AddSsrc(uint32_t ssrc) { this->ssrcs.push_back(ssrc); } void SetReason(const std::string& reason) { this->reason = reason; } const std::string& GetReason() const { return this->reason; } Iterator Begin() { return this->ssrcs.begin(); } Iterator End() { return this->ssrcs.end(); } /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetCount() const override { return this->ssrcs.size(); } size_t GetSize() const override { size_t size = Packet::CommonHeaderSize; size += ssrcs.size() * 4u; if (!this->reason.empty()) { size += 1u; // Length field. size += this->reason.length(); } // http://stackoverflow.com/questions/11642210/computing-padding-required-for-n-byte-alignment // Consider pading to 32 bits (4 bytes) boundary. return (size + 3) & ~3; } private: std::vector ssrcs; std::string reason; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/CompoundPacket.hpp ================================================ #ifndef MS_RTC_RTCP_COMPOUND_PACKET_HPP #define MS_RTC_RTCP_COMPOUND_PACKET_HPP #include "common.hpp" #include "RTC/RTCP/ReceiverReport.hpp" #include "RTC/RTCP/Sdes.hpp" #include "RTC/RTCP/SenderReport.hpp" #include "RTC/RTCP/XrDelaySinceLastRr.hpp" #include "RTC/RTCP/XrReceiverReferenceTime.hpp" #include namespace RTC { namespace RTCP { class CompoundPacket { public: CompoundPacket() = default; public: const uint8_t* GetData() const { return this->header; } size_t GetSize(); size_t GetSenderReportCount() const { return this->senderReportPacket.GetCount(); } size_t GetReceiverReportCount() const { return this->receiverReportPacket.GetCount(); } void Dump(int indentation = 0); // RTCP additions per Consumer (non pipe). // Adds the given data and returns true if there is enough space to hold it, // false otherwise. bool Add( SenderReport* senderReport, SdesChunk* sdesChunk, DelaySinceLastRr::SsrcInfo* delaySinceLastRrSsrcInfo); // RTCP additions per Consumer (pipe). // Adds the given data and returns true if there is enough space to hold it, // false otherwise. bool Add( std::vector& senderReports, std::vector& sdesChunks, std::vector& delaySinceLastRrSsrcInfos); // RTCP additions per Producer. // Adds the given data and returns true if there is enough space to hold it, // false otherwise. bool Add(std::vector&, ReceiverReferenceTime*); void AddSenderReport(SenderReport* report); void AddReceiverReport(ReceiverReport* report); void AddSdesChunk(SdesChunk* chunk); bool HasSenderReport() { return this->senderReportPacket.Begin() != this->senderReportPacket.End(); } bool HasReceiverReferenceTime() { return std::any_of( this->xrPacket.Begin(), this->xrPacket.End(), [](const ExtendedReportBlock* report) { return report->GetType() == ExtendedReportBlock::Type::RRT; }); } bool HasDelaySinceLastRr() { return std::any_of( this->xrPacket.Begin(), this->xrPacket.End(), [](const ExtendedReportBlock* report) { return report->GetType() == ExtendedReportBlock::Type::DLRR; }); } void Serialize(uint8_t* data); private: uint8_t* header{ nullptr }; SenderReportPacket senderReportPacket; ReceiverReportPacket receiverReportPacket; SdesPacket sdesPacket; ExtendedReportPacket xrPacket; DelaySinceLastRr* delaySinceLastRr{ nullptr }; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/Feedback.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_HPP #define MS_RTC_RTCP_FEEDBACK_HPP #include "common.hpp" #include "RTC/RTCP/Packet.hpp" #include namespace RTC { namespace RTCP { template class FeedbackPacket : public Packet { public: /** * Struct for RTP Feedback message. * * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Header { uint32_t senderSsrc; uint32_t mediaSsrc; }; public: static const size_t HeaderSize{ 8 }; static RTCP::Type rtcpType; static FeedbackPacket* Parse(const uint8_t* data, size_t len); static const std::string& MessageTypeToString(typename T::MessageType type); private: static const absl::flat_hash_map MessageType2String; public: typename T::MessageType GetMessageType() const { return this->messageType; } uint32_t GetSenderSsrc() const { return ntohl(this->header->senderSsrc); } void SetSenderSsrc(uint32_t ssrc) { this->header->senderSsrc = htonl(ssrc); } uint32_t GetMediaSsrc() const { return ntohl(this->header->mediaSsrc); } void SetMediaSsrc(uint32_t ssrc) { this->header->mediaSsrc = htonl(ssrc); } /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetCount() const override { return static_cast(GetMessageType()); } size_t GetSize() const override { return Packet::CommonHeaderSize + HeaderSize; } public: explicit FeedbackPacket(CommonHeader* commonHeader); FeedbackPacket(typename T::MessageType messageType, uint32_t senderSsrc, uint32_t mediaSsrc); ~FeedbackPacket() override; private: Header* header{ nullptr }; uint8_t* raw{ nullptr }; typename T::MessageType messageType; }; class FeedbackPs { public: enum class MessageType : uint8_t { PLI = 1, SLI = 2, RPSI = 3, FIR = 4, TSTR = 5, TSTN = 6, VBCM = 7, PSLEI = 8, ROI = 9, AFB = 15, EXT = 31 }; }; class FeedbackRtp { public: enum class MessageType : uint8_t { NACK = 1, TMMBR = 3, TMMBN = 4, SR_REQ = 5, RAMS = 6, TLLEI = 7, ECN = 8, PS = 9, TCC = 15, EXT = 31 }; }; using FeedbackPsPacket = FeedbackPacket; using FeedbackRtpPacket = FeedbackPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackItem.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_ITEM_HPP #define MS_RTC_RTCP_FEEDBACK_ITEM_HPP #include "common.hpp" namespace RTC { namespace RTCP { class FeedbackItem { public: template static Item* Parse(const uint8_t* data, size_t len) { // data size must be >= header. if (Item::HeaderSize > len) { return nullptr; } auto* header = const_cast(reinterpret_cast(data)); return new Item(header); } public: bool IsCorrect() const { return this->isCorrect; } public: virtual ~FeedbackItem() { delete[] this->raw; } public: virtual void Dump(int indentation = 0) const = 0; virtual void Serialize() { delete[] this->raw; this->raw = new uint8_t[this->GetSize()]; this->Serialize(this->raw); } virtual size_t Serialize(uint8_t* buffer) = 0; virtual size_t GetSize() const = 0; protected: uint8_t* raw{ nullptr }; bool isCorrect{ true }; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPs.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_PS_HPP #define MS_RTC_RTCP_FEEDBACK_PS_HPP #include "common.hpp" #include "RTC/RTCP/Feedback.hpp" #include "RTC/RTCP/FeedbackItem.hpp" #include namespace RTC { namespace RTCP { template class FeedbackPsItemsPacket : public FeedbackPsPacket { public: using Iterator = typename std::vector::iterator; public: static FeedbackPsItemsPacket* Parse(const uint8_t* data, size_t len); public: // Parsed Report. Points to an external data. explicit FeedbackPsItemsPacket(CommonHeader* commonHeader) : FeedbackPsPacket(commonHeader) { } explicit FeedbackPsItemsPacket(uint32_t senderSsrc, uint32_t mediaSsrc = 0) : FeedbackPsPacket(Item::MessageType, senderSsrc, mediaSsrc) { } ~FeedbackPsItemsPacket() override { for (auto* item : this->items) { delete item; } } void AddItem(Item* item) { this->items.push_back(item); } Iterator Begin() { return this->items.begin(); } Iterator End() { return this->items.end(); } /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { size_t size = FeedbackPsPacket::GetSize(); for (auto* item : this->items) { size += item->GetSize(); } return size; } private: std::vector items; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPsAfb.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_PS_AFB_HPP #define MS_RTC_RTCP_FEEDBACK_PS_AFB_HPP #include "common.hpp" #include "RTC/RTCP/Feedback.hpp" namespace RTC { namespace RTCP { class FeedbackPsAfbPacket : public FeedbackPsPacket { public: enum class Application : uint8_t { UNKNOWN = 0, REMB = 1 }; public: static FeedbackPsAfbPacket* Parse(const uint8_t* data, size_t len); public: // Parsed Report. Points to an external data. explicit FeedbackPsAfbPacket( CommonHeader* commonHeader, Application application = Application::UNKNOWN) : FeedbackPsPacket(commonHeader) { this->size = ((static_cast(ntohs(commonHeader->length)) + 1) * 4) - (Packet::CommonHeaderSize + FeedbackPacket::HeaderSize); this->data = reinterpret_cast(commonHeader) + Packet::CommonHeaderSize + FeedbackPacket::HeaderSize; this->application = application; } FeedbackPsAfbPacket( uint32_t senderSsrc, uint32_t mediaSsrc, Application application = Application::UNKNOWN) : FeedbackPsPacket(FeedbackPs::MessageType::AFB, senderSsrc, mediaSsrc) { this->application = application; } ~FeedbackPsAfbPacket() override = default; Application GetApplication() const { return this->application; } /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackPsPacket::GetSize() + this->size; } private: Application application{ Application::UNKNOWN }; uint8_t* data{ nullptr }; size_t size{ 0 }; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPsFir.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_PS_FIR_HPP #define MS_RTC_RTCP_FEEDBACK_PS_FIR_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackPs.hpp" /* RFC 5104 * Full Intra Request (FIR) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Seq nr. | | Reserved | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class FeedbackPsFirItem : public FeedbackItem { public: /** * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Header { uint32_t ssrc; uint8_t sequenceNumber; #ifdef _WIN32 uint8_t reserved[3]; // Alignment. #else uint32_t reserved : 24; #endif }; public: static const size_t HeaderSize = 8; static const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::FIR }; public: explicit FeedbackPsFirItem(Header* header) : header(header) { } explicit FeedbackPsFirItem(FeedbackPsFirItem* item) : header(item->header) { } FeedbackPsFirItem(uint32_t ssrc, uint8_t sequenceNumber); ~FeedbackPsFirItem() override = default; uint32_t GetSsrc() const { return ntohl(this->header->ssrc); } uint8_t GetSequenceNumber() const { return this->header->sequenceNumber; } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackPsFirItem::HeaderSize; } private: Header* header{ nullptr }; }; // Fir packet declaration. using FeedbackPsFirPacket = FeedbackPsItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPsLei.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_PS_LEI_HPP #define MS_RTC_RTCP_FEEDBACK_PS_LEI_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackPs.hpp" /* RFC 6642 * Payload-Specific Third-Party Loss Early Indication (PSLEI) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class FeedbackPsLeiItem : public FeedbackItem { public: /** * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Header { uint32_t ssrc; }; public: static const size_t HeaderSize{ 4 }; static const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::PSLEI }; public: explicit FeedbackPsLeiItem(Header* header) : header(header) { } explicit FeedbackPsLeiItem(FeedbackPsLeiItem* item) : header(item->header) { } explicit FeedbackPsLeiItem(uint32_t ssrc); ~FeedbackPsLeiItem() override = default; uint32_t GetSsrc() const { return ntohl(this->header->ssrc); } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackPsLeiItem::HeaderSize; } private: Header* header{ nullptr }; }; // Lei packet declaration. using FeedbackPsLeiPacket = FeedbackPsItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPsPli.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_PS_PLI_HPP #define MS_RTC_RTCP_FEEDBACK_PS_PLI_HPP #include "common.hpp" #include "RTC/RTCP/Feedback.hpp" namespace RTC { namespace RTCP { class FeedbackPsPliPacket : public FeedbackPsPacket { public: static FeedbackPsPliPacket* Parse(const uint8_t* data, size_t len); public: // Parsed Report. Points to an external data. explicit FeedbackPsPliPacket(CommonHeader* commonHeader) : FeedbackPsPacket(commonHeader) { } FeedbackPsPliPacket(uint32_t senderSsrc, uint32_t mediaSsrc) : FeedbackPsPacket(FeedbackPs::MessageType::PLI, senderSsrc, mediaSsrc) { } ~FeedbackPsPliPacket() override = default; public: void Dump(int indentation = 0) const override; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPsRemb.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_REMB_HPP #define MS_RTC_RTCP_FEEDBACK_REMB_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackPsAfb.hpp" #include /* draft-alvestrand-rmcat-remb-03 * RTCP message for Receiver Estimated Maximum Bitrate (REMB) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P| FMT=15 | PT=206 | length | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | SSRC of packet sender | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC of media source | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Unique identifier 'R' 'E' 'M' 'B' | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Num SSRC | BR Exp | BR Mantissa | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC feedback | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ... | */ namespace RTC { namespace RTCP { class FeedbackPsRembPacket : public FeedbackPsAfbPacket { public: // 'R' 'E' 'M' 'B'. static const uint32_t UniqueIdentifier{ 0x52454D42 }; static const size_t UniqueIdentifierSize{ 4 }; public: static FeedbackPsRembPacket* Parse(const uint8_t* data, size_t len); public: // Parsed Report. Points to an external data. FeedbackPsRembPacket(uint32_t senderSsrc, uint32_t mediaSsrc) : FeedbackPsAfbPacket(senderSsrc, mediaSsrc, FeedbackPsAfbPacket::Application::REMB) { } FeedbackPsRembPacket(CommonHeader* commonHeader, size_t availableLen); ~FeedbackPsRembPacket() override = default; bool IsCorrect() const { return this->isCorrect; } void SetBitrate(uint64_t bitrate) { this->bitrate = bitrate; } void SetSsrcs(const std::vector& ssrcs) { this->ssrcs = ssrcs; } uint64_t GetBitrate() const { return this->bitrate; } const std::vector& GetSsrcs() { return this->ssrcs; } /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { // NOLINTNEXTLINE(bugprone-parent-virtual-call) return FeedbackPsPacket::GetSize() + 8 + (4u * this->ssrcs.size()); } private: std::vector ssrcs; // Bitrate represented in bps. uint64_t bitrate{ 0 }; bool isCorrect{ true }; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPsRpsi.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_PS_RPSI_HPP #define MS_RTC_RTCP_FEEDBACK_PS_RPSI_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackPs.hpp" /* RFC 4585 * Reference Picture Selection Indication (RPSI) * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | PB |0| Payload Type| Native RPSI bit string | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | defined per codec ... | Padding (0) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class FeedbackPsRpsiItem : public FeedbackItem { static const size_t MaxBitStringSize{ 6 }; public: /** * @remarks * - This struct is guaranteed to be aligned to 1 byte. */ struct Header { uint8_t paddingBits; #if defined(MS_LITTLE_ENDIAN) uint8_t payloadType : 7; uint8_t zero : 1; #elif defined(MS_BIG_ENDIAN) uint8_t zero : 1; uint8_t payloadType : 7; #endif uint8_t bitString[MaxBitStringSize]; }; public: static const size_t HeaderSize = 8; static const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::RPSI }; public: explicit FeedbackPsRpsiItem(Header* header); explicit FeedbackPsRpsiItem(FeedbackPsRpsiItem* item) : header(item->header) { } FeedbackPsRpsiItem(uint8_t payloadType, uint8_t* bitString, size_t length); ~FeedbackPsRpsiItem() override = default; bool IsCorrect() const { return this->isCorrect; } uint8_t GetPayloadType() const { return this->header->payloadType; } uint8_t* GetBitString() const { return this->header->bitString; } size_t GetLength() const { return this->length; } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackPsRpsiItem::HeaderSize; } private: Header* header{ nullptr }; size_t length{ 0 }; }; // Rpsi packet declaration. using FeedbackPsRpsiPacket = FeedbackPsItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPsSli.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_PS_SLI_HPP #define MS_RTC_RTCP_FEEDBACK_PS_SLI_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackPs.hpp" /* RFC 4585 * Slice Loss Indication (SLI) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | First | Number | PictureID | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class FeedbackPsSliItem : public FeedbackItem { public: /** * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Header { uint32_t compact; }; public: static const size_t HeaderSize{ 4 }; static const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::SLI }; public: explicit FeedbackPsSliItem(Header* header); explicit FeedbackPsSliItem(FeedbackPsSliItem* item); FeedbackPsSliItem(uint16_t first, uint16_t number, uint8_t pictureId); ~FeedbackPsSliItem() override = default; uint16_t GetFirst() const { return this->first; } void SetFirst(uint16_t first) { this->first = first; } uint16_t GetNumber() const { return this->number; } void SetNumber(uint16_t number) { this->number = number; } uint8_t GetPictureId() const { return this->pictureId; } void SetPictureId(uint8_t pictureId) { this->pictureId = pictureId; } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackPsSliItem::HeaderSize; } private: Header* header{ nullptr }; uint16_t first{ 0 }; uint16_t number{ 0 }; uint8_t pictureId{ 0 }; }; // Sli packet declaration. using FeedbackPsSliPacket = FeedbackPsItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPsTst.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_PS_TST_HPP #define MS_RTC_RTCP_FEEDBACK_PS_TST_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackPs.hpp" /* RFC 5104 * Temporal-Spatial Trade-off Request (TSTR) * Temporal-Spatial Trade-off Notification (TSTN) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Seq nr. | Reserved | Index | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { template class FeedbackPsTstItem : public FeedbackItem { public: #ifdef _WIN32 #pragma pack(push, 1) #define MEDIASOUP_PACKED #else #define MEDIASOUP_PACKED __attribute__((packed)) #endif /** * @remarks * - This struct is guaranteed to be aligned to 1 byte due to the usage * of `pack()` and `packed` macros. */ struct Header { uint32_t ssrc; uint8_t sequenceNumber; uint16_t reserved1; #if defined(MS_LITTLE_ENDIAN) uint8_t index : 5; uint8_t reserved2 : 3; #elif defined(MS_BIG_ENDIAN) uint8_t reserved2 : 3; uint8_t index : 5; #endif } MEDIASOUP_PACKED; #ifdef _WIN32 #pragma pack(pop) #endif #undef MEDIASOUP_PACKED public: static const size_t HeaderSize{ 8 }; static const FeedbackPs::MessageType MessageType; public: explicit FeedbackPsTstItem(Header* header) : header(header) { } explicit FeedbackPsTstItem(FeedbackPsTstItem* item) : header(item->header) { } FeedbackPsTstItem(uint32_t ssrc, uint8_t sequenceNumber, uint8_t index); ~FeedbackPsTstItem() override = default; uint32_t GetSsrc() const { return ntohl(this->header->ssrc); } uint8_t GetSequenceNumber() const { return this->header->sequenceNumber; } uint8_t GetIndex() const { return this->header->index; } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackPsTstItem::HeaderSize; } private: Header* header{ nullptr }; }; class Tstr { }; class Tstn { }; // Tst classes declaration. using FeedbackPsTstrItem = FeedbackPsTstItem; using FeedbackPsTstnItem = FeedbackPsTstItem; // Tst packets declaration. using FeedbackPsTstrPacket = FeedbackPsItemsPacket; using FeedbackPsTstnPacket = FeedbackPsItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackPsVbcm.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_PS_VBCM_HPP #define MS_RTC_RTCP_FEEDBACK_PS_VBCM_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackPs.hpp" /* RFC 5104 * H.271 Video Back Channel Message (VBCM) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Seq nr. | |0| Payload Vbcm| | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | VBCM Octet String.... | Padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class FeedbackPsVbcmItem : public FeedbackItem { public: /** * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Header { uint32_t ssrc; uint8_t sequenceNumber; #if defined(MS_LITTLE_ENDIAN) uint8_t payloadType : 7; uint8_t zero : 1; #elif defined(MS_BIG_ENDIAN) uint8_t zero : 1; uint8_t payloadType : 7; #endif uint16_t length; uint8_t value[]; }; public: static const size_t HeaderSize{ 8 }; static const FeedbackPs::MessageType MessageType{ FeedbackPs::MessageType::FIR }; public: explicit FeedbackPsVbcmItem(Header* header) : header(header) { } explicit FeedbackPsVbcmItem(FeedbackPsVbcmItem* item) : header(item->header) { } FeedbackPsVbcmItem( uint32_t ssrc, uint8_t sequenceNumber, uint8_t payloadType, uint16_t length, uint8_t* value); ~FeedbackPsVbcmItem() override = default; uint32_t GetSsrc() const { return ntohl(this->header->ssrc); } uint8_t GetSequenceNumber() const { return this->header->sequenceNumber; } uint8_t GetPayloadType() const { return uint8_t{ this->header->payloadType }; } uint16_t GetLength() const { return ntohs(this->header->length); } uint8_t* GetValue() const { return this->header->value; } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { size_t size = FeedbackPsVbcmItem::HeaderSize + static_cast(GetLength()); // Consider pading to 32 bits (4 bytes) boundary. return (size + 3) & ~3; } private: Header* header{ nullptr }; }; // Vbcm packet declaration. using FeedbackPsVbcmPacket = FeedbackPsItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackRtp.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_RTP_HPP #define MS_RTC_RTCP_FEEDBACK_RTP_HPP #include "common.hpp" #include "RTC/RTCP/Feedback.hpp" #include "RTC/RTCP/FeedbackItem.hpp" #include namespace RTC { namespace RTCP { template class FeedbackRtpItemsPacket : public FeedbackRtpPacket { public: using Iterator = typename std::vector::iterator; public: static FeedbackRtpItemsPacket* Parse(const uint8_t* data, size_t len); public: // Parsed Report. Points to an external data. explicit FeedbackRtpItemsPacket(CommonHeader* commonHeader) : FeedbackRtpPacket(commonHeader) { } explicit FeedbackRtpItemsPacket(uint32_t senderSsrc, uint32_t mediaSsrc = 0) : FeedbackRtpPacket(Item::MessageType, senderSsrc, mediaSsrc) { } ~FeedbackRtpItemsPacket() override { for (auto* item : this->items) { delete item; } } void AddItem(Item* item) { this->items.push_back(item); } Iterator Begin() { return this->items.begin(); } Iterator End() { return this->items.end(); } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { size_t size = FeedbackRtpPacket::GetSize(); for (auto* item : this->items) { size += item->GetSize(); } return size; } private: std::vector items; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackRtpEcn.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_RTP_ECN_HPP #define MS_RTC_RTCP_FEEDBACK_RTP_ECN_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackRtp.hpp" /* RFC 6679 * Explicit Congestion Notification (ECN) for RTP over UDP 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Extended Highest Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ECT (0) Counter | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ECT (1) Counter | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ECN-CE Counter | | not-ECT Counter | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Lost Packets Counter | | Duplication Counter | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class FeedbackRtpEcnItem : public FeedbackItem { public: /** * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Header { uint32_t sequenceNumber; uint32_t ect0Counter; uint32_t ect1Counter; uint16_t ecnCeCounter; uint16_t notEctCounter; uint16_t lostPackets; uint16_t duplicatedPackets; }; public: static const size_t HeaderSize{ 20 }; static const FeedbackRtp::MessageType MessageType{ FeedbackRtp::MessageType::ECN }; public: explicit FeedbackRtpEcnItem(Header* header) : header(header) { } explicit FeedbackRtpEcnItem(FeedbackRtpEcnItem* item) : header(item->header) { } ~FeedbackRtpEcnItem() override = default; uint32_t GetSequenceNumber() const { return ntohl(this->header->sequenceNumber); } uint32_t GetEct0Counter() const { return ntohl(this->header->ect0Counter); } uint32_t GetEct1Counter() const { return ntohl(this->header->ect1Counter); } uint16_t GetEcnCeCounter() const { return ntohs(this->header->ecnCeCounter); } uint16_t GetNotEctCounter() const { return ntohs(this->header->notEctCounter); } uint16_t GetLostPackets() const { return ntohs(this->header->lostPackets); } uint16_t GetDuplicatedPackets() const { return ntohs(this->header->duplicatedPackets); } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackRtpEcnItem::HeaderSize; } private: Header* header{ nullptr }; }; // Ecn packet declaration. using FeedbackRtpEcnPacket = FeedbackRtpItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackRtpNack.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_RTP_NACK_HPP #define MS_RTC_RTCP_FEEDBACK_RTP_NACK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/RTCP/FeedbackRtp.hpp" /* RFC 4585 * Generic NACK message (NACK) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | PID | BPL | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class FeedbackRtpNackItem : public FeedbackItem { public: /** * @remarks * - This struct is guaranteed to be aligned to 2 bytes. */ struct Header { uint16_t packetId; uint16_t lostPacketBitmask; }; public: static const size_t HeaderSize{ 4 }; static const FeedbackRtp::MessageType MessageType{ FeedbackRtp::MessageType::NACK }; public: explicit FeedbackRtpNackItem(Header* header) : header(header) { } explicit FeedbackRtpNackItem(FeedbackRtpNackItem* item) : header(item->header) { } FeedbackRtpNackItem(uint16_t packetId, uint16_t lostPacketBitmask); ~FeedbackRtpNackItem() override = default; uint16_t GetPacketId() const { return ntohs(this->header->packetId); } uint16_t GetLostPacketBitmask() const { return ntohs(this->header->lostPacketBitmask); } size_t CountRequestedPackets() const { return Utils::Bits::CountSetBits(this->header->lostPacketBitmask) + 1; } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackRtpNackItem::HeaderSize; } private: Header* header{ nullptr }; }; // Nack packet declaration. using FeedbackRtpNackPacket = FeedbackRtpItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackRtpSrReq.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_RTP_SR_REQ_HPP #define MS_RTC_RTCP_FEEDBACK_RTP_SR_REQ_HPP #include "common.hpp" #include "RTC/RTCP/Feedback.hpp" namespace RTC { namespace RTCP { class FeedbackRtpSrReqPacket : public FeedbackRtpPacket { public: static FeedbackRtpSrReqPacket* Parse(const uint8_t* data, size_t len); public: // Parsed Report. Points to an external data. explicit FeedbackRtpSrReqPacket(CommonHeader* commonHeader) : FeedbackRtpPacket(commonHeader) { } FeedbackRtpSrReqPacket(uint32_t senderSsrc, uint32_t mediaSsrc) : FeedbackRtpPacket(FeedbackRtp::MessageType::SR_REQ, senderSsrc, mediaSsrc) { } ~FeedbackRtpSrReqPacket() override = default; void Dump(int indentation = 0) const override; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackRtpTllei.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_RTP_TLLEI_HPP #define MS_RTC_RTCP_FEEDBACK_RTP_TLLEI_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackRtp.hpp" /* RFC 4585 * Transport-Layer Third-Party Loss Early Indication (TLLEI) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | PID | BPL | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class FeedbackRtpTlleiItem : public FeedbackItem { public: /** * @remarks * - This struct is guaranteed to be aligned to 2 bytes. */ struct Header { uint16_t packetId; uint16_t lostPacketBitmask; }; public: static const size_t HeaderSize{ 4 }; static const FeedbackRtp::MessageType MessageType{ FeedbackRtp::MessageType::TLLEI }; public: explicit FeedbackRtpTlleiItem(Header* header) : header(header) { } explicit FeedbackRtpTlleiItem(FeedbackRtpTlleiItem* item) : header(item->header) { } FeedbackRtpTlleiItem(uint16_t packetId, uint16_t lostPacketBitmask); ~FeedbackRtpTlleiItem() override = default; uint16_t GetPacketId() const { return ntohs(this->header->packetId); } uint16_t GetLostPacketBitmask() const { return ntohs(this->header->lostPacketBitmask); } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackRtpTlleiItem::HeaderSize; } private: Header* header{ nullptr }; }; // Tllei packet declaration. using FeedbackRtpTlleiPacket = FeedbackRtpItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackRtpTmmb.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_RTP_TMMB_HPP #define MS_RTC_RTCP_FEEDBACK_RTP_TMMB_HPP #include "common.hpp" #include "RTC/RTCP/FeedbackRtp.hpp" /* RFC 5104 * Temporary Maximum Media Stream Bit Rate Request (TMMBR) * Temporary Maximum Media Stream Bit Rate Notification (TMMBN) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | MxTBR Exp | MxTBR Mantissa |Measured Overhead| +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { template class FeedbackRtpTmmbItem : public FeedbackItem { public: /** * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Header { uint32_t ssrc; uint32_t compact; }; public: static const size_t HeaderSize{ 8 }; static const FeedbackRtp::MessageType MessageType; public: FeedbackRtpTmmbItem() = default; explicit FeedbackRtpTmmbItem(const uint8_t* data); explicit FeedbackRtpTmmbItem(const Header* header); ~FeedbackRtpTmmbItem() override = default; uint32_t GetSsrc() const { return this->ssrc; } void SetSsrc(uint32_t ssrc) { this->ssrc = ssrc; } uint64_t GetBitrate() const { return this->bitrate; } void SetBitrate(uint64_t bitrate) { this->bitrate = bitrate; } uint16_t GetOverhead() const { return this->overhead; } void SetOverhead(uint16_t overhead) { this->overhead = overhead; } /* Virtual methods inherited from FeedbackItem. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { return FeedbackRtpTmmbItem::HeaderSize; } private: uint32_t ssrc{ 0 }; uint64_t bitrate{ 0 }; uint16_t overhead{ 0 }; }; // Tmmb types declaration. class FeedbackRtpTmmbr { }; class FeedbackRtpTmmbn { }; // Tmmbn classes declaration. using FeedbackRtpTmmbrItem = FeedbackRtpTmmbItem; using FeedbackRtpTmmbnItem = FeedbackRtpTmmbItem; // Tmmbn packets declaration. using FeedbackRtpTmmbrPacket = FeedbackRtpItemsPacket; using FeedbackRtpTmmbnPacket = FeedbackRtpItemsPacket; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/FeedbackRtpTransport.hpp ================================================ #ifndef MS_RTC_RTCP_FEEDBACK_RTP_TRANSPORT_HPP #define MS_RTC_RTCP_FEEDBACK_RTP_TRANSPORT_HPP #include "common.hpp" #include "RTC/RTCP/Feedback.hpp" #include /* RTP Extensions for Transport-wide Congestion Control * draft-holmer-rmcat-transport-wide-cc-extensions-01 0 1 2 3 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P| FMT=15 | PT=205 | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC of packet sender | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC of media source | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | base sequence number | packet status count | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | reference time | fb pkt. count | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | packet chunk | packet chunk | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | packet chunk | recv delta | recv delta | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ . . . . +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | recv delta | recv delta | zero padding | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class FeedbackRtpTransportPacket : public FeedbackRtpPacket { public: static constexpr int64_t BaseTimeTick = 64; static constexpr int64_t TimeWrapPeriod = BaseTimeTick * (1ll << 24); public: struct PacketResult { PacketResult(uint16_t sequenceNumber, bool received) : sequenceNumber(sequenceNumber), received(received) { } uint16_t sequenceNumber; // Wide sequence number. int16_t delta{ 0 }; // Delta. bool received{ false }; // Packet received or not. int64_t receivedAtMs{ 0 }; // Received time (ms) in remote timestamp reference. }; public: enum class AddPacketResult : uint8_t { SUCCESS, MAX_SIZE_EXCEEDED, FATAL }; private: enum Status : uint8_t { NotReceived = 0, SmallDelta, LargeDelta, Reserved, None }; private: struct Context { bool allSameStatus{ true }; Status currentStatus{ Status::None }; std::vector statuses; }; private: class Chunk { public: static Chunk* Parse(const uint8_t* data, size_t len, uint16_t count); public: Chunk() = default; virtual ~Chunk() = default; virtual bool AddDeltas( const uint8_t* data, size_t len, std::vector& deltas, size_t& offset) = 0; virtual void Dump(int indentation = 0) const = 0; virtual uint16_t GetCount() const = 0; virtual uint16_t GetReceivedStatusCount() const = 0; virtual void FillResults( std::vector& packetResults, uint16_t& currentSequenceNumber) const = 0; virtual size_t Serialize(uint8_t* buffer) = 0; }; private: class RunLengthChunk : public Chunk { public: RunLengthChunk(Status status, uint16_t count) : status(status), count(count) { } explicit RunLengthChunk(uint16_t buffer); public: bool AddDeltas( const uint8_t* data, size_t len, std::vector& deltas, size_t& offset) override; Status GetStatus() const { return this->status; } void Dump(int indentation = 0) const override; uint16_t GetCount() const override { return this->count; } uint16_t GetReceivedStatusCount() const override; void FillResults( std::vector& packetResults, uint16_t& currentSequenceNumber) const override; size_t Serialize(uint8_t* buffer) override; private: Status status{ Status::None }; uint16_t count{ 0u }; }; private: class OneBitVectorChunk : public Chunk { public: explicit OneBitVectorChunk(const std::vector& statuses) : statuses(statuses) { } OneBitVectorChunk(uint16_t buffer, uint16_t count); public: bool AddDeltas( const uint8_t* data, size_t len, std::vector& deltas, size_t& offset) override; void Dump(int indentation = 0) const override; uint16_t GetCount() const override { return this->statuses.size(); } uint16_t GetReceivedStatusCount() const override; void FillResults( std::vector& packetResults, uint16_t& currentSequenceNumber) const override; size_t Serialize(uint8_t* buffer) override; private: std::vector statuses; }; private: class TwoBitVectorChunk : public Chunk { public: explicit TwoBitVectorChunk(const std::vector& statuses) : statuses(statuses) { } TwoBitVectorChunk(uint16_t buffer, uint16_t count); public: bool AddDeltas( const uint8_t* data, size_t len, std::vector& deltas, size_t& offset) override; void Dump(int indentation = 0) const override; uint16_t GetCount() const override { return this->statuses.size(); } uint16_t GetReceivedStatusCount() const override; void FillResults( std::vector& packetResults, uint16_t& currentSequenceNumber) const override; size_t Serialize(uint8_t* buffer) override; private: std::vector statuses; }; public: static size_t fixedHeaderSize; static uint16_t maxMissingPackets; static uint16_t maxPacketStatusCount; static int16_t maxPacketDelta; public: static FeedbackRtpTransportPacket* Parse(const uint8_t* data, size_t len); private: static const absl::flat_hash_map Status2String; public: FeedbackRtpTransportPacket(uint32_t senderSsrc, uint32_t mediaSsrc) : FeedbackRtpPacket(RTC::RTCP::FeedbackRtp::MessageType::TCC, senderSsrc, mediaSsrc) { } FeedbackRtpTransportPacket(CommonHeader* commonHeader, size_t availableLen); ~FeedbackRtpTransportPacket() override; public: bool IsBaseSet() const { return this->baseSet; } void SetBase(uint16_t sequenceNumber, uint64_t timestamp); AddPacketResult AddPacket(uint16_t sequenceNumber, uint64_t timestamp, size_t maxRtcpPacketLen); // Just for locally generated packets. void Finish(); bool IsFull() const { // NOTE: Since AddPendingChunks() is called at the end, we cannot track // the exact ongoing value of packetStatusCount. Hence, let's reserve 7 // packets just in case. return this->packetStatusCount >= FeedbackRtpTransportPacket::maxPacketStatusCount - 7; } bool IsSerializable() const { return !this->deltas.empty(); } bool IsCorrect() const // Just for locally generated packets. { return this->isCorrect; } uint16_t GetBaseSequenceNumber() const { return this->baseSequenceNumber; } uint16_t GetPacketStatusCount() const { return this->packetStatusCount; } int32_t GetReferenceTime() const { return this->referenceTime; } // NOTE: We only use this for testing purpose. void SetReferenceTime(int64_t referenceTime) { this->referenceTime = (referenceTime % TimeWrapPeriod) / BaseTimeTick; } int64_t GetReferenceTimestamp() const // Reference time in ms. { return TimeWrapPeriod + (static_cast(this->referenceTime) * BaseTimeTick); } int64_t GetBaseDelta(const int64_t previousTimestampMs) const { int64_t delta = GetReferenceTimestamp() - previousTimestampMs; // Compensate for wrap around. if (std::abs(delta - TimeWrapPeriod) < std::abs(delta)) { delta -= TimeWrapPeriod; } else if (std::abs(delta + TimeWrapPeriod) < std::abs(delta)) { delta += TimeWrapPeriod; } return delta; } uint8_t GetFeedbackPacketCount() const { return this->feedbackPacketCount; } void SetFeedbackPacketCount(uint8_t count) { this->feedbackPacketCount = count; } uint16_t GetLatestSequenceNumber() const // Just for locally generated packets. { return this->latestSequenceNumber; } uint64_t GetLatestTimestamp() const // Just for locally generated packets. { return this->latestTimestamp; } std::vector GetPacketResults() const; uint8_t GetPacketFractionLost() const; /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { if (this->size) { return this->size; } // Fixed packet size. size_t size = FeedbackRtpPacket::GetSize(); size += FeedbackRtpTransportPacket::fixedHeaderSize; size += this->deltasAndChunksSize; // 32 bits padding. size += (-size) & 3; return size; } private: void FillChunk(uint16_t previousSequenceNumber, uint16_t sequenceNumber, int16_t delta); void CreateRunLengthChunk(Status status, uint16_t count); void CreateOneBitVectorChunk(std::vector& statuses); void CreateTwoBitVectorChunk(std::vector& statuses); void AddPendingChunks(); private: // Whether baseSequenceNumber has been set. bool baseSet{ false }; uint16_t baseSequenceNumber{ 0u }; // 24 bits signed integer. int32_t referenceTime{ 0 }; // Just for locally generated packets. uint16_t latestSequenceNumber{ 0u }; // Just for locally generated packets. uint64_t latestTimestamp{ 0u }; uint16_t packetStatusCount{ 0u }; uint8_t feedbackPacketCount{ 0u }; std::vector chunks; std::vector deltas; // Just for locally generated packets. Context context; size_t deltasAndChunksSize{ 0u }; size_t size{ 0 }; bool isCorrect{ true }; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/Packet.hpp ================================================ #ifndef MS_RTC_RTCP_PACKET_HPP #define MS_RTC_RTCP_PACKET_HPP #include "common.hpp" #include #include namespace RTC { namespace RTCP { // Internal buffer for RTCP serialization. constexpr size_t SerializationBufferSize{ 65536 }; extern uint8_t SerializationBuffer[SerializationBufferSize]; // Maximum interval for regular RTCP mode. constexpr uint16_t MaxAudioIntervalMs{ 5000 }; constexpr uint16_t MaxVideoIntervalMs{ 1000 }; enum class Type : uint8_t { SR = 200, RR = 201, SDES = 202, BYE = 203, APP = 204, RTPFB = 205, PSFB = 206, XR = 207 }; class Packet { public: /** * Struct for RTCP common header. * * @remarks * - This struct is guaranteed to be aligned to 2 bytes. */ struct CommonHeader { #if defined(MS_LITTLE_ENDIAN) uint8_t count : 5; uint8_t padding : 1; uint8_t version : 2; #elif defined(MS_BIG_ENDIAN) uint8_t version : 2; uint8_t padding : 1; uint8_t count : 5; #endif uint8_t packetType; uint16_t length; }; public: static const size_t CommonHeaderSize{ 4 }; static bool IsRtcp(const uint8_t* data, size_t len) { auto* header = const_cast(reinterpret_cast(data)); // clang-format off return ( (len >= CommonHeaderSize) && // @see RFC 7983. (data[0] > 127 && data[0] < 192) && // RTP Version must be 2. (header->version == 2) && // RTCP packet types defined by IANA: // http://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml#rtp-parameters-4 // RFC 5761 (RTCP-mux) states this range for secure RTCP/RTP detection. (header->packetType >= 192 && header->packetType <= 223) ); // clang-format on } static Packet* Parse(const uint8_t* data, size_t len); static const std::string& TypeToString(Type type); private: static const absl::flat_hash_map Type2String; public: explicit Packet(Type type) : type(type) { } explicit Packet(CommonHeader* commonHeader) { this->type = RTCP::Type(commonHeader->packetType); this->header = commonHeader; } virtual ~Packet() = default; void SetNext(Packet* packet) { this->next = packet; } Packet* GetNext() const { return this->next; } const uint8_t* GetData() const { return reinterpret_cast(this->header); } public: virtual void Dump(int indentation = 0) const = 0; virtual size_t Serialize(uint8_t* buffer) = 0; virtual Type GetType() const { return this->type; } virtual size_t GetCount() const { return 0u; } virtual size_t GetSize() const = 0; private: Type type; Packet* next{ nullptr }; CommonHeader* header{ nullptr }; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/ReceiverReport.hpp ================================================ #ifndef MS_RTC_RTCP_RECEIVER_REPORT_HPP #define MS_RTC_RTCP_RECEIVER_REPORT_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/RTCP/Packet.hpp" #include namespace RTC { namespace RTCP { class ReceiverReport { public: /** * Struct for RTCP receiver report. * * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Header { uint32_t ssrc; uint32_t fractionLost : 8; uint32_t totalLost : 24; uint32_t lastSeq; uint32_t jitter; uint32_t lsr; uint32_t dlsr; }; public: static const size_t HeaderSize{ 24 }; static ReceiverReport* Parse(const uint8_t* data, size_t len); public: // Locally generated Report. Holds the data internally. ReceiverReport() { this->header = reinterpret_cast(this->raw); } // Parsed Report. Points to an external data. explicit ReceiverReport(Header* header) : header(header) { } explicit ReceiverReport(ReceiverReport* report) : header(report->header) { } void Dump(int indentation = 0) const; size_t Serialize(uint8_t* buffer); size_t GetSize() const { return HeaderSize; } uint32_t GetSsrc() const { return ntohl(this->header->ssrc); } void SetSsrc(uint32_t ssrc) { this->header->ssrc = htonl(ssrc); } uint8_t GetFractionLost() const { return Utils::Byte::Get1Byte(reinterpret_cast(this->header), 4); } void SetFractionLost(uint8_t fractionLost) { Utils::Byte::Set1Byte(reinterpret_cast(this->header), 4, fractionLost); } int32_t GetTotalLost() const { return Utils::Byte::Get3BytesSigned(reinterpret_cast(this->header), 5); } void SetTotalLost(int32_t totalLost) { Utils::Byte::Set3BytesSigned(reinterpret_cast(this->header), 5, totalLost); } uint32_t GetLastSeq() const { return ntohl(this->header->lastSeq); } void SetLastSeq(uint32_t lastSeq) { this->header->lastSeq = htonl(lastSeq); } uint32_t GetJitter() const { return ntohl(this->header->jitter); } void SetJitter(float jitter) { this->header->jitter = htonl(static_cast(jitter)); } uint32_t GetLastSenderReport() const { return ntohl(this->header->lsr); } void SetLastSenderReport(uint32_t lsr) { this->header->lsr = htonl(lsr); } uint32_t GetDelaySinceLastSenderReport() const { return ntohl(this->header->dlsr); } void SetDelaySinceLastSenderReport(uint32_t dlsr) { this->header->dlsr = htonl(dlsr); } private: Header* header{ nullptr }; uint8_t raw[HeaderSize]{ 0u }; }; class ReceiverReportPacket : public Packet { public: static size_t maxReportsPerPacket; using Iterator = std::vector::iterator; public: static ReceiverReportPacket* Parse(const uint8_t* data, size_t len, size_t offset = 0); public: ReceiverReportPacket() : Packet(Type::RR) { } explicit ReceiverReportPacket(CommonHeader* commonHeader) : Packet(commonHeader) { } ~ReceiverReportPacket() override { for (auto* report : this->reports) { delete report; } } uint32_t GetSsrc() const { return this->ssrc; } void SetSsrc(uint32_t ssrc) { this->ssrc = ssrc; } void AddReport(ReceiverReport* report) { this->reports.push_back(report); } void RemoveReport(ReceiverReport* report) { auto it = std::find(this->reports.begin(), this->reports.end(), report); if (it != this->reports.end()) { this->reports.erase(it); } } Iterator Begin() { return this->reports.begin(); } Iterator End() { return this->reports.end(); } /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; // NOTE: We need to force this since when we parse a SenderReportPacket // that contains receive report blocks we also generate a second // ReceiverReportPacket/ from same data and len, so parent // Packet::GetType() would return this->type which would be SR instead of // RR. Type GetType() const override { return Type::RR; } size_t GetCount() const override { return this->reports.size(); } size_t GetSize() const override { // A serialized packet can contain a maximum of 31 reports. // If number of reports exceeds 31 then the required number of packets // will be serialized which will take the size calculated below. size_t size = (Packet::CommonHeaderSize + 4u /* this->ssrc */) * ((this->GetCount() / maxReportsPerPacket) + 1); size += ReceiverReport::HeaderSize * this->GetCount(); return size; } private: // SSRC of packet sender. uint32_t ssrc{ 0u }; std::vector reports; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/Sdes.hpp ================================================ #ifndef MS_RTC_RTCP_SDES_HPP #define MS_RTC_RTCP_SDES_HPP #include "common.hpp" #include "RTC/RTCP/Packet.hpp" #include #include namespace RTC { namespace RTCP { /* SDES Item. */ class SdesItem { public: enum class Type : uint8_t { END = 0, CNAME, NAME, EMAIL, PHONE, LOC, TOOL, NOTE, PRIV }; #ifdef MS_TEST public: #else private: #endif /** * @remarks * - This struct is guaranteed to be aligned to 1 byte. */ struct Header { SdesItem::Type type; uint8_t length; char value[]; }; public: static const size_t HeaderSize = 2; static SdesItem* Parse(const uint8_t* data, size_t len); static const std::string& TypeToString(SdesItem::Type type); public: explicit SdesItem(Header* header) : header(header) { } explicit SdesItem(SdesItem* item) : header(item->header) { } SdesItem(SdesItem::Type type, size_t len, const char* value); ~SdesItem() = default; void Dump(int indentation = 0) const; size_t Serialize(uint8_t* buffer); size_t GetSize() const { return 2 + size_t{ this->header->length }; } SdesItem::Type GetType() const { return this->header->type; } uint8_t GetLength() const { return this->header->length; } char* GetValue() const { return this->header->value; } private: // Passed by argument. Header* header{ nullptr }; std::unique_ptr raw; private: static const absl::flat_hash_map Type2String; }; class SdesChunk { public: using Iterator = std::vector::iterator; public: static SdesChunk* Parse(const uint8_t* data, size_t len); public: explicit SdesChunk(uint32_t ssrc) { this->ssrc = ssrc; } explicit SdesChunk(SdesChunk* chunk) { this->ssrc = chunk->ssrc; for (auto it = chunk->Begin(); it != chunk->End(); ++it) { this->AddItem(new SdesItem(*it)); } } ~SdesChunk() { for (auto* item : this->items) { delete item; } } void Dump(int indentation = 0) const; void Serialize(); size_t Serialize(uint8_t* buffer); size_t GetSize() const { size_t size = 4u /*ssrc*/; for (auto* item : this->items) { size += item->GetSize(); } // Add the mandatory null octet. ++size; // Consider pading to 32 bits (4 bytes) boundary. // http://stackoverflow.com/questions/11642210/computing-padding-required-for-n-byte-alignment return (size + 3) & ~3; } uint32_t GetSsrc() const { return this->ssrc; } void SetSsrc(uint32_t ssrc) { this->ssrc = htonl(ssrc); } void AddItem(SdesItem* item) { this->items.push_back(item); } Iterator Begin() { return this->items.begin(); } Iterator End() { return this->items.end(); } private: uint32_t ssrc{ 0u }; std::vector items; }; class SdesPacket : public Packet { public: static size_t maxChunksPerPacket; using Iterator = std::vector::iterator; public: static SdesPacket* Parse(const uint8_t* data, size_t len); public: SdesPacket() : Packet(RTCP::Type::SDES) { } explicit SdesPacket(CommonHeader* commonHeader) : Packet(commonHeader) { } ~SdesPacket() override { for (auto* chunk : this->chunks) { delete chunk; } } void AddChunk(SdesChunk* chunk) { this->chunks.push_back(chunk); } void RemoveChunk(SdesChunk* chunk) { auto it = std::find(this->chunks.begin(), this->chunks.end(), chunk); if (it != this->chunks.end()) { this->chunks.erase(it); } } Iterator Begin() { return this->chunks.begin(); } Iterator End() { return this->chunks.end(); } /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetCount() const override { return this->chunks.size(); } size_t GetSize() const override { // A serialized packet can contain a maximum of 31 chunks. // If number of chunks exceeds 31 then the required number of packets // will be serialized which will take the size calculated below. size_t size = Packet::CommonHeaderSize * ((this->GetCount() / (SdesPacket::maxChunksPerPacket + 1)) + 1); for (auto* chunk : this->chunks) { size += chunk->GetSize(); } return size; } private: std::vector chunks; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/SenderReport.hpp ================================================ #ifndef MS_RTC_RTCP_SENDER_REPORT_HPP #define MS_RTC_RTCP_SENDER_REPORT_HPP #include "common.hpp" #include "RTC/RTCP/Packet.hpp" #include namespace RTC { namespace RTCP { class SenderReport { public: /** * Struct for RTCP sender report. * * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Header { uint32_t ssrc; uint32_t ntpSec; uint32_t ntpFrac; uint32_t rtpTs; uint32_t packetCount; uint32_t octetCount; }; public: static const size_t HeaderSize{ 24 }; static SenderReport* Parse(const uint8_t* data, size_t len); public: // Locally generated Report. Holds the data internally. SenderReport() { this->header = reinterpret_cast(this->raw); } // Parsed Report. Points to an external data. explicit SenderReport(Header* header) : header(header) { } explicit SenderReport(SenderReport* report) : header(report->header) { } void Dump(int indentation = 0) const; size_t Serialize(uint8_t* buffer); size_t GetSize() const { return HeaderSize; } uint32_t GetSsrc() const { return ntohl(this->header->ssrc); } void SetSsrc(uint32_t ssrc) { this->header->ssrc = htonl(ssrc); } uint32_t GetNtpSec() const { return ntohl(this->header->ntpSec); } void SetNtpSec(uint32_t ntpSec) { this->header->ntpSec = htonl(ntpSec); } uint32_t GetNtpFrac() const { return ntohl(this->header->ntpFrac); } void SetNtpFrac(uint32_t ntpFrac) { this->header->ntpFrac = htonl(ntpFrac); } uint32_t GetRtpTs() const { return ntohl(this->header->rtpTs); } void SetRtpTs(uint32_t rtpTs) { this->header->rtpTs = htonl(rtpTs); } uint32_t GetPacketCount() const { return ntohl(this->header->packetCount); } void SetPacketCount(uint32_t packetCount) { this->header->packetCount = htonl(packetCount); } uint32_t GetOctetCount() const { return ntohl(this->header->octetCount); } void SetOctetCount(uint32_t octetCount) { this->header->octetCount = htonl(octetCount); } private: Header* header{ nullptr }; uint8_t raw[HeaderSize]{ 0 }; }; class SenderReportPacket : public Packet { public: using Iterator = std::vector::iterator; public: static SenderReportPacket* Parse(const uint8_t* data, size_t len); public: SenderReportPacket() : Packet(Type::SR) { } explicit SenderReportPacket(CommonHeader* commonHeader) : Packet(commonHeader) { } ~SenderReportPacket() override { for (auto* report : this->reports) { delete report; } } void AddReport(SenderReport* report) { this->reports.push_back(report); } void RemoveReport(SenderReport* report) { auto it = std::find(this->reports.begin(), this->reports.end(), report); if (it != this->reports.end()) { this->reports.erase(it); } } Iterator Begin() { return this->reports.begin(); } Iterator End() { return this->reports.end(); } /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetCount() const override { return this->reports.size(); } size_t GetSize() const override { // A serialized packet consists of a series of SR packets with // one SR report each. size_t size{ 0 }; for (auto* report : this->reports) { size += Packet::CommonHeaderSize; size += report->GetSize(); } return size; } private: std::vector reports; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/XR.hpp ================================================ #ifndef MS_RTC_RTCP_XR_HPP #define MS_RTC_RTCP_XR_HPP #include "common.hpp" #include "RTC/RTCP/Packet.hpp" #include /* https://tools.ietf.org/html/rfc3611 * RTP Control Protocol Extended Reports (RTCP XR) 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |V=2|P|reserved | PT=XR=207 | length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | SSRC | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ : report blocks : +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class ExtendedReportBlock { public: enum class Type : uint8_t { LRLE = 1, DRLE = 2, PRT = 3, RRT = 4, DLRR = 5, SS = 6, VM = 7 }; public: /** * Struct for Extended Report Block common header. * * @remarks * - This struct is guaranteed to be aligned to 2 bytes. */ struct CommonHeader { uint8_t blockType; uint8_t reserved; uint16_t length; }; public: static const size_t CommonHeaderSize{ 4 }; static ExtendedReportBlock* Parse(const uint8_t* data, size_t len); public: explicit ExtendedReportBlock(Type type) : type(type) { this->header = reinterpret_cast(this->raw); this->header->reserved = 0; } virtual ~ExtendedReportBlock() = default; public: virtual void Dump(int indentation = 0) const = 0; virtual size_t Serialize(uint8_t* buffer) = 0; virtual size_t GetSize() const = 0; ExtendedReportBlock::Type GetType() const { return this->type; } protected: Type type; CommonHeader* header{ nullptr }; private: uint8_t raw[CommonHeaderSize] = { 0 }; }; class ExtendedReportPacket : public Packet { public: using Iterator = std::vector::iterator; public: static ExtendedReportPacket* Parse(const uint8_t* data, size_t len); public: ExtendedReportPacket() : Packet(Type::XR) { } explicit ExtendedReportPacket(CommonHeader* commonHeader) : Packet(commonHeader) { } ~ExtendedReportPacket() override { for (auto* report : this->reports) { delete report; } } void AddReport(ExtendedReportBlock* report) { this->reports.push_back(report); } void RemoveReport(ExtendedReportBlock* report) { auto it = std::find(this->reports.begin(), this->reports.end(), report); if (it != this->reports.end()) { this->reports.erase(it); } } uint32_t GetSsrc() const { return this->ssrc; } void SetSsrc(uint32_t ssrc) { this->ssrc = ssrc; } Iterator Begin() { return this->reports.begin(); } Iterator End() { return this->reports.end(); } /* Pure virtual methods inherited from Packet. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetCount() const override { return 0; } size_t GetSize() const override { size_t size = Packet::CommonHeaderSize + 4u /*ssrc*/; for (auto* report : this->reports) { size += report->GetSize(); } return size; } private: uint32_t ssrc{ 0u }; std::vector reports; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/XrDelaySinceLastRr.hpp ================================================ #ifndef MS_RTC_RTCP_XR_DELAY_SINCE_LAST_RR_HPP #define MS_RTC_RTCP_XR_DELAY_SINCE_LAST_RR_HPP #include "common.hpp" #include "RTC/RTCP/XR.hpp" /* https://tools.ietf.org/html/rfc3611 * Delay Since Last Receiver Report (DLRR) Report Block 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | BT=5 | reserved | block length | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | SSRC_1 (SSRC of first receiver) | sub- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block | last RR (LRR) | 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | delay since last RR (DLRR) | +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | SSRC_2 (SSRC of second receiver) | sub- +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block : ... : 2 +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ */ namespace RTC { namespace RTCP { class DelaySinceLastRr : public ExtendedReportBlock { public: static DelaySinceLastRr* Parse(const uint8_t* data, size_t len); public: class SsrcInfo { public: static const size_t BodySize{ 12 }; static SsrcInfo* Parse(const uint8_t* data, size_t len); public: /** * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Body { uint32_t ssrc; uint32_t lrr; uint32_t dlrr; }; public: // Locally generated Report. Holds the data internally. SsrcInfo() { this->body = reinterpret_cast(this->raw); } // Parsed Report. Points to an external data. explicit SsrcInfo(Body* body) : body(body) { } void Dump(int indentation = 0) const; size_t Serialize(uint8_t* buffer); size_t GetSize() const { return BodySize; } uint32_t GetSsrc() const { return ntohl(this->body->ssrc); } void SetSsrc(uint32_t ssrc) { this->body->ssrc = htonl(ssrc); } uint32_t GetLastReceiverReport() const { return ntohl(this->body->lrr); } void SetLastReceiverReport(uint32_t lrr) { this->body->lrr = htonl(lrr); } uint32_t GetDelaySinceLastReceiverReport() const { return ntohl(this->body->dlrr); } void SetDelaySinceLastReceiverReport(uint32_t dlrr) { this->body->dlrr = htonl(dlrr); } private: Body* body{ nullptr }; uint8_t raw[BodySize] = { 0 }; }; public: using Iterator = std::vector::iterator; public: DelaySinceLastRr() : ExtendedReportBlock(ExtendedReportBlock::Type::DLRR) { } explicit DelaySinceLastRr(CommonHeader* header) : ExtendedReportBlock(ExtendedReportBlock::Type::DLRR) { this->header = header; } ~DelaySinceLastRr() override { for (auto* ssrcInfo : this->ssrcInfos) { delete ssrcInfo; } } public: void AddSsrcInfo(SsrcInfo* ssrcInfo) { this->ssrcInfos.push_back(ssrcInfo); } // NOTE: This method not only removes given number of ssrc info sub-blocks // but also deletes their SsrcInfo instances. void RemoveLastSsrcInfos(size_t number) { while (!this->ssrcInfos.empty() && number-- > 0) { auto* ssrcInfo = this->ssrcInfos.back(); this->ssrcInfos.pop_back(); delete ssrcInfo; } } Iterator Begin() { return this->ssrcInfos.begin(); } Iterator End() { return this->ssrcInfos.end(); } /* Pure virtual methods inherited from ExtendedReportBlock. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { size_t size{ 4u }; // Common header. for (auto* ssrcInfo : this->ssrcInfos) { size += ssrcInfo->GetSize(); } return size; } private: std::vector ssrcInfos; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTCP/XrReceiverReferenceTime.hpp ================================================ #ifndef MS_RTC_RTCP_XR_RECEIVER_REFERENCE_TIME_HPP #define MS_RTC_RTCP_XR_RECEIVER_REFERENCE_TIME_HPP #include "common.hpp" #include "RTC/RTCP/XR.hpp" /* https://tools.ietf.org/html/rfc3611 * Receiver Reference Time Report Block 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | BT=4 | reserved | block length = 2 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | NTP timestamp, most significant word | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | NTP timestamp, least significant word | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTCP { class ReceiverReferenceTime : public ExtendedReportBlock { public: /** * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct Body { uint32_t ntpSec; uint32_t ntpFrac; }; public: static const size_t BodySize{ 8 }; static ReceiverReferenceTime* Parse(const uint8_t* data, size_t len); public: // Locally generated Report. Holds the data internally. ReceiverReferenceTime() : ExtendedReportBlock(RTCP::ExtendedReportBlock::Type::RRT) { this->body = reinterpret_cast(this->raw); } // Parsed Report. Points to an external data. explicit ReceiverReferenceTime(CommonHeader* header) : ExtendedReportBlock(ExtendedReportBlock::Type::RRT) { this->header = header; this->body = reinterpret_cast((header) + 1); } public: uint32_t GetNtpSec() const { return ntohl(this->body->ntpSec); } void SetNtpSec(uint32_t ntpSec) { this->body->ntpSec = htonl(ntpSec); } uint32_t GetNtpFrac() const { return ntohl(this->body->ntpFrac); } void SetNtpFrac(uint32_t ntpFrac) { this->body->ntpFrac = htonl(ntpFrac); } /* Pure virtual methods inherited from ExtendedReportBlock. */ public: void Dump(int indentation = 0) const override; size_t Serialize(uint8_t* buffer) override; size_t GetSize() const override { size_t size{ 4 }; // Common header. size += BodySize; return size; } private: Body* body{ nullptr }; uint8_t raw[BodySize] = { 0 }; }; } // namespace RTCP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/Codecs/AV1.hpp ================================================ #ifndef MS_RTC_RTP_CODECS_AV1_HPP #define MS_RTC_RTP_CODECS_AV1_HPP #include "common.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Packet.hpp" namespace RTC { namespace RTP { namespace Codecs { class AV1 { public: struct PayloadDescriptor : public Codecs::PayloadDescriptor { struct EncodingData { uint32_t maxSpatialLayer{ 0 }; uint32_t maxTemporalLayer{ 0 }; }; struct Encoder : public Codecs::PayloadDescriptor::Encoder { ~Encoder() override = default; explicit Encoder(EncodingData encodingData) : encodingData(encodingData) { } void Encode(AV1::PayloadDescriptor* payloadDescriptor) const; EncodingData encodingData; }; explicit PayloadDescriptor(std::unique_ptr& dependencyDescriptor); /* Pure virtual methods inherited from Codecs::PayloadDescriptor. */ ~PayloadDescriptor() override = default; void Dump(int indentation = 0) const override; void UpdateListener(Codecs::DependencyDescriptor::Listener* listener) const { if (this->dependencyDescriptor) { this->dependencyDescriptor->UpdateListener(listener); } } void Encode(); void Restore() const; void UpdateActiveDecodeTargets(uint16_t spatialLayer, uint16_t temporalLayer); std::unique_ptr GetEncoder() const { if (this->encoder.has_value()) { return std::make_unique(this->encoder.value()); } else { return nullptr; } } void CreateEncoder(EncodingData encodingData) { this->encoder = Encoder(encodingData); } // Fields in Dependency Descriptor extension. bool startOfFrame{ false }; bool endOfFrame{ false }; uint16_t frameNumber{ 0 }; uint8_t spatialLayer{ 0 }; uint8_t temporalLayer{ 0 }; std::unique_ptr dependencyDescriptor{ nullptr }; // Parsed values. bool isKeyFrame{ false }; std::optional encoder{ std::nullopt }; }; public: static AV1::PayloadDescriptor* Parse( std::unique_ptr& dependencyDescriptor); static void ProcessRtpPacket( RTP::Packet* packet, std::unique_ptr& templateDependencyStructure); public: class EncodingContext : public Codecs::EncodingContext { public: explicit EncodingContext(Codecs::EncodingContext::Params& params) : Codecs::EncodingContext(params) { } ~EncodingContext() override = default; /* Pure virtual methods inherited from Codecs::EncodingContext. */ public: void SyncRequired() override { this->syncRequired = true; } public: bool syncRequired{ false }; }; class PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler { public: explicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor); ~PayloadDescriptorHandler() override = default; public: void Dump(int indentation = 0) const override { this->payloadDescriptor->Dump(indentation); } bool Process( Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override; void RtpPacketChanged(RTP::Packet* packet) override { this->payloadDescriptor->UpdateListener(packet); }; std::unique_ptr GetEncoder() const override { return this->payloadDescriptor->GetEncoder(); } void Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override; void Restore(RTP::Packet* packet) override; uint8_t GetSpatialLayer() const override { return this->payloadDescriptor->spatialLayer; } uint8_t GetTemporalLayer() const override { return this->payloadDescriptor->temporalLayer; } bool IsKeyFrame() const override { return this->payloadDescriptor->isKeyFrame; } private: std::unique_ptr payloadDescriptor; }; }; } // namespace Codecs } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/Codecs/DependencyDescriptor.hpp ================================================ #ifndef MS_RTC_RTP_CODECS_DEPENDENCY_DESCRIPTOR_HPP #define MS_RTC_RTP_CODECS_DEPENDENCY_DESCRIPTOR_HPP #include "common.hpp" #include "Utils.hpp" // BitStream. namespace RTC { namespace RTP { namespace Codecs { struct DependencyDescriptor { class Listener { public: virtual ~Listener() = default; public: virtual void OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) = 0; }; public: enum class DecodeTargetIndication : uint8_t { NOT_PRESENT = 0, DISCARDABLE = 1, SWITCH = 2, REQUIRED = 3 }; private: static std::unordered_map dtiToString; private: struct FrameDependencyTemplate { FrameDependencyTemplate(uint32_t spatialLayer, uint32_t temporalLayer) : spatialLayer(spatialLayer), temporalLayer(temporalLayer) { } uint32_t spatialLayer; uint32_t temporalLayer; std::vector decodeTargetIndications; std::vector frameDiffs; std::vector frameDiffChains; }; public: struct TemplateDependencyStructure { uint32_t spatialLayers{ 0 }; uint32_t temporalLayers{ 0 }; uint8_t templateIdOffset{ 0 }; uint8_t decodeTargetCount{ 0 }; std::vector templateLayers; }; public: bool startOfFrame{ false }; bool endOfFrame{ false }; uint8_t frameDependencyTemplateId{ 0 }; uint16_t frameNumber{ 0 }; uint8_t templateId{ 0 }; // Given by argument. TemplateDependencyStructure* templateDependencyStructure; std::vector decodeTargetProtectedBy; std::optional activeDecodeTargetsBitmask{ std::nullopt }; // Calculated. uint8_t temporalLayer{ 0 }; uint8_t spatialLayer{ 0 }; // Whether the frame is a key frame. Set to true if the descriptor contains template layers. bool isKeyFrame{ false }; private: DependencyDescriptor::Listener* listener{ nullptr }; public: static DependencyDescriptor* Parse( const uint8_t* data, size_t len, DependencyDescriptor::Listener* listener, std::unique_ptr& templateDependencyStructure); DependencyDescriptor( const uint8_t* data, size_t len, DependencyDescriptor::Listener* listener, TemplateDependencyStructure* templateDependencyStructure); void Dump(int indentation = 0) const; void UpdateListener(DependencyDescriptor::Listener* listener); const uint8_t* Serialize(uint8_t& len); bool UpdateActiveDecodeTargets(uint32_t maxSpatialLayer, uint32_t maxTemporalLayer); private: uint8_t GetSpatialLayer() const; uint8_t GetTemporalLayer() const; bool ReadMandatoryDescriptorFields(); bool ReadExtendedDescriptorFields(); bool ReadTemplateDependencyStructure(); bool ReadTemplateLayers(); bool ReadTemplateDecodeTargetIndications(); bool ReadTemplateFrameDiffs(); bool ReadTemplateFrameDiffChains(); bool ReadFrameDependencyDefinition(); bool WriteMandatoryDescriptorFields(); bool WriteExtendedDescriptorFields(); private: Utils::BitStream bitStream; }; } // namespace Codecs } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/Codecs/H264.hpp ================================================ #ifndef MS_RTC_RTP_CODECS_H264_HPP #define MS_RTC_RTP_CODECS_H264_HPP #include "common.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Packet.hpp" namespace RTC { namespace RTP { namespace Codecs { class H264 { public: struct PayloadDescriptor : public Codecs::PayloadDescriptor { /* Pure virtual methods inherited from Codecs::PayloadDescriptor. */ ~PayloadDescriptor() override = default; void Dump(int indentation = 0) const override; // Fields in Dependency Descriptor extension. bool startOfFrame{ false }; bool endOfFrame{ false }; uint8_t spatialLayer{ 0 }; uint8_t temporalLayer{ 0 }; // Parsed values. bool isKeyFrame{ false }; }; public: static H264::PayloadDescriptor* Parse( const uint8_t* data, size_t len, Codecs::DependencyDescriptor* dependencyDescriptor); static bool IsKeyFrame(const uint8_t* data, size_t len); static void ProcessRtpPacket( RTP::Packet* packet, std::unique_ptr& templateDependencyStructure); public: class EncodingContext : public Codecs::EncodingContext { public: explicit EncodingContext(Codecs::EncodingContext::Params& params) : Codecs::EncodingContext(params) { } ~EncodingContext() override = default; /* Pure virtual methods inherited from Codecs::EncodingContext. */ public: void SyncRequired() override { } }; public: class PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler { public: explicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor); ~PayloadDescriptorHandler() override = default; public: void Dump(int indentation = 0) const override { this->payloadDescriptor->Dump(indentation); } bool Process( Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override; void RtpPacketChanged(RTP::Packet* packet) override {}; std::unique_ptr GetEncoder() const override { return nullptr; } void Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override { } void Restore(RTP::Packet* packet) override { } uint8_t GetSpatialLayer() const override { return 0u; } uint8_t GetTemporalLayer() const override { return this->payloadDescriptor->temporalLayer; } bool IsKeyFrame() const override { return this->payloadDescriptor->isKeyFrame; } private: std::unique_ptr payloadDescriptor; }; }; } // namespace Codecs } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/Codecs/Opus.hpp ================================================ #ifndef MS_RTC_RTP_CODECS_OPUS_HPP #define MS_RTC_RTP_CODECS_OPUS_HPP #include "common.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Packet.hpp" namespace RTC { namespace RTP { namespace Codecs { class Opus { public: struct PayloadDescriptor : public Codecs::PayloadDescriptor { /* Pure virtual methods inherited from Codecs::PayloadDescriptor. */ ~PayloadDescriptor() override = default; void Dump(int indentation = 0) const override; // Mandatory fields. uint8_t stereo : 1; uint8_t code : 2; // Parsed values. bool isDtx{ false }; }; public: static Opus::PayloadDescriptor* Parse(const uint8_t* data, size_t len); static void ProcessRtpPacket(RTP::Packet* packet); public: class EncodingContext : public Codecs::EncodingContext { public: explicit EncodingContext(Codecs::EncodingContext::Params& params) : Codecs::EncodingContext(params) { } ~EncodingContext() override = default; /* Pure virtual methods inherited from Codecs::EncodingContext. */ public: void SyncRequired() override { this->syncRequired = true; } public: bool syncRequired{ false }; }; public: class PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler { public: explicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor); ~PayloadDescriptorHandler() override = default; public: void Dump(int indentation = 0) const override { this->payloadDescriptor->Dump(indentation); } bool Process( Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override; void RtpPacketChanged(RTP::Packet* packet) override {}; std::unique_ptr GetEncoder() const override { return nullptr; } void Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override { } void Restore(RTP::Packet* packet) override { } uint8_t GetSpatialLayer() const override { return 0u; } uint8_t GetTemporalLayer() const override { return 0u; } bool IsKeyFrame() const override { return false; } private: std::unique_ptr payloadDescriptor; }; }; } // namespace Codecs } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/Codecs/PayloadDescriptorHandler.hpp ================================================ #ifndef MS_RTC_RTP_CODECS_PAYLOAD_DESCRIPTOR_HANDLER_HPP #define MS_RTC_RTP_CODECS_PAYLOAD_DESCRIPTOR_HANDLER_HPP #include "common.hpp" #include "RTC/ConsumerTypes.hpp" #include "RTC/RTP/Codecs/DependencyDescriptor.hpp" #include "RTC/SeqManager.hpp" #include namespace RTC { namespace RTP { class Packet; namespace Codecs { // Codec payload descriptor. struct PayloadDescriptor { struct Encoder { virtual ~Encoder() = default; }; virtual ~PayloadDescriptor() = default; virtual void Dump(int indentation = 0) const = 0; }; class PictureIdList { static constexpr uint16_t MaxCurrentLayerPictureIdNum{ 1000u }; public: explicit PictureIdList() = default; ~PictureIdList() { this->layerChanges.clear(); } void Push(uint16_t pictureId, int16_t layer) { for (const auto& it : this->layerChanges) { // Layers can be changed only with ordered pictureId values. // If pictureId is lower than the previous one, then it has rolled over the max value. uint16_t diff = pictureId > it.first ? pictureId - it.first : pictureId + RTC::SeqManager::MaxValue - it.first; if (diff > MaxCurrentLayerPictureIdNum) { this->layerChanges.pop_front(); } else { break; } } this->layerChanges.emplace_back(pictureId, layer); } int16_t GetLayer(uint16_t pictureId) const { if (this->layerChanges.size() <= 1) { return -1; } for (auto it = std::next(this->layerChanges.begin()); it != this->layerChanges.end(); ++it) { if (RTC::SeqManager::IsSeqHigherThan(it->first, pictureId)) { return std::prev(it)->second; } } return -1; } private: // List populated with the spatial/temporal layer changes // indexed by the corresponding pictureId. std::deque> layerChanges; }; // Encoding context used by PayloadDescriptorHandler to properly rewrite the // PayloadDescriptor. class EncodingContext { public: struct Params { uint8_t spatialLayers{ 1u }; uint8_t temporalLayers{ 1u }; bool ksvc{ false }; }; public: explicit EncodingContext(Codecs::EncodingContext::Params& params) : params(params) { } virtual ~EncodingContext() = default; public: uint8_t GetSpatialLayers() const { return this->params.spatialLayers; } uint8_t GetTemporalLayers() const { return this->params.temporalLayers; } bool IsKSvc() const { return this->params.ksvc; } int16_t GetTargetSpatialLayer() const { return this->targetLayers.spatial; } int16_t GetTargetTemporalLayer() const { return this->targetLayers.temporal; } const RTC::ConsumerTypes::VideoLayers& GetTargetLayers() const { return this->targetLayers; } int16_t GetCurrentSpatialLayer() const { return this->currentLayers.spatial; } int16_t GetCurrentTemporalLayer() const { return this->currentLayers.temporal; } const RTC::ConsumerTypes::VideoLayers& GetCurrentLayers() const { return this->currentLayers; } bool GetIgnoreDtx() const { return this->ignoreDtx; } void SetTargetSpatialLayer(int16_t spatialLayer) { this->targetLayers.spatial = spatialLayer; } void SetTargetTemporalLayer(int16_t temporalLayer) { this->targetLayers.temporal = temporalLayer; } void SetCurrentSpatialLayer(int16_t spatialLayer) { this->currentLayers.spatial = spatialLayer; } void SetCurrentTemporalLayer(int16_t temporalLayer) { this->currentLayers.temporal = temporalLayer; } void SetIgnoreDtx(bool ignoreDtx) { this->ignoreDtx = ignoreDtx; } virtual void SyncRequired() = 0; void SetCurrentSpatialLayer(int16_t spatialLayer, uint16_t pictureId) { if (this->currentLayers.spatial == spatialLayer) { return; } this->spatialLayerPictureIdList.Push(pictureId, spatialLayer); this->currentLayers.spatial = spatialLayer; } void SetCurrentTemporalLayer(int16_t temporalLayer, uint16_t pictureId) { if (this->currentLayers.temporal == temporalLayer) { return; } this->temporalLayerPictureIdList.Push(pictureId, temporalLayer); this->currentLayers.temporal = temporalLayer; } int16_t GetSpatialLayerForPictureId(uint16_t pictureId) const { int16_t layer = this->spatialLayerPictureIdList.GetLayer(pictureId); if (layer > -1) { return layer; } return this->currentLayers.spatial; } int16_t GetTemporalLayerForPictureId(uint16_t pictureId) const { int16_t layer = this->temporalLayerPictureIdList.GetLayer(pictureId); if (layer > -1) { return layer; } return this->currentLayers.temporal; } private: Params params; RTC::ConsumerTypes::VideoLayers targetLayers; RTC::ConsumerTypes::VideoLayers currentLayers; bool ignoreDtx{ false }; private: // List of spatial/temporal layer changes indexed by the corresponding pictureId. PictureIdList spatialLayerPictureIdList; PictureIdList temporalLayerPictureIdList; }; class PayloadDescriptorHandler { public: virtual ~PayloadDescriptorHandler() = default; public: virtual void Dump(int indentation = 0) const = 0; virtual bool Process(EncodingContext* context, RTP::Packet* packet, bool& marker) = 0; virtual void RtpPacketChanged(RTP::Packet* packet) = 0; virtual std::unique_ptr GetEncoder() const = 0; virtual void Encode(RTP::Packet* packet, PayloadDescriptor::Encoder* encoder) = 0; virtual void Restore(RTP::Packet* packet) = 0; virtual uint8_t GetSpatialLayer() const = 0; virtual uint8_t GetTemporalLayer() const = 0; virtual bool IsKeyFrame() const = 0; }; } // namespace Codecs } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/Codecs/Tools.hpp ================================================ #ifndef MS_RTC_RTP_CODECS_TOOLS_HPP #define MS_RTC_RTP_CODECS_TOOLS_HPP #include "common.hpp" #include "RTC/RTP/Codecs/AV1.hpp" #include "RTC/RTP/Codecs/H264.hpp" #include "RTC/RTP/Codecs/Opus.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Codecs/VP8.hpp" #include "RTC/RTP/Codecs/VP9.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RtpDictionaries.hpp" namespace RTC { namespace RTP { namespace Codecs { class Tools { public: static bool CanBeKeyFrame(const RTC::RtpCodecMimeType& mimeType) { switch (mimeType.type) { case RTC::RtpCodecMimeType::Type::VIDEO: { switch (mimeType.subtype) { case RTC::RtpCodecMimeType::Subtype::VP8: case RTC::RtpCodecMimeType::Subtype::VP9: case RTC::RtpCodecMimeType::Subtype::H264: { return true; } default: { return false; } } } default: { return false; } } } static void ProcessRtpPacket( RTP::Packet* packet, const RTC::RtpCodecMimeType& mimeType, std::unique_ptr& templateDependencyStructure) { switch (mimeType.type) { case RTC::RtpCodecMimeType::Type::VIDEO: { switch (mimeType.subtype) { case RTC::RtpCodecMimeType::Subtype::VP8: { Codecs::VP8::ProcessRtpPacket(packet); break; } case RTC::RtpCodecMimeType::Subtype::VP9: { Codecs::VP9::ProcessRtpPacket(packet); break; } case RTC::RtpCodecMimeType::Subtype::H264: { Codecs::H264::ProcessRtpPacket(packet, templateDependencyStructure); break; } case RTC::RtpCodecMimeType::Subtype::AV1: { Codecs::AV1::ProcessRtpPacket(packet, templateDependencyStructure); break; } default:; } } case RTC::RtpCodecMimeType::Type::AUDIO: { switch (mimeType.subtype) { case RTC::RtpCodecMimeType::Subtype::OPUS: case RTC::RtpCodecMimeType::Subtype::MULTIOPUS: { Codecs::Opus::ProcessRtpPacket(packet); break; } default:; } } default:; } } static bool IsValidTypeForCodec( RTC::RtpParameters::Type type, const RTC::RtpCodecMimeType& mimeType) { switch (type) { case RTC::RtpParameters::Type::SIMPLE: { return true; } case RTC::RtpParameters::Type::SIMULCAST: { switch (mimeType.type) { case RTC::RtpCodecMimeType::Type::VIDEO: { switch (mimeType.subtype) { case RTC::RtpCodecMimeType::Subtype::VP8: case RTC::RtpCodecMimeType::Subtype::H264: { return true; } default: { return false; } } } default: { return false; } } } case RTC::RtpParameters::Type::SVC: { switch (mimeType.type) { case RTC::RtpCodecMimeType::Type::VIDEO: { switch (mimeType.subtype) { case RTC::RtpCodecMimeType::Subtype::VP9: case RTC::RtpCodecMimeType::Subtype::AV1: { return true; } default: { return false; } } } default: { return false; } } } case RTC::RtpParameters::Type::PIPE: { return true; } NO_DEFAULT_GCC(); } } static EncodingContext* GetEncodingContext( const RTC::RtpCodecMimeType& mimeType, Codecs::EncodingContext::Params& params) { switch (mimeType.type) { case RTC::RtpCodecMimeType::Type::VIDEO: { switch (mimeType.subtype) { case RTC::RtpCodecMimeType::Subtype::VP8: { return new Codecs::VP8::EncodingContext(params); } case RTC::RtpCodecMimeType::Subtype::VP9: { return new Codecs::VP9::EncodingContext(params); } case RTC::RtpCodecMimeType::Subtype::AV1: { return new Codecs::AV1::EncodingContext(params); } case RTC::RtpCodecMimeType::Subtype::H264: { return new Codecs::H264::EncodingContext(params); } default: { return nullptr; } } } case RTC::RtpCodecMimeType::Type::AUDIO: { switch (mimeType.subtype) { case RTC::RtpCodecMimeType::Subtype::OPUS: case RTC::RtpCodecMimeType::Subtype::MULTIOPUS: { return new Codecs::Opus::EncodingContext(params); } default: { return nullptr; } } } default: { return nullptr; } } } }; } // namespace Codecs } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/Codecs/VP8.hpp ================================================ #ifndef MS_RTC_RTP_CODECS_VP8_HPP #define MS_RTC_RTP_CODECS_VP8_HPP #include "common.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/SeqManager.hpp" /* RFC 7741 * VP8 Payload Descriptor Single octet PictureID (M = 0) Dual octet PictureID (M = 1) ============================== ============================ 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ |X|R|N|S|R| PID | (REQUIRED) |X|R|N|S|R| PID | (REQUIRED) +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ X: |I|L|T|K| RSV | (OPTIONAL) X: |I|L|T|K| RSV | (OPTIONAL) +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ I: |M| PictureID | (OPTIONAL) I: |M| PictureID | (OPTIONAL) +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ L: | TL0PICIDX | (OPTIONAL) | PictureID | +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ T/K: |TID|Y| KEYIDX | (OPTIONAL) L: | TL0PICIDX | (OPTIONAL) +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ T/K: |TID|Y| KEYIDX | (OPTIONAL) +-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTP { namespace Codecs { class VP8 { public: struct PayloadDescriptor : public Codecs::PayloadDescriptor { struct EncodingData { uint16_t pictureId; uint8_t tl0PictureIndex; }; struct Encoder : public Codecs::PayloadDescriptor::Encoder { ~Encoder() override = default; explicit Encoder(EncodingData encodingData) : encodingData(encodingData) { } void Encode(uint8_t* data, const VP8::PayloadDescriptor* payloadDescriptor) const; EncodingData encodingData; }; /* Pure virtual methods inherited from Codecs::PayloadDescriptor. */ ~PayloadDescriptor() override = default; void Dump(int indentation = 0) const override; // Rewrite the buffer with the given pictureId and tl0PictureIndex values. void Encode(uint8_t* data, uint16_t pictureId, uint8_t tl0PictureIndex) const; void Encode(uint8_t* data) const; void Restore(uint8_t* data) const; std::unique_ptr GetEncoder() const { if (this->encoder.has_value()) { return std::make_unique(this->encoder.value()); } else { return nullptr; } } void CreateEncoder(EncodingData encodingData) { this->encoder = Encoder(encodingData); } // Mandatory fields. uint8_t extended : 1; uint8_t nonReference : 1; uint8_t start : 1; uint8_t partitionIndex : 4; // Optional field flags. uint8_t i : 1; // PictureID present. uint8_t l : 1; // TL0PICIDX present. uint8_t t : 1; // TID present. uint8_t k : 1; // KEYIDX present. // Optional fields. uint16_t pictureId; uint8_t tl0PictureIndex; uint8_t tlIndex : 2; uint8_t y : 1; uint8_t keyIndex : 5; // Parsed values. bool isKeyFrame{ false }; bool hasPictureId{ false }; bool hasOneBytePictureId{ false }; bool hasTwoBytesPictureId{ false }; bool hasTl0PictureIndex{ false }; bool hasTlIndex{ false }; std::optional encoder{ std::nullopt }; }; public: static VP8::PayloadDescriptor* Parse(const uint8_t* data, size_t len); static void ProcessRtpPacket(RTP::Packet* packet); public: class EncodingContext : public Codecs::EncodingContext { public: explicit EncodingContext(Codecs::EncodingContext::Params& params) : Codecs::EncodingContext(params) { } ~EncodingContext() override = default; /* Pure virtual methods inherited from Codecs::EncodingContext. */ public: void SyncRequired() override { this->syncRequired = true; } public: RTC::SeqManager pictureIdManager; RTC::SeqManager tl0PictureIndexManager; bool syncRequired{ false }; }; public: class PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler { public: explicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor); ~PayloadDescriptorHandler() override = default; public: void Dump(int indentation = 0) const override { this->payloadDescriptor->Dump(indentation); } bool Process( Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override; void RtpPacketChanged(RTP::Packet* packet) override {}; std::unique_ptr GetEncoder() const override { return this->payloadDescriptor->GetEncoder(); } void Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override; void Restore(RTP::Packet* packet) override; uint8_t GetSpatialLayer() const override { return 0u; } uint8_t GetTemporalLayer() const override { return this->payloadDescriptor->hasTlIndex ? this->payloadDescriptor->tlIndex : 0u; } bool IsKeyFrame() const override { return this->payloadDescriptor->isKeyFrame; } private: std::unique_ptr payloadDescriptor; }; }; } // namespace Codecs } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/Codecs/VP9.hpp ================================================ #ifndef MS_RTC_RTP_CODECS_VP9_HPP #define MS_RTC_RTP_CODECS_VP9_HPP #include "common.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/SeqManager.hpp" /* https://tools.ietf.org/html/draft-ietf-payload-vp9-06 * VP9 Payload Descriptor Flexible mode (with the F bit below set to 1) ============================================= 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ |I|P|L|F|B|E|V|-| (REQUIRED) +-+-+-+-+-+-+-+-+ I: |M| PICTURE ID | (REQUIRED) +-+-+-+-+-+-+-+-+ M: | EXTENDED PID | (RECOMMENDED) +-+-+-+-+-+-+-+-+ L: | TID |U| SID |D| (CONDITIONALLY RECOMMENDED) +-+-+-+-+-+-+-+-+ -\ P,F: | P_DIFF |N| (CONDITIONALLY REQUIRED) - up to 3 times +-+-+-+-+-+-+-+-+ -/ V: | SS | | .. | +-+-+-+-+-+-+-+-+ Non-flexible mode (with the F bit below set to 0) ================================================= 0 1 2 3 4 5 6 7 +-+-+-+-+-+-+-+-+ |I|P|L|F|B|E|V|-| (REQUIRED) +-+-+-+-+-+-+-+-+ I: |M| PICTURE ID | (RECOMMENDED) +-+-+-+-+-+-+-+-+ M: | EXTENDED PID | (RECOMMENDED) +-+-+-+-+-+-+-+-+ L: | TID |U| SID |D| (CONDITIONALLY RECOMMENDED) +-+-+-+-+-+-+-+-+ | TL0PICIDX | (CONDITIONALLY REQUIRED) +-+-+-+-+-+-+-+-+ V: | SS | | .. | +-+-+-+-+-+-+-+-+ */ namespace RTC { namespace RTP { namespace Codecs { class VP9 { public: struct PayloadDescriptor : public Codecs::PayloadDescriptor { /* Pure virtual methods inherited from Codecs::PayloadDescriptor. */ ~PayloadDescriptor() override = default; void Dump(int indentation = 0) const override; // Header. uint8_t i : 1; // I: Picture ID (PID) present. uint8_t p : 1; // P: Inter-picture predicted layer frame. uint8_t l : 1; // L: Layer indices present. uint8_t f : 1; // F: Flexible mode. uint8_t b : 1; // B: Start of a layer frame. uint8_t e : 1; // E: End of a layer frame. uint8_t v : 1; // V: Scalability structure (SS) data present. // Extension fields. uint16_t pictureId{ 0 }; uint8_t slIndex{ 0 }; uint8_t tlIndex{ 0 }; uint8_t tl0PictureIndex; uint8_t switchingUpPoint : 1; uint8_t interLayerDependency : 1; // Parsed values. bool isKeyFrame{ false }; bool hasPictureId{ false }; bool hasOneBytePictureId{ false }; bool hasTwoBytesPictureId{ false }; bool hasSlIndex{ false }; bool hasTl0PictureIndex{ false }; bool hasTlIndex{ false }; }; public: static VP9::PayloadDescriptor* Parse(const uint8_t* data, size_t len); static void ProcessRtpPacket(RTP::Packet* packet); public: class EncodingContext : public Codecs::EncodingContext { public: explicit EncodingContext(Codecs::EncodingContext::Params& params) : Codecs::EncodingContext(params) { } ~EncodingContext() override = default; /* Pure virtual methods inherited from Codecs::EncodingContext. */ public: void SyncRequired() override { this->syncRequired = true; } public: RTC::SeqManager pictureIdManager; bool syncRequired{ false }; }; class PayloadDescriptorHandler : public Codecs::PayloadDescriptorHandler { public: explicit PayloadDescriptorHandler(PayloadDescriptor* payloadDescriptor); ~PayloadDescriptorHandler() override = default; public: void Dump(int indentation = 0) const override { this->payloadDescriptor->Dump(indentation); } bool Process( Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& marker) override; void RtpPacketChanged(RTP::Packet* packet) override {}; std::unique_ptr GetEncoder() const override { return nullptr; } void Encode(RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) override { } void Restore(RTP::Packet* packet) override { } uint8_t GetSpatialLayer() const override { return this->payloadDescriptor->hasSlIndex ? this->payloadDescriptor->slIndex : 0u; } uint8_t GetTemporalLayer() const override { return this->payloadDescriptor->hasTlIndex ? this->payloadDescriptor->tlIndex : 0u; } bool IsKeyFrame() const override { return this->payloadDescriptor->isKeyFrame; } private: std::unique_ptr payloadDescriptor; }; }; } // namespace Codecs } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/HeaderExtensionIds.hpp ================================================ #ifndef MS_RTC_RTP_HEADER_EXTENSION_IDS_HPP #define MS_RTC_RTP_HEADER_EXTENSION_IDS_HPP #include "common.hpp" namespace RTC { namespace RTP { // RTP header extension ids. Some of these are shared by all Producers using // the same Transport. Others are different for each Producer. struct HeaderExtensionIds { // 0 means no id. uint8_t mid{ 0u }; uint8_t rid{ 0u }; uint8_t rrid{ 0u }; uint8_t absSendTime{ 0u }; uint8_t transportWideCc01{ 0u }; uint8_t ssrcAudioLevel{ 0u }; uint8_t dependencyDescriptor{ 0u }; uint8_t videoOrientation{ 0u }; uint8_t timeOffset{ 0u }; uint8_t absCaptureTime{ 0u }; uint8_t playoutDelay{ 0u }; uint8_t mediasoupPacketId{ 0u }; }; } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/Packet.hpp ================================================ #ifndef MS_RTC_RTP_PACKET_HPP #define MS_RTC_RTP_PACKET_HPP #include "common.hpp" #include "Utils.hpp" #include "FBS/rtpPacket.h" #include "RTC/RTP/Codecs/DependencyDescriptor.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/HeaderExtensionIds.hpp" #include "RTC/RtpDictionaries.hpp" #include "RTC/Serializable.hpp" #ifdef MS_RTC_LOGGER_RTP #include "RTC/RtcLogger.hpp" #endif #include #include #include #include #include namespace RTC { namespace RTP { /** * RTP Packet. * * @see RFC 3550. */ class Packet : public Serializable, public Codecs::DependencyDescriptor::Listener { public: /** * RTP Fixed Header. * * @see RFC 3550. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |V=2|P|X| CC |M| PT | sequence number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | timestamp | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | synchronization source (SSRC) identifier | * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * | contributing source (CSRC) identifiers | * | .... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct FixedHeader { #if defined(MS_LITTLE_ENDIAN) uint8_t csrcCount : 4; uint8_t extension : 1; uint8_t padding : 1; uint8_t version : 2; uint8_t payloadType : 7; uint8_t marker : 1; #elif defined(MS_BIG_ENDIAN) uint8_t version : 2; uint8_t padding : 1; uint8_t extension : 1; uint8_t csrcCount : 4; uint8_t marker : 1; uint8_t payloadType : 7; #endif uint16_t sequenceNumber; uint32_t timestamp; uint32_t ssrc; }; #ifdef MS_TEST public: #else private: #endif /** * RTP Header Extension. * * @see RFC 3350. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | defined by profile | length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | header extension | * | .... | * * @remarks * - This struct is guaranteed to be aligned to 2 bytes. */ struct HeaderExtension { /** * Defined by profile. */ uint16_t id; /** * Number of 32-bit words in the extension, excluding the id & length * four-octet. */ uint16_t len; uint8_t value[]; }; public: /** * One-Byte and Two-Bytes Extension types. * * @see RFC 8285. */ enum class ExtensionsType : uint8_t { /** * Auto means that One-Byte or Two-Bytes is choosen based on given * Extensions. */ Auto = 0, OneByte = 1, TwoBytes = 2 }; #ifdef MS_TEST public: #else private: #endif /** * @remarks * - This struct is guaranteed to be aligned to 1 byte. */ struct OneByteExtension { #if defined(MS_LITTLE_ENDIAN) uint8_t len : 4; uint8_t id : 4; #elif defined(MS_BIG_ENDIAN) uint8_t id : 4; uint8_t len : 4; #endif uint8_t value[]; }; #ifdef MS_TEST public: #else private: #endif /** * @remarks * - This struct is guaranteed to be aligned to 1 byte. */ struct TwoBytesExtension { uint8_t id; uint8_t len; uint8_t value[]; }; public: /** * Struct for setting and replacing Extensions. * * @remarks * - This struct is NOT guaranteed to be aligned to any fixed number of * bytes because it contains a pointer, which is 4 or 8 bytes depending * on the architecture. Anyway we never cast any buffer to this struct. */ struct Extension { Extension(RTC::RtpHeaderExtensionUri::Type type, uint8_t id, uint8_t len, uint8_t* value) : type(type), id(id), len(len), value(value) {}; RTC::RtpHeaderExtensionUri::Type type; uint8_t id; uint8_t len; uint8_t* value; }; public: /** * Minimum size (in bytes) of the RTP Fixed Header (without CSRC fields). */ static const size_t FixedHeaderMinLength{ 12 }; /** * Whether given buffer could be a valid RTP Packet. * * @remarks * - Before calling this static method, the caller should verify whether * the given buffer is a RTCP packet. */ static bool IsRtp(const uint8_t* buffer, size_t bufferLength); /** * Parse a RTP Packet. * * @remarks * - `packetLength` must be the exact length of the Packet. * - `bufferLength` must be >= `packetLength`. * * @throw MediaSoupTypeError - If `bufferLength` is lower than * `packetLength`. */ static Packet* Parse(const uint8_t* buffer, size_t packetLength, size_t bufferLength); /** * Parse a RTP Packet. * * @remarks * - `bufferLength` must be the exact length of the Packet. */ static Packet* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a RTP Packet. */ static Packet* Factory(uint8_t* buffer, size_t bufferLength); /** * Generate value for "urn:mediasoup:params:rtp-hdrext:packet-id" * mediasoup custom Extension. */ static uint32_t GetNextMediasoupPacketId(); private: static thread_local uint32_t nextMediasoupPacketId; private: /** * Constructor is private because we only want to create Packet instances * via Parse() and Factory(). */ Packet(uint8_t* buffer, size_t bufferLength); public: ~Packet() override; void Dump(int indentation = 0) const final; Packet* Clone(uint8_t* buffer, size_t bufferLength) const final; flatbuffers::Offset FillBuffer(flatbuffers::FlatBufferBuilder& builder) const; uint8_t GetVersion() const { return GetFixedHeaderPointer()->version; } uint8_t GetPayloadType() const { return GetFixedHeaderPointer()->payloadType; } void SetPayloadType(uint8_t payloadType); bool HasMarker() const { return GetFixedHeaderPointer()->marker; } void SetMarker(bool marker); uint16_t GetSequenceNumber() const { return ntohs(GetFixedHeaderPointer()->sequenceNumber); } void SetSequenceNumber(uint16_t seq); uint32_t GetTimestamp() const { return ntohl(GetFixedHeaderPointer()->timestamp); } void SetTimestamp(uint32_t timestamp); uint32_t GetSsrc() const { return ntohl(GetFixedHeaderPointer()->ssrc); } void SetSsrc(uint32_t ssrc); bool HasCsrcs() const { return (GetFixedHeaderPointer()->csrcCount != 0); } bool HasHeaderExtension() const { return (GetFixedHeaderPointer()->extension); } /** * Get the Extension Header id or 0 if there isn't. * * @remarks * - This method doesn't validate whether there is indeed space for the * announced Header Extension. * - This method is guaranteed to return valid value once @ref Validate() * was succesfully called. */ uint16_t GetHeaderExtensionId() const { if (!HasHeaderExtension()) { return 0; } return ntohs(GetHeaderExtensionPointer()->id); } /** * Pointer to the Header Extension value or `nullptr` if there is no * Header Extension or its has no value. * * @remarks * - This method doesn't validate whether there is indeed space for the * announced Header Extension. * - This method is guaranteed to return valid value once @ref Validate() * was succesfully called. */ uint8_t* GetHeaderExtensionValue() const { auto headerExtensionValueLength = GetHeaderExtensionValueLength(); if (headerExtensionValueLength == 0) { return nullptr; } return GetHeaderExtensionPointer()->value; } /** * Length of the Header Extension value (excluding the id & length * four-octet). */ size_t GetHeaderExtensionValueLength() const { if (!HasHeaderExtension()) { return 0; } return static_cast(ntohs(GetHeaderExtensionPointer()->len) * 4); } /** * Remove the Header Extension. */ void RemoveHeaderExtension(); /** * Whether the Packet has One-Byte or Two-Bytes Extensions. * * @see RFC 8285. */ bool HasExtensions() const { return HasOneByteExtensions() || HasTwoBytesExtensions(); } /** * Whether the Packet has One-Byte Extensions. * * @see RFC 8285. */ bool HasOneByteExtensions() const { return GetHeaderExtensionId() == 0xBEDE; } /** * Whether the Packet has Two-Bytes Extensions. * * @see RFC 8285. */ bool HasTwoBytesExtensions() const { return (GetHeaderExtensionId() & 0b1111111111110000) == 0b0001000000000000; } /** * Whether the One-Byte or Two-Bytes Extension with given `id` exists in * the Packet. * * @see RFC 8285. * * @remarks * - If the length of the Extension value is 0 this method returns `true`. */ bool HasExtension(uint8_t id) const { if (id == 0) { return false; } else if (HasOneByteExtensions()) { if (id > 14) { return false; } // `-1` because we have 14 elements total 0..13 and `id` is in the // range 1..14. const auto offset = this->oneByteExtensions[id - 1]; return offset != -1; } else if (HasTwoBytesExtensions()) { return this->twoBytesExtensions.find(id) != this->twoBytesExtensions.end(); } else { return false; } } /** * Get a pointer to the value of the the One-Byte or Two-Bytes Extension * with given `id` and set its value length into given `len`. * * @see RFC 8285. */ uint8_t* GetExtensionValue(uint8_t id, uint8_t& len) const { len = 0; if (id == 0) { len = 0; return nullptr; } else if (HasOneByteExtensions()) { if (id > 14) { len = 0; return nullptr; } // `-1` because we have 14 elements total 0..13 and `id` is in the // range 1..14. const auto offset = this->oneByteExtensions[id - 1]; if (offset == -1) { len = 0; return nullptr; } auto* extension = reinterpret_cast(GetHeaderExtensionValue() + offset); // In One-Byte Extensions value length 0 means 1. len = extension->len + 1; return extension->value; } else if (HasTwoBytesExtensions()) { const auto it = this->twoBytesExtensions.find(id); if (it == this->twoBytesExtensions.end()) { len = 0; return nullptr; } const auto offset = it->second; auto* extension = reinterpret_cast(GetHeaderExtensionValue() + offset); len = extension->len; return extension->value; } else { len = 0; return nullptr; } } /** * Add or replace Extensions. * * @see RFC 8285. * * @throw MediaSoupTypeError - If there is no space available for given * Extensions or if given Extensions are invalid/wrong. */ void SetExtensions(ExtensionsType type, const std::vector& extensions); /** * Assign Extension ids. * * @see RFC 8285. */ void AssignExtensionIds(RTP::HeaderExtensionIds& headerExtensionIds); bool ReadMid(std::string& mid) const; bool UpdateMid(const std::string& mid); bool ReadRid(std::string& rid) const; /** * @remarks * - `absSendTime` is set with the raw 3 bytes unsigned integer stored * in the Extension value. */ bool ReadAbsSendTime(uint32_t& absSendtime) const; /** * @remarks * - Contrary to `ReadAbsSendTime()` method, given `ms` is internally * converted to ABS Send Time. */ bool UpdateAbsSendTime(uint64_t ms) const; bool ReadTransportWideCc01(uint16_t& wideSeqNumber) const; bool UpdateTransportWideCc01(uint16_t wideSeqNumber) const; bool ReadSsrcAudioLevel(uint8_t& volume, bool& voice) const; bool ReadDependencyDescriptor( std::unique_ptr& dependencyDescriptor, std::unique_ptr& templateDependencyStructure) const; bool UpdateDependencyDescriptor(const uint8_t* data, size_t len); bool ReadVideoOrientation(bool& camera, bool& flip, uint16_t& rotation) const; bool ReadAbsCaptureTime(uint64_t& absCaptureTimestamp, int64_t& estimatedCaptureClockOffset) const; bool ReadPlayoutDelay(uint16_t& minDelay, uint16_t& maxDelay) const; bool ReadMediasoupPacketId(uint32_t& mediasoupPacketId) const; /** * Whether this Packet has payload. * * @remarks * - This method doesn't validate whether the padding length announced in * the last byte of the Packet is valid. * - This method is guaranteed to return valid value once @ref Validate() * was succesfully called. */ bool HasPayload() const { return GetPayloadLength() > 0; } /** * Pointer to the beginning of the payload (if any). Payload length is * set into given `length`. * * @remarks * - This method doesn't validate whether the padding length announced in * the last byte of the Packet is valid. * - This method is guaranteed to return valid value once @ref Validate() * was succesfully called. * - This method doens't take into account padding, so in a padding-only * Packet this method returns `nullptr` with `len` 0. */ uint8_t* GetPayload(size_t& len) const { auto* payloadPointer = GetPayloadPointer(); const size_t availablePayloadAndPaddingLength = GetLength() - (payloadPointer - GetBuffer()); const auto paddingLength = GetPaddingLength(); // If there is announced padding, compute effective payload length // without padding. if (availablePayloadAndPaddingLength > paddingLength) { len = availablePayloadAndPaddingLength - paddingLength; return payloadPointer; } // If there are more announced padding bytes than the available length // for payload and padding, assume payload length 0. else { len = 0; return nullptr; } } /** * Pointer to the beginning of the payload (if any). * * @remarks * - This method doesn't validate whether the padding length announced in * the last byte of the Packet is valid. * - This method is guaranteed to return valid value once @ref Validate() * was succesfully called. * - This method doens't take into account padding, so in a padding-only * Packet this method returns `nullptr`. */ uint8_t* GetPayload() const { thread_local size_t len; return GetPayload(len); } /** * Length of the payload excluding padding bytes. * * @remarks * - This method doesn't validate whether the padding length announced in * the last byte of the Packet is valid. * - This method is guaranteed to return valid value once @ref Validate() * was succesfully called. */ size_t GetPayloadLength() const { const size_t availablePayloadAndPaddingLength = GetLength() - (GetPayloadPointer() - GetBuffer()); const auto paddingLength = GetPaddingLength(); // If there is announced padding, compute effective payload length // without padding. if (availablePayloadAndPaddingLength >= paddingLength) { return availablePayloadAndPaddingLength - paddingLength; } // If there are more announced padding bytes than the available length // for payload and padding, return 0. else { return 0; } } /** * Set the payload. It copies the given `payload` into the Packet. * * @remarks * - This method removes existing padding (if any). * * @throw MediaSoupTypeError - If given `payloadLength` is higher than * available length for the payload of if `nullptr` is given as * `payload` with `payloadLength` higher than 0. */ void SetPayload(const uint8_t* payload, size_t payloadLength); /** * Set the payload length. * * @remarks * - This method removes existing padding (if any). * * @throw MediaSoupTypeError - If given `payloadLength` is higher than * available length for the payload. */ void SetPayloadLength(size_t payloadLength); /** * Remove the payload. * * @remarks * - This method removes existing padding (if any). */ void RemovePayload() { SetPayload(nullptr, 0); } /** * Shift or unshift the content of the payload `delta` bytes after * `payloadOffset`. * * @remarks * - This method removes existing padding (if any). * * @throw MediaSoupTypeError - If wrong values are given. */ void ShiftPayload(size_t payloadOffset, int32_t delta); /** * Whether this Packet has padding. */ bool HasPadding() const { return (GetFixedHeaderPointer()->padding); } /** * Length of the padding. * * @remarks * - This method doesn't validate whether the padding length announced in * the last byte of the Packet is valid. * - This method is guaranteed to return valid value once @ref Validate() * was succesfully called. */ uint8_t GetPaddingLength() const { if (!HasPadding()) { return 0; } return Utils::Byte::Get1Byte(GetBuffer(), GetLength() - 1); } /** * Set padding length. * * @remarks * - This method removes existing padding (if any). * * @throw MediaSoupTypeError - If given `paddingLength` is higher than * available length for the padding. */ void SetPaddingLength(uint8_t paddingLength); /** * Whether Packet length is padded to 4 bytes. */ bool IsPaddedTo4Bytes() const { return Utils::Byte::IsPaddedTo4Bytes(GetLength()); } /** * Pad Packet length to 4 bytes by modifying padding bytes. * * @remarks * - This method removes existing padding and adds up to 3 bytes of * padding bytes if needed. * * @throw MediaSoupTypeError - If needed padding length is higher than * available length for the padding. */ void PadTo4Bytes(); /** * Encode the Packet into a RTX Packet. * * @remarks * - This method removes existing padding (if any). * * @throw MediaSoupTypeError - If there is no space for the new computed * payload. */ void RtxEncode(uint8_t payloadType, uint32_t ssrc, uint16_t seq); /** * Decode the RTX Packet into a regular RTP Packet. * * @return `true` if RTX decoding iwas done. * * @remarks * - This method removes existing padding (if any). */ bool RtxDecode(uint8_t payloadType, uint32_t ssrc); /** * Set payload descritor handler. */ void SetPayloadDescriptorHandler(Codecs::PayloadDescriptorHandler* payloadDescriptorHandler); /** * Process the payload. */ bool ProcessPayload(Codecs::EncodingContext* context, bool& marker); /** * Get the payload encoder. */ std::unique_ptr GetPayloadEncoder() const; /** * Encode the payload. */ void EncodePayload(Codecs::PayloadDescriptor::Encoder* encoder); /** * Restore the payload. */ void RestorePayload(); /** * Whether the payload contains a video keyframe. */ bool IsKeyFrame() const { if (!this->payloadDescriptorHandler) { return false; } return this->payloadDescriptorHandler->IsKeyFrame(); } /** * Spatial layer of the video codec. */ uint8_t GetSpatialLayer() const { if (!this->payloadDescriptorHandler) { return 0u; } return this->payloadDescriptorHandler->GetSpatialLayer(); } /** * Temporal layer of the video codec. */ uint8_t GetTemporalLayer() const { if (!this->payloadDescriptorHandler) { return 0u; } return this->payloadDescriptorHandler->GetTemporalLayer(); } private: /** * @remarks * - Returns FixedHeader* instead of const FixedHeader* since we may want * to modify its fields. */ FixedHeader* GetFixedHeaderPointer() const { return reinterpret_cast(const_cast(GetBuffer())); } /** * Pointer to the location where the CSRC list is supposed to begin. */ uint8_t* GetCsrcsPointer() const { return const_cast(GetBuffer()) + Packet::FixedHeaderMinLength; } /** * Number of entries in the CSRC list. * * @remarks * - This method doesn't validate whether there is indeed space for the * announced CSRC list. * - This method is guaranteed to return valid value once @ref Validate() * was succesfully called. */ size_t GetCsrcCount() const { return GetFixedHeaderPointer()->csrcCount * sizeof(GetFixedHeaderPointer()->ssrc); } /** * Pointer to the location where Extension Header is supposed to begin. */ HeaderExtension* GetHeaderExtensionPointer() const { return reinterpret_cast(GetCsrcsPointer() + GetCsrcCount()); } /** * Length of the Header Extension including the id & length four-octet. * * @remarks * - This method doesn't validate whether there is indeed space for the * announced Header Extension. * - This method is guaranteed to return valid value once @ref Validate() * was succesfully called. */ size_t GetHeaderExtensionLength() const { if (!HasHeaderExtension()) { return 0; } return 4 + static_cast(ntohs(GetHeaderExtensionPointer()->len) * 4); } /** * Pointer to the location where the payload is supposed to begin. */ uint8_t* GetPayloadPointer() const { return reinterpret_cast(GetHeaderExtensionPointer()) + GetHeaderExtensionLength(); } /** * Validates whether the Packet is valid. It also stores internal * containers holding Extensions if `storeExtensions` is `true`. */ #ifdef MS_TEST public: #endif bool Validate(bool storeExtensions); #ifdef MS_TEST private: #endif /** * Parses Extensions. Returns `true` if they are valid. It also stores * internal containers holding Extensions if `storeExtensions` is `true`. * * @see RFC 8285. */ bool ParseExtensions(bool storeExtensions); /** * Set the value length of the Extension with given `id`. * * @remarks * - The caller is responsible of not setting a length higher than the * available one (taking into account existing padding bytes). * - If the Extension with `id` doesn't exist this methods terminates * the process. */ void SetExtensionLength(uint8_t id, uint8_t len); /* Pure virtual methods inherited from Codecs::DependencyDescriptor::Listener. */ public: void OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) override; #ifdef MS_RTC_LOGGER_RTP public: RtcLogger::RtpPacket logger; #endif private: // Array of One Byte Extensions. Index is the id - 1 of the Extension, // each entry is the offset (in bytes) from the beginning of the Header // Extension value to the beginning of the Extension. std::array oneByteExtensions{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; // Ordered map of Two Bytes Extensions. Key is the id 1 of the Extension, // each entry is the offset (in bytes) from the beginning of the Header // Extension value to the beginning of the Extension. std::map twoBytesExtensions; // Extension ids. RTP::HeaderExtensionIds headerExtensionIds{}; // Codec related. std::shared_ptr payloadDescriptorHandler; }; } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/ProbationGenerator.hpp ================================================ #ifndef MS_RTC_RTP_PROBATION_GENERATOR_HPP #define MS_RTC_RTP_PROBATION_GENERATOR_HPP #include "common.hpp" #include "RTC/RTP/Packet.hpp" namespace RTC { namespace RTP { class ProbationGenerator { public: /** * Maximum length of a probation RTP Packet. */ static const size_t ProbationPacketMaxLength{ 1400 }; /** * SSRC of the probation RTP stream. */ static const uint32_t Ssrc{ 1234 }; /** * Codec payload type of the probation RTP stream. */ static const uint8_t PayloadType{ 127u }; public: explicit ProbationGenerator(); ~ProbationGenerator(); public: RTP::Packet* GetNextPacket(size_t len); size_t GetProbationPacketMinLength() const { return this->probationPacketMinLength; } private: // Allocated by this. std::unique_ptr probationPacket; // Others. // The length of the probation RTP Packet without payload or padding. size_t probationPacketMinLength{ 0 }; }; } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/RetransmissionBuffer.hpp ================================================ #ifndef MS_RTC_RTP_RETRANSMISSION_BUFFER_HPP #define MS_RTC_RTP_RETRANSMISSION_BUFFER_HPP #include "common.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/SharedPacket.hpp" #include namespace RTC { namespace RTP { // Special container that stores `Item`* elements addressable by their `uint16_t` // sequence number, while only taking as little memory as necessary to store // the range covering a maximum of `MaxRetransmissionDelayForVideoMs` or // `MaxRetransmissionDelayForAudioMs` ms. class RetransmissionBuffer { public: struct Item { void Reset(); // Original packet. RTP::SharedPacket sharedPacket{ nullptr }; // Payload descriptor encoder. std::unique_ptr encoder{ nullptr }; // Correct SSRC since original packet may not have the same. uint32_t ssrc{ 0u }; // Correct sequence number since original packet may not have the same. uint16_t sequenceNumber{ 0u }; // Correct timestamp since original packet may not have the same. uint32_t timestamp{ 0u }; // Correct marker bit since original packet may not have the same. bool marker{ false }; // Last time this packet was resent. uint64_t resentAtMs{ 0u }; // Number of times this packet was resent. uint8_t sentTimes{ 0u }; }; private: static Item* FillItem(Item* item, RTP::Packet* packet, const RTP::SharedPacket& sharedPacket); public: RetransmissionBuffer(uint16_t maxItems, uint32_t maxRetransmissionDelayMs, uint32_t clockRate); ~RetransmissionBuffer(); void Dump(int indentation = 0) const; Item* Get(uint16_t seq) const; bool Insert(RTP::Packet* packet, const RTP::SharedPacket& sharedPacket); void Clear(); private: Item* GetOldest() const; Item* GetNewest() const; void RemoveOldest(); void RemoveOldest(uint16_t numItems); bool ClearTooOldByTimestamp(uint32_t newestTimestamp); bool IsTooOldTimestamp(uint32_t timestamp, uint32_t newestTimestamp) const; protected: // Make buffer protected for testing purposes. std::deque buffer; private: // Given as argument. uint16_t maxItems; uint32_t maxRetransmissionDelayMs; uint32_t clockRate; }; } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/RtpStream.hpp ================================================ #ifndef MS_RTC_RTP_RTP_STREAM_HPP #define MS_RTC_RTP_RTP_STREAM_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "FBS/rtpStream.h" #include "RTC/RTCP/FeedbackPsFir.hpp" #include "RTC/RTCP/FeedbackPsPli.hpp" #include "RTC/RTCP/FeedbackRtpNack.hpp" #include "RTC/RTCP/Packet.hpp" #include "RTC/RTCP/ReceiverReport.hpp" #include "RTC/RTCP/Sdes.hpp" #include "RTC/RTCP/SenderReport.hpp" #include "RTC/RTCP/XrDelaySinceLastRr.hpp" #include "RTC/RTCP/XrReceiverReferenceTime.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtxStream.hpp" #include "RTC/RtpDictionaries.hpp" #include #include namespace RTC { namespace RTP { class RtpStream { protected: class Listener { public: virtual ~Listener() = default; public: virtual void OnRtpStreamScore( RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) = 0; }; public: struct Params { flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; size_t encodingIdx{ 0u }; uint32_t ssrc{ 0u }; uint8_t payloadType{ 0u }; RTC::RtpCodecMimeType mimeType; uint32_t clockRate{ 0u }; std::string rid; std::string cname; uint32_t rtxSsrc{ 0u }; uint8_t rtxPayloadType{ 0u }; bool useNack{ false }; bool usePli{ false }; bool useFir{ false }; bool useInBandFec{ false }; bool useDtx{ false }; uint8_t spatialLayers{ 1u }; uint8_t temporalLayers{ 1u }; }; public: RtpStream( RTP::RtpStream::Listener* listener, SharedInterface* shared, RTP::RtpStream::Params& params, uint8_t initialScore); virtual ~RtpStream(); flatbuffers::Offset FillBuffer(flatbuffers::FlatBufferBuilder& builder) const; virtual flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder); uint32_t GetEncodingIdx() const { return this->params.encodingIdx; } uint32_t GetSsrc() const { return this->params.ssrc; } uint8_t GetPayloadType() const { return this->params.payloadType; } const RTC::RtpCodecMimeType& GetMimeType() const { return this->params.mimeType; } uint32_t GetClockRate() const { return this->params.clockRate; } const std::string& GetRid() const { return this->params.rid; } const std::string& GetCname() const { return this->params.cname; } bool HasRtx() const { return this->rtxStream != nullptr; } virtual void SetRtx(uint8_t payloadType, uint32_t ssrc); uint32_t GetRtxSsrc() const { return this->params.rtxSsrc; } uint8_t GetRtxPayloadType() const { return this->params.rtxPayloadType; } uint8_t GetSpatialLayers() const { return this->params.spatialLayers; } bool HasDtx() const { return this->params.useDtx; } uint8_t GetTemporalLayers() const { return this->params.temporalLayers; } virtual bool ReceiveStreamPacket(const RTP::Packet* packet); virtual void Pause() = 0; virtual void Resume() = 0; virtual uint32_t GetBitrate(uint64_t nowMs) = 0; virtual uint32_t GetBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) = 0; virtual uint32_t GetSpatialLayerBitrate(uint64_t nowMs, uint8_t spatialLayer) = 0; virtual uint32_t GetLayerBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) = 0; void ResetScore(uint8_t score, bool notify); uint8_t GetFractionLost() const { return this->fractionLost; } float GetLossPercentage() const { return static_cast(this->fractionLost) * 100 / 256; } float GetRtt() const { return this->rtt; } uint64_t GetMaxPacketMs() const { return this->maxPacketMs; } uint32_t GetMaxPacketTs() const { return this->maxPacketTs; } uint64_t GetSenderReportNtpMs() const { return this->lastSenderReportNtpMs; } uint32_t GetSenderReportTs() const { return this->lastSenderReportTs; } uint8_t GetScore() const { return this->score; } uint64_t GetActiveMs() const { return this->shared->GetTimeMs() - this->activeSinceMs; } protected: bool UpdateSeq(const RTP::Packet* packet); void UpdateScore(uint8_t score); void PacketRetransmitted(const RTP::Packet* packet); void PacketRepaired(const RTP::Packet* packet); uint32_t GetExpectedPackets() const { return (this->cycles + this->maxSeq) - this->baseSeq + 1; } private: void InitSeq(uint16_t seq); /* Pure virtual method that must be implemented by the subclass. */ protected: virtual void UserOnSequenceNumberReset() = 0; protected: // Given as argument. RTP::RtpStream::Listener* listener{ nullptr }; SharedInterface* shared{ nullptr }; Params params; // Others. // https://tools.ietf.org/html/rfc3550#appendix-A.1 stuff. // Highest seq. number seen. uint16_t maxSeq{ 0u }; // Shifted count of seq. number cycles. uint32_t cycles{ 0u }; // Base seq number. uint32_t baseSeq{ 0u }; // Last 'bad' seq number + 1. uint32_t badSeq{ 0u }; // Highest timestamp seen. uint32_t maxPacketTs{ 0u }; // When the packet with highest timestammp was seen. uint64_t maxPacketMs{ 0u }; int32_t packetsLost{ 0 }; uint8_t fractionLost{ 0u }; // Jitter in RTP timestamp units. As per spec it's kept as floating value // although it's exposed as integer in the stats. float jitter{ 0 }; size_t packetsDiscarded{ 0u }; size_t packetsRetransmitted{ 0u }; size_t packetsRepaired{ 0u }; size_t nackCount{ 0u }; size_t nackPacketCount{ 0u }; size_t pliCount{ 0u }; size_t firCount{ 0u }; // Packets repaired at last interval for score calculation. size_t repairedPriorScore{ 0u }; // Packets retransmitted at last interval for score calculation. size_t retransmittedPriorScore{ 0u }; // NTP timestamp in last Sender Report (in ms). uint64_t lastSenderReportNtpMs{ 0u }; // RTP timestamp in last Sender Report. uint32_t lastSenderReportTs{ 0u }; float rtt{ 0.0f }; // Instance of RtxStream. RTP::RtxStream* rtxStream{ nullptr }; private: // Score related. uint8_t score{ 0u }; std::vector scores; // Whether at least a RTP packet has been received. bool started{ false }; // Last time since the stream is active. uint64_t activeSinceMs{ 0u }; }; } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/RtpStreamRecv.hpp ================================================ #ifndef MS_RTC_RTP_RTP_STREAM_RECV_HPP #define MS_RTC_RTP_RTP_STREAM_RECV_HPP #include "RTC/NackGenerator.hpp" #include "RTC/RTCP/XrDelaySinceLastRr.hpp" #include "RTC/RTP/RtpStream.hpp" #include "RTC/RateCalculator.hpp" #include "handles/TimerHandleInterface.hpp" #include namespace RTC { namespace RTP { class RtpStreamRecv : public RTP::RtpStream, public RTC::NackGenerator::Listener, public TimerHandleInterface::Listener { public: class Listener : public RTP::RtpStream::Listener { public: virtual void OnRtpStreamSendRtcpPacket( RTP::RtpStreamRecv* rtpStream, RTC::RTCP::Packet* packet) = 0; virtual void OnRtpStreamNeedWorstRemoteFractionLost( RTP::RtpStreamRecv* rtpStream, uint8_t& worstRemoteFractionLost) = 0; }; public: class TransmissionCounter { public: TransmissionCounter( SharedInterface* shared, uint8_t spatialLayers, uint8_t temporalLayers, size_t windowSize); void Update(const RTP::Packet* packet); uint32_t GetBitrate(uint64_t nowMs); uint32_t GetBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer); uint32_t GetSpatialLayerBitrate(uint64_t nowMs, uint8_t spatialLayer); uint32_t GetLayerBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer); size_t GetPacketCount() const; size_t GetBytes() const; private: std::vector> spatialLayerCounters; }; public: RtpStreamRecv( RTP::RtpStreamRecv::Listener* listener, SharedInterface* shared, RTP::RtpStream::Params& params, unsigned int sendNackDelayMs, bool useRtpInactivityCheck); ~RtpStreamRecv() override; flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder) override; bool ReceivePacket(RTP::Packet* packet); bool ReceiveRtxPacket(RTP::Packet* packet); RTC::RTCP::ReceiverReport* GetRtcpReceiverReport(); RTC::RTCP::ReceiverReport* GetRtxRtcpReceiverReport(); void ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report); void ReceiveRtxRtcpSenderReport(RTC::RTCP::SenderReport* report); void ReceiveRtcpXrDelaySinceLastRr(RTC::RTCP::DelaySinceLastRr::SsrcInfo* ssrcInfo); void RequestKeyFrame(); void Pause() override; void Resume() override; uint32_t GetBitrate(uint64_t nowMs) override { return this->transmissionCounter.GetBitrate(nowMs); } uint32_t GetBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) override { return this->transmissionCounter.GetBitrate(nowMs, spatialLayer, temporalLayer); } uint32_t GetSpatialLayerBitrate(uint64_t nowMs, uint8_t spatialLayer) override { return this->transmissionCounter.GetSpatialLayerBitrate(nowMs, spatialLayer); } uint32_t GetLayerBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) override { return this->transmissionCounter.GetLayerBitrate(nowMs, spatialLayer, temporalLayer); } bool HasRtpInactivityCheckEnabled() const { return this->useRtpInactivityCheck; } private: void CalculateJitter(uint32_t rtpTimestamp); void UpdateScore(); /* Pure virtual methods inherited from RTP::RtpStream. */ public: void UserOnSequenceNumberReset() override; /* Pure virtual methods inherited from TimerHandleInterface. */ protected: void OnTimer(TimerHandleInterface* timer) override; /* Pure virtual methods inherited from RTC::NackGenerator. */ protected: void OnNackGeneratorNackRequired(const std::vector& seqNumbers) override; void OnNackGeneratorKeyFrameRequired() override; private: // Passed by argument. unsigned int sendNackDelayMs{ 0u }; bool useRtpInactivityCheck{ false }; // Others. // Packets expected at last interval. uint32_t expectedPrior{ 0u }; // Packets expected at last interval for score calculation. uint32_t expectedPriorScore{ 0u }; // Packets received at last interval. uint32_t receivedPrior{ 0u }; // Packets received at last interval for score calculation. uint32_t receivedPriorScore{ 0u }; // The middle 32 bits out of 64 in the NTP timestamp received in the most // recent sender report. uint32_t lastSrTimestamp{ 0u }; // Wallclock time representing the most recent sender report arrival. uint64_t lastSrReceived{ 0u }; // Relative transit time for prev packet. int32_t transit{ 0u }; uint8_t firSeqNumber{ 0u }; int32_t reportedPacketsLost{ 0 }; std::unique_ptr nackGenerator; TimerHandleInterface* inactivityCheckPeriodicTimer{ nullptr }; bool inactive{ false }; // Valid media + valid RTX. TransmissionCounter transmissionCounter; // Just valid media. RTC::RtpDataCounter mediaTransmissionCounter; // Template dependency structure for Dependency Descriptor. std::unique_ptr templateDependencyStructure; }; } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/RtpStreamSend.hpp ================================================ #ifndef MS_RTC_RTP_RTP_STREAM_SEND_HPP #define MS_RTC_RTP_RTP_STREAM_SEND_HPP #include "RTC/RTP/RetransmissionBuffer.hpp" #include "RTC/RTP/RtpStream.hpp" #include "RTC/RTP/SharedPacket.hpp" #include "RTC/RateCalculator.hpp" namespace RTC { namespace RTP { class RtpStreamSend : public RTP::RtpStream { public: // Maximum retransmission buffer size for video (ms). static const uint32_t MaxRetransmissionDelayForVideoMs; // Maximum retransmission buffer size for audio (ms). static const uint32_t MaxRetransmissionDelayForAudioMs; public: enum class ReceivePacketResult : uint8_t { DISCARDED, ACCEPTED_AND_NOT_STORED, ACCEPTED_AND_STORED }; public: class Listener : public RTP::RtpStream::Listener { public: virtual void OnRtpStreamRetransmitRtpPacket( RTP::RtpStreamSend* rtpStream, RTP::Packet* packet) = 0; }; public: RtpStreamSend( RTP::RtpStreamSend::Listener* listener, SharedInterface* shared, RTP::RtpStream::Params& params, std::string& mid); ~RtpStreamSend() override; flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder) override; void SetRtx(uint8_t payloadType, uint32_t ssrc) override; ReceivePacketResult ReceivePacket(RTP::Packet* packet, const RTP::SharedPacket& sharedPacket); void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket); void ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType); void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report); void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report); RTC::RTCP::SenderReport* GetRtcpSenderReport(uint64_t nowMs); RTC::RTCP::DelaySinceLastRr::SsrcInfo* GetRtcpXrDelaySinceLastRrSsrcInfo(uint64_t nowMs); RTC::RTCP::SdesChunk* GetRtcpSdesChunk(); void Pause() override; void Resume() override; uint32_t GetBitrate(uint64_t nowMs) override { return this->transmissionCounter.GetBitrate(nowMs); } uint32_t GetBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) override; uint32_t GetSpatialLayerBitrate(uint64_t nowMs, uint8_t spatialLayer) override; uint32_t GetLayerBitrate(uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) override; private: void FillRetransmissionContainer(uint16_t seq, uint16_t bitmask); void UpdateScore(RTC::RTCP::ReceiverReport* report); /* Pure virtual methods inherited from RTP::RtpStream. */ public: void UserOnSequenceNumberReset() override; private: // Packets lost at last interval for score calculation. int32_t lostPriorScore{ 0 }; // Packets sent at last interval for score calculation. uint32_t sentPriorScore{ 0u }; std::string mid; uint16_t rtxSeq{ 0u }; RTC::RtpDataCounter transmissionCounter; RTP::RetransmissionBuffer* retransmissionBuffer{ nullptr }; // The middle 32 bits out of 64 in the NTP timestamp received in the most // recent receiver reference timestamp. uint32_t lastRrTimestamp{ 0u }; // Wallclock time representing the most recent receiver reference timestamp // arrival. uint64_t lastRrReceivedMs{ 0u }; }; } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/RtxStream.hpp ================================================ #ifndef MS_RTC_RTP_RTX_STREAM_HPP #define MS_RTC_RTP_RTX_STREAM_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "FBS/rtxStream.h" #include "RTC/RTCP/ReceiverReport.hpp" #include "RTC/RTCP/SenderReport.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RtpDictionaries.hpp" #include namespace RTC { namespace RTP { class RtxStream { public: struct Params { flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; uint32_t ssrc{ 0 }; uint8_t payloadType{ 0 }; RTC::RtpCodecMimeType mimeType; uint32_t clockRate{ 0 }; std::string rrid; std::string cname; }; public: explicit RtxStream(SharedInterface* shared, RTP::RtxStream::Params& params); virtual ~RtxStream(); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; uint32_t GetSsrc() const { return this->params.ssrc; } uint8_t GetPayloadType() const { return this->params.payloadType; } const RTC::RtpCodecMimeType& GetMimeType() const { return this->params.mimeType; } uint32_t GetClockRate() const { return this->params.clockRate; } const std::string& GetRrid() const { return this->params.rrid; } const std::string& GetCname() const { return this->params.cname; } uint8_t GetFractionLost() const { return this->fractionLost; } float GetLossPercentage() const { return static_cast(this->fractionLost) * 100 / 256; } size_t GetPacketsDiscarded() const { return this->packetsDiscarded; } bool ReceivePacket(const RTP::Packet* packet); RTC::RTCP::ReceiverReport* GetRtcpReceiverReport(); void ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report); protected: bool UpdateSeq(const RTP::Packet* packet); uint32_t GetExpectedPackets() const { return (this->cycles + this->maxSeq) - this->baseSeq + 1; } private: void InitSeq(uint16_t seq); protected: // Given as argument. SharedInterface* shared{ nullptr }; Params params; // Others. // https://tools.ietf.org/html/rfc3550#appendix-A.1 stuff. uint16_t maxSeq{ 0u }; // Highest seq. number seen. uint32_t cycles{ 0u }; // Shifted count of seq. number cycles. uint32_t baseSeq{ 0u }; // Base seq number. uint32_t badSeq{ 0u }; // Last 'bad' seq number + 1. uint32_t maxPacketTs{ 0u }; // Highest timestamp seen. uint64_t maxPacketMs{ 0u }; // When the packet with highest timestammp was seen. int32_t packetsLost{ 0 }; uint8_t fractionLost{ 0u }; size_t packetsDiscarded{ 0u }; size_t packetsCount{ 0u }; private: // Whether at least a RTP packet has been received. bool started{ false }; // Fields for generating Receiver Reports. uint32_t expectedPrior{ 0u }; uint32_t receivedPrior{ 0u }; uint32_t lastSrTimestamp{ 0u }; uint64_t lastSrReceived{ 0u }; int32_t reportedPacketsLost{ 0 }; }; } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RTP/SharedPacket.hpp ================================================ #ifndef MS_RTC_RTP_SHARED_PACKET_HPP #define MS_RTC_RTP_SHARED_PACKET_HPP #include "common.hpp" #include "RTC/RTP/Packet.hpp" namespace RTC { namespace RTP { class SharedPacket { public: #ifdef MS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE static thread_local uint64_t allocatedMemory; #endif public: /** * Empty constructor. */ SharedPacket(); /** * Constructor with RTP Packet pointer. If a packet is given it's internally * cloned. */ explicit SharedPacket(RTP::Packet* packet); /** * Copy constructor. * * @remarks * No need to declare it but let's be explicit. */ SharedPacket(const SharedPacket&) = default; /** * Copy assignment operator. * * @remarks * - No need to declare it but let's be explicit. * * @throws MediasoupError if the Packet is too big. */ SharedPacket& operator=(const SharedPacket&) = default; /** * Destructor. */ ~SharedPacket() = default; public: void Dump(int indentation = 0) const; bool HasPacket() const { return this->sharedPtr->get() != nullptr; } RTP::Packet* GetPacket() const { return this->sharedPtr->get(); } /** * Assign given packet (could be nullptr). If packet is given it's internally * cloned. * * @throws MediasoupError if the Packet is too big. */ void Assign(RTP::Packet* packet); /** * Resets the internal packet to nullptr. * * @remarks * This affects to ALL copies of this SharedPacket object. */ void Reset(); /** * Assert that RTP Packet contained in this SharedPacket is a clone of the * given other packet (or there is no packet inside and no other packet has * been given). */ void AssertSamePacket(const RTP::Packet* otherPacket) const; private: void StorePacket(RTP::Packet* packet); private: // NOTE: This needs to be a shared pointer that holds an unique pointer. // Otherwise, when copying/storing the shared pointer in other locations // (buffers, etc), reseting its internal value wouldn't affect other copies // of the shared pointer. std::shared_ptr> sharedPtr; }; } // namespace RTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RateCalculator.hpp ================================================ #ifndef MS_RTC_RATE_CALCULATOR_HPP #define MS_RTC_RATE_CALCULATOR_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/RTP/Packet.hpp" #include namespace RTC { // It is considered that the time source increases monotonically. // ie: the current timestamp can never be minor than a timestamp in the past. class RateCalculator { public: static constexpr size_t DefaultWindowSize{ 1000u }; static constexpr float DefaultBpsScale{ 8000.0f }; static constexpr uint16_t DefaultWindowItems{ 100u }; public: explicit RateCalculator( size_t windowSizeMs = DefaultWindowSize, float scale = DefaultBpsScale, uint16_t windowItems = DefaultWindowItems); void Update(size_t size, uint64_t nowMs); uint32_t GetRate(uint64_t nowMs); size_t GetBytes() const { return this->bytes; } void Reset(); private: void RemoveOldData(uint64_t nowMs); private: struct BufferItem { size_t count{ 0u }; uint64_t time{ 0u }; }; private: // Window Size (in milliseconds). size_t windowSizeMs{ DefaultWindowSize }; // Scale in which the rate is represented. float scale{ DefaultBpsScale }; // Window Size (number of items). uint16_t windowItems{ DefaultWindowItems }; // Item Size (in milliseconds), calculated as: windowSizeMs / windowItems. size_t itemSizeMs{ 0u }; // Buffer to keep data. std::vector buffer; // Time (in milliseconds) for last item in the time window. std::optional newestItemStartTime{ std::nullopt }; // Index for the last item in the time window. int32_t newestItemIndex{ -1 }; // Time (in milliseconds) for oldest item in the time window. std::optional oldestItemStartTime{ std::nullopt }; // Index for the oldest item in the time window. int32_t oldestItemIndex{ -1 }; // Total count in the time window. size_t totalCount{ 0u }; // Total bytes transmitted. size_t bytes{ 0u }; // Last value calculated by GetRate(). uint32_t lastRate{ 0u }; // Last time GetRate() was called. std::optional lastTime{ std::nullopt }; }; class RtpDataCounter { public: explicit RtpDataCounter( SharedInterface* shared, bool ignorePaddingOnlyPackets, size_t windowSizeMs = 2500) : shared(shared), ignorePaddingOnlyPackets(ignorePaddingOnlyPackets), rate(windowSizeMs) { } public: void Update(const RTC::RTP::Packet* packet); uint32_t GetBitrate(uint64_t nowMs) { return this->rate.GetRate(nowMs); } size_t GetPacketCount() const { return this->packets; } size_t GetBytes() const { return this->rate.GetBytes(); } private: SharedInterface* shared{ nullptr }; // Whether the size of padding only RTP packets should not be taken into // account bool ignorePaddingOnlyPackets{ false }; RateCalculator rate; size_t packets{ 0u }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/Router.hpp ================================================ #ifndef MS_RTC_ROUTER_HPP #define MS_RTC_ROUTER_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "Channel/ChannelRequest.hpp" #include "RTC/Consumer.hpp" #include "RTC/DataConsumer.hpp" #include "RTC/DataProducer.hpp" #include "RTC/Producer.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" #include "RTC/RtpObserver.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/Transport.hpp" #include "RTC/WebRtcServer.hpp" #include #include #include #include namespace RTC { class Router : public RTC::Transport::Listener, public RTC::RtpObserver::Listener, public Channel::ChannelSocket::RequestHandler { public: class Listener { public: virtual ~Listener() = default; public: virtual RTC::WebRtcServer* OnRouterNeedWebRtcServer( RTC::Router* router, std::string& webRtcServerId) = 0; }; public: explicit Router(SharedInterface* shared, const std::string& id, Listener* listener); ~Router() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; private: RTC::Transport* GetTransportById(const std::string& transportId) const; RTC::RtpObserver* GetRtpObserverById(const std::string& rtpObserverId) const; void CheckNoTransport(const std::string& transportId) const; void CheckNoRtpObserver(const std::string& rtpObserverId) const; /* Pure virtual methods inherited from RTC::Transport::Listener. */ public: void OnTransportNewProducer(RTC::Transport* transport, RTC::Producer* producer) override; void OnTransportProducerClosed(RTC::Transport* transport, RTC::Producer* producer) override; void OnTransportProducerPaused(RTC::Transport* transport, RTC::Producer* producer) override; void OnTransportProducerResumed(RTC::Transport* transport, RTC::Producer* producer) override; void OnTransportProducerNewRtpStream( RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void OnTransportProducerRtpStreamScore( RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; void OnTransportProducerRtcpSenderReport( RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; void OnTransportProducerRtpPacketReceived( RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::Packet* packet) override; void OnTransportNeedWorstRemoteFractionLost( RTC::Transport* transport, RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override; void OnTransportNewConsumer( RTC::Transport* transport, RTC::Consumer* consumer, const std::string& producerId) override; void OnTransportConsumerClosed(RTC::Transport* transport, RTC::Consumer* consumer) override; void OnTransportConsumerProducerClosed(RTC::Transport* transport, RTC::Consumer* consumer) override; void OnTransportConsumerKeyFrameRequested( RTC::Transport* transport, RTC::Consumer* consumer, uint32_t mappedSsrc) override; void OnTransportNewDataProducer(RTC::Transport* transport, RTC::DataProducer* dataProducer) override; void OnTransportDataProducerClosed(RTC::Transport* transport, RTC::DataProducer* dataProducer) override; void OnTransportDataProducerPaused(RTC::Transport* transport, RTC::DataProducer* dataProducer) override; void OnTransportDataProducerResumed( RTC::Transport* transport, RTC::DataProducer* dataProducer) override; // TODO: SCTP: Remove when we migrate to the new SCTP stack. void OnTransportDataProducerMessageReceived( RTC::Transport* transport, RTC::DataProducer* dataProducer, const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel) override; void OnTransportDataProducerMessageReceived( RTC::Transport* transport, RTC::DataProducer* dataProducer, RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel) override; void OnTransportNewDataConsumer( RTC::Transport* transport, RTC::DataConsumer* dataConsumer, std::string& dataProducerId) override; void OnTransportDataConsumerClosed(RTC::Transport* transport, RTC::DataConsumer* dataConsumer) override; void OnTransportDataConsumerDataProducerClosed( RTC::Transport* transport, RTC::DataConsumer* dataConsumer) override; void OnTransportListenServerClosed(RTC::Transport* transport) override; /* Pure virtual methods inherited from RTC::RtpObserver::Listener. */ public: RTC::Producer* RtpObserverGetProducer(RTC::RtpObserver* rtpObserver, const std::string& id) override; void OnRtpObserverAddProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer) override; void OnRtpObserverRemoveProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer) override; public: // Passed by argument. std::string id; private: // Passed by argument. SharedInterface* shared{ nullptr }; Listener* listener{ nullptr }; // Allocated by this. absl::flat_hash_map mapTransports; absl::flat_hash_map mapRtpObservers; // Others. absl::flat_hash_map> mapProducerConsumers; absl::flat_hash_map mapConsumerProducer; absl::flat_hash_map> mapProducerRtpObservers; absl::flat_hash_map mapProducers; absl::flat_hash_map> mapDataProducerDataConsumers; absl::flat_hash_map mapDataConsumerDataProducer; absl::flat_hash_map mapDataProducers; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RtcLogger.hpp ================================================ #ifndef MS_RTC_RTC_LOGGER_HPP #define MS_RTC_RTC_LOGGER_HPP #include "common.hpp" #include namespace RTC { namespace RtcLogger { class RtpPacket { public: enum class DiscardReason : uint8_t { NONE = 0, PRODUCER_NOT_FOUND, RECV_RTP_STREAM_NOT_FOUND, RECV_RTP_STREAM_DISCARDED, RECV_RTP_RTX_STREAM_DISCARDED, CONSUMER_INACTIVE, INVALID_TARGET_LAYER, UNSUPPORTED_PAYLOAD_TYPE, NOT_A_KEYFRAME, EMPTY_PAYLOAD, SPATIAL_LAYER_MISMATCH, PACKET_PREVIOUS_TO_SPATIAL_LAYER_SWITCH, DROPPED_BY_CODEC, TOO_HIGH_TIMESTAMP_EXTRA_NEEDED, SEND_RTP_STREAM_DISCARDED }; static const absl::flat_hash_map DiscardReason2String; RtpPacket() = default; ~RtpPacket() = default; void Sent(); void Discarded(DiscardReason discardReason); private: void Log() const; void Clear(); public: uint64_t timestamp{}; std::string recvTransportId; std::string sendTransportId; std::string routerId; std::string producerId; std::string consumerId; uint32_t recvRtpTimestamp{}; uint32_t sendRtpTimestamp{}; uint16_t recvSeqNumber{}; uint16_t sendSeqNumber{}; bool discarded{}; DiscardReason discardReason{ DiscardReason::NONE }; }; }; // namespace RtcLogger } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RtpDictionaries.hpp ================================================ #ifndef MS_RTC_RTP_DICTIONARIES_HPP #define MS_RTC_RTP_DICTIONARIES_HPP #include "common.hpp" #include "FBS/rtpParameters.h" #include "RTC/Parameters.hpp" #include #include #include namespace RTC { class Media { public: enum class Kind : uint8_t { AUDIO, VIDEO }; }; // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) class RtpCodecMimeType { public: enum class Type : uint8_t { AUDIO, VIDEO }; public: enum class Subtype { // Audio codecs: OPUS = 100, // Multi-channel Opus. MULTIOPUS, PCMA, PCMU, ISAC, G722, ILBC, SILK, // Video codecs: VP8 = 200, VP9, H264, AV1, // Complementary codecs: CN = 300, TELEPHONE_EVENT, // Feature codecs: RTX = 400, ULPFEC, X_ULPFECUC, FLEXFEC, RED }; public: static const absl::flat_hash_map String2Type; static const absl::flat_hash_map Type2String; static const absl::flat_hash_map String2Subtype; static const absl::flat_hash_map Subtype2String; public: RtpCodecMimeType() = default; bool operator==(const RtpCodecMimeType& other) const { return this->type == other.type && this->subtype == other.subtype; } bool operator!=(const RtpCodecMimeType& other) const { return !(*this == other); } void SetMimeType(const std::string& mimeType); void UpdateMimeType(); const std::string& ToString() const { return this->mimeType; } bool IsMediaCodec() const { return this->subtype >= Subtype(100) && this->subtype < (Subtype)300; } bool IsComplementaryCodec() const { return this->subtype >= Subtype(300) && this->subtype < (Subtype)400; } bool IsFeatureCodec() const { return this->subtype >= Subtype(400); } public: Type type; Subtype subtype; private: std::string mimeType; }; class RtpHeaderExtensionUri { public: enum class Type : uint8_t { MID = 1, RTP_STREAM_ID = 2, REPAIRED_RTP_STREAM_ID = 3, ABS_SEND_TIME = 4, TRANSPORT_WIDE_CC_01 = 5, SSRC_AUDIO_LEVEL = 6, DEPENDENCY_DESCRIPTOR = 7, VIDEO_ORIENTATION = 8, TIME_OFFSET = 9, ABS_CAPTURE_TIME = 10, PLAYOUT_DELAY = 11, MEDIASOUP_PACKET_ID = 12 }; public: static RtpHeaderExtensionUri::Type TypeFromFbs(FBS::RtpParameters::RtpHeaderExtensionUri uri); static FBS::RtpParameters::RtpHeaderExtensionUri TypeToFbs(RtpHeaderExtensionUri::Type uri); }; class RtcpFeedback { public: RtcpFeedback() = default; explicit RtcpFeedback(const FBS::RtpParameters::RtcpFeedback* data); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; public: std::string type; std::string parameter; }; class RtpCodecParameters { public: RtpCodecParameters() = default; explicit RtpCodecParameters(const FBS::RtpParameters::RtpCodecParameters* data); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; private: void CheckCodec() const; public: RtpCodecMimeType mimeType; uint8_t payloadType{ 0u }; uint32_t clockRate{ 0u }; uint8_t channels{ 1u }; RTC::Parameters parameters; std::vector rtcpFeedback; }; class RtpRtxParameters { public: RtpRtxParameters() = default; explicit RtpRtxParameters(const FBS::RtpParameters::Rtx* data); flatbuffers::Offset FillBuffer(flatbuffers::FlatBufferBuilder& builder) const; public: uint32_t ssrc{ 0u }; }; class RtpEncodingParameters { public: RtpEncodingParameters() = default; explicit RtpEncodingParameters(const FBS::RtpParameters::RtpEncodingParameters* data); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; public: uint32_t ssrc{ 0u }; std::string rid; uint8_t codecPayloadType{ 0u }; bool hasCodecPayloadType{ false }; RtpRtxParameters rtx; bool hasRtx{ false }; uint32_t maxBitrate{ 0u }; double maxFramerate{ 0 }; bool dtx{ false }; std::string scalabilityMode{ "S1T1" }; uint8_t spatialLayers{ 1u }; uint8_t temporalLayers{ 1u }; bool ksvc{ false }; }; // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) class RtpHeaderExtensionParameters { public: RtpHeaderExtensionParameters() = default; explicit RtpHeaderExtensionParameters(const FBS::RtpParameters::RtpHeaderExtensionParameters* data); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; public: RtpHeaderExtensionUri::Type type; uint8_t id{ 0u }; bool encrypt{ false }; RTC::Parameters parameters; }; class RtcpParameters { public: RtcpParameters() = default; explicit RtcpParameters(const FBS::RtpParameters::RtcpParameters* data); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; public: std::string cname; bool reducedSize{ true }; }; class RtpParameters { public: enum class Type : uint8_t { SIMPLE, SIMULCAST, SVC, PIPE }; public: static std::optional GetType(const RtpParameters& rtpParameters); static const std::string& GetTypeString(Type type); static FBS::RtpParameters::Type TypeToFbs(Type type); private: static const absl::flat_hash_map Type2String; public: RtpParameters() = default; explicit RtpParameters(const FBS::RtpParameters::RtpParameters* data); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; const RTC::RtpCodecParameters* GetCodecForEncoding(RtpEncodingParameters& encoding) const; const RTC::RtpCodecParameters* GetRtxCodecForEncoding(RtpEncodingParameters& encoding) const; private: void ValidateCodecs(); void ValidateEncodings(); void SetType(); public: std::string mid; std::vector codecs; std::vector encodings; std::vector headerExtensions; RtcpParameters rtcp; std::string msid; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RtpListener.hpp ================================================ #ifndef MS_RTC_RTP_LISTENER_HPP #define MS_RTC_RTP_LISTENER_HPP #include "common.hpp" #include "RTC/Producer.hpp" #include "RTC/RTP/Packet.hpp" #include #include namespace RTC { class RtpListener { public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; void AddProducer(RTC::Producer* producer); void RemoveProducer(RTC::Producer* producer); RTC::Producer* GetProducer(const RTC::RTP::Packet* packet); RTC::Producer* GetProducer(uint32_t ssrc) const; public: // Table of SSRC / Producer pairs. std::unordered_map ssrcTable; // Table of MID / Producer pairs. std::unordered_map midTable; // Table of RID / Producer pairs. std::unordered_map ridTable; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/RtpObserver.hpp ================================================ #ifndef MS_RTC_RTP_OBSERVER_HPP #define MS_RTC_RTP_OBSERVER_HPP #include "SharedInterface.hpp" #include "RTC/Producer.hpp" #include "RTC/RTP/Packet.hpp" #include namespace RTC { class RtpObserver : public Channel::ChannelSocket::RequestHandler { public: class Listener { public: virtual ~Listener() = default; public: virtual RTC::Producer* RtpObserverGetProducer( RTC::RtpObserver* rtpObserver, const std::string& id) = 0; virtual void OnRtpObserverAddProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer) = 0; virtual void OnRtpObserverRemoveProducer( RTC::RtpObserver* rtpObserver, RTC::Producer* producer) = 0; }; public: RtpObserver(SharedInterface* shared, const std::string& id, RTC::RtpObserver::Listener* listener); ~RtpObserver() override; public: void Pause(); void Resume(); bool IsPaused() const { return this->paused; } virtual void AddProducer(RTC::Producer* producer) = 0; virtual void RemoveProducer(RTC::Producer* producer) = 0; virtual void ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) = 0; virtual void ProducerPaused(RTC::Producer* producer) = 0; virtual void ProducerResumed(RTC::Producer* producer) = 0; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; protected: virtual void Paused() = 0; virtual void Resumed() = 0; public: // Passed by argument. std::string id; protected: // Passed by argument. SharedInterface* shared{ nullptr }; private: // Passed by argument. RTC::RtpObserver::Listener* listener{ nullptr }; // Others. bool paused{ false }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/TODO_SCTP.md ================================================ # TODO STCP ## Related to mediasoup SCTP implementation - dcsctp uses µs (webrtc::Timestamp::Micros()) internally, while mediasoup uses ms (`DepLibUV::GetTimeMs()`). When porting dcsctp timeout/duration logic, make sure to convert accordingly. Do not mix units in the same field. - `Association`: When transitioning to CLOSED (due to failure while connecting or closure) we should emit a new event "stcpclosed" in all `DataProducers/Consumers`. - When receiving SCTP RE-CONFIG, we should emit "streamclosed" in those `DataProducers/DataConsumers` whose stream ID have been closed. - `OnAssociationFailed()` and `OnAssociationClosed()` should report an error (if present) to JS. - Rename all "Packet", "Chunk", "Parameter", "Error Cause", "Association", etc to lowcase everywhere (in code and comments). - Rename all "I_DATA" etc to `I-DATA" everywhere (in code and comments). - Probably add many more fields in `SctpOptions` given to the `Association` in `Transport.cpp`. - When running `test-PipeTransport.ts` and `test-werift-sctp.ts` with `useBuiltInSctpStack: true`, tests pass but those errors show up: ``` mediasoup:ERROR:Worker (stderr) UnixStreamSocketHandle::Write() | uv_try_write() failed, trying uv_write(): broken pipe ``` - We must remove `numSctpStreams` option given to `router.createXxxTransport()` and `NumSctpStreams` type. `OS` and `MIS` in `numSctpStreams` are just the max announced number of outbound and incoming SCTP streams, but in the new SCTP stack those should always be 65535. The max number of incoming and outgoing streams will be negotiated later with the SCTP INIT and INIT_ACK and will be the minimum of our values (65535) and the OS and MIS that the peer announces in its INIT or INIT_ACK. - This is a breaking change. - Remove it from `sctpParameters.fbs` and other FBS types (look for `MIS` or `mis`, etc). - Remove it in Rust layer. - We must also remove `device.sctpCapabilities` getter from mediasoup-client because anyway we are making up those values! - Also must update the website documentation. - When we invoke `close()` on a `DataProducer/Consumer` in server, we must end calling `sctpAssociation->ResetStream([streamId])` so it sends `ReConfig` to peer. - In `transport.dump()` (maybe also in `getStats()`) we must properly obtain `OS` and `MIS` according to the number of SCTP streams negotiated via INIT + INIT_ACK. And if SCTP is not yet established, then... not sure. - In `Association::FillBuffer()` we should not pass `this->sctpOptions.negotiatedMaxOutboundStreams/negotiatedMaxInboundStreams` but the current values. - We need to pass `isDataChannel` to `SCTP::Association` constructor as we do in former `SctpAssociation`. Also use it in `Association::FillBuffer()`. - Well, let's see. If it's only for when changing number of OS/MIS... then the new SCTP stack doesn't support it so... - Fix `dataConsumer.getBufferedAmount()` which in usrsctp returns the data buffered for all data consumers in the transport but now it will be per `DataConsumer` (SCTP stream). - In `DataConsumer` class rename `SetAssociationBufferedAmount()` to `SetBufferedAmount()`. - In `DataConsumer` class revisit `SctpAssociationSendBufferFull()` method. - Fix the documentation in the website which says: "The underlaying SCTP association uses a common send buffer for all data consumers, hence the value given by this method indicates the data buffered for all data consumers in the transport." - Look for "TODO: SCTP" everywhere (also in `worker/test/`). - Test Chrome/Canary with I-DATA (message interleaving): ```bash /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary \ --force-fieldtrials="WebRTC-DataChannelMessageInterleaving/Enabled/" \ --enable-logging=stderr \ --v=1 \ ``` ### Problem in ReassemblyQueue In dsctp there is an `absl::AnyInvocable`, which is a move-only callable, unlike `std::function` which requires the callable to be copyable. The standard equivalent is `std::move_only_function`, introduced in C++23. If you use C++23: ```cpp std::vector> deferredActions; ``` In C++20 there is no `std::move_only_function` (that is C++23). The problem is that `absl::AnyInvocable` accepts move-only callables, while `std::function` requires them to be copyable. This is relevant because in dcsctp the lambda captures `data = std::move(data)`, and `UserData` has its copy constructor deleted, so the resulting lambda is not copyable and `std::function` will reject it. The solution for C++20 is to move the `UserData` into a `shared_ptr` so that the lambda becomes copyable: ```cpp std::vector> deferredActions; ``` And when adding the action, instead of: ```cpp // dcsctp - It works because AnyInvocable accepts move-only callables deferred_actions.push_back( [this, tsn, data = std::move(data)]() mutable { queued_bytes_ -= data.size(); Add(tsn, std::move(data)); }); ``` In your code: ```cpp // C++20 - UserData is not copyable, so it is wrapped in shared_ptr auto sharedData = std::make_shared(std::move(data)); this->deferredResetStreams->deferredActions.push_back( [this, tsn, sharedData]() mutable { this->queuedBytes -= sharedData->GetPayloadLength(); this->Add(tsn, std::move(*sharedData)); }); ``` ================================================ FILE: worker/include/RTC/SCTP/association/Association.hpp ================================================ #ifndef MS_RTC_SCTP_ASSOCIATION_HPP #define MS_RTC_SCTP_ASSOCIATION_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/SCTP/association/AssociationListenerDeferrer.hpp" #include "RTC/SCTP/association/NegotiatedCapabilities.hpp" #include "RTC/SCTP/association/PacketSender.hpp" #include "RTC/SCTP/association/StateCookie.hpp" #include "RTC/SCTP/association/TransmissionControlBlock.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp" #include "RTC/SCTP/packet/chunks/AnyDataChunk.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/AnyInitChunk.hpp" #include "RTC/SCTP/packet/chunks/CookieAckChunk.hpp" #include "RTC/SCTP/packet/chunks/CookieEchoChunk.hpp" #include "RTC/SCTP/packet/chunks/DataChunk.hpp" #include "RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp" #include "RTC/SCTP/packet/chunks/IDataChunk.hpp" #include "RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/InitAckChunk.hpp" #include "RTC/SCTP/packet/chunks/InitChunk.hpp" #include "RTC/SCTP/packet/chunks/OperationErrorChunk.hpp" #include "RTC/SCTP/packet/chunks/ReConfigChunk.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownChunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp" #include "RTC/SCTP/packet/chunks/UnknownChunk.hpp" #include "RTC/SCTP/public/AssociationInterface.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include "RTC/SCTP/public/AssociationMetrics.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include "RTC/SCTP/tx/RoundRobinSendQueue.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include #include #include #include namespace RTC { namespace SCTP { /** * This is the implementation of the AssociationInterface. */ class Association : public AssociationInterface, public PacketSender::Listener, public BackoffTimerHandleInterface::Listener { public: /** * Internal SCTP association state. This is different from the public SCTP * Association state (`SCTP::Types::AssociationState`). */ enum class State : uint8_t { NEW, CLOSED, COOKIE_WAIT, // NOTE: TCB is valid in these states: COOKIE_ECHOED, ESTABLISHED, SHUTDOWN_PENDING, SHUTDOWN_SENT, SHUTDOWN_RECEIVED, SHUTDOWN_ACK_SENT }; static constexpr std::string_view StateToString(State state) { // NOTE: We cannot use MS_TRACE() here because clang in Linux will // complain about "read of non-constexpr variable 'configuration' is not // allowed in a constant expression". switch (state) { case State::NEW: { return "NEW"; } case State::CLOSED: { return "CLOSED"; } case State::COOKIE_WAIT: { return "COOKIE_WAIT"; } case State::COOKIE_ECHOED: { return "COOKIE_ECHOED"; } case State::ESTABLISHED: { return "ESTABLISHED"; } case State::SHUTDOWN_PENDING: { return "SHUTDOWN_PENDING"; } case State::SHUTDOWN_SENT: { return "SHUTDOWN_SENT"; } case State::SHUTDOWN_RECEIVED: { return "SHUTDOWN_RECEIVED"; } case State::SHUTDOWN_ACK_SENT: { return "SHUTDOWN_ACK_SENT"; } NO_DEFAULT_GCC(); } } /** * Struct holding local verification tag and initial TSN between having * sent the INIT Chunk until the connection is established (there is no * TCB in between). * * @remarks * - This is how dcSCTP does, despite RFC 9260 states that the TCB should * also be created when an INIT Chunk is sent. */ struct PreTransmissionControlBlock { uint32_t localVerificationTag{ 0 }; uint32_t localInitialTsn{ 0 }; }; /** * Metrics that are directly filled by the Association class. * * @remarks * - This struct is a subset of the public AssociationMetrics struct. */ struct AssociationPrivateMetrics { uint64_t txPacketsCount{ 0 }; uint64_t txMessagesCount{ 0 }; uint64_t rxPacketsCount{ 0 }; uint64_t rxMessagesCount{ 0 }; Types::SctpImplementation peerImplementation{ Types::SctpImplementation::UNKNOWN }; uint16_t negotiatedMaxOutboundStreams{ 0 }; uint16_t negotiatedMaxInboundStreams{ 0 }; bool usesPartialReliability{ false }; bool usesMessageInterleaving{ false }; bool usesReConfig{ false }; bool usesZeroChecksum{ false }; }; public: explicit Association( const SctpOptions& sctpOptions, AssociationListenerInterface* listener, SharedInterface* shared); ~Association() override; public: void Dump(int indentation = 0) const override; flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const override; Types::AssociationState GetAssociationState() const override; /** * May invoke `Connect()` but only if the parent transport is ready for * SCTP transmission (e.g. the WebRtcTransport has ICE and DTLS connected). */ void MayConnect() override; /** * Initiate the SCTP association with the remote peer. It sends an INIT * Chunk. * * @remarks * - The SCTP association must be in New state. * - Despite this method is public, it's never invoked since `MayConnect()` * method is invoked instead. */ void Connect() override; /** * Gracefully shutdowns the Association and sends all outstanding data. * This is an asynchronous operation and `OnAssociationClosed()` will be * called on success. * * @remarks * - libwebrtc never calls the corresponding DcSctpSocket::Shutdown() * method due to a bug and hence we shouldn't either. * * @see https://issues.webrtc.org/issues/42222897 */ void Shutdown() override; /** * Closes the Association non-gracefully. Will send ABORT if the connection * is not already closed. No callbacks will be made after Close() has * returned. However, before Close() returns, it may have called * `OnAssociationClosed()` or `OnAssociationAborted()` callbacks. */ void Close() override; /** * Retrieves the latest metrics. If the Association is not fully connected, * `std::nullopt` will be returned. */ std::optional GetMetrics() const override; /** * Returns the currently set priority for an outgoing stream. The initial * value, when not set, is `SctpOptions::defaultStreamPriority`. */ uint16_t GetStreamPriority(uint16_t streamId) const override; /** * Sets the priority of an outgoing stream. The initial value, when not * set, is `SctpOptions::defaultStreamPriority`. */ void SetStreamPriority(uint16_t streamId, uint16_t priority) override; /** * Sets the maximum size of sent messages. The initial value, when not * set, is `SctpOptions::maxSendMessageSize`. */ void SetMaxSendMessageSize(size_t maxMessageSize) override; /** * Returns the number of bytes of data currently queued to be sent on a * given stream. */ size_t GetStreamBufferedAmount(uint16_t streamId) const override; /** * Returns the number of buffered outgoing bytes that is considered "low" * for a given stream. See `SetStreamBufferedAmountLowThreshold()`. */ size_t GetStreamBufferedAmountLowThreshold(uint16_t streamId) const override; /** * Specifies the number of bytes of buffered outgoing data that is * considered "low" for a given stream, which will trigger * `OnAssociationStreamBufferedAmountLow()` event. The default value is 0. */ void SetBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) override; /** * Resetting streams is an asynchronous operation and the results will be * notified using `OnAssociationStreamsResetPerformed()` on success and * `OnAssociationStreamsResetFailed()` on failure. * * When it's known that the peer has reset its own outgoing streams, * `OnAssociationInboundStreamsReset()` is called. * * Resetting streams can only be done on an established association that * supports stream resetting. Calling this method on e.g. a closed SCTP * association or streams that don't support resetting will not perform * any operation. * * @remarks * - Only outbound streams can be reset. * - Resetting a stream will also remove all queued messages on those * streams, but will ensure that the currently sent message (if any) is * fully sent before closing the stream. */ Types::ResetStreamsStatus ResetStreams(std::span outboundStreamIds) override; /** * Sends an SCTP message using the provided send options. Sending a message * is an asynchronous operation, and the `OnAssociationError()` callback * may be invoked to indicate any errors in sending the message. * * The association does not have to be established before calling this * method. If it's called before there is an established association, the * message will be queued. * * @remarks * - Copy constructor is disabled and there is move constructor. That's why * we don't pass a reference here. We could pass `Message&&` but that's * worse opens the door to bugs. */ Types::SendMessageStatus SendMessage( Message message, const SendMessageOptions& sendMessageOptions) override; /** * Sends SCTP messages using the provided send options. Sending a message * is an asynchronous operation, and the `OnAssociationError()` callback * may be invoked to indicate any errors in sending a message. * * The association does not have to be established before calling this * method. If it's called before there is an established association, the * message will be queued. * * This has identical semantics to `SendMessage()', except that it may * coalesce many messages into a single SCTP Packet if they would fit. * * @remarks * - Same as in `SendMessage()`. */ std::vector SendManyMessages( std::span messages, const SendMessageOptions& sendMessageOptions) override; /** * Receives SCTP data (hopefully an SCTP Packet) from the remote peer. */ void ReceiveSctpData(const uint8_t* data, size_t len) override; private: void InternalClose(Types::ErrorKind errorKind, const std::string_view& message); void SetState(State state, const std::string_view& message); void AddCapabilitiesParametersToInitOrInitAckChunk(AnyInitChunk* chunk) const; void CreateTransmissionControlBlock( uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities); std::unique_ptr CreatePacket() const; std::unique_ptr CreatePacketWithVerificationTag(uint32_t verificationTag) const; void SendInitChunk(); void SendShutdownChunk(); void SendShutdownAckChunk(); /** * Sends SHUTDOWN or SHUTDOWN-ACK if the Association is shutting down and * if all outstanding data has been acknowledged. */ void MaySendShutdownOrShutdownAckChunk(); /** * If the Association is shutting down, responds SHUTDOWN to any incoming * DATA. */ void MaySendShutdownOnPacketReceived(const Packet* receivedPacket); /** * If there are streams pending to be reset, send a request to reset them. */ void MaySendResetStreamsRequest(); /** * Called whenever data has been received, or the cumulative acknowledgment * TSN has moved, that may result in delivering messages. */ void MayDeliverMessages(); Types::SendMessageStatus InternalSendMessageCheck( const Message& message, const SendMessageOptions& sendMessageOptions); bool ValidateReceivedPacket(const Packet* receivedPacket); bool HandleReceivedChunk(const Packet* receivedPacket, const Chunk* receivedChunk); void HandleReceivedInitChunk(const Packet* receivedPacket, const InitChunk* receivedInitChunk); void HandleReceivedInitAckChunk( const Packet* receivedPacket, const InitAckChunk* receivedInitAckChunk); void HandleReceivedCookieEchoChunk( const Packet* receivedPacket, const CookieEchoChunk* receivedCookieEchoChunk); bool HandleReceivedCookieEchoChunkWithTcb(const Packet* receivedPacket, const StateCookie* cookie); void HandleReceivedCookieAckChunk( const Packet* receivedPacket, const CookieAckChunk* receivedCookieAckChunk); void HandleReceivedShutdownChunk( const Packet* receivedPacket, const ShutdownChunk* receivedShutdownChunk); void HandleReceivedShutdownAckChunk( const Packet* receivedPacket, const ShutdownAckChunk* receivedShutdownAckChunk); void HandleReceivedShutdownCompleteChunk( const Packet* receivedPacket, const ShutdownCompleteChunk* receivedShutdownCompleteChunk); void HandleReceivedOperationErrorChunk( const Packet* receivedPacket, const OperationErrorChunk* receivedOperationErrorChunk); void HandleReceivedAbortAssociationChunk( const Packet* receivedPacket, const AbortAssociationChunk* receivedAbortAssociationChunk); void HandleReceivedHeartbeatRequestChunk( const Packet* receivedPacket, const HeartbeatRequestChunk* receivedHeartbeatRequestChunk); void HandleReceivedHeartbeatAckChunk( const Packet* receivedPacket, const HeartbeatAckChunk* receivedHeartbeatAckChunk); void HandleReceivedReConfigChunk( const Packet* receivedPacket, const ReConfigChunk* receivedReConfigChunk); void HandleReceivedForwardTsnChunk( const Packet* receivedPacket, const ForwardTsnChunk* receivedForwardTsnChunk); void HandleReceivedIForwardTsnChunk( const Packet* receivedPacket, const IForwardTsnChunk* receivedIForwardTsnChunk); void HandleReceivedAnyForwardTsnChunk( const Packet* receivedPacket, const AnyForwardTsnChunk* receivedAnyForwardTsnChunk); void HandleReceivedDataChunk(const Packet* receivedPacket, const DataChunk* receivedDataChunk); void HandleReceivedIDataChunk(const Packet* receivedPacket, const IDataChunk* receivedIDataChunk); void HandleReceivedAnyDataChunk( const Packet* receivedPacket, const AnyDataChunk* receivedAnyDataChunk); void HandleReceivedSackChunk(const Packet* receivedPacket, const SackChunk* receivedSackChunk); bool HandleReceivedUnknownChunk( const Packet* receivedPacket, const UnknownChunk* receivedUnknownChunk); void OnT1InitTimer(uint64_t& baseTimeoutMs, bool& stop); void OnT1CookieTimer(uint64_t& baseTimeoutMs, bool& stop); void OnT2ShutdownTimer(uint64_t& baseTimeoutMs, bool& stop); template void AssertState(States... expectedStates) const; template void AssertNotState(States... unexpectedStates) const; /** * Returns true if there is a TCB, and false otherwise (and reports an * error). */ bool ValidateHasTcb(); void AssertHasTcb() const; void AssertIsConsistent() const; /* Pure virtual methods inherited from PacketSender::Listener. */ public: void OnPacketSenderPacketSent(PacketSender* packetSender, const Packet* packet, bool sent) override; /* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */ public: void OnBackoffTimer( BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override; private: // SCTP options given in the constructor. SctpOptions sctpOptions; // Listener. It's a `AssociationListenerDeferrer` which implements // `AssociationListenerInterface`. AssociationListenerDeferrer associationListenerDeferrer; SharedInterface* shared; // SCTP association internal state. State state{ State::NEW }; // Packet sender. PacketSender packetSender; // The actual send queue implementation. As data can be sent before the // connection is established, this component is not in the TCB. RoundRobinSendQueue sendQueue; // To keep settings between sending of INIT Chunk and establishment of // the connection. PreTransmissionControlBlock preTcb; // Once the SCTP association is established a Transmission Control Block // is created. std::unique_ptr tcb; // Private metrics. AssociationPrivateMetrics privateMetrics; // T1-init timer. const std::unique_ptr t1InitTimer; // T1-cookie timer. const std::unique_ptr t1CookieTimer; // T2-shutdown timer. const std::unique_ptr t2ShutdownTimer; // Max SCTP Packet length. const size_t maxPacketLength; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/association/AssociationListenerDeferrer.hpp ================================================ #ifndef MS_RTC_SCTP_ASSOCIATION_LISTENER_DEFERRED_HPP #define MS_RTC_SCTP_ASSOCIATION_LISTENER_DEFERRED_HPP #include "common.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include #include #include #include #include namespace RTC { namespace SCTP { class AssociationListenerDeferrer : public AssociationListenerInterface { public: class ScopedDeferrer { public: explicit ScopedDeferrer(AssociationListenerDeferrer& listenerDeferrer); ~ScopedDeferrer(); private: AssociationListenerDeferrer& listenerDeferrer; }; private: struct Error { Types::ErrorKind errorKind; std::string message; }; struct StreamReset { std::vector streamIds; std::string errorMessage; }; // Use a pre-sized variant for storage to avoid double heap allocation. This // variant can hold all cases of stored data. using CallbackData = std::variant; using Callback = std::function; public: explicit AssociationListenerDeferrer(AssociationListenerInterface* innerListener); private: void SetReady(); void TriggerDeferredCallbacks(); public: /* Pure virtual methods inherited from RTC::STCP::AssociationListenerInterface. */ bool OnAssociationSendData(const uint8_t* data, size_t len) override; void OnAssociationConnecting() override; void OnAssociationConnected() override; void OnAssociationFailed(Types::ErrorKind errorKind, std::string_view errorMessage) override; void OnAssociationClosed(Types::ErrorKind errorKind, std::string_view errorMessage) override; void OnAssociationRestarted() override; void OnAssociationError(Types::ErrorKind errorKind, std::string_view errorMessage) override; void OnAssociationMessageReceived(Message message) override; void OnAssociationStreamsResetPerformed(std::span outboundStreamIds) override; void OnAssociationStreamsResetFailed( std::span outboundStreamIds, std::string_view errorMessage) override; void OnAssociationInboundStreamsReset(std::span inboundStreamIds) override; void OnAssociationStreamBufferedAmountLow(uint16_t streamId) override; void OnAssociationTotalBufferedAmountLow() override; bool OnAssociationIsTransportReadyForSctp() override; void OnAssociationLifecycleMessageFullySent(uint64_t lifecycleId) override; void OnAssociationLifecycleMessageExpired(uint64_t lifecycleId, bool maybeDelivered) override; void OnAssociationLifecycleMessageDelivered(uint64_t lifecycleId) override; void OnAssociationLifecycleMessageEnd(uint64_t lifecycleId) override; private: AssociationListenerInterface* innerListener; bool ready{ false }; std::vector> deferredCallbacks; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/association/HeartbeatHandler.hpp ================================================ #ifndef MS_RTC_SCTP_HEARTBEAT_HANDLER_HPP #define MS_RTC_SCTP_HEARTBEAT_HANDLER_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "handles/BackoffTimerHandleInterface.hpp" namespace RTC { namespace SCTP { /** * HeartbeatHandler handles all logic around sending heartbeats and receiving * the responses, as well as receiving incoming heartbeat requests. * * Heartbeats are sent on idle connections to ensure that the connection is * still healthy and to measure the RTT. If a number of heartbeats time out, * the connection will eventually be closed. */ class HeartbeatHandler : public BackoffTimerHandleInterface::Listener { public: HeartbeatHandler( AssociationListenerInterface& associationListener, const SctpOptions& sctpOptions, SharedInterface* shared, TransmissionControlBlockContextInterface* tcbContext); ~HeartbeatHandler() override; public: /** * Called when the heartbeat interval timer should be restarted. This is * generally done every time data is sent, which makes the timer expire * when the connection is idle. */ void RestartTimer(); /** * Called on received HEARTBEAT_REQUEST Chunk. */ void HandleReceivedHeartbeatRequestChunk( const HeartbeatRequestChunk* receivedHeartbeatRequestChunk); /** * Called on received HEARTBEAT_ACK Chunk. */ void HandleReceivedHeartbeatAckChunk(const HeartbeatAckChunk* receivedHeartbeatAckChunk); private: void OnIntervalTimer(uint64_t& baseTimeoutMs, bool& stop); void OnTimeoutTimer(uint64_t& baseTimeoutMs, bool& stop); /* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */ public: void OnBackoffTimer( BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override; private: AssociationListenerInterface& associationListener; const SctpOptions sctpOptions; SharedInterface* shared; TransmissionControlBlockContextInterface* tcbContext; // The time for a connection to be idle before a heartbeat is sent. const uint64_t intervalDurationMs; // Adding RTT to the duration will add some jitter, which is good in // production, but less good in unit tests, which is why it can be disabled. const bool intervalDurationShouldIncludeRtt; const std::unique_ptr intervalTimer; const std::unique_ptr timeoutTimer; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/association/NegotiatedCapabilities.hpp ================================================ #ifndef MS_RTC_SCTP_NEGOTIATED_CAPABILITIES_HPP #define MS_RTC_SCTP_NEGOTIATED_CAPABILITIES_HPP #include "common.hpp" #include "RTC/SCTP/packet/chunks/AnyInitChunk.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" namespace RTC { namespace SCTP { /** * Those are the SCTP association capabilities negotiated during the * handshake, meaning that both endpoints support them and have agreed on * them. */ struct NegotiatedCapabilities { /** * Create a NegotiatedCapabilities struct. Intended to be used during * the SCTP association handshake flow. * * @remarks * - Given `remoteChunk` must be an INIT or an INIT_ACK Chunk. */ static NegotiatedCapabilities Factory( const SctpOptions& sctpOptions, const AnyInitChunk* remoteChunk); /** * Negotiated maximum number of outbound streams (OS). */ uint16_t negotiatedMaxOutboundStreams{ 0 }; /** * Negotiated maximum number of inbound streams (MIS). */ uint16_t negotiatedMaxInboundStreams{ 0 }; /** * Partial Reliability Extension. * * @see RFC 3758. */ bool partialReliability{ false }; /** * Stream Schedulers and User Message Interleaving (I-DATA Chunks). * * @see RFC 8260. */ bool messageInterleaving{ false }; /** * Stream Re-Configuration. * * @see RFC 6525. */ bool reConfig{ false }; /** * Zero Checksum. * * @see RFC 9653. */ bool zeroChecksum{ false }; void Dump(int indentation = 0) const; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/association/PacketSender.hpp ================================================ #ifndef MS_RTC_SCTP_PACKET_SENDER_HPP #define MS_RTC_SCTP_PACKET_SENDER_HPP #include "common.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" namespace RTC { namespace SCTP { class PacketSender { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnPacketSenderPacketSent( PacketSender* packetSender, const Packet* packet, bool sent) = 0; }; public: PacketSender(Listener* listener, AssociationListenerInterface& associationListener); ~PacketSender(); public: /** * Notifies the parent about a Packet to be sent to the peer and returns a * boolean indicating whether the Packet was sent or not. * * This method also writes the Packet checksum field depending on the value * of `writeChecksum`. * * @remarks * - This method does not delete the given `packet`. The caller must do it * after invoking this method. */ bool SendPacket(Packet* packet, bool writeChecksum = true); private: Listener* listener; AssociationListenerInterface& associationListener; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/association/StateCookie.hpp ================================================ #ifndef MS_RTC_SCTP_STATE_COOKIE_HPP #define MS_RTC_SCTP_STATE_COOKIE_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/association/NegotiatedCapabilities.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include "RTC/Serializable.hpp" #include namespace RTC { namespace SCTP { /** * This is the State Cookie we generate and put into a State Cookie * Parameter when we send INIT_ACK Chunk to the remote peer. * * The syntax we use is as follows. Note that we use a fixed length of * StateCookieLength bytes. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Magic 1 | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Local Verification Tag | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Remote Verification Tag | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Local Initial TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Remote Initial TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Remote Advertised Receiver Window Credit (a_rwnd) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Tie-Tag | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Negotiated Capabilities / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * Negotiated Capabilities are serialized as follows: * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | (Reserved) | |D|C|B|A| Magic 2 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Max Outbound Streams | Max Inbound Streams | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Flag A (partialReliability): Partial Reliability Extension. * - Flag B (messageInterleaving): Stream Schedulers and User Message * Interleaving (I-DATA). * - Flag C (reconfig): Stream Reconfiguration. * - Flag D (zeroChecksum): Zero Checksum. */ class StateCookie : public Serializable { private: struct NegotiatedCapabilitiesField { uint8_t reserved; #if defined(MS_LITTLE_ENDIAN) uint8_t bitA : 1; uint8_t bitB : 1; uint8_t bitC : 1; uint8_t bitD : 1; uint8_t unusedBits : 4; #elif defined(MS_BIG_ENDIAN) uint8_t unusedBits : 4; uint8_t bitD : 1; uint8_t bitC : 1; uint8_t bitB : 1; uint8_t bitA : 1; #endif uint16_t magic2; uint16_t negotiatedMaxOutboundStreams; uint16_t negotiatedMaxInboundStreams; }; public: // Fixed total length of our generated State Cookies. static constexpr size_t StateCookieLength{ 44 }; // Offset in the State Cookie where the Negotiated Capabilitied are // located. static constexpr size_t NegotiatedCapabilitiesOffset{ 36 }; // Magic value we prefix the State Cookie with. Note that it is // "msworker" in ASCII bytes. static constexpr uint64_t Magic1{ 0x6D73776F726B6572 }; static constexpr size_t Magic1Length{ 8 }; // Magic value used within the Negotiated Capabilities block. static constexpr uint16_t Magic2{ 0xAD81 }; public: /** * Whether the given buffer is a StateCookie generated by mediasoup. */ static bool IsMediasoupStateCookie(const uint8_t* buffer, size_t bufferLength); /** * Parse a StateCookie supposely generated by mediasoup. * * @remarks * `bufferLength` must be the exact length of the State Cookie. */ static StateCookie* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a StateCookie. * * @remarks * `bufferLength` could be greater than the real length of the State * Cookie. */ static StateCookie* Factory( uint8_t* buffer, size_t bufferLength, uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities); /** * Serialize a StateCookie (based on given arguments) in the given buffer. */ static void Write( uint8_t* buffer, size_t bufferLength, uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities); /** * Determine the SCTP implementation of the generator of State Cookie * given in the buffer. */ static Types::SctpImplementation DetermineSctpImplementation( const uint8_t* buffer, size_t bufferLength); public: StateCookie(uint8_t* buffer, size_t bufferLength); ~StateCookie() override; void Dump(int indentation = 0) const final; StateCookie* Clone(uint8_t* buffer, size_t bufferLength) const final; /** * The value of the Initiate Tag field we put in our INIT or INIT_ACK * Chunk. Packets sent by the remote peer must include this value in * their Verification Tag field. */ uint32_t GetLocalVerificationTag() const { return Utils::Byte::Get4Bytes(GetBuffer(), 8); } /** * The value of the Initiate Tag field the peer put in its INIT or * INIT_ACK Chunk. Packets sent by us to the peer must include this value * in their Verification Tag field. */ uint32_t GetRemoteVerificationTag() const { return Utils::Byte::Get4Bytes(GetBuffer(), 12); } /** * The value of the Initial TSN field we put in our INIT or INIT_ACK * Chunk. */ uint32_t GetLocalInitialTsn() const { return Utils::Byte::Get4Bytes(GetBuffer(), 16); } /** * The value of the Initial TSN field the peer put in its INIT or * INIT_ACK Chunk. */ uint32_t GetRemoteInitialTsn() const { return Utils::Byte::Get4Bytes(GetBuffer(), 20); } /** * The value of the Advertised Receiver Window Credit field we put in our * INIT or INIT_ACK Chunk. */ uint32_t GetRemoteAdvertisedReceiverWindowCredit() const { return Utils::Byte::Get4Bytes(GetBuffer(), 24); } /** * Tie-Tag used as a nonce when connecting. */ uint64_t GetTieTag() const { return Utils::Byte::Get8Bytes(GetBuffer(), 28); } /** * Negotiated association capabilities. */ NegotiatedCapabilities GetNegotiatedCapabilities() const; private: NegotiatedCapabilitiesField* GetNegotiatedCapabilitiesField() const { return reinterpret_cast( const_cast(GetBuffer()) + StateCookie::NegotiatedCapabilitiesOffset); } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/association/StreamResetHandler.hpp ================================================ #ifndef MS_RTC_SCTP_STREAM_RESET_HANDLER_HPP #define MS_RTC_SCTP_STREAM_RESET_HANDLER_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/chunks/ReConfigChunk.hpp" #include "RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp" #include "RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp" #include "RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include "RTC/SCTP/rx/DataTracker.hpp" #include "RTC/SCTP/rx/ReassemblyQueue.hpp" #include "RTC/SCTP/tx/RetransmissionQueue.hpp" #include "Utils/UnwrappedSequenceNumber.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include #include namespace RTC { namespace SCTP { /** * StreamResetHandler handles sending outgoing stream reset requests (to * close an SCTP stream, which translates to closing a data channel in * WebRTC). * * It also handles incoming "outgoing stream reset requests", when the peer * wants to close its streams. * * Resetting streams is an asynchronous operation where the client will * request a request a stream to be reset, but then it might not be * performed exactly at this point. First, the sender might need to discard * all messages that have been enqueued for this stream, or it may select to * wait until all have been sent. At least, it must wait for the currently * sending fragmented message to be fully sent, because a stream can't be * reset while having received half a message. In the stream reset request, * the "sender's last assigned TSN" is provided, which is simply the TSN for * which the receiver should've received all messages before this value, * before the stream can be reset. Since fragments can get lost or sent * out-of-order, the receiver of a request may not have received all the * data just yet, and then it will respond to the sender: "In progress". In * other words, try again. The sender will then need to start a timer and * try the very same request again (but with a new sequence number) until * the receiver successfully performs the operation. * * All this can take some time, and may be driven by timers, so the client * will ultimately be notified using callbacks. * * In this implementation, when a stream is reset, the queued but * not-yet-sent messages will be discarded, but that may change in the future. * RFC8831 allows both behaviors. */ class StreamResetHandler : public BackoffTimerHandleInterface::Listener { private: enum class ReqSeqNbrValidationResult : uint8_t { VALID, RETRANSMISSION, BAD_SEQUENCE_NUMBER, }; /** * Represents a stream request operation. There can only be one ongoing at * any time, and a sent request may either succeed, fail or result in the * receiver signaling that it can't process it right now, and then it will * be retried. */ class CurrentRequest { public: CurrentRequest(uint32_t senderLastAssignedTsn, std::vector streamIds) : reqSeqNbr(std::nullopt), senderLastAssignedTsn(senderLastAssignedTsn), streamIds(std::move(streamIds)) { } /** * Returns the current request sequence number, if this request has been * sent (check `HasBeenFirst()` first). Will return 0 if the request is * just prepared (or scheduled for retransmission) but not yet sent. */ uint32_t GetReqSeqNbr() const { return this->reqSeqNbr.value_or(0); } /** * The sender's last assigned TSN, from the retransmission queue. The * receiver uses this to know when all data up to this TSN has been * received, to know when to safely reset the stream. */ uint32_t GetSenderLastAssignedTsn() const { return senderLastAssignedTsn; } /** * The streams that are to be reset. */ const std::vector& GetStreamIds() const { return this->streamIds; } /** * If this request has been sent yet. If not, then it's either because * it has only been prepared and not yet sent, or because the received * couldn't apply the request, and then the exact same request will be * retried, but with a new sequence number. */ bool HasBeenSent() const { return this->reqSeqNbr.has_value(); } /** * If the receiver can't apply the request yet (and answered "In * Progress"), this will be called to prepare the request to be * retransmitted at a later time. */ void PrepareRetransmission() { this->reqSeqNbr = std::nullopt; } /** * If the request hasn't been sent yet, this assigns it a request * number. */ void PrepareToSend(uint32_t newReqSeqNbr) { this->reqSeqNbr = newReqSeqNbr; } void SetDeferred(bool isDeferred) { this->isDeferred = isDeferred; } bool IsDeferred() const { return this->isDeferred; } private: // If this is set, this request has been sent. If it's not set, the // request has been prepared, but has not yet been sent. This is // typically used when the peer responded "in progress" and the same // request (but a different request number) must be sent again. std::optional reqSeqNbr{ 0 }; // The sender's (that's us) last assigned TSN, from the retransmission // queue. uint32_t senderLastAssignedTsn{ 0 }; // The streams that are to be reset in this request. std::vector streamIds; // If the request is deferred (received "In Progress"), the next timeout // should not be treated as a timeout. bool isDeferred{ false }; }; private: using UnwrappedReConfigRequestSn = Utils::UnwrappedSequenceNumber; public: StreamResetHandler( AssociationListenerInterface& associationListener, SharedInterface* shared, TransmissionControlBlockContextInterface* tcbContext, DataTracker* dataTracker, ReassemblyQueue* reassemblyQueue, RetransmissionQueue* retransmissionQueue); ~StreamResetHandler() override; public: /** * Initiates reset of the provided streams. While there can only be one * ongoing stream reset request at any time, this method can be called at * any time and also multiple times. It will enqueue requests that can't * be directly fulfilled, and will asynchronously process them when any * ongoing request has completed. */ void ResetStreams(std::span outgoingStreamIds); /** * Whether a Reset Streams request should be send. Will return `false` if * there is no need to create a request (no streams to reset) or if there * already is an ongoing stream reset request that hasn't completed yet. */ bool ShouldSendStreamResetRequest() const; /** * Adds a Reset Streams request to the given Packet. Will start the * reconfig timer. * * @remarks * - The caller must check `ShouldCreateStreamResetRequest()` first and * only invoke this method if the former returns `true`. */ void AddStreamResetRequest(Packet* packet); /** * Called when handling and incoming RE-CONFIG chunk. Processes a stream * reconfiguration chunk and may send a RE-CONFIG back to the peer with * either 1 or 2 responses. */ void HandleReceivedReConfigChunk(const ReConfigChunk* receivedReConfigChunk); private: /** * Called to validate a received RE-CONFIG chunk. */ bool ValidateReceivedReConfigChunk(const ReConfigChunk* receivedReConfigChunk); /** * Adds the actual RE-CONFIG chunk to the given Packet. A request (which * set `this->currentRequest`) must have been created prior. */ void AddReConfigChunk(Packet* packet); /** * Called to validate the `reqSeqNbr`, that it's the next in sequence. */ ReqSeqNbrValidationResult ValidateReqSeqNbr(UnwrappedReConfigRequestSn reqSeqNbr); /** * Called when this Association receives an outgoing stream reset request. * It might either be performed straight away, or have to be deferred, and * the result of that will be put in `responses`. */ void HandleReceivedOutgoingSsnResetRequestParameter( const OutgoingSsnResetRequestParameter* receivedOutgoingSsnResetRequestParameter, ReConfigChunk* reConfigChunk); /** * Called when this Association receives an incoming stream reset request. * This isn't really supported, but a successful response is put in * `responses`. */ void HandleReceivedIncomingSsnResetRequestParameter( const IncomingSsnResetRequestParameter* receivedIncomingSsnResetRequestParameter, ReConfigChunk* reConfigChunk); /** * Called when receiving a response to an outgoing stream reset request. * It will either commit the stream resetting, if the operation was * successful, or will schedule a retry if it was deferred. And if it * failed, the operation will be rolled back. */ void HandleReceivedReconfigurationResponseParameter( const ReconfigurationResponseParameter* receivedReconfigurationResponseParameter); void OnReConfigTimer(uint64_t& baseTimeoutMs, bool& stop); /* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */ public: void OnBackoffTimer( BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override; private: AssociationListenerInterface& associationListener; SharedInterface* shared; TransmissionControlBlockContextInterface* tcbContext; DataTracker* dataTracker; ReassemblyQueue* reassemblyQueue; RetransmissionQueue* retransmissionQueue; UnwrappedReConfigRequestSn::Unwrapper incomingReConfigRequestSnUnwrapper; const std::unique_ptr reConfigTimer; // The next sequence number for outgoing stream requests. uint32_t nextOutgoingReqSeqNbr; // For incoming requests. Last processed request sequence number. UnwrappedReConfigRequestSn lastProcessedReqSeqNbr; // The result from last processed incoming request. ReconfigurationResponseParameter::Result lastProcessedReqResult; // The current stream request operation. std::optional currentRequest; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/association/TransmissionControlBlock.hpp ================================================ #ifndef MS_RTC_SCTP_TRANSMISSION_CONTROL_BLOCK_HPP #define MS_RTC_SCTP_TRANSMISSION_CONTROL_BLOCK_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/SCTP/association/HeartbeatHandler.hpp" #include "RTC/SCTP/association/NegotiatedCapabilities.hpp" #include "RTC/SCTP/association/PacketSender.hpp" #include "RTC/SCTP/association/StreamResetHandler.hpp" #include "RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SCTP/rx/DataTracker.hpp" #include "RTC/SCTP/rx/ReassemblyQueue.hpp" #include "RTC/SCTP/tx/RetransmissionErrorCounter.hpp" #include "RTC/SCTP/tx/RetransmissionQueue.hpp" #include "RTC/SCTP/tx/RetransmissionTimeout.hpp" #include "RTC/SCTP/tx/SendQueueInterface.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include #include namespace RTC { namespace SCTP { /** * The Transmission Control Block (TCB) represents an SCTP connection with * a peer and holds all its state. * * @see https://datatracker.ietf.org/doc/html/rfc9260#section-14 */ class TransmissionControlBlock : public TransmissionControlBlockContextInterface, public RetransmissionQueue::Listener, public BackoffTimerHandleInterface::Listener { public: TransmissionControlBlock( AssociationListenerInterface& associationListener, const SctpOptions& sctpOptions, SharedInterface* shared, SendQueueInterface& sendQueue, PacketSender& packetSender, uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities, size_t maxPacketLength, std::function isAssociationEstablished); ~TransmissionControlBlock() override; public: void Dump(int indentation = 0) const; /** * @remarks * - Implements TransmissionControlBlockContextInterface. */ bool IsAssociationEstablished() const override { return this->isAssociationEstablished(); } /** * The value of the Initiate Tag field we put in our INIT or INIT_ACK * Chunk. Packets sent by the remote peer must include this value in * their Verification Tag field. */ uint32_t GetLocalVerificationTag() const { return this->localVerificationTag; } /** * The value of the Initiate Tag field the peer put in its INIT or * INIT_ACK Chunk. Packets sent by us to the peer must include this value * in their Verification Tag field. */ uint32_t GetRemoteVerificationTag() const { return this->remoteVerificationTag; } /** * The value of the Initial TSN field we put in our INIT or INIT_ACK * Chunk. * * @remarks * - Implements TransmissionControlBlockContextInterface. */ uint32_t GetLocalInitialTsn() const override { return this->localInitialTsn; } /** * The value of the Initial TSN field the peer put in its INIT or * INIT_ACK Chunk. * * @remarks * - Implements TransmissionControlBlockContextInterface. */ uint32_t GetRemoteInitialTsn() const override { return this->remoteInitialTsn; } /** * The value of the Advertised Receiver Window Credit field we put in our * INIT or INIT_ACK Chunk. */ uint32_t GetRemoteAdvertisedReceiverWindowCredit() const { return this->remoteAdvertisedReceiverWindowCredit; } /** * Tie-Tag used as a nonce when connecting. */ uint64_t GetTieTag() const { return this->tieTag; } /** * Negotiated association capabilities. */ const NegotiatedCapabilities& GetNegotiatedCapabilities() const { return this->negotiatedCapabilities; } /** * @remarks * - Implements TransmissionControlBlockContextInterface. */ void ObserveRttMs(uint64_t rttMs) override; size_t GetCwnd() const { return this->retransmissionQueue.GetCwnd(); } /** * @remarks * - Implements TransmissionControlBlockContextInterface. */ uint64_t GetCurrentRtoMs() const override { return this->rto.GetRtoMs(); } uint64_t GetCurrentSrttMs() const { return this->rto.GetSrttMs(); } /** * @remarks * - Implements TransmissionControlBlockContextInterface. */ std::unique_ptr CreatePacket() const override; std::unique_ptr CreatePacketWithVerificationTag(uint32_t verificationTag) const; /** * @remarks * - Implements TransmissionControlBlockContextInterface. */ bool SendPacket(Packet* packet) override; DataTracker& GetDataTracker() { return this->dataTracker; } ReassemblyQueue& GetReassemblyQueue() { return this->reassemblyQueue; } RetransmissionQueue& GetRetransmissionQueue() { return this->retransmissionQueue; } StreamResetHandler& GetStreamResetHandler() { return this->streamResetHandler; } HeartbeatHandler& GetHeartbeatHandler() { return this->heartbeatHandler; } /** * Will be set while the Association is in COOKIE_ECHOED state. In this * state, there can only be a single Packet outstanding, and it must * contain the COOKIE_ECHO Chunk as the first Chunk in that Packet, until * the COOKIE_ACK has been received, which will make the socket call * `ClearRemoteStateCookie()`. */ void SetRemoteStateCookie(std::vector remoteStateCookie); /** * Called when the COOKIE_ACK Chunk has been received, to allow further * Packets to be sent. */ void ClearRemoteStateCookie(); bool HasRemoteStateCookie() const { return this->remoteStateCookie.has_value(); } /** * Sends a SACK Chunk, if there is a need to. */ void MaySendSackChunk(); /** * May add a FORWARD-TSN or I-FORWARD-TSN Chunk to the given Packet if it * is needed and allowed (rate-limited). */ void MayAddForwardTsnChunk(Packet* packet, uint64_t nowMs); void MaySendFastRetransmit(); /** * Create and fill Packets with control and DATA/I-DATA Chunks, and sends * them as much as can be allowed by the congestion control algorithm. * * @remarks * - If `this->remoteStateCookie` is present, then only one Packet will be * sent, with this Chunk as the first Chunk. * - Cannot pass `addCookieAckChunk=true` if `this->remoteStateCookie` is * present (will throw). */ void SendBufferedPackets(uint64_t nowMs, bool addCookieAckChunk = false); /** * @remarks * - Implements TransmissionControlBlockContextInterface. */ bool IncrementTxErrorCounter(std::string_view reason) override { return this->txErrorCounter.Increment(reason); } /** * @remarks * - Implements TransmissionControlBlockContextInterface. */ void ClearTxErrorCounter() override { return this->txErrorCounter.Clear(); } /** * @remarks * - Implements TransmissionControlBlockContextInterface. */ bool HasTooManyTxErrors() const override { return this->txErrorCounter.IsExhausted(); } private: void OnT3RtxTimer(uint64_t& baseTimeoutMs, bool& stop); void OnDelayedAckTimer(uint64_t& baseTimeoutMs, bool& stop); /* Pure virtual methods inherited from RetransmissionQueue::Listener. */ public: void OnRetransmissionQueueNewRttMs(uint64_t newRttMs) override; void OnRetransmissionQueueClearRetransmissionCounter() override; ; /* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */ public: void OnBackoffTimer( BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) override; private: AssociationListenerInterface& associationListener; const SctpOptions sctpOptions; SharedInterface* shared; PacketSender& packetSender; uint32_t localVerificationTag; uint32_t remoteVerificationTag; uint32_t localInitialTsn; uint32_t remoteInitialTsn; uint32_t remoteAdvertisedReceiverWindowCredit; // Nonce, used to detect reconnections. uint64_t tieTag; NegotiatedCapabilities negotiatedCapabilities; // Max SCTP Packet length. const size_t maxPacketLength; std::function isAssociationEstablished; // The data retransmission timer. const std::unique_ptr t3RtxTimer; // Delayed ack timer, which triggers when acks should be sent (when // delayed). const std::unique_ptr delayedAckTimer; RetransmissionTimeout rto; RetransmissionErrorCounter txErrorCounter; DataTracker dataTracker; ReassemblyQueue reassemblyQueue; RetransmissionQueue retransmissionQueue; StreamResetHandler streamResetHandler; HeartbeatHandler heartbeatHandler; // Rate limiting of FORWARD_TSN. Next can be sent at or after this // timestamp. uint64_t limitForwardTsnUntilMs{ 0 }; // Only valid when state is State::COOKIE_ECHOED. In this state, the // Association must wait for COOKIE_ACK to continue sending any packets (not // including a COOKIE_ECHO). So if this state cookie is present, the // `SendBufferedChunks()` method will always only send one Packet, with // a CookieEchoChunk containing this cookie as the first Chunk in the Packet. std::optional> remoteStateCookie; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp ================================================ #ifndef MS_RTC_SCTP_TRANSMISSION_CONTROL_BLOCK_CONTEXT_INTERFACE_HPP #define MS_RTC_SCTP_TRANSMISSION_CONTROL_BLOCK_CONTEXT_INTERFACE_HPP #include "common.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include namespace RTC { namespace SCTP { class TransmissionControlBlockContextInterface { public: virtual ~TransmissionControlBlockContextInterface() = default; /** * Indicates if the SCTP Association has been established. */ virtual bool IsAssociationEstablished() const = 0; /** * The value of the Initiate Tag field the peer put in its INIT or * INIT_ACK Chunk. */ virtual uint32_t GetLocalInitialTsn() const = 0; /** * The value of the Initial TSN field the peer put in its INIT or * INIT_ACK Chunk. */ virtual uint32_t GetRemoteInitialTsn() const = 0; /** * To be called when a RTT (ms) has been measured, to update the RTO * value. */ virtual void ObserveRttMs(uint64_t rttMs) = 0; /** * Returns the Retransmission Timeout (RTO) value. */ virtual uint64_t GetCurrentRtoMs() const = 0; /** * Increments the transmission error counter, given a human readable * reason. Returns `true` if the maximum error count has been reached, * `false` will be returned. */ virtual bool IncrementTxErrorCounter(std::string_view reason) = 0; /** * Clears the transmission error counter. */ virtual void ClearTxErrorCounter() = 0; /** * Returns true if there have been too many retransmission errors. */ virtual bool HasTooManyTxErrors() const = 0; virtual std::unique_ptr CreatePacket() const = 0; /** * Sends a Packet and returns a boolean indicating whether the Packet was * sent or not. */ virtual bool SendPacket(Packet* packet) = 0; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/Chunk.hpp ================================================ #ifndef MS_RTC_SCTP_CHUNK_HPP #define MS_RTC_SCTP_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/TLV.hpp" #include #include #include namespace RTC { namespace SCTP { /** * SCTP Chunk. * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Chunk Type | Chunk Flags | Chunk Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Chunk Value / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits). * - Chunk Flags (8 bits). * - Chunk Length (16 bits): Total length of the Chunk * excluding padding bytes. Minimum value is 4 (if Chunk Value is 0 * bytes). Maximum value is 65535, which means 1 byte of padding. * - Chunk Value (variable length). * - Padding: Bytes of padding to make the Chunk total length be * multiple of 4 bytes. */ // Forward declaration. class Packet; class Chunk : public TLV { // We need that Packet calls protected and private methods in this class. friend class Packet; public: using ParametersIterator = typename std::vector::const_iterator; using ErrorCausesIterator = typename std::vector::const_iterator; public: /** * Chunk Type. */ enum class ChunkType : uint8_t { DATA = 0x00, INIT = 0x01, INIT_ACK = 0x02, SACK = 0x03, HEARTBEAT_REQUEST = 0x04, HEARTBEAT_ACK = 0x05, ABORT = 0x06, SHUTDOWN = 0x07, SHUTDOWN_ACK = 0x08, OPERATION_ERROR = 0x09, // NOTE: Cannot use ERROR (MSVC complains). COOKIE_ECHO = 0x0A, COOKIE_ACK = 0x0B, ECNE = 0x0C, // NOTE: Not implemented. CWR = 0x0D, // NOTE: Not implemented. SHUTDOWN_COMPLETE = 0x0E, FORWARD_TSN = 0xC0, // Type: 192, RFC 3758 RE_CONFIG = 0x82, // Type 130, RFC 6525 I_DATA = 0x40, // Type: 64, RFC 8260 I_FORWARD_TSN = 0xC2, // Type: 194, RFC 8260 }; /** * Action that is taken if the processing endpoint does not recognize the * Chunk Type. */ enum class ActionForUnknownChunkType : uint8_t { STOP = 0b00, STOP_AND_REPORT = 0b01, SKIP = 0b10, SKIP_AND_REPORT = 0b11 }; /** * Struct of an SCTP Chunk Header. * * @remarks * - This struct is guaranteed to be aligned to 2 bytes. */ struct ChunkHeader { ChunkType type; uint8_t flags; /** * The value of the Chunk Length field, which represents the total * length of the Chunk in bytes, including the Chunk Type, Chunk Flags, * Chunk Length and Chunk Value fields. So if the Chunk Value field is * zero-length, the Length field must be 4. The Chunk Length field does * not count any padding. */ uint16_t length; }; #ifdef MS_TEST public: #else private: #endif /** * Access to individual bit in the Chunk Flags field. bit0 corresponds * to the least significant bit. * * @remarks * - This struct is guaranteed to be aligned to 1 byte. */ struct ChunkFlags { #if defined(MS_LITTLE_ENDIAN) uint8_t bit0 : 1; uint8_t bit1 : 1; uint8_t bit2 : 1; uint8_t bit3 : 1; uint8_t bit4 : 1; uint8_t bit5 : 1; uint8_t bit6 : 1; uint8_t bit7 : 1; #elif defined(MS_BIG_ENDIAN) uint8_t bit7 : 1; uint8_t bit6 : 1; uint8_t bit5 : 1; uint8_t bit4 : 1; uint8_t bit3 : 1; uint8_t bit2 : 1; uint8_t bit1 : 1; uint8_t bit0 : 1; #endif }; public: static const size_t ChunkHeaderLength{ 4 }; public: /** * Whether given buffer could be a a valid Chunk. * * @param buffer * @param bufferLength - Can be greater than real Chunk length. * @param chunkType - If given buffer is a valid Chunk then `chunkType` * is rewritten to parsed ChunkType. * @param chunkLength - If given buffer is a valid Chunk then * `chunkLength` is rewritten to the value of the Chunk Length field. * @param padding - If given buffer is a valid Chunk then `padding` is * rewritten to the number of padding bytes in the Chunk (only the * necessary ones to make total length multiple of 4). */ static bool IsChunk( const uint8_t* buffer, size_t bufferLength, ChunkType& chunkType, uint16_t& chunkLength, uint8_t& padding); static const std::string& ChunkTypeToString(ChunkType chunkType); private: static const std::unordered_map ChunkType2String; protected: /** * Constructor is protected because we only want to create Chunk * instances via Parse() and Factory() in subclasses. */ Chunk(uint8_t* buffer, size_t bufferLength); public: ~Chunk() override; void Dump(int indentation = 0) const override = 0; void Serialize(uint8_t* buffer, size_t bufferLength) final; /** * Can be overridden by each subclass. */ Chunk* Clone(uint8_t* buffer, size_t bufferLength) const override = 0; virtual ChunkType GetType() const final { return GetHeaderPointer()->type; } /** * False by default. UnknownChunk class overrides this method to return * true instead. */ virtual bool HasUnknownType() const { return false; } virtual ActionForUnknownChunkType GetActionForUnknownChunkType() const final { return static_cast(GetBuffer()[0] >> 6); } virtual uint8_t GetFlags() const final { return GetHeaderPointer()->flags; } /** * Whether this type of Chunk can have Parameters. Subclasses must * override this method. */ virtual bool CanHaveParameters() const = 0; virtual bool HasParameters() const final { return this->parameters.size() > 0; } virtual size_t GetParametersCount() const final { return this->parameters.size(); } virtual ParametersIterator ParametersBegin() const final { return this->parameters.begin(); } virtual ParametersIterator ParametersEnd() const final { return this->parameters.end(); } virtual const Parameter* GetParameterAt(size_t idx) const final { if (idx >= this->parameters.size()) { return nullptr; } return this->parameters[idx]; } template const T* GetFirstParameterOfType() const { for (const auto* parameter : this->parameters) { if (typeid(*parameter) == typeid(T)) { return static_cast(parameter); } } return nullptr; } /** * Clone given Parameter into Chunk's buffer. * * @remarks * - Once this method is called, the caller may want to free the original * given Parameter (otherwise it will leak since the Chunk manages a clone * of it). * * @throw * - MediaSoupError - If the Chunk subclass cannot have Parameters. * - MediaSoupError - If `BuildParameterInPlace()` or * `BuildErrorCauseInPlace()` was called before and the caller didn't * invoke `Consolidate()` on the returned Parameter or Error Cause yet. */ virtual void AddParameter(const Parameter* parameter) final; /** * Build a Parameter within the Chunk's buffer and append it to the list * of Parameters. The caller can perform modifications in that Parameter * and those will affect the Chunk body where the Parameter is serialized. * The desired Parameter class type is given via template argument. * * @returns Pointer of the created Parameter specific class. * * @throw * - MediaSoupError - If the Chunk subclass cannot have Parameters. * - MediaSoupError - If `BuildParameterInPlace()` or * `BuildErrorCauseInPlace()` was called before and the caller didn't * invoke `Consolidate()` on the returned Parameter or Error Cause yet. * * @remarks * - The caller MUST invoke `Consolidate()` once the Parameter is * completed. * - The caller MUST NOT free the obtained Parameter pointer since it's * now part of the Chunk. * - The caller MUST free the obtained Parameter only in case the * `Consolidate()` method on the Parameter throws. * - Method implemented in header file due to C++ template usage. * * @example * ```c++ * auto* ipv4Parameter = * chunk->BuildParameterInPlace(); * ``` */ template T* BuildParameterInPlace() { AssertCanHaveParameters(); AssertDoesNotNeedConsolidation(); // The new Parameter will be added after other Parameters in the Chunk, // this is, at the end of the Chunk, whose length we know it's padded to // 4 bytes, and each Parameter total length is also multiple of 4 bytes. auto* ptr = const_cast(GetBuffer()) + GetLength(); // The remaining length in the buffer is the potential buffer length // of the Parameter. size_t parameterMaxBufferLength = GetBufferLength() - (ptr - GetBuffer()); auto* parameter = T::Factory(ptr, parameterMaxBufferLength); // NOTE: Do not fix/update the Parameter buffer length since the caller // probably wants to modify the Parameter. HandleInPlaceParameter(parameter); return parameter; } /** * Whether this type of Chunk can have Error Causes. Subclasses must * override this method. */ virtual bool CanHaveErrorCauses() const = 0; virtual bool HasErrorCauses() const final { return this->errorCauses.size() > 0; } virtual size_t GetErrorCausesCount() const final { return this->errorCauses.size(); } virtual ErrorCausesIterator ErrorCausesBegin() const final { return this->errorCauses.begin(); } virtual ErrorCausesIterator ErrorCausesEnd() const final { return this->errorCauses.end(); } virtual const ErrorCause* GetErrorCauseAt(size_t idx) const final { if (idx >= this->errorCauses.size()) { return nullptr; } return this->errorCauses[idx]; } template const T* GetFirstErrorCauseOfCode() const { for (const auto* errorCause : this->errorCauses) { if (typeid(*errorCause) == typeid(T)) { return static_cast(errorCause); } } return nullptr; } /** * Clone given Error Cause into Chunk's buffer. * * @remarks * - Once this method is called, the caller may want to free the original * given Error Cause (otherwise it will leak since the Chunk manages a * clone of it). * * @throw * - MediaSoupError - If the Chunk subclass cannot have Error Causes. * - MediaSoupError - If `BuildParameterInPlace()` or * `BuildErrorCauseInPlace()` was called before and the caller didn't * invoke `Consolidate()` on the returned Parameter or Error Cause yet. */ virtual void AddErrorCause(const ErrorCause* errorCause) final; /** * Build a Error Cause within the Chunk's buffer and append it to the * list of Error Causes. The caller can perform modifications in that * Error Cause and those will affect the Chunk body where the Error Cause * is serialzed. The desired Error Cause class type is given via template * argument. * * @returns Pointer of the created Error Cause specific class. * * @throw * - MediaSoupError - If the Chunk subclass cannot have Error Causes. * - MediaSoupError - If `BuildParameterInPlace()` or * `BuildErrorCauseInPlace()` was called before and the caller didn't * invoke `Consolidate()` on the returned Parameter or Error Cause yet. * * @remarks * - The caller MUST invoke `Consolidate()` once the Error Cause is * completed. * - The caller MUST NOT free the obtained Error Cause pointer since it's * now part of the Chunk. * - The caller MUST free the obtained Error Cause only in case the * `Consolidate()` method on the Error Cause throws. * - Method implemented in header file due to C++ template usage. * * @example * ```c++ * auto* noUserDataErrorCause = * chunk->BuildErrorCauseInPlace(); * ``` */ template T* BuildErrorCauseInPlace() { AssertCanHaveErrorCauses(); AssertDoesNotNeedConsolidation(); // The new Error Cause will be added after other Error Causes in the // Chunk, this is, at the end of the Chunk, whose length we know it's // padded to 4 bytes, and each Error Cause total length is also // multiple of 4 bytes. auto* ptr = const_cast(GetBuffer()) + GetLength(); // The remaining length in the buffer is the potential buffer length // of the Error Cause. size_t errorCauseMaxBufferLength = GetBufferLength() - (ptr - GetBuffer()); auto* errorCause = T::Factory(ptr, errorCauseMaxBufferLength); // NOTE: Do not fix/update the Error Cause buffer length since the // caller probably wants to modify the Error Cause. HandleInPlaceErrorCause(errorCause); return errorCause; } /** * Whether `BuildParameterInPlace()` or `BuildErrorCauseInPlace()` was * called before and the caller didn't invoke `Consolidate()` on the * returned Parameter or Error Cause yet. */ virtual bool NeedsConsolidation() const final { return this->needsConsolidation; } protected: /** * Subclasses must invoke this method within their Dump() method. */ void DumpCommon(int indentation) const final; /** * Subclasses must invoke this method within their Dump() method. */ virtual void DumpParameters(int indentation) const final; /** * Subclasses must invoke this method within their Dump() method. */ virtual void DumpErrorCauses(int indentation) const final; virtual void SoftSerialize(const uint8_t* buffer) final; /** * Can be overridden by each subclass. */ virtual Chunk* SoftClone(const uint8_t* buffer) const = 0; virtual void SoftCloneInto(Chunk* chunk) const final; virtual void InitializeHeader(ChunkType chunkType, uint8_t flags, uint16_t lengthFieldValue) final; virtual bool GetBit0() const final { return GetFlagsPointer()->bit0; } virtual void SetBit0(bool flag) final { GetFlagsPointer()->bit0 = flag; } virtual bool GetBit1() const final { return GetFlagsPointer()->bit1; } virtual void SetBit1(bool flag) final { GetFlagsPointer()->bit1 = flag; } virtual bool GetBit2() const final { return GetFlagsPointer()->bit2; } virtual void SetBit2(bool flag) final { GetFlagsPointer()->bit2 = flag; } virtual bool GetBit3() const final { return GetFlagsPointer()->bit3; } virtual void SetBit3(bool flag) final { GetFlagsPointer()->bit3 = flag; } virtual bool GetBit4() const final { return GetFlagsPointer()->bit4; } virtual void SetBit4(bool flag) final { GetFlagsPointer()->bit4 = flag; } virtual bool GetBit5() const final { return GetFlagsPointer()->bit5; } virtual void SetBit5(bool flag) final { GetFlagsPointer()->bit5 = flag; } virtual bool GetBit6() const final { return GetFlagsPointer()->bit6; } virtual void SetBit6(bool flag) final { GetFlagsPointer()->bit6 = flag; } virtual bool GetBit7() const final { return GetFlagsPointer()->bit7; } virtual void SetBit7(bool flag) final { GetFlagsPointer()->bit7 = flag; } /** * Chunk subclasses with header bigger than default one (4 bytes) must * override this method and return their header length (excluding * variable-length field considered "value", Optional/Variable-Length * Parameters and Error Causes). */ size_t GetHeaderLength() const override { return Chunk::ChunkHeaderLength; } /** * To be called by each subclass of Chunk if Parameters parsing is * needed. It creates Parameter subclasses and adds them to the Chunk. * * @remarks * - This method assumes that the Chunk basic parsing has been made * already so current length of the Chunk is the fixed length of the * specific Chunk class. * * @return True if no error happened while parsing Parameters. * * @throw * - MediaSoupError - If the Chunk subclass cannot have Chunk Parameters. */ virtual bool ParseParameters() final; /** * To be called by each subclass of Chunk if Error Causes parsing is * needed. It creates ErrorCause subclasses and adds them to the Chunk. * * @remarks * - This method assumes that the Chunk basic parsing has been made * already so current length of the Chunk is the fixed length of the * specific Chunk class. * * @return True if no error happened while parsing Error Causes. * * @throw * - MediaSoupError - If the Chunk subclass cannot have Chunk Parameters. */ virtual bool ParseErrorCauses() final; private: /** * NOTE: Return ChunkHeader* instead of const ChunkHeader* since we may * want to modify its fields. */ virtual ChunkHeader* GetHeaderPointer() const final { return reinterpret_cast(const_cast(GetBuffer())); } virtual void SetType(ChunkType chunkType) final { GetHeaderPointer()->type = chunkType; } virtual void SetFlags(uint8_t flags) final { GetHeaderPointer()->flags = flags; } virtual ChunkFlags* GetFlagsPointer() const final { return reinterpret_cast(const_cast(GetBuffer()) + 1); } virtual void HandleInPlaceParameter(Parameter* parameter) final; virtual void HandleInPlaceErrorCause(ErrorCause* errorCause) final; virtual void AssertCanHaveParameters() const final; virtual void AssertCanHaveErrorCauses() const final; virtual void AssertDoesNotNeedConsolidation() const final; private: // Parameters. std::vector parameters; // Error Causes. std::vector errorCauses; // Whether `BuildParameterInPlace()` or `BuildErrorCauseInPlace()` was // called and the caller didn't invoke `Consolidate()` on the returned // Parameter or Error Cause yet. bool needsConsolidation{ false }; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/ErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_ERROR_CAUSE_HPP #define MS_RTC_SCTP_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/TLV.hpp" #include #include namespace RTC { namespace SCTP { /** * SCTP Error Cause. * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code | Cause Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Cause-Specific Information / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Cause Code (16 bits). * - Cause Length (16 bits): Set to the size of the Error Cause in bytes, * including the Cause Code, Cause Length, and Cause-Specific Information * fields (padding excluded). * - Cause-Specific Information (variable length): This field carries the * details of the error condition. * - Padding: Bytes of padding to make the Error Cause total length be * multiple of 4 bytes. */ // Forward declaration. class Chunk; class ErrorCause : public TLV { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Error Cause Code. * NOTE: This field MUST be 2 bytes long. */ // NOLINTNEXTLINE(performance-enum-size) enum class ErrorCauseCode : uint16_t { INVALID_STREAM_IDENTIFIER = 0x0001, MISSING_MANDATORY_PARAMETER = 0x0002, STALE_COOKIE = 0x0003, OUT_OF_RESOURCE = 0x0004, UNRESOLVABLE_ADDRESS = 0x0005, UNRECOGNIZED_CHUNK_TYPE = 0x0006, INVALID_MANDATORY_PARAMETER = 0x0007, UNRECOGNIZED_PARAMETERS = 0x0008, NO_USER_DATA = 0x0009, COOKIE_RECEIVED_WHILE_SHUTTING_DOWN = 0x000A, RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES = 0x000B, USER_INITIATED_ABORT = 0x000C, PROTOCOL_VIOLATION = 0x000D, }; /** * Struct of an SCTP Error Cause Header. * * @remarks * - This struct is guaranteed to be aligned to 2 bytes. */ struct ErrorCauseHeader { ErrorCauseCode code; /** * The value of the Error Cause Length field, which represents the * total length of the Error Cause in bytes, including the Cause Code, * Cause Length and Cause-Specific Information fields. So if the * Cause-Specific Information field is zero-length, the Length field * must be 4. The Cause Length field does not count any padding. */ uint16_t length; }; public: static const size_t ErrorCauseHeaderLength{ 4 }; public: /** * Whether given buffer could be a a valid Error Cause. * * @param buffer * @param bufferLength - Can be greater than real Error Cause length. * @param causeCode - If given buffer is a valid Error Cause then * `causeCode` is rewritten to parsed ErrorCauseCode. * @param causeLength - If given buffer is a valid Error Cause then * `causeLength` is rewritten to the value of the Cause Length field. * @param padding - If given buffer is a valid Error Cause then `padding` * is rewritten to the number of padding bytes in the Error Cause (only * the necessary ones to make total length multiple of 4). */ static bool IsErrorCause( const uint8_t* buffer, size_t bufferLength, ErrorCauseCode& causeCode, uint16_t& causeLength, uint8_t& padding); static const std::string& ErrorCauseCodeToString(ErrorCauseCode causeCode); private: static const std::unordered_map ErrorCauseCode2String; protected: /** * Constructor is protected because we only want to create ErrorCause * instances via Parse() and Factory() in subclasses. */ ErrorCause(uint8_t* buffer, size_t bufferLength); public: ~ErrorCause() override; void Dump(int indentation = 0) const override = 0; ErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const override = 0; virtual ErrorCauseCode GetCode() const final { return static_cast(ntohs(static_cast(GetHeaderPointer()->code))); } /** * False by default. UnknownErrorCause class overrides this method to * return true instead. */ virtual bool HasUnknownCode() const { return false; } virtual const std::string ToString() const final { // Get the custom content from the subclass. const auto contentToString = ContentToString(); if (contentToString.size() > 0) { return ErrorCause::ErrorCauseCodeToString(GetCode()) + " (" + std::to_string(static_cast(GetCode())) + ") " + contentToString; } else { return ErrorCause::ErrorCauseCodeToString(GetCode()) + " (" + std::to_string(static_cast(GetCode())) + ")"; } } protected: /** * Subclasses must invoke this method within their Dump() method. */ void DumpCommon(int indentation) const final; virtual void SoftSerialize(const uint8_t* buffer) final; virtual ErrorCause* SoftClone(const uint8_t* buffer) const = 0; virtual void SoftCloneInto(ErrorCause* errorCause) const final; virtual void InitializeHeader(ErrorCauseCode errorCauseCode, uint16_t lengthFieldValue) final; /** * Error Cause subclasses with header bigger than default one (4 bytes) * must override this method and return their header length (excluding * variable-length field considered "value"). */ size_t GetHeaderLength() const override { return ErrorCause::ErrorCauseHeaderLength; } private: /** * NOTE: Return ErrorCauseHeader* instead of const ErrorCauseHeader* * since we may want to modify its fields. */ virtual ErrorCauseHeader* GetHeaderPointer() const final { return reinterpret_cast(const_cast(GetBuffer())); } virtual void SetCode(ErrorCauseCode causeCode) final { GetHeaderPointer()->code = static_cast(htons(static_cast(causeCode))); } /** * Subclasses can override this method. */ virtual const std::string ContentToString() const { return ""; } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/Packet.hpp ================================================ #ifndef MS_RTC_SCTP_PACKET_HPP #define MS_RTC_SCTP_PACKET_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/Serializable.hpp" #include namespace RTC { namespace SCTP { /** * SCTP Packet. * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Common Header | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Chunk #1 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | ... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Chunk #n | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * It's mandatory that the Packet total length is multiple of 4 bytes. */ /** * SCTP Common Header. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Source Port Number | Destination Port Number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Verification Tag | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Checksum | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Source port (16 bits). * - Destination port (16 bits). * - Verification Tag (32 bits). * - Checksum (32 bits). */ class Packet : public Serializable { public: using ChunksIterator = typename std::vector::const_iterator; public: /** * Struct of an SCTP Packet Common Header. * * @remarks * - This struct is guaranteed to be aligned to 4 bytes. */ struct CommonHeader { uint16_t sourcePort; uint16_t destinationPort; uint32_t verificationTag; uint32_t checksum; }; public: static const size_t CommonHeaderLength{ 12 }; /** * Whether given buffer could be a valid SCTP Packet. * * @remarks * - `bufferLength` must be the exact length of the Packet. * - This check is very lazy. It should NEVER be done before checking if * given buffer is an RTP or RTCP packet. */ static bool IsSctp(const uint8_t* buffer, size_t bufferLength); /** * Parse an SCTP Packet. * * @remarks * - `bufferLength` must be the exact length of the Packet. */ static Packet* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create an SCTP Packet. * * @remarks * - `bufferLength` must be the exact length of the STUN Packet. * - If `transactionId` is not given then a random Transaction ID is * generated. */ static Packet* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Constructor is private because we only want to create Packet instances * via Parse() and Factory(). */ Packet(uint8_t* buffer, size_t bufferLength); public: ~Packet() override; void Dump(int indentation = 0) const final; void Serialize(uint8_t* buffer, size_t bufferLength) final; Packet* Clone(uint8_t* buffer, size_t bufferLength) const final; uint16_t GetSourcePort() const { return ntohs(GetHeaderPointer()->sourcePort); } void SetSourcePort(uint16_t sourcePort); uint16_t GetDestinationPort() const { return ntohs(GetHeaderPointer()->destinationPort); } void SetDestinationPort(uint16_t destinationPort); uint32_t GetVerificationTag() const { return ntohl(GetHeaderPointer()->verificationTag); } void SetVerificationTag(uint32_t verificationTag); uint32_t GetChecksum() const { return ntohl(GetHeaderPointer()->checksum); } void SetChecksum(uint32_t checksum); bool HasChunks() const { return this->chunks.size() > 0; } size_t GetChunksCount() const { return this->chunks.size(); } ChunksIterator ChunksBegin() const { return this->chunks.begin(); } ChunksIterator ChunksEnd() const { return this->chunks.end(); } const Chunk* GetChunkAt(size_t idx) const { if (idx >= this->chunks.size()) { return nullptr; } return this->chunks[idx]; } template const T* GetFirstChunkOfType() const { for (const auto* chunk : this->chunks) { if (typeid(*chunk) == typeid(T)) { return static_cast(chunk); } } return nullptr; } /** * Clone given Chunk into Packet's buffer. * * @remarks * - Once this method is called, the caller may want to free the original * given Chunk (otherwise it will leak since the Packet manages a clone * of it). * * @throw * - MediaSoupError - If `BuildChunkInPlace()` was called before and the * caller didn't invoke `Consolidate()` on the returned Chunk yet. */ void AddChunk(const Chunk* chunk); /** * Build a Chunk within the Packet's buffer and append it to the list of * Chunks. The caller can perform modifications in that Chunk and those * will affect the Packet body where the Chunk is serialzed. The desired * Chunk class type is given via template argument. * * @returns Pointer of the created Chunk specific class. * * @throw * - MediaSoupError - If `BuildChunkInPlace()` was called before and the * caller didn't invoke `Consolidate()` on the returned Chunk yet. * * @remarks * - The caller MUST invoke `Consolidate()` once the Chunk is completed. * - The caller MUST NOT call `BuildChunkInPlace()` while other Chunk is * in progress. * - The caller MUST NOT free the obtained Chunk pointer since it's now * part of the Packet. * - The caller MUST free the obtained Chunk only in case the * `Consolidate()` method on the Chunk throws. * - Method implemented in header file due to C++ template usage. * * @example * ```c++ * auto* initChunk = packet->BuildChunkInPlace(); * ``` */ template T* BuildChunkInPlace() { AssertDoesNotNeedConsolidation(); // The new Chunk will be added after other Chunks in the Packet, this is, // at the end of the Packet, whose length we know it's padded to 4 // bytes, and each Parameter total length is also multiple of 4 bytes. auto* ptr = const_cast(GetBuffer()) + GetLength(); // The remaining length in the buffer is the potential buffer length // of the Chunk. size_t chunkMaxBufferLength = GetBufferLength() - (ptr - GetBuffer()); auto* chunk = T::Factory(ptr, chunkMaxBufferLength); // NOTE: Do not fix/update the Chunk buffer length since the caller // probably wants to modify the Chunk. HandleInPlaceChunk(chunk); return chunk; } /** * Whether `BuildChunkInPlace()` was called and the caller didn't invoke * `Consolidate()` on the returned Chunk yet. */ bool NeedsConsolidation() const { return this->needsConsolidation; } /** * Calculate CRC32C value of the whole Packet and insert it into the * Checksum field. */ void WriteCRC32cChecksum(); /** * Validate CRC32C value in the Checksum field. */ bool ValidateCRC32cChecksum() const; private: /** * NOTE: Return CommonHeader* instead of const CommonHeader* since we may * want to modify its fields. */ CommonHeader* GetHeaderPointer() const { return reinterpret_cast(const_cast(GetBuffer())); } uint8_t* GetChunksPointer() const { return const_cast(GetBuffer()) + Packet::CommonHeaderLength; } virtual void HandleInPlaceChunk(Chunk* chunk) final; virtual void AssertDoesNotNeedConsolidation() const final; private: // Chunks. std::vector chunks; // Whether `BuildChunkInPlace()` was called and the caller didn't invoke // `Consolidate()` on the returned Chunk yet. bool needsConsolidation{ false }; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/Parameter.hpp ================================================ #ifndef MS_RTC_SCTP_PARAMETER_HPP #define MS_RTC_SCTP_PARAMETER_HPP #include "common.hpp" #include "RTC/SCTP/packet/TLV.hpp" #include #include namespace RTC { namespace SCTP { /** * SCTP Parameter. * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Parameter Type | Parameter Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Parameter Value / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Parameter Type (16 bits). * - Parameter Length (16 bits): Total length of the Parameter, including * the Parameter Type, Parameter Length and Parameter Value fields * (padding is excluded). Thus, a Parameter with a zero-length Parameter * Value field would have a Parameter Length field of 4. * - Parameter Value (variable length). * - Padding: Bytes of padding to make the Parameter total length be * multiple of 4 bytes. */ // Forward declaration. class Chunk; class Parameter : public TLV { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parameter Type. */ enum class ParameterType : uint16_t { HEARTBEAT_INFO = 0x0001, IPV4_ADDRESS = 0x0005, IPV6_ADDRESS = 0x0006, STATE_COOKIE = 0x0007, UNRECOGNIZED_PARAMETER = 0x0008, COOKIE_PRESERVATIVE = 0x0009, SUPPORTED_ADDRESS_TYPES = 0x000C, FORWARD_TSN_SUPPORTED = 0xC000, // Type 49152, RFC 3758 SUPPORTED_EXTENSIONS = 0x8008, // Type 32776, RFC 5061 OUTGOING_SSN_RESET_REQUEST = 0x000D, // Type 13, RFC 6525 INCOMING_SSN_RESET_REQUEST = 0x000E, // Type 14, RFC 6525 SSN_TSN_RESET_REQUEST = 0x000F, // Type 15, RFC 6525 RECONFIGURATION_RESPONSE = 0x0010, // Type 16, RFC 6525 ADD_OUTGOING_STREAMS_REQUEST = 0x0011, // Type 17, RFC 6525 ADD_INCOMING_STREAMS_REQUEST = 0x0012, // Type 18, RFC 6525 ZERO_CHECKSUM_ACCEPTABLE = 0x8001, // Type 32769, RFC 9653 }; /** * Action that is taken if the processing endpoint does not recognize the * Parameter. */ enum class ActionForUnknownParameterType : uint8_t { STOP = 0b00, STOP_AND_REPORT = 0b01, SKIP = 0b10, SKIP_AND_REPORT = 0b11 }; /** * Struct of an SCTP Parameter Header. * * @remarks * - This struct is guaranteed to be aligned to 2 bytes. */ struct ParameterHeader { ParameterType type; /** * The value of the Parameter Length field, which represents the total * length of the Parameter in bytes, including the Parameter Type, * Parameter Length and Parameter Value fields. So if the Parameter * Value field is zero-length, the Length field must be 4. The * Parameter Length field does not count any padding. */ uint16_t length; }; public: static const size_t ParameterHeaderLength{ 4 }; public: /** * Whether given buffer could be a a valid Parameter. * * @param buffer * @param bufferLength - Can be greater than real Parameter length. * @param parameterType - If given buffer is a valid Parameter then * `parameterType` is rewritten to parsed ParameterType. * @param parameterLength - If given buffer is a valid Parameter then * `parameterLength` is rewritten to the value of the Parameter Length * field. * @param padding - If given buffer is a valid Parameter then `padding` * is rewritten to the number of padding bytes in the Parameter (only * the necessary ones to make total length multiple of 4). */ static bool IsParameter( const uint8_t* buffer, size_t bufferLength, ParameterType& parameterType, uint16_t& parameterLength, uint8_t& padding); static const std::string& ParameterTypeToString(ParameterType parameterType); private: static const std::unordered_map ParameterType2String; protected: /** * Constructor is protected because we only want to create Parameter * instances via Parse() and Factory() in subclasses. */ Parameter(uint8_t* buffer, size_t bufferLength); public: ~Parameter() override; void Dump(int indentation = 0) const override = 0; Parameter* Clone(uint8_t* buffer, size_t bufferLength) const override = 0; virtual ParameterType GetType() const final { return static_cast(ntohs(static_cast(GetHeaderPointer()->type))); } /** * False by default. UnknownParameter class overrides this method to * return true instead. */ virtual bool HasUnknownType() const { return false; } virtual ActionForUnknownParameterType GetActionForUnknownParameterType() const final { return static_cast(GetBuffer()[0] >> 6); } protected: /** * Subclasses must invoke this method within their Dump() method. */ void DumpCommon(int indentation) const final; virtual void SoftSerialize(const uint8_t* buffer) final; virtual Parameter* SoftClone(const uint8_t* buffer) const = 0; virtual void SoftCloneInto(Parameter* parameter) const final; virtual void InitializeHeader(ParameterType parameterType, uint16_t lengthFieldValue) final; /** * Parameter subclasses with header bigger than default one (4 bytes) * must override this method and return their header length (excluding * variable-length field considered "value"). */ size_t GetHeaderLength() const override { return Parameter::ParameterHeaderLength; } private: /** * NOTE: Return ParameterHeader* instead of const ParameterHeader* since * we may want to modify its fields. */ virtual ParameterHeader* GetHeaderPointer() const final { return reinterpret_cast(const_cast(GetBuffer())); } virtual void SetType(ParameterType parameterType) final { GetHeaderPointer()->type = static_cast(htons(static_cast(parameterType))); } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/TLV.hpp ================================================ #ifndef MS_RTC_SCTP_TLV_HPP #define MS_RTC_SCTP_TLV_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/Serializable.hpp" namespace RTC { namespace SCTP { /** * SCTP TLV (Type-Length-Value). * * This is the base class of all items in an SCTP Packet, this is: * - SCTP Chunk, * - SCTP Parameter, and * - SCTP Error Cause. * * All those items have the same Length field and 4-byte padded length. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Value / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ class TLV : public Serializable { public: static const size_t TLVHeaderLength{ 4 }; public: /** * Whether given buffer could be a a valid SCTP TLV. */ static bool IsTLV( const uint8_t* buffer, size_t bufferLength, uint16_t& itemLength, uint8_t& padding); protected: TLV(uint8_t* buffer, size_t bufferLength); public: ~TLV() override; protected: /** * Subclasses must invoke this method within their Dump() method. */ virtual void DumpCommon(int indentation) const; virtual void InitializeTLVHeader(uint16_t lengthFieldValue) final; /** * Subclasses with header bigger than default one (4 bytes) must override * this method and return their header length (excluding variable-length * field considered "value", Optional/Variable-Length * Parameters and Error Causes). */ virtual size_t GetHeaderLength() const { return TLV::TLVHeaderLength; } /** * The value of the Length field, which includes the length of the header * and content (padding excluded). */ virtual uint16_t GetLengthField() const final { return Utils::Byte::Get2Bytes(GetBuffer(), 2); } /** * A pointer to the position in the buffer where the variable-length value * (if any) starts or should start. */ virtual uint8_t* GetVariableLengthValuePointer() const final { return const_cast(GetBuffer()) + GetHeaderLength(); } /** * Whether this item contains a variable-length value. * * @see GetVariableLengthValue() */ virtual bool HasVariableLengthValue() const final { return GetLengthField() > GetHeaderLength(); } /** * Variable-length value of this item. * * @remarks * - The variable-length value starts after the fixed header, which can be * different and have different length in each item definition. * - In the case of SCTP Chunk class and subclasses (which implements this * class) we assume that a Chunk having variable-length value does not * have Parameters or Error Causes. */ virtual const uint8_t* GetVariableLengthValue() const final { if (!HasVariableLengthValue()) { return nullptr; } return GetVariableLengthValuePointer(); } /** * Set the variable-length value. It copies the given value into the * the variable-length value of the item and updates both the length of * the Serializable and the Length field. * * @throw MediaSoupTypeError - If given `valueLength` is higher than * available length. * * @see GetVariableLengthValue() */ virtual void SetVariableLengthValue(const uint8_t* value, size_t valueLength) final; /** * The length of the variable-length value. */ virtual uint16_t GetVariableLengthValueLength() const final { if (!HasVariableLengthValue()) { return 0u; } return GetLengthField() - GetHeaderLength(); } /** * Set the length of the variable-length value. It doesn't copy any value * into the variable-length value. This method is used in items that have * variable-length value but it doesn't consist on a buffer + length, but * instead is an structure with fields (with variable length). * * @see GetVariableLengthValue() */ virtual void SetVariableLengthValueLength(size_t valueLength) final; /** * This method doesn't really add an item into the item (that must be done * by each subcass) but updates the length of the Serializable and the * value of the Length field by incrementing it with the length of the * given item. */ virtual void AddItem(const TLV* item) final; private: /** * @throw MediaSoupTypeError - If given `length` is higher than maximum * allowed one (65535). */ virtual void SetLengthField(size_t lengthField) final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/UserData.hpp ================================================ #ifndef MS_RTC_SCTP_USER_DATA_HPP #define MS_RTC_SCTP_USER_DATA_HPP #include "common.hpp" #include #include namespace RTC { namespace SCTP { /** * Represents user data extracted from a DATA or I_DATA Chunk. */ class UserData { public: UserData( uint16_t streamId, uint16_t ssn, uint32_t mid, uint32_t fsn, uint32_t ppid, std::vector payload, bool isBeginning, bool isEnd, bool isUnordered); /** * Move constructor. No need to do anything special since std::vector * already implements move. */ UserData(UserData&& other) = default; /** * Move assignment. No need to do anything special since std::vector * already implements move. */ UserData& operator=(UserData&& other) = default; /** * Copy constructor disabled. */ UserData(const UserData&) = delete; /** * Copy assignment disabled. */ UserData& operator=(const UserData&) = delete; bool operator==(const UserData& other) const { return ( this->streamId == other.streamId && this->ssn == other.ssn && this->mid == other.mid && this->fsn == other.fsn && this->ppid == other.ppid && this->payload == other.payload && this->isBeginning == other.isBeginning && this->isEnd == other.isEnd && this->isUnordered == other.isUnordered); } ~UserData(); public: void Dump(int indentation = 0) const; /** * Stream Identifier (in DATA and I_DATA chunks). */ uint16_t GetStreamId() const { return this->streamId; } /** * Stream Sequence Number (only in DATA chunks). */ uint16_t GetStreamSequenceNumber() const { return this->ssn; } /** * Message Identifier (MID) (only in I_DATA chunks). */ uint32_t GetMessageId() const { return this->mid; } /** * Fragment Sequence Number (FSN) (only in I_DATA chunks). */ uint32_t GetFragmentSequenceNumber() const { return this->fsn; } uint32_t GetPayloadProtocolId() const { return this->ppid; } std::vector& GetPayload() { return this->payload; } size_t GetPayloadLength() const { return this->payload.size(); } /** * Useful to extract the payload and its ownership when destructing the * UserData. * * @remarks * - && at the end means that it can only be called from a rvalue. * * @usage * ```c++ * const auto payload = std::move(userData).ReleasePayload(); * ``` */ std::vector ReleasePayload() && { // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move) return std::move(this->payload); } UserData Clone() const { return UserData( this->streamId, this->ssn, this->mid, this->fsn, this->ppid, this->payload, this->isBeginning, this->isEnd, this->isUnordered); } bool IsBeginning() const { return this->isBeginning; } bool IsEnd() const { return this->isEnd; } bool IsUnordered() const { return this->isUnordered; } private: uint16_t streamId; uint16_t ssn; uint32_t mid; uint32_t fsn; uint32_t ppid; std::vector payload; bool isBeginning; bool isEnd; bool isUnordered; }; /** * For Catch2 to print it nicely. */ inline std::ostream& operator<<(std::ostream& os, const UserData& d) { return os << "{streamId:" << d.GetStreamId() << ", ssn:" << d.GetStreamSequenceNumber() << ", mid:" << d.GetMessageId() << ", fsn:" << d.GetFragmentSequenceNumber() << ", ppid:" << d.GetPayloadProtocolId() << ", payloadLen:" << d.GetPayloadLength() << ", B:" << d.IsBeginning() << ", E:" << d.IsEnd() << ", U:" << d.IsUnordered() << "}"; } } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp ================================================ #ifndef MS_RTC_SCTP_ABORT_ASSOCIATION_ERROR_CHUNK_HPP #define MS_RTC_SCTP_ABORT_ASSOCIATION_ERROR_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Abort Association Chunk (ABORT) (6). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 6 | Reserved |T| Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / zero or more Error Causes / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 6. * - T bit (1 bit): The T bit is set to 0 if the sender filled in the * Verification Tag expected by the peer. If the Verification Tag is * reflected, the T bit MUST be set to 1. Reflecting means that the sent * Verification Tag is the same as the received one. * - Length (16 bits). * * Optional Variable-Length Error Causes (anyone). */ // Forward declaration. class Packet; class AbortAssociationChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a AbortAssociationChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static AbortAssociationChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a AbortAssociationChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static AbortAssociationChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a AbortAssociationChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static AbortAssociationChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ AbortAssociationChunk(uint8_t* buffer, size_t bufferLength); public: ~AbortAssociationChunk() override; void Dump(int indentation = 0) const final; AbortAssociationChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool GetT() const { return GetBit0(); } void SetT(bool flag); bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return true; } protected: AbortAssociationChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/AnyDataChunk.hpp ================================================ #ifndef MS_RTC_SCTP_ANY_DATA_CHUNK_HPP #define MS_RTC_SCTP_ANY_DATA_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/UserData.hpp" namespace RTC { namespace SCTP { /** * Base class for DataChunk and IDataChunk. */ class AnyDataChunk : public Chunk { protected: AnyDataChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { } public: /** * The (I)mmediate bit (in DATA and I_DATA chunks). */ virtual bool GetI() const = 0; /** * The (U)nordered bit (in DATA and I_DATA chunks). */ virtual bool GetU() const = 0; /** * The (B)eginning fragment bit (in DATA and I_DATA chunks). */ virtual bool GetB() const = 0; /** * The (E)nding fragment bit (in DATA and I_DATA chunks). */ virtual bool GetE() const = 0; /** * TSN (in DATA and I_DATA chunks). */ virtual uint32_t GetTsn() const = 0; /** * Stream Identifier (in DATA and I_DATA chunks). */ virtual uint16_t GetStreamId() const = 0; /** * Stream Sequence Number (only in DATA chunks). */ virtual uint16_t GetStreamSequenceNumber() const = 0; /** * Message Identifier (MID) (only in I_DATA chunks). */ virtual uint32_t GetMessageId() const = 0; /** * Fragment Sequence Number (FSN) (only in I_DATA chunks). */ virtual uint32_t GetFragmentSequenceNumber() const = 0; /** * Payload Protocol Identifier (PPID) (in DATA and I_DATA chunks). */ virtual uint32_t GetPayloadProtocolId() const = 0; virtual bool HasUserDataPayload() const = 0; virtual const uint8_t* GetUserDataPayload() const = 0; virtual uint16_t GetUserDataPayloadLength() const = 0; virtual UserData MakeUserData() const = 0; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp ================================================ #ifndef MS_RTC_SCTP_ANY_FORWARD_TSN_CHUNK_HPP #define MS_RTC_SCTP_ANY_FORWARD_TSN_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include #include namespace RTC { namespace SCTP { /** * Base class for ForwardTsnChunk and IForwardTsnChunk. */ class AnyForwardTsnChunk : public Chunk { public: struct SkippedStream { SkippedStream(uint16_t streamId, uint16_t ssn) : streamId(streamId), ssn(ssn), mid(0), unordered(false) { } SkippedStream(bool unordered, uint16_t streamId, uint32_t mid) : streamId(streamId), ssn(0), mid(mid), unordered(unordered) { } uint16_t streamId; /** * Only set for FORWARD_TSN. */ uint16_t ssn; /** * Only set for I_FORWARD_TSN. */ uint32_t mid; /** * Only set for I_FORWARD_TSN. */ bool unordered; bool operator==(const SkippedStream& other) const { return streamId == other.streamId && ssn == other.ssn && mid == other.mid && unordered == other.unordered; } }; protected: AnyForwardTsnChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { } public: virtual uint32_t GetNewCumulativeTsn() const = 0; virtual uint16_t GetNumberOfSkippedStreams() const = 0; virtual std::vector GetSkippedStreams() const = 0; }; /** * For logging purposes in Catch2 tests. */ inline std::ostream& operator<<(std::ostream& os, const AnyForwardTsnChunk::SkippedStream& s) { return os << "{streamId:" << s.streamId << ", ssn:" << s.ssn << ", mid:" << s.mid << ", unordered:" << s.unordered << "}"; } } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/AnyInitChunk.hpp ================================================ #ifndef MS_RTC_SCTP_ANY_INIT_CHUNK_HPP #define MS_RTC_SCTP_ANY_INIT_CHUNK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * Base class for InitChunk and InitAckChunk. */ class AnyInitChunk : public Chunk { protected: AnyInitChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { } public: virtual uint32_t GetInitiateTag() const = 0; virtual uint32_t GetAdvertisedReceiverWindowCredit() const = 0; virtual uint16_t GetNumberOfOutboundStreams() const = 0; virtual uint16_t GetNumberOfInboundStreams() const = 0; virtual uint32_t GetInitialTsn() const = 0; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/CookieAckChunk.hpp ================================================ #ifndef MS_RTC_SCTP_COOKIE_ACK_CHUNK_HPP #define MS_RTC_SCTP_COOKIE_ACK_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Cookie Acknowledgement Chunk (COOKIE_ACK) (11). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 11 | Chunk Flags | Length = 4 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 11. * - Flags (8 bits): All set to 0. * - Length (16 bits): 4. */ // Forward declaration. class Packet; class CookieAckChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a CookieAckChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static CookieAckChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a CookieAckChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static CookieAckChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a CookieAckChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static CookieAckChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ CookieAckChunk(uint8_t* buffer, size_t bufferLength); public: ~CookieAckChunk() override; void Dump(int indentation = 0) const final; CookieAckChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } protected: CookieAckChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/CookieEchoChunk.hpp ================================================ #ifndef MS_RTC_SCTP_COOKIE_ECHO_CHUNK_HPP #define MS_RTC_SCTP_COOKIE_ECHO_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Cookie Echo Chunk (COOKIE_ECHO) (10). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 10 | Chunk Flags | Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Cookie / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 10. * - Flags (8 bits): All set to 0. * - Length (16 bits). * - Cookie (variable length). */ // Forward declaration. class Packet; class CookieEchoChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a CookieEchoChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static CookieEchoChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a CookieEchoChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static CookieEchoChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a CookieEchoChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static CookieEchoChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ CookieEchoChunk(uint8_t* buffer, size_t bufferLength); public: ~CookieEchoChunk() override; void Dump(int indentation = 0) const final; CookieEchoChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } bool HasCookie() const { return HasVariableLengthValue(); } const uint8_t* GetCookie() const { return GetVariableLengthValue(); } uint16_t GetCookieLength() const { return GetVariableLengthValueLength(); } void SetCookie(const uint8_t* cookie, uint16_t cookieLength); protected: CookieEchoChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/DataChunk.hpp ================================================ #ifndef MS_RTC_SCTP_DATA_CHUNK_HPP #define MS_RTC_SCTP_DATA_CHUNK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/AnyDataChunk.hpp" #include namespace RTC { namespace SCTP { /** * SCTP Payload Data Chunk (DATA) (0). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 0 | Res |I|U|B|E| Length = Variable | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream Identifier S | Stream Sequence Number n | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Payload Protocol Identifier | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / User Data (seq n of Stream S) / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 0. * - Res (4 bits): All set to 0. * - I bit (1 bit): The (I)mmediate bit MAY be set by the sender whenever * the sender of a DATA chunk can benefit from the corresponding SACK * chunk being sent back without delay. * - U bit (1 bit): The (U)nordered bit, if set to 1, indicates that this * is an unordered DATA chunk, and there is no Stream Sequence Number * assigned to this DATA chunk. * - B bit (1 bit): The (B)eginning fragment bit, if set, indicates the * first fragment of a user message. * - E bit (1 bit): The (E)nding fragment bit, if set, indicates the last * fragment of a user message. * - Length (16 bits): This field indicates the length of the DATA chunk in * bytes from the beginning of the type field to the end of the User Data * field excluding any padding. A DATA chunk with one byte of user data * will have the Length field set to 17 (indicating 17 bytes). A DATA * chunk with a User Data field of length L will have the Length field * set to (16 + L) (indicating 16 + L bytes) where L MUST be greater than * 0. * - TSN (32 bits): This value represents the TSN for this DATA chunk. The * valid range of TSN is from 0 to 4294967295 (232 - 1). TSN wraps back * to 0 after reaching 4294967295. * - Stream Identifier S (16 bits): Identifies the stream to which the * following user data belongs. * - Stream Sequence Number n (16 bits): This value represents the Stream * Sequence Number of the following user data within the stream S. Valid * range is 0 to 65535. When a user message is fragmented by SCTP for * transport, the same Stream Sequence Number MUST be carried in each of * the fragments of the message. * - Payload Protocol Identifier (PPID) (32 bits): This value represents an * application (or upper layer) specified protocol identifier. * - User Data (variable length): This is the payload user data. The * implementation MUST pad the end of the data to a 4-byte boundary with * all zero bytes. Any padding MUST NOT be included in the Length field. */ // Forward declaration. class Packet; class DataChunk : public AnyDataChunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: static const size_t DataChunkHeaderLength{ 16 }; public: /** * Parse a DataChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static DataChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a DataChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static DataChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a DataChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static DataChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ DataChunk(uint8_t* buffer, size_t bufferLength); public: ~DataChunk() override; void Dump(int indentation = 0) const final; DataChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } bool GetI() const final { return GetBit3(); } void SetI(bool flag); bool GetU() const final { return GetBit2(); } void SetU(bool flag); bool GetB() const final { return GetBit1(); } void SetB(bool flag); bool GetE() const final { return GetBit0(); } void SetE(bool flag); uint32_t GetTsn() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetTsn(uint32_t value); uint16_t GetStreamId() const final { return Utils::Byte::Get2Bytes(const_cast(GetBuffer()), 8); } void SetStreamId(uint16_t value); uint16_t GetStreamSequenceNumber() const final { return Utils::Byte::Get2Bytes(const_cast(GetBuffer()), 10); } void SetStreamSequenceNumber(uint16_t value); /** * @remarks Only in I_DATA chunks. */ uint32_t GetMessageId() const final { return 0; } uint32_t GetPayloadProtocolId() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 12); } /** * @remarks Only in I_DATA chunks. */ uint32_t GetFragmentSequenceNumber() const final { return 0; } void SetPayloadProtocolId(uint32_t value); bool HasUserDataPayload() const final { return HasVariableLengthValue(); } const uint8_t* GetUserDataPayload() const final { return GetVariableLengthValue(); } uint16_t GetUserDataPayloadLength() const final { return GetVariableLengthValueLength(); } void SetUserDataPayload(const uint8_t* userDataPayload, uint16_t userDataPayloadLength); UserData MakeUserData() const final { const auto* userData = GetUserDataPayload(); const uint16_t userDataLen = GetUserDataPayloadLength(); std::vector payload(userData, userData + userDataLen); return UserData( GetStreamId(), GetStreamSequenceNumber(), GetMessageId(), GetFragmentSequenceNumber(), GetPayloadProtocolId(), std::move(payload), GetB(), GetE(), GetU()); } void SetUserData(UserData userData); protected: DataChunk* SoftClone(const uint8_t* buffer) const final; /** * We need to override this method since this Chunk has a variable-length * value and the fixed header doesn't have default length. */ size_t GetHeaderLength() const final { return DataChunk::DataChunkHeaderLength; } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp ================================================ #ifndef MS_RTC_SCTP_FORWARD_TSN_CHUNK_HPP #define MS_RTC_SCTP_FORWARD_TSN_CHUNK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Forward Cumulative TSN Chunk (FORWARD_TSN) (192) * * @see RFC 3758. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 192 | Flags = 0x00 | Length = Variable | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | New Cumulative TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream-1 | Stream Sequence-1 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ / * / \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream-N | Stream Sequence-N | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 192. * - Flags: All set to 0. * - Length (16 bits). * - New Cumulative TSN (32 bits): This indicates the new cumulative TSN to * the data receiver. * - Stream-N (16 bits): Stream number that was skipped by this FWD-TSN. * - Stream Sequence-N (16 bit): Sequence number associated with the stream * that was skipped. */ // Forward declaration. class Packet; class ForwardTsnChunk : public AnyForwardTsnChunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: static const size_t ForwardTsnChunkHeaderLength{ 8 }; public: /** * Parse a ForwardTsnChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static ForwardTsnChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a ForwardTsnChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static ForwardTsnChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a ForwardTsnChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static ForwardTsnChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ ForwardTsnChunk(uint8_t* buffer, size_t bufferLength); public: ~ForwardTsnChunk() override; void Dump(int indentation = 0) const final; ForwardTsnChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } uint32_t GetNewCumulativeTsn() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetNewCumulativeTsn(uint32_t value); uint16_t GetNumberOfSkippedStreams() const final { return GetVariableLengthValueLength() / 4; } std::vector GetSkippedStreams() const final; void AddSkippedStream(const AnyForwardTsnChunk::SkippedStream& skippedStream); protected: ForwardTsnChunk* SoftClone(const uint8_t* buffer) const final; /** * We need to override this method since this Chunk has a variable-length * value and the fixed header doesn't have default length. */ size_t GetHeaderLength() const final { return ForwardTsnChunk::ForwardTsnChunkHeaderLength; } private: uint16_t GetSkippedStreamIdAt(uint16_t idx) const { return Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 4)); } uint16_t GetStreamSequenceAt(uint16_t idx) const { return Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 4) + 2); } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp ================================================ #ifndef MS_RTC_SCTP_HEARTBEAT_ACK_CHUNK_HPP #define MS_RTC_SCTP_HEARTBEAT_ACK_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Heartbeat Acknowledgement Chunk (HEARTBEAT_ACK) (5) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 5 | Chunk Flags | Heartbeat Ack Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Heartbeat Information TLV (Variable-Length) / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 5. * - Flags (8 bits): All set to 0. * - Length (16 bits). * - Heartbeat Information (variable length). * * Mandatory Variable-Length Parameters: * - Heartbeat Info (1), mandatory. */ // Forward declaration. class Packet; class HeartbeatAckChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a HeartbeatAckChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static HeartbeatAckChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a HeartbeatAckChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static HeartbeatAckChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a HeartbeatAckChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static HeartbeatAckChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ HeartbeatAckChunk(uint8_t* buffer, size_t bufferLength); public: ~HeartbeatAckChunk() override; void Dump(int indentation = 0) const final; HeartbeatAckChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return true; } bool CanHaveErrorCauses() const final { return false; } protected: HeartbeatAckChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp ================================================ #ifndef MS_RTC_SCTP_HEARTBEAT_REQUEST_CHUNK_HPP #define MS_RTC_SCTP_HEARTBEAT_REQUEST_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Heartbeat Request Chunk (HEARTBEAT_REQUEST) (4). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 4 | Chunk Flags | Heartbeat Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Heartbeat Information TLV (Variable-Length) / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 4. * - Flags (8 bits): All set to 0. * - Length (16 bits). * - Heartbeat Information (variable length). * * Mandatory Variable-Length Parameters: * - Heartbeat Info (1), mandatory. */ // Forward declaration. class Packet; class HeartbeatRequestChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a HeartbeatRequestChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static HeartbeatRequestChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a HeartbeatRequestChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static HeartbeatRequestChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a HeartbeatRequestChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static HeartbeatRequestChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ HeartbeatRequestChunk(uint8_t* buffer, size_t bufferLength); public: ~HeartbeatRequestChunk() override; void Dump(int indentation = 0) const final; HeartbeatRequestChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return true; } bool CanHaveErrorCauses() const final { return false; } protected: HeartbeatRequestChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/IDataChunk.hpp ================================================ #ifndef MS_RTC_SCTP_I_DATA_CHUNK_HPP #define MS_RTC_SCTP_I_DATA_CHUNK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/AnyDataChunk.hpp" #include namespace RTC { namespace SCTP { /** * SCTP I-Data Chunk (I_DATA) (64). * * @see RFC 8260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 64 | Res |I|U|B|E| Length = Variable | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream Identifier | (Reserved) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Message Identifier | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Payload Protocol Identifier / Fragment Sequence Number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / User Data / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 64. * - Res (4 bits): All set to 0. * - I bit (1 bit): The (I)mmediate bit, if set, indicates that the * receiver SHOULD NOT delay the sending of the corresponding SACK chunk. * - U bit (1 bit): The (U)nordered bit, if set, indicates the user message * is unordered. * - B bit (1 bit): The (B)eginning fragment bit, if set, indicates the * first fragment of a user message. * - E bit (1 bit): The (E)nding fragment bit, if set, indicates the last * fragment of a user message. * - Length (16 bits): This field indicates the length of the I-DATA chunk in * bytes from the beginning of the type field to the end of the User Data * field excluding any padding. * - TSN (32 bits): This value represents the TSN for this I-DATA chunk. * - Stream Identifier (16 bits): Identifies the stream to which the user * data belongs. * - Reserved (16 bits): All set to zero. * - Message Identifier (MID) (32 bits): The MID is the same for all * fragments of a user message. It is used to determine which fragments * (enumerated by the FSN) belong to the same user message. For ordered * user messages, the MID is also used by the SCTP receiver to deliver * the user messages in the correct order to the upper layer. * - Payload Protocol Identifier (PPID) / Fragment Sequence Number (FSN) * (32 bits): If the B bit is set, this field contains the PPID of the * user message. If the B bit is not set, this field contains the FSN. * The FSN is used to enumerate all fragments of a single user message, * starting from 0 and incremented by 1. The last fragment of a message * MUST have the E bit set. * - User Data (variable length): This is the payload user data. */ // Forward declaration. class Packet; class IDataChunk : public AnyDataChunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: static const size_t IDataChunkHeaderLength{ 20 }; public: /** * Parse a IDataChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static IDataChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a IDataChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static IDataChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a IDataChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static IDataChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ IDataChunk(uint8_t* buffer, size_t bufferLength); public: ~IDataChunk() override; void Dump(int indentation = 0) const final; IDataChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } bool GetI() const final { return GetBit3(); } void SetI(bool flag); bool GetU() const final { return GetBit2(); } void SetU(bool flag); bool GetB() const final { return GetBit1(); } void SetB(bool flag); bool GetE() const final { return GetBit0(); } void SetE(bool flag); uint32_t GetTsn() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetTsn(uint32_t value); uint16_t GetStreamId() const final { return Utils::Byte::Get2Bytes(const_cast(GetBuffer()), 8); } void SetStreamId(uint16_t value); /** * @remarks Only in DATA chunks. */ uint16_t GetStreamSequenceNumber() const final { return 0; } uint32_t GetMessageId() const final { return Utils::Byte::Get4Bytes(const_cast(GetBuffer()), 12); } void SetMessageId(uint32_t value); uint32_t GetPayloadProtocolId() const final { if (GetB()) { return Utils::Byte::Get4Bytes(GetBuffer(), 16); } else { return 0; } } /** * @throw MediaSoupError - If the B bit is not set. */ void SetPayloadProtocolId(uint32_t value); uint32_t GetFragmentSequenceNumber() const final { if (!GetB()) { return Utils::Byte::Get4Bytes(GetBuffer(), 16); } else { return 0; } } /** * @throw MediaSoupError - If the B bit is set. */ void SetFragmentSequenceNumber(uint32_t value); bool HasUserDataPayload() const final { return HasVariableLengthValue(); } const uint8_t* GetUserDataPayload() const final { return GetVariableLengthValue(); } uint16_t GetUserDataPayloadLength() const final { return GetVariableLengthValueLength(); } void SetUserDataPayload(const uint8_t* userDataPayload, uint16_t userDataPayloadLength); UserData MakeUserData() const final { const auto* userData = GetUserDataPayload(); const uint16_t userDataLen = GetUserDataPayloadLength(); std::vector payload(userData, userData + userDataLen); return UserData( GetStreamId(), GetStreamSequenceNumber(), GetMessageId(), GetFragmentSequenceNumber(), GetPayloadProtocolId(), std::move(payload), GetB(), GetE(), GetU()); } void SetUserData(UserData userData); protected: IDataChunk* SoftClone(const uint8_t* buffer) const final; /** * We need to override this method since this Chunk has a variable-length * value and the fixed header doesn't have default length. */ size_t GetHeaderLength() const final { return IDataChunk::IDataChunkHeaderLength; } private: void SetReserved(); }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp ================================================ #ifndef MS_RTC_SCTP_I_FORWARD_TSN_CHUNK_HPP #define MS_RTC_SCTP_I_FORWARD_TSN_CHUNK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" namespace RTC { namespace SCTP { /** * SCTP I-Forward Cumulative TSN Chunk (I_FORWARD_TSN) (194) * * @see RFC 8260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 194 | Flags = 0x00 | Length = Variable | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | New Cumulative TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream Identifier | (Reserved) |U| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Message Identifier | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / / * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream Identifier | (Reserved) |U| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Message Identifier | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 194. * - Flags: All set to 0. * - Length (16 bits). * - New Cumulative TSN (32 bits): This indicates the new cumulative TSN to * the data receiver. * - Stream Identifier (SID) (16 bits): This field holds the stream number * this entry refers to. * - Reserved (15 bits). * - U bit (1 bit): The U bit specifies if the Message Identifier of this * entry refers to unordered messages (U bit is set) or ordered messages * (U bit is not set). * - Message Identifier (MID) (32 bits): This field holds the largest * Message Identifier for ordered or unordered messages indicated by the * U bit that was skipped for the stream specified by the Stream * Identifier. For ordered messages, this is similar to the FORWARD-TSN * chunk, just replacing the 16-bit SSN by the 32-bit MID. */ // Forward declaration. class Packet; class IForwardTsnChunk : public AnyForwardTsnChunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: static const size_t IForwardTsnChunkHeaderLength{ 8 }; public: /** * Parse a IForwardTsnChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static IForwardTsnChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a IForwardTsnChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static IForwardTsnChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a IForwardTsnChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static IForwardTsnChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ IForwardTsnChunk(uint8_t* buffer, size_t bufferLength); public: ~IForwardTsnChunk() override; void Dump(int indentation = 0) const final; IForwardTsnChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } uint32_t GetNewCumulativeTsn() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetNewCumulativeTsn(uint32_t value); uint16_t GetNumberOfSkippedStreams() const final { return GetVariableLengthValueLength() / 8; } std::vector GetSkippedStreams() const final; void AddSkippedStream(const AnyForwardTsnChunk::SkippedStream& skippedStream); protected: IForwardTsnChunk* SoftClone(const uint8_t* buffer) const final; /** * We need to override this method since this Chunk has a variable-length * value and the fixed header doesn't have default length. */ size_t GetHeaderLength() const final { return IForwardTsnChunk::IForwardTsnChunkHeaderLength; } private: uint16_t GetSkippedStreamIdAt(uint16_t idx) const { return Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 8)); } bool GetUFlagAt(uint16_t idx) const { return (Utils::Byte::Get1Byte(GetVariableLengthValuePointer(), (idx * 8) + 3) & 0x01) != 0; } uint32_t GetMessageIdentifierAt(uint16_t idx) const { return Utils::Byte::Get4Bytes(GetVariableLengthValuePointer(), (idx * 8) + 4); } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/InitAckChunk.hpp ================================================ #ifndef MS_RTC_SCTP_INIT_ACK_CHUNK_HPP #define MS_RTC_SCTP_INIT_ACK_CHUNK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/chunks/AnyInitChunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Initiation Acknowledgement Chunk (INIT_ACK) (2). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 2 | Chunk Flags | Chunk Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Initiate Tag | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Advertised Receiver Window Credit (a_rwnd) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Number of Outbound Streams | Number of Inbound Streams | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Initial TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Optional/Variable-Length Parameters / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 2. * - Flags (8 bits): All set to 0. * - Initiate Tag (32 bits): The receiver of the INIT ACK chunk records the * value of the Initiate Tag parameter. This value MUST be placed into * the Verification Tag field of every SCTP packet that the receiver of * the INIT ACK chunk transmits within this association. * - Advertised Receiver Window Credit (a_rwnd) (32 bits): This value * represents the dedicated buffer space, in number of bytes, the sender * of the INIT ACK chunk has reserved in association with this window. * The Advertised Receiver Window Credit MUST NOT be smaller than 1500. * - Number of Outbound Streams (OS) (16 bits): Defines the number of * outbound streams the sender of this INIT ACK chunk wishes to create in * this association. The value of 0 MUST NOT be used. * - Number of Inbound Streams (MIS) (16 bits): Defines the maximum number * of streams the sender of this INIT ACK chunk allows the peer end to * create in this association. The value 0 MUST NOT be used. * - Initial TSN (I-TSN) (32 bits): Defines the TSN that the sender of the * INIT ACK chunk will use initially. * * Variable-Length Parameters: * - State Cookie (7), mandatory. * - IPv4 Address (5), optional. * - IPv6 Address (6), optional. * - Unrecognized Parameter (8), optional. * - Reserved for ECN Capable (32768, 0x8000), optional. * - Host Name Address (11), deprecated: The receiver of an INIT chunk or an * INIT ACK containing a Host Name Address parameter MUST send an ABORT * chunk and MAY include an "Unresolvable Address" error cause. */ // Forward declaration. class Packet; class InitAckChunk : public AnyInitChunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: static const size_t InitAckChunkHeaderLength{ 20 }; public: /** * Parse a InitAckChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static InitAckChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a InitAckChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static InitAckChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a InitAckChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static InitAckChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ InitAckChunk(uint8_t* buffer, size_t bufferLength); public: ~InitAckChunk() override; void Dump(int indentation = 0) const final; InitAckChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return true; } bool CanHaveErrorCauses() const final { return false; } uint32_t GetInitiateTag() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetInitiateTag(uint32_t value); uint32_t GetAdvertisedReceiverWindowCredit() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 8); } void SetAdvertisedReceiverWindowCredit(uint32_t value); uint16_t GetNumberOfOutboundStreams() const final { return Utils::Byte::Get2Bytes(GetBuffer(), 12); } void SetNumberOfOutboundStreams(uint16_t value); uint16_t GetNumberOfInboundStreams() const final { return Utils::Byte::Get2Bytes(GetBuffer(), 14); } void SetNumberOfInboundStreams(uint16_t value); uint32_t GetInitialTsn() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 16); } void SetInitialTsn(uint32_t value); protected: InitAckChunk* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Chunk doesn't * have variable-length value (despite the fixed header doesn't have * default length). Optional/Variable-Length Parameters and/or Error * Causes don't account here. */ size_t GetHeaderLength() const final { return InitAckChunk::InitAckChunkHeaderLength; } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/InitChunk.hpp ================================================ #ifndef MS_RTC_SCTP_INIT_CHUNK_HPP #define MS_RTC_SCTP_INIT_CHUNK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/chunks/AnyInitChunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Initiation Chunk (INIT) (1). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 1 | Chunk Flags | Chunk Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Initiate Tag | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Advertised Receiver Window Credit (a_rwnd) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Number of Outbound Streams | Number of Inbound Streams | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Initial TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Optional/Variable-Length Parameters / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 1. * - Flags (8 bits): All set to 0. * - Initiate Tag (32 bits): The receiver of the INIT chunk (the responding * end) records the value of the Initiate Tag parameter. This value MUST * be placed into the Verification Tag field of every SCTP packet that * the receiver of the INIT chunk transmits within this association. * - Advertised Receiver Window Credit (a_rwnd) (32 bits): This value * represents the dedicated buffer space, in number of bytes, the sender * of the INIT chunk has reserved in association with this window. The * Advertised Receiver Window Credit MUST NOT be smaller than 1500. * - Number of Outbound Streams (OS) (16 bits): Defines the number of * outbound streams the sender of this INIT chunk wishes to create in * this association. The value of 0 MUST NOT be used. * - Number of Inbound Streams (MIS) (16 bits): Defines the maximum number * of streams the sender of this INIT chunk allows the peer end to create * in this association. The value 0 MUST NOT be used. * - Initial TSN (I-TSN) (32 bits): Defines the TSN that the sender of the * INIT chunk will use initially. * * Variable-Length Parameters: * - IPv4 Address (5), optional. * - IPv6 Address (6), optional. * - Cookie Preservative (9), optional. * - Reserved for ECN Capable (32768, 0x8000), optional. * - Host Name Address (11), deprecated: The receiver of an INIT chunk or an * INIT ACK containing a Host Name Address parameter MUST send an ABORT * chunk and MAY include an "Unresolvable Address" error cause. * - Supported Address Types (12), optional. */ // Forward declaration. class Packet; class InitChunk : public AnyInitChunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: static const size_t InitChunkHeaderLength{ 20 }; public: /** * Parse a InitChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static InitChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a InitChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static InitChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a InitChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static InitChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ InitChunk(uint8_t* buffer, size_t bufferLength); public: ~InitChunk() override; void Dump(int indentation = 0) const final; InitChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return true; } bool CanHaveErrorCauses() const final { return false; } uint32_t GetInitiateTag() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetInitiateTag(uint32_t value); uint32_t GetAdvertisedReceiverWindowCredit() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 8); } void SetAdvertisedReceiverWindowCredit(uint32_t value); uint16_t GetNumberOfOutboundStreams() const final { return Utils::Byte::Get2Bytes(GetBuffer(), 12); } void SetNumberOfOutboundStreams(uint16_t value); uint16_t GetNumberOfInboundStreams() const final { return Utils::Byte::Get2Bytes(GetBuffer(), 14); } void SetNumberOfInboundStreams(uint16_t value); uint32_t GetInitialTsn() const final { return Utils::Byte::Get4Bytes(GetBuffer(), 16); } void SetInitialTsn(uint32_t value); protected: InitChunk* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Chunk doesn't * have variable-length value (despite the fixed header doesn't have * default length). Optional/Variable-Length Parameters and/or Error * Causes don't account here. */ size_t GetHeaderLength() const final { return InitChunk::InitChunkHeaderLength; } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/OperationErrorChunk.hpp ================================================ #ifndef MS_RTC_SCTP_OPERATION_ERROR_CHUNK_HPP #define MS_RTC_SCTP_OPERATION_ERROR_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Operation Error Chunk (OPERATION_ERROR) (9). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 9 | Chunk Flags | Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / one or more Error Causes / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 9. * - Flags (8 bits): All set to 0. * - Length (16 bits). * * Optional Variable-Length Error Causes (anyone). */ // Forward declaration. class Packet; class OperationErrorChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a OperationErrorChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static OperationErrorChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a OperationErrorChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static OperationErrorChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a OperationErrorChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static OperationErrorChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ OperationErrorChunk(uint8_t* buffer, size_t bufferLength); public: ~OperationErrorChunk() override; void Dump(int indentation = 0) const final; OperationErrorChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return true; } protected: OperationErrorChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/ReConfigChunk.hpp ================================================ #ifndef MS_RTC_SCTP_RE_CONFIG_CHUNK_HPP #define MS_RTC_SCTP_RE_CONFIG_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Re-Config Chunk (RE_CONFIG) (130). * * @see RFC 6525. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 130 | Chunk Flags | Chunk Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Re-configuration Parameter / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Re-configuration Parameter (optional) / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 130. * - Flags (8 bits): All set to 0. * - Length (16 bits). * - Re-configuration Parameter (variable Length): This field holds a * Re-configuration Request Parameter or a Re-configuration Response * Parameter. * * Variable-Length Parameters: * - Re-configuration Request Parameter or a Re-configuration Response * Parameter, mandatory. * - Another Parameter, optional. */ // Forward declaration. class Packet; class ReConfigChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a ReConfigChunk. * * @remarks * - `bufferLength` may exceed the exact length of the Chunk. */ static ReConfigChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a ReConfigChunk. * * @remarks * - `bufferLength` could be greater than the Chunk real length. */ static ReConfigChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a ReConfigChunk. * * @remarks * - To be used only by `Packet::Parse()`. */ static ReConfigChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ ReConfigChunk(uint8_t* buffer, size_t bufferLength); public: ~ReConfigChunk() override; void Dump(int indentation = 0) const final; ReConfigChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return true; } bool CanHaveErrorCauses() const final { return false; } protected: ReConfigChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/SackChunk.hpp ================================================ #ifndef MS_RTC_SCTP_SACK_CHUNK_HPP #define MS_RTC_SCTP_SACK_CHUNK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include #include namespace RTC { namespace SCTP { /** * SCTP Selective Acknowledgement Chunk (SACK) (3) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 3 | Chunk Flags | Chunk Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cumulative TSN Ack | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Advertised Receiver Window Credit (a_rwnd) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Number of Gap Ack Blocks = N | Number of Duplicate TSNs = M | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Gap Ack Block #1 Start | Gap Ack Block #1 End | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / / * \ ... \ * / / * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Gap Ack Block #N Start | Gap Ack Block #N End | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Duplicate TSN 1 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / / * \ ... \ * / / * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Duplicate TSN M | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 3. * - Res (4 bits): All set to 0. * - Length (16 bits). * - Cumulative TSN Ack (32 bits): The largest TSN, such that all TSNs * smaller than or equal to it have been received and the next one has * not been received. In the case where no DATA chunk has been received, * this value is set to the peer's Initial TSN minus one. * - Advertised Receiver Window Credit (a_rwnd) (32 bits): This field * indicates the updated receive buffer space in bytes of the sender of * this SACK chunk. * - Number of Gap Ack Blocks (16 bits): Indicates the number of Gap Ack * Blocks included in this SACK chunk. * - Number of Duplicate TSNs (16 bit): This field contains the number of * duplicate TSNs the endpoint has received. Each duplicate TSN is listed * following the Gap Ack Block list. * - Gap Ack Blocks: These fields contain the Gap Ack Blocks. They are * repeated for each Gap Ack Block up to the number of Gap Ack Blocks * defined in the Number of Gap Ack Blocks field. * - Gap Ack Block Start (16 bits): Indicates the Start offset TSN for this * Gap Ack Block. * - Gap Ack Block End (16 bits): Indicates the End offset TSN for this Gap * Ack Block. * - Duplicate TSN (32 bits): Indicates the number of times a TSN was * received in duplicate since the last SACK chunk was sent. */ // Forward declaration. class Packet; class SackChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: struct GapAckBlock { GapAckBlock(uint16_t start, uint16_t end) : start(start), end(end) { } GapAckBlock() = default; uint16_t start; uint16_t end; bool operator==(const GapAckBlock& other) const { return start == other.start && end == other.end; } }; public: static const size_t SackChunkHeaderLength{ 16 }; public: /** * Parse a SackChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static SackChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a SackChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static SackChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a SackChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static SackChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ SackChunk(uint8_t* buffer, size_t bufferLength); public: ~SackChunk() override; void Dump(int indentation = 0) const final; SackChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } uint32_t GetCumulativeTsnAck() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetCumulativeTsnAck(uint32_t tsn); uint32_t GetAdvertisedReceiverWindowCredit() const { return Utils::Byte::Get4Bytes(GetBuffer(), 8); } void SetAdvertisedReceiverWindowCredit(uint32_t aRwnd); std::vector GetGapAckBlocks() const; std::vector GetValidatedGapAckBlocks() const; std::vector GetDuplicateTsns() const; void AddAckBlock(uint16_t start, uint16_t end); void AddAckBlock(GapAckBlock gapAckBlock) { AddAckBlock(gapAckBlock.start, gapAckBlock.end); } void AddDuplicateTsn(uint32_t tsn); protected: SackChunk* SoftClone(const uint8_t* buffer) const final; /** * We need to override this method since this Chunk has a variable-length * value and the fixed header doesn't have default length. */ size_t GetHeaderLength() const final { return SackChunk::SackChunkHeaderLength; } private: void SetNumberOfGapAckBlocks(uint16_t value); void SetNumberOfDuplicateTsns(uint16_t value); uint8_t* GetAckBlocksPointer() const { return GetVariableLengthValuePointer(); } uint8_t* GetDuplicateTsnsPointer() const { return GetVariableLengthValuePointer() + (GetNumberOfGapAckBlocks() * 4); } uint16_t GetNumberOfGapAckBlocks() const { return Utils::Byte::Get2Bytes(GetBuffer(), 12); } uint16_t GetAckBlockStartAt(uint16_t idx) const { return Utils::Byte::Get2Bytes(GetAckBlocksPointer(), (idx * 4)); } uint16_t GetAckBlockEndAt(uint16_t idx) const { return Utils::Byte::Get2Bytes(GetAckBlocksPointer(), (idx * 4) + 2); } uint16_t GetNumberOfDuplicateTsns() const { return Utils::Byte::Get2Bytes(GetBuffer(), 14); } uint32_t GetDuplicateTsnAt(uint16_t idx) const { return Utils::Byte::Get4Bytes(GetDuplicateTsnsPointer(), (idx * 4)); } bool ValidateGapAckBlocks() const; }; /** * For logging purposes in Catch2 tests. */ inline std::ostream& operator<<(std::ostream& os, const SackChunk::GapAckBlock& s) { return os << "{start:" << s.start << ", end:" << s.end << "}"; } } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp ================================================ #ifndef MS_RTC_SCTP_SHUTDOWN_ACK_CHUNK_HPP #define MS_RTC_SCTP_SHUTDOWN_ACK_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Shutdown Acknowledgement Chunk (SHUTDOWN_ACK) (8). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 8 | Chunk Flags | Length = 4 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 8. * - Flags (8 bits): All set to 0. * - Length (16 bits): 4. */ // Forward declaration. class Packet; class ShutdownAckChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a ShutdownAckChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static ShutdownAckChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a ShutdownAckChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static ShutdownAckChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a ShutdownAckChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static ShutdownAckChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ ShutdownAckChunk(uint8_t* buffer, size_t bufferLength); public: ~ShutdownAckChunk() override; void Dump(int indentation = 0) const final; ShutdownAckChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } protected: ShutdownAckChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/ShutdownChunk.hpp ================================================ #ifndef MS_RTC_SCTP_SHUTDOWN_CHUNK_HPP #define MS_RTC_SCTP_SHUTDOWN_CHUNK_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Shutdown Association Chunk (SHUTDOWN) (7). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 7 | Chunk Flags | Length = 8 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cumulative TSN Ack | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 7. * - Flags (8 bits): All set to 0. * - Length (16 bits): 8. * - Cumulative TSN Ack (32 bits): The largest TSN, such that all TSNs * smaller than or equal to it have been received and the next one has * not been received. */ // Forward declaration. class Packet; class ShutdownChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: static const size_t ShutdownChunkHeaderLength{ 8 }; public: /** * Parse a ShutdownChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static ShutdownChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a ShutdownChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static ShutdownChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a ShutdownChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static ShutdownChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ ShutdownChunk(uint8_t* buffer, size_t bufferLength); public: ~ShutdownChunk() override; void Dump(int indentation = 0) const final; ShutdownChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } uint32_t GetCumulativeTsnAck() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetCumulativeTsnAck(uint32_t value); protected: ShutdownChunk* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Chunk doesn't * have variable-length value (despite the fixed header doesn't have * default length). */ size_t GetHeaderLength() const final { return ShutdownChunk::ShutdownChunkHeaderLength; } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp ================================================ #ifndef MS_RTC_SCTP_SHUTDOWN_COMPLETE_CHUNK_HPP #define MS_RTC_SCTP_SHUTDOWN_COMPLETE_CHUNK_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Shutdown Complete Chunk (SHUTDOWN_COMPLETE) (14). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 14 | Reserved |T| Length = 4 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * - Chunk Type (8 bits): 14 * - T bit (1 bit): The T bit is set to 0 if the sender filled in the * Verification Tag expected by the peer. If the Verification Tag is * reflected, the T bit MUST be set to 1. Reflecting means that the sent * Verification Tag is the same as the received one. * - Length (16 bits): 4. */ // Forward declaration. class Packet; class ShutdownCompleteChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a ShutdownCompleteChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static ShutdownCompleteChunk* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a ShutdownCompleteChunk. * * @remarks * `bufferLength` could be greater than the Chunk real length. */ static ShutdownCompleteChunk* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a ShutdownCompleteChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static ShutdownCompleteChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ ShutdownCompleteChunk(uint8_t* buffer, size_t bufferLength); public: ~ShutdownCompleteChunk() override; void Dump(int indentation = 0) const final; ShutdownCompleteChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } bool GetT() const { return GetBit0(); } void SetT(bool flag); protected: ShutdownCompleteChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/chunks/UnknownChunk.hpp ================================================ #ifndef MS_RTC_SCTP_DATA_UNKNOWN_HPP #define MS_RTC_SCTP_DATA_UNKNOWN_HPP #include "common.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /** * SCTP Unknown Chunk (UNKNOWN). * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Chunk Type | Chunk Flags | Chunk Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Unknown Value / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Packet; class UnknownChunk : public Chunk { // We need that Packet calls protected and private methods in this class. friend class Packet; public: /** * Parse a UnknownChunk. * * @remarks * `bufferLength` may exceed the exact length of the Chunk. */ static UnknownChunk* Parse(const uint8_t* buffer, size_t bufferLength); private: /** * Parse a UnknownChunk. * * @remarks * To be used only by `Packet::Parse()`. */ static UnknownChunk* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding); private: /** * Only used by Parse() and ParseStrict() static methods. */ UnknownChunk(uint8_t* buffer, size_t bufferLength); public: ~UnknownChunk() override; void Dump(int indentation = 0) const final; UnknownChunk* Clone(uint8_t* buffer, size_t bufferLength) const final; bool CanHaveParameters() const final { return false; } bool CanHaveErrorCauses() const final { return false; } bool HasUnknownType() const override { return true; } bool HasUnknownValue() const { return HasVariableLengthValue(); } const uint8_t* GetUnknownValue() const { return GetVariableLengthValue(); } uint16_t GetUnknownValueLength() const { return GetVariableLengthValueLength(); } protected: UnknownChunk* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_COOKIE_RECEIVED_WHILE_SHUTTING_DOWN_ERROR_CAUSE_HPP #define MS_RTC_SCTP_COOKIE_RECEIVED_WHILE_SHUTTING_DOWN_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Cookie Received While Shutting Down Error Cause * (COOKIE_RECEIVED_WHILE_SHUTTING_DOWN) (10) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 10 | Cause Length = 4 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class CookieReceivedWhileShuttingDownErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a CookieReceivedWhileShuttingDownErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static CookieReceivedWhileShuttingDownErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a CookieReceivedWhileShuttingDownErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static CookieReceivedWhileShuttingDownErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a CookieReceivedWhileShuttingDownErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static CookieReceivedWhileShuttingDownErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ CookieReceivedWhileShuttingDownErrorCause(uint8_t* buffer, size_t bufferLength); public: ~CookieReceivedWhileShuttingDownErrorCause() override; void Dump(int indentation = 0) const final; CookieReceivedWhileShuttingDownErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; protected: CookieReceivedWhileShuttingDownErrorCause* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_OUT_OF_INVALID_MANDATORY_PARAMETER_ERROR_CAUSE_HPP #define MS_RTC_SCTP_OUT_OF_INVALID_MANDATORY_PARAMETER_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Invalid Mandatory Parameter Error Cause * (INVALID_MANDATORY_PARAMETER) (7) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 7 | Cause Length = 4 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class InvalidMandatoryParameterErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a InvalidMandatoryParameterErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static InvalidMandatoryParameterErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a InvalidMandatoryParameterErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static InvalidMandatoryParameterErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a InvalidMandatoryParameterErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static InvalidMandatoryParameterErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ InvalidMandatoryParameterErrorCause(uint8_t* buffer, size_t bufferLength); public: ~InvalidMandatoryParameterErrorCause() override; void Dump(int indentation = 0) const final; InvalidMandatoryParameterErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; protected: InvalidMandatoryParameterErrorCause* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_INVALID_STREAM_IDENTIFIER_ERROR_CAUSE_HPP #define MS_RTC_SCTP_INVALID_STREAM_IDENTIFIER_ERROR_CAUSE_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Invalid Stream Identifier Error Cause (INVALID_STREAM_IDENTIFIER) * (1) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 1 | Cause Length = 8 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream Identifier | (Reserved) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class InvalidStreamIdentifierErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t InvalidStreamIdentifierErrorCauseHeaderLength{ 8 }; public: /** * Parse a InvalidStreamIdentifierErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static InvalidStreamIdentifierErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a InvalidStreamIdentifierErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static InvalidStreamIdentifierErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a InvalidStreamIdentifierErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static InvalidStreamIdentifierErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ InvalidStreamIdentifierErrorCause(uint8_t* buffer, size_t bufferLength); public: ~InvalidStreamIdentifierErrorCause() override; void Dump(int indentation = 0) const final; InvalidStreamIdentifierErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; uint16_t GetStreamIdentifier() const { return Utils::Byte::Get2Bytes(GetBuffer(), 4); } void SetStreamIdentifier(uint16_t value); protected: InvalidStreamIdentifierErrorCause* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Error Cause * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength; } virtual const std::string ContentToString() const override final; private: void SetReserved(); }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_MISSING_MANDATORY_PARAMETER_ERROR_CAUSE_HPP #define MS_RTC_SCTP_MISSING_MANDATORY_PARAMETER_ERROR_CAUSE_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Missing Mandatory Parameter Error Cause * (MISSING_MANDATORY_PARAMETER) (2) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 2 | Cause Length = 8 + N * 2 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Number of missing params = N | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Missing Param Type #1 | Missing Param Type #2 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Missing Param Type #N-1 | Missing Param Type #N | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class MissingMandatoryParameterErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t MissingMandatoryParameterErrorCauseHeaderLength{ 8 }; public: /** * Parse a MissingMandatoryParameterErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static MissingMandatoryParameterErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a MissingMandatoryParameterErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static MissingMandatoryParameterErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a MissingMandatoryParameterErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static MissingMandatoryParameterErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ MissingMandatoryParameterErrorCause(uint8_t* buffer, size_t bufferLength); public: ~MissingMandatoryParameterErrorCause() override; void Dump(int indentation = 0) const final; MissingMandatoryParameterErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetNumberOfMissingParameters() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } Parameter::ParameterType GetMissingParameterTypeAt(uint32_t idx) const { return static_cast( Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 2))); } void AddMissingParameterType(Parameter::ParameterType parameterType); protected: MissingMandatoryParameterErrorCause* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Error Cause * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength; } virtual const std::string ContentToString() const override final; private: void SetNumberOfMissingParameters(uint32_t value); }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_NO_USER_DATA_ERROR_CAUSE_HPP #define MS_RTC_SCTP_NO_USER_DATA_ERROR_CAUSE_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP No User Data Error Cause (NO_USER_DATA) (9) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 9 | Cause Length = 8 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class NoUserDataErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t NoUserDataErrorCauseHeaderLength{ 8 }; public: /** * Parse a NoUserDataErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static NoUserDataErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a NoUserDataErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static NoUserDataErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a NoUserDataErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static NoUserDataErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ NoUserDataErrorCause(uint8_t* buffer, size_t bufferLength); public: ~NoUserDataErrorCause() override; void Dump(int indentation = 0) const final; NoUserDataErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetTsn() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetTsn(uint32_t value); protected: NoUserDataErrorCause* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Error Cause * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength; } virtual const std::string ContentToString() const override final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_OUT_OF_RESOURCE_ERROR_CAUSE_HPP #define MS_RTC_SCTP_OUT_OF_RESOURCE_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Out of Resource Error Cause (OUT_OF_RESOURCE) (4) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 4 | Cause Length = 4 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class OutOfResourceErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a OutOfResourceErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static OutOfResourceErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a OutOfResourceErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static OutOfResourceErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a OutOfResourceErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static OutOfResourceErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ OutOfResourceErrorCause(uint8_t* buffer, size_t bufferLength); public: ~OutOfResourceErrorCause() override; void Dump(int indentation = 0) const final; OutOfResourceErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; protected: OutOfResourceErrorCause* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_PROTOCOL_VIOLATION_ERROR_CAUSE_HPP #define MS_RTC_SCTP_PROTOCOL_VIOLATION_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Protocol Violation Error Cause (PROTOCOL_VIOLATION) (13) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 13 | Cause Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Additional Information / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class ProtocolViolationErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a ProtocolViolationErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static ProtocolViolationErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a ProtocolViolationErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static ProtocolViolationErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a ProtocolViolationErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static ProtocolViolationErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse() and ParseStrict() static methods. */ ProtocolViolationErrorCause(uint8_t* buffer, size_t bufferLength); public: ~ProtocolViolationErrorCause() override; void Dump(int indentation = 0) const final; ProtocolViolationErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; virtual bool HasAdditionalInformation() const final { return HasVariableLengthValue(); } const uint8_t* GetAdditionalInformation() const { return GetVariableLengthValue(); } uint16_t GetAdditionalInformationLength() const { return GetVariableLengthValueLength(); } void SetAdditionalInformation(const uint8_t* info, uint16_t infoLength); void SetAdditionalInformation(const std::string& info); protected: ProtocolViolationErrorCause* SoftClone(const uint8_t* buffer) const final; virtual const std::string ContentToString() const override final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES_ERROR_CAUSE_HPP #define MS_RTC_SCTP_RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Restart of an Association with New Addresses Error Cause * (RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES) (11) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 11 | Cause Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / New Address TLVs / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class RestartOfAnAssociationWithNewAddressesErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a RestartOfAnAssociationWithNewAddressesErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static RestartOfAnAssociationWithNewAddressesErrorCause* Parse( const uint8_t* buffer, size_t bufferLength); /** * Create a RestartOfAnAssociationWithNewAddressesErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static RestartOfAnAssociationWithNewAddressesErrorCause* Factory( uint8_t* buffer, size_t bufferLength); private: /** * Parse a RestartOfAnAssociationWithNewAddressesErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static RestartOfAnAssociationWithNewAddressesErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse() and ParseStrict() static methods. */ RestartOfAnAssociationWithNewAddressesErrorCause(uint8_t* buffer, size_t bufferLength); public: ~RestartOfAnAssociationWithNewAddressesErrorCause() override; void Dump(int indentation = 0) const final; RestartOfAnAssociationWithNewAddressesErrorCause* Clone( uint8_t* buffer, size_t bufferLength) const final; virtual bool HasNewAddressTlvs() const final { return HasVariableLengthValue(); } const uint8_t* GetNewAddressTlvs() const { return GetVariableLengthValue(); } uint16_t GetNewAddressTlvsLength() const { return GetVariableLengthValueLength(); } void SetNewAddressTlvs(const uint8_t* tlvs, uint16_t tlvsLength); protected: RestartOfAnAssociationWithNewAddressesErrorCause* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_STALE_COOKIE_ERROR_CAUSE_HPP #define MS_RTC_SCTP_STALE_COOKIE_ERROR_CAUSE_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Stale Cookie Error Cause (STALE_COOKIE) (3) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 3 | Cause Length = 8 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Measure of Staleness (usec.) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class StaleCookieErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t StaleCookieErrorCauseHeaderLength{ 8 }; public: /** * Parse a StaleCookieErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static StaleCookieErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a StaleCookieErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static StaleCookieErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a StaleCookieErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static StaleCookieErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ StaleCookieErrorCause(uint8_t* buffer, size_t bufferLength); public: ~StaleCookieErrorCause() override; void Dump(int indentation = 0) const final; StaleCookieErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetMeasureOfStaleness() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetMeasureOfStaleness(uint32_t value); protected: StaleCookieErrorCause* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Error Cause * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength; } virtual const std::string ContentToString() const override final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_UNKNOWN_ERROR_CAUSE_HPP #define MS_RTC_SCTP_UNKNOWN_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Unknown Error Cause (UNKNOWN) * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code | Cause Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Unknown Value / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class UnknownErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a UnknownErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static UnknownErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); private: /** * Parse a UnknownErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static UnknownErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse() and ParseStrict() static methods. */ UnknownErrorCause(uint8_t* buffer, size_t bufferLength); public: ~UnknownErrorCause() override; void Dump(int indentation = 0) const final; UnknownErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; bool HasUnknownCode() const override { return true; } virtual bool HasUnknownValue() const final { return HasVariableLengthValue(); } const uint8_t* GetUnknownValue() const { return GetVariableLengthValue(); } uint16_t GetUnknownValueLength() const { return GetVariableLengthValueLength(); } protected: UnknownErrorCause* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_UNRECOGNIZED_CHUNK_TYPE_ERROR_CAUSE_HPP #define MS_RTC_SCTP_UNRECOGNIZED_CHUNK_TYPE_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Unrecognized Chunk Type Error Cause (UNRECOGNIZED_CHUNK_TYPE) (6) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 6 | Cause Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Unrecognized Chunk / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class UnrecognizedChunkTypeErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a UnrecognizedChunkTypeErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static UnrecognizedChunkTypeErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a UnrecognizedChunkTypeErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static UnrecognizedChunkTypeErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a UnrecognizedChunkTypeErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static UnrecognizedChunkTypeErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse() and ParseStrict() static methods. */ UnrecognizedChunkTypeErrorCause(uint8_t* buffer, size_t bufferLength); public: ~UnrecognizedChunkTypeErrorCause() override; void Dump(int indentation = 0) const final; UnrecognizedChunkTypeErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; virtual bool HasUnrecognizedChunk() const final { return HasVariableLengthValue(); } const uint8_t* GetUnrecognizedChunk() const { return GetVariableLengthValue(); } uint16_t GetUnrecognizedChunkLength() const { return GetVariableLengthValueLength(); } void SetUnrecognizedChunk(const uint8_t* chunk, uint16_t chunkLength); protected: UnrecognizedChunkTypeErrorCause* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_UNRECOGNIZED_PARAMETERS_ERROR_CAUSE_HPP #define MS_RTC_SCTP_UNRECOGNIZED_PARAMETERS_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Unrecognized Parameters Error Cause (UNRECOGNIZED_PARAMETERS) (8) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 8 | Cause Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Unrecognized Parameters / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class UnrecognizedParametersErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a UnrecognizedParametersErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static UnrecognizedParametersErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a UnrecognizedParametersErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static UnrecognizedParametersErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a UnrecognizedParametersErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static UnrecognizedParametersErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse() and ParseStrict() static methods. */ UnrecognizedParametersErrorCause(uint8_t* buffer, size_t bufferLength); public: ~UnrecognizedParametersErrorCause() override; void Dump(int indentation = 0) const final; UnrecognizedParametersErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; virtual bool HasUnrecognizedParameters() const final { return HasVariableLengthValue(); } const uint8_t* GetUnrecognizedParameters() const { return GetVariableLengthValue(); } uint16_t GetUnrecognizedParametersLength() const { return GetVariableLengthValueLength(); } void SetUnrecognizedParameters(const uint8_t* parameters, uint16_t parametersLength); protected: UnrecognizedParametersErrorCause* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_UNRESOLVABLE_ADDRESS_ERROR_CAUSE_HPP #define MS_RTC_SCTP_UNRESOLVABLE_ADDRESS_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" namespace RTC { namespace SCTP { /** * SCTP Unresolvable Address Error Cause (UNRESOLVABLE_ADDRESS) (5) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 5 | Cause Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Unresolvable Address / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class UnresolvableAddressErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a UnresolvableAddressErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static UnresolvableAddressErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a UnresolvableAddressErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static UnresolvableAddressErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a UnresolvableAddressErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static UnresolvableAddressErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse() and ParseStrict() static methods. */ UnresolvableAddressErrorCause(uint8_t* buffer, size_t bufferLength); public: ~UnresolvableAddressErrorCause() override; void Dump(int indentation = 0) const final; UnresolvableAddressErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; virtual bool HasUnresolvableAddress() const final { return HasVariableLengthValue(); } const uint8_t* GetUnresolvableAddress() const { return GetVariableLengthValue(); } uint16_t GetUnresolvableAddressLength() const { return GetVariableLengthValueLength(); } void SetUnresolvableAddress(const uint8_t* address, uint16_t addressLength); protected: UnresolvableAddressErrorCause* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp ================================================ #ifndef MS_RTC_SCTP_USER_INITIATED_ABORT_ERROR_CAUSE_HPP #define MS_RTC_SCTP_USER_INITIATED_ABORT_ERROR_CAUSE_HPP #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include namespace RTC { namespace SCTP { /** * SCTP User-Initiated Abort Error Cause (USER_INITIATED_ABORT) (12) * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Cause Code = 12 | Cause Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Upper Layer Abort Reason / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class UserInitiatedAbortErrorCause : public ErrorCause { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a UserInitiatedAbortErrorCause. * * @remarks * `bufferLength` may exceed the exact length of the Error Cause. */ static UserInitiatedAbortErrorCause* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a UserInitiatedAbortErrorCause. * * @remarks * `bufferLength` could be greater than the Error Cause real length. */ static UserInitiatedAbortErrorCause* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a UserInitiatedAbortErrorCause. * * @remarks * To be used only by `Chunk::ParseErrorCauses()`. */ static UserInitiatedAbortErrorCause* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding); private: /** * Only used by Parse() and ParseStrict() static methods. */ UserInitiatedAbortErrorCause(uint8_t* buffer, size_t bufferLength); public: ~UserInitiatedAbortErrorCause() override; void Dump(int indentation = 0) const final; UserInitiatedAbortErrorCause* Clone(uint8_t* buffer, size_t bufferLength) const final; virtual bool HasUpperLayerAbortReason() const final { return HasVariableLengthValue(); } const std::string_view GetUpperLayerAbortReason() const { const auto* value = GetVariableLengthValue(); if (!value) { return {}; } return std::string_view(reinterpret_cast(value), GetVariableLengthValueLength()); } void SetUpperLayerAbortReason(const std::string_view& reason); protected: UserInitiatedAbortErrorCause* SoftClone(const uint8_t* buffer) const final; virtual const std::string ContentToString() const override final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.hpp ================================================ #ifndef MS_RTC_SCTP_ADD_INCOMING_STREAMS_REQUEST_PARAMETER_HPP #define MS_RTC_SCTP_ADD_INCOMING_STREAMS_REQUEST_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Add Incoming Streams Request Parameter * (ADD_INCOMING_STREAMS_REQUEST) (18). * * @see RFC 6525. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Parameter Type = 18 | Parameter Length = 12 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Re-configuration Request Sequence Number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Number of new streams | (Reserved) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class AddIncomingStreamsRequestParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t AddIncomingStreamsRequestParameterHeaderLength{ 12 }; public: /** * Parse a AddIncomingStreamsRequestParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static AddIncomingStreamsRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a AddIncomingStreamsRequestParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static AddIncomingStreamsRequestParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a AddIncomingStreamsRequestParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static AddIncomingStreamsRequestParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ AddIncomingStreamsRequestParameter(uint8_t* buffer, size_t bufferLength); public: ~AddIncomingStreamsRequestParameter() override; void Dump(int indentation = 0) const final; AddIncomingStreamsRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetReconfigurationRequestSequenceNumber() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetReconfigurationRequestSequenceNumber(uint32_t value); uint16_t GetNumberOfNewStreams() const { return Utils::Byte::Get2Bytes(GetBuffer(), 8); } void SetNumberOfNewStreams(uint16_t value); protected: AddIncomingStreamsRequestParameter* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Parameter * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength; } private: void SetReserved(); }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.hpp ================================================ #ifndef MS_RTC_SCTP_ADD_OUTGOING_STREAMS_REQUEST_PARAMETER_HPP #define MS_RTC_SCTP_ADD_OUTGOING_STREAMS_REQUEST_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Add Outgoing Streams Request Parameter * (ADD_OUTGOING_STREAMS_REQUEST) (17). * * @see RFC 6525. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Parameter Type = 17 | Parameter Length = 12 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Re-configuration Request Sequence Number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Number of new streams | (Reserved) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class AddOutgoingStreamsRequestParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t AddOutgoingStreamsRequestParameterHeaderLength{ 12 }; public: /** * Parse a AddOutgoingStreamsRequestParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static AddOutgoingStreamsRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a AddOutgoingStreamsRequestParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static AddOutgoingStreamsRequestParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a AddOutgoingStreamsRequestParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static AddOutgoingStreamsRequestParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ AddOutgoingStreamsRequestParameter(uint8_t* buffer, size_t bufferLength); public: ~AddOutgoingStreamsRequestParameter() override; void Dump(int indentation = 0) const final; AddOutgoingStreamsRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetReconfigurationRequestSequenceNumber() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetReconfigurationRequestSequenceNumber(uint32_t value); uint16_t GetNumberOfNewStreams() const { return Utils::Byte::Get2Bytes(GetBuffer(), 8); } void SetNumberOfNewStreams(uint16_t value); protected: AddOutgoingStreamsRequestParameter* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Parameter * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength; } private: void SetReserved(); }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp ================================================ #ifndef MS_RTC_SCTP_COOKIE_PRESERVATIVE_PARAMETER_HPP #define MS_RTC_SCTP_COOKIE_PRESERVATIVE_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Cookie Preservative Parameter (COOKIE_PRESERVATIVE) (9). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 9 | Length = 8 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Suggested Cookie Life-Span Increment (msec.) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class CookiePreservativeParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t CookiePreservativeParameterHeaderLength{ 8 }; public: /** * Parse a CookiePreservativeParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static CookiePreservativeParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a CookiePreservativeParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static CookiePreservativeParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a CookiePreservativeParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static CookiePreservativeParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ CookiePreservativeParameter(uint8_t* buffer, size_t bufferLength); public: ~CookiePreservativeParameter() override; void Dump(int indentation = 0) const final; CookiePreservativeParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetLifeSpanIncrement() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetLifeSpanIncrement(uint32_t increment); protected: CookiePreservativeParameter* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Parameter * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return CookiePreservativeParameter::CookiePreservativeParameterHeaderLength; } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp ================================================ #ifndef MS_RTC_SCTP_FORWARD_TSN_SUPPORTED_PARAMETER_HPP #define MS_RTC_SCTP_FORWARD_TSN_SUPPORTED_PARAMETER_HPP #include "common.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Forward-TSN-Supported Parameter (FORWARD_TSN_SUPPORTED) (49152). * * @see RFC 3758. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Parameter Type = 49152 | Parameter Length = 4 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class ForwardTsnSupportedParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a ForwardTsnSupportedParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static ForwardTsnSupportedParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a ForwardTsnSupportedParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static ForwardTsnSupportedParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a ForwardTsnSupportedParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static ForwardTsnSupportedParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ ForwardTsnSupportedParameter(uint8_t* buffer, size_t bufferLength); public: ~ForwardTsnSupportedParameter() override; void Dump(int indentation = 0) const final; ForwardTsnSupportedParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; protected: ForwardTsnSupportedParameter* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp ================================================ #ifndef MS_RTC_SCTP_HEARTBEAT_INFO_PARAMETER_HPP #define MS_RTC_SCTP_HEARTBEAT_INFO_PARAMETER_HPP #include "common.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP HeartbeatInfo Parameter (HEARBEAT_INFO) (1). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 1 | HB Info Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Sender-Specific Heartbeat Info / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class HeartbeatInfoParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a HeartbeatInfoParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static HeartbeatInfoParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a HeartbeatInfoParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static HeartbeatInfoParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a HeartbeatInfoParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static HeartbeatInfoParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ HeartbeatInfoParameter(uint8_t* buffer, size_t bufferLength); public: ~HeartbeatInfoParameter() override; void Dump(int indentation = 0) const final; HeartbeatInfoParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; virtual bool HasInfo() const final { return HasVariableLengthValue(); } const uint8_t* GetInfo() const { return GetVariableLengthValue(); } uint16_t GetInfoLength() const { return GetVariableLengthValueLength(); } void SetInfo(const uint8_t* info, uint16_t infoLength); protected: HeartbeatInfoParameter* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp ================================================ #ifndef MS_RTC_SCTP_IPV4_ADDRESS_PARAMETER_HPP #define MS_RTC_SCTP_IPV4_ADDRESS_PARAMETER_HPP #include "common.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP IPv4 Adress Parameter (IPV4 ADDRESS) (5). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 5 | Length = 8 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | IPv4 Address | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class IPv4AddressParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t IPv4AddressParameterHeaderLength{ 8 }; public: /** * Parse a IPv4AddressParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static IPv4AddressParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a IPv4AddressParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static IPv4AddressParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a IPv4AddressParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static IPv4AddressParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ IPv4AddressParameter(uint8_t* buffer, size_t bufferLength); public: ~IPv4AddressParameter() override; void Dump(int indentation = 0) const final; IPv4AddressParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; /** * @return A pointer to a 4 bytes unsigned integer in network order * representing the binary encoded IPv4 value. */ const uint8_t* GetIPv4Address() const { return GetBuffer() + 4; } /** * @param ip - A pointer to a 4 bytes unsigned integer in network order * representing the binary encoded IPv4 value. */ void SetIPv4Address(const uint8_t* ip); protected: IPv4AddressParameter* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Parameter * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return IPv4AddressParameter::IPv4AddressParameterHeaderLength; } private: void ResetIPv4Address(); }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp ================================================ #ifndef MS_RTC_SCTP_IPV6_ADDRESS_PARAMETER_HPP #define MS_RTC_SCTP_IPV6_ADDRESS_PARAMETER_HPP #include "common.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP IPv6 Adress Parameter (IPV6_ADDRESS) (6). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 6 | Length = 20 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | IPv6 Address | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class IPv6AddressParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t IPv6AddressParameterHeaderLength{ 20 }; public: /** * Parse a IPv6AddressParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static IPv6AddressParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a IPv6AddressParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static IPv6AddressParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a IPv6AddressParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static IPv6AddressParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ IPv6AddressParameter(uint8_t* buffer, size_t bufferLength); public: ~IPv6AddressParameter() override; void Dump(int indentation = 0) const final; IPv6AddressParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; /** * @return A pointer to a 16 bytes unsigned integer in network order * representing the binary encoded IPv6 value. */ const uint8_t* GetIPv6Address() const { return GetBuffer() + 4; } /** * @param ip - A pointer to a 16 bytes unsigned integer in network order * representing the binary encoded IPv6 value. */ void SetIPv6Address(const uint8_t* ip); protected: IPv6AddressParameter* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Parameter * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return IPv6AddressParameter::IPv6AddressParameterHeaderLength; } private: void ResetIPv6Address(); }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp ================================================ #ifndef MS_RTC_SCTP_INCOMING_SSN_RESET_REQUEST_PARAMETER_HPP #define MS_RTC_SCTP_INCOMING_SSN_RESET_REQUEST_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include namespace RTC { namespace SCTP { /** * SCTP Incoming SSN Reset Request Parameter (INCOMING_SSN_RESET_REQUEST) * (14). * * @see RFC 6525. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Parameter Type = 14 | Parameter Length = 8 + 2 * N | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Re-configuration Request Sequence Number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream Number 1 (optional) | Stream Number 2 (optional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / ...... / * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream Number N-1 (optional) | Stream Number N (optional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class IncomingSsnResetRequestParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t IncomingSsnResetRequestParameterHeaderLength{ 8 }; public: /** * Parse a IncomingSsnResetRequestParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static IncomingSsnResetRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a IncomingSsnResetRequestParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static IncomingSsnResetRequestParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a IncomingSsnResetRequestParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static IncomingSsnResetRequestParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ IncomingSsnResetRequestParameter(uint8_t* buffer, size_t bufferLength); public: ~IncomingSsnResetRequestParameter() override; void Dump(int indentation = 0) const final; IncomingSsnResetRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetReconfigurationRequestSequenceNumber() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetReconfigurationRequestSequenceNumber(uint32_t value); std::vector GetStreamIds() const; void AddStreamId(uint16_t streamId); protected: IncomingSsnResetRequestParameter* SoftClone(const uint8_t* buffer) const final; /** * We need to override this method since this Chunk has a variable-length * value and the fixed header doesn't have default length. */ size_t GetHeaderLength() const final { return IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength; } private: uint16_t GetNumberOfStreams() const { return GetVariableLengthValueLength() / 2; } uint16_t GetStreamAt(uint16_t idx) const { return Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 2)); } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp ================================================ #ifndef MS_RTC_SCTP_OUTGOING_SSN_RESET_REQUEST_PARAMETER_HPP #define MS_RTC_SCTP_OUTGOING_SSN_RESET_REQUEST_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include namespace RTC { namespace SCTP { /** * SCTP Outgoing SSN Reset Request Parameter (OUTGOING_SSN_RESET_REQUEST) * (13). * * @see RFC 6525. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Parameter Type = 13 | Parameter Length = 16 + 2 * N | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Re-configuration Request Sequence Number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Re-configuration Response Sequence Number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Sender's Last Assigned TSN | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream Number 1 (optional) | Stream Number 2 (optional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / ...... / * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Stream Number N-1 (optional) | Stream Number N (optional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class OutgoingSsnResetRequestParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t OutgoingSsnResetRequestParameterHeaderLength{ 16 }; public: /** * Parse a OutgoingSsnResetRequestParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static OutgoingSsnResetRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a OutgoingSsnResetRequestParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static OutgoingSsnResetRequestParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a OutgoingSsnResetRequestParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static OutgoingSsnResetRequestParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ OutgoingSsnResetRequestParameter(uint8_t* buffer, size_t bufferLength); public: ~OutgoingSsnResetRequestParameter() override; void Dump(int indentation = 0) const final; OutgoingSsnResetRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetReconfigurationRequestSequenceNumber() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetReconfigurationRequestSequenceNumber(uint32_t value); uint32_t GetReconfigurationResponseSequenceNumber() const { return Utils::Byte::Get4Bytes(GetBuffer(), 8); } void SetReconfigurationResponseSequenceNumber(uint32_t value); uint32_t GetSenderLastAssignedTsn() const { return Utils::Byte::Get4Bytes(GetBuffer(), 12); } void SetSenderLastAssignedTsn(uint32_t value); std::vector GetStreamIds() const; void AddStreamId(uint16_t streamId); protected: OutgoingSsnResetRequestParameter* SoftClone(const uint8_t* buffer) const final; /** * We need to override this method since this Chunk has a variable-length * value and the fixed header doesn't have default length. */ size_t GetHeaderLength() const final { return OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength; } private: uint16_t GetNumberOfStreams() const { return GetVariableLengthValueLength() / 2; } uint16_t GetStreamAt(uint16_t idx) const { return Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 2)); } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp ================================================ #ifndef MS_RTC_SCTP_RECONFIGURATION_RESPONSE_PARAMETER_HPP #define MS_RTC_SCTP_RECONFIGURATION_RESPONSE_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include #include namespace RTC { namespace SCTP { /** * SCTP Re-configuration Response Parameter (RECONFIGURATION_RESPONSE) * (16). * * @see RFC 6525. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Parameter Type = 16 | Parameter Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Re-configuration Response Sequence Number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Result | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Sender's Next TSN (optional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Receiver's Next TSN (optional) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class ReconfigurationResponseParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: // NOLINTNEXTLINE(performance-enum-siz) enum class Result : uint32_t { SUCCESS_NOTHING_TO_DO = 0x00000000, SUCCESS_PERFORMED = 0x00000001, DENIED = 0x00000002, ERROR_WRONG_SSN = 0x00000003, ERROR_REQUEST_ALREADY_IN_PROGRESS = 0x00000004, ERROR_BAD_SEQUENCE_NUMBER = 0x00000005, IN_PROGRESS = 0x00000006, }; public: static const size_t ReconfigurationResponseParameterHeaderLength{ 12 }; static const size_t ReconfigurationResponseParameterHeaderLengthWithOptionalFields{ 20 }; public: /** * Parse a ReconfigurationResponseParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static ReconfigurationResponseParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a ReconfigurationResponseParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static ReconfigurationResponseParameter* Factory(uint8_t* buffer, size_t bufferLength); static const std::string& ResultToString(Result result); private: /** * Parse a ReconfigurationResponseParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static ReconfigurationResponseParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: static const std::unordered_map Result2String; private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ ReconfigurationResponseParameter(uint8_t* buffer, size_t bufferLength); public: ~ReconfigurationResponseParameter() override; void Dump(int indentation = 0) const final; ReconfigurationResponseParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetReconfigurationResponseSequenceNumber() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetReconfigurationResponseSequenceNumber(uint32_t value); Result GetResult() const { return static_cast(Utils::Byte::Get4Bytes(GetBuffer(), 8)); } void SetResult(Result result); bool HasNextTsns() const { return GetVariableLengthValueLength() == ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLengthWithOptionalFields - ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength; } uint32_t GetSenderNextTsn() const { if (!HasNextTsns()) { return 0; } return Utils::Byte::Get4Bytes(GetBuffer(), 12); } uint32_t GetReceiverNextTsn() const { if (!HasNextTsns()) { return 0; } return Utils::Byte::Get4Bytes(GetBuffer(), 16); } void SetNextTsns(uint32_t senderNextTsn, uint32_t receiverNextTsn); protected: ReconfigurationResponseParameter* SoftClone(const uint8_t* buffer) const final; /** * We need to override this method since this Chunk has a variable-length * value and the fixed header doesn't have default length. */ size_t GetHeaderLength() const final { return ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength; } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.hpp ================================================ #ifndef MS_RTC_SCTP_SSN_TSN_RESET_REQUEST_PARAMETER_HPP #define MS_RTC_SCTP_SSN_TSN_RESET_REQUEST_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Zero Checksum Acceptable Parameter (SSN_TSN_RESET_REQUEST) * (15). * * @see RFC 6525. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 0x000F | Length = 8 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Re-configuration Request Sequence Number | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class SsnTsnResetRequestParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: static const size_t SsnTsnResetRequestParameterHeaderLength{ 8 }; public: /** * Parse a SsnTsnResetRequestParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static SsnTsnResetRequestParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a SsnTsnResetRequestParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static SsnTsnResetRequestParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a SsnTsnResetRequestParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static SsnTsnResetRequestParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ SsnTsnResetRequestParameter(uint8_t* buffer, size_t bufferLength); public: ~SsnTsnResetRequestParameter() override; void Dump(int indentation = 0) const final; SsnTsnResetRequestParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; uint32_t GetReconfigurationRequestSequenceNumber() const { return Utils::Byte::Get4Bytes(GetBuffer(), 4); } void SetReconfigurationRequestSequenceNumber(uint32_t value); protected: SsnTsnResetRequestParameter* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Parameter * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength; } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/StateCookieParameter.hpp ================================================ #ifndef MS_RTC_SCTP_STATE_COOKIE_PARAMETER_HPP #define MS_RTC_SCTP_STATE_COOKIE_PARAMETER_HPP #include "common.hpp" #include "RTC/SCTP/association/NegotiatedCapabilities.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP State Cookie Parameter (STATE_COOKIE) (7). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 7 | Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Cookie / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class StateCookieParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a StateCookieParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static StateCookieParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a StateCookieParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static StateCookieParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a StateCookieParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static StateCookieParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ StateCookieParameter(uint8_t* buffer, size_t bufferLength); public: ~StateCookieParameter() override; void Dump(int indentation = 0) const final; StateCookieParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; virtual bool HasCookie() const final { return HasVariableLengthValue(); } const uint8_t* GetCookie() const { return GetVariableLengthValue(); } uint16_t GetCookieLength() const { return GetVariableLengthValueLength(); } void SetCookie(const uint8_t* cookie, uint16_t cookieLength); /** * Write a locally generated StateCookie in place within the Cookie * field. * * This method is more performant than SetCookie() since it doesn't * require neither the allocation of a StateCookie class instance nor a * copy of its buffer to the StateCookieParameter. */ void WriteStateCookieInPlace( uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities); protected: StateCookieParameter* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp ================================================ #ifndef MS_RTC_SCTP_SUPPORTED_ADDRESS_TYPES_PARAMETER_HPP #define MS_RTC_SCTP_SUPPORTED_ADDRESS_TYPES_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Supported Address Types Parameter (SUPPORTED_ADDRESS_TYPES) (12). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 12 | Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Address Type #1 | Address Type #2 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | ...... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+-+-+-+-+-+-+-+-+-+-+-++-+-+-+ */ // Forward declaration. class Chunk; class SupportedAddressTypesParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a SupportedAddressTypesParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static SupportedAddressTypesParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a SupportedAddressTypesParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static SupportedAddressTypesParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a SupportedAddressTypesParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static SupportedAddressTypesParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ SupportedAddressTypesParameter(uint8_t* buffer, size_t bufferLength); public: ~SupportedAddressTypesParameter() override; void Dump(int indentation = 0) const final; SupportedAddressTypesParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; uint16_t GetNumberOfAddressTypes() const { return GetVariableLengthValueLength() / 2; } uint16_t GetAddressTypeAt(uint16_t idx) const { return Utils::Byte::Get2Bytes(GetVariableLengthValuePointer(), (idx * 2)); } void AddAddressType(uint16_t addressType); protected: SupportedAddressTypesParameter* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp ================================================ #ifndef MS_RTC_SCTP_SUPPORTED_EXTENSIONS_PARAMETER_HPP #define MS_RTC_SCTP_SUPPORTED_EXTENSIONS_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Supported Extensions Parameter (SUPPORTED_EXTENSIONS) (32776). * * @see RFC 5061. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Parameter Type = 0x8008 | Parameter Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | CHUNK TYPE 1 | CHUNK TYPE 2 | CHUNK TYPE 3 | CHUNK TYPE 4 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | .... | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | CHUNK TYPE N | PAD | PAD | PAD | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class SupportedExtensionsParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a SupportedExtensionsParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static SupportedExtensionsParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a SupportedExtensionsParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static SupportedExtensionsParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a SupportedExtensionsParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static SupportedExtensionsParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ SupportedExtensionsParameter(uint8_t* buffer, size_t bufferLength); public: ~SupportedExtensionsParameter() override; void Dump(int indentation = 0) const final; SupportedExtensionsParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; uint16_t GetNumberOfChunkTypes() const { return GetVariableLengthValueLength(); } Chunk::ChunkType GetChunkTypeAt(uint16_t idx) const { return static_cast( Utils::Byte::Get1Byte(GetVariableLengthValuePointer(), idx)); } bool IncludesChunkType(Chunk::ChunkType chunkType) const { for (size_t idx{ 0 }; idx < GetNumberOfChunkTypes(); ++idx) { if (chunkType == GetChunkTypeAt(idx)) { return true; } } return false; } void AddChunkType(Chunk::ChunkType chunkType); protected: SupportedExtensionsParameter* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/UnknownParameter.hpp ================================================ #ifndef MS_RTC_SCTP_UNKNOWN_PARAMETER_HPP #define MS_RTC_SCTP_UNKNOWN_PARAMETER_HPP #include "common.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Unknown Parameter (UNKNOWN). * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Parameter Type | Parameter Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * \ \ * / Unknown Value / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class UnknownParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a UnknownParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static UnknownParameter* Parse(const uint8_t* buffer, size_t bufferLength); private: /** * Parse a UnknownParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static UnknownParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse() and ParseStrict() static methods. */ UnknownParameter(uint8_t* buffer, size_t bufferLength); public: ~UnknownParameter() override; void Dump(int indentation = 0) const final; UnknownParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; bool HasUnknownType() const override { return true; } virtual bool HasUnknownValue() const final { return HasVariableLengthValue(); } const uint8_t* GetUnknownValue() const { return GetVariableLengthValue(); } uint16_t GetUnknownValueLength() const { return GetVariableLengthValueLength(); } protected: UnknownParameter* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.hpp ================================================ #ifndef MS_RTC_SCTP_UNRECOGNIZED_PARAMETER_PARAMETER_HPP #define MS_RTC_SCTP_UNRECOGNIZED_PARAMETER_PARAMETER_HPP #include "common.hpp" #include "RTC/SCTP/packet/Parameter.hpp" namespace RTC { namespace SCTP { /** * SCTP Unrecognized Parameter Parameter (UNRECOGNIZED_PARAMETER) (7). * * @see RFC 9260. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 8 | Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * / Unrecognized Parameter / * \ \ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class UnrecognizedParameterParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Parse a UnrecognizedParameterParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static UnrecognizedParameterParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a UnrecognizedParameterParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static UnrecognizedParameterParameter* Factory(uint8_t* buffer, size_t bufferLength); private: /** * Parse a UnrecognizedParameterParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static UnrecognizedParameterParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ UnrecognizedParameterParameter(uint8_t* buffer, size_t bufferLength); public: ~UnrecognizedParameterParameter() override; void Dump(int indentation = 0) const final; UnrecognizedParameterParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; virtual bool HasUnrecognizedParameter() const final { return HasVariableLengthValue(); } const uint8_t* GetUnrecognizedParameter() const { return GetVariableLengthValue(); } uint16_t GetUnrecognizedParameterLength() const { return GetVariableLengthValueLength(); } void SetUnrecognizedParameter(const uint8_t* parameter, uint16_t parameterLength); protected: UnrecognizedParameterParameter* SoftClone(const uint8_t* buffer) const final; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp ================================================ #ifndef MS_RTC_SCTP_ZERO_CHECKSUM_ACCEPTABLE_PARAMETER_HPP #define MS_RTC_SCTP_ZERO_CHECKSUM_ACCEPTABLE_PARAMETER_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include #include namespace RTC { namespace SCTP { /** * SCTP Zero Checksum Acceptable Parameter (ZERO_CHECKSUM_ACCEPTABLE) * (32769). * * @see RFC 9653. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type = 0x8001 | Length = 8 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Error Detection Method Identifier (EDMID) | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ // Forward declaration. class Chunk; class ZeroChecksumAcceptableParameter : public Parameter { // We need that Chunk calls protected and private methods in this class. friend class Chunk; public: /** * Zero Checksum Alternate Error Detection Method. */ // NOLINTNEXTLINE(performance-enum-size) enum class AlternateErrorDetectionMethod : uint32_t { NONE = 0x0000, SCTP_OVER_DTLS = 0x0001, }; public: static const size_t ZeroChecksumAcceptableParameterHeaderLength{ 8 }; public: /** * Parse a ZeroChecksumAcceptableParameter. * * @remarks * `bufferLength` may exceed the exact length of the Parameter. */ static ZeroChecksumAcceptableParameter* Parse(const uint8_t* buffer, size_t bufferLength); /** * Create a ZeroChecksumAcceptableParameter. * * @remarks * `bufferLength` could be greater than the Parameter real length. */ static ZeroChecksumAcceptableParameter* Factory(uint8_t* buffer, size_t bufferLength); static const std::string& AlternateErrorDetectionMethodToString( AlternateErrorDetectionMethod alternateErrorDetectionMethod); private: /** * Parse a ZeroChecksumAcceptableParameter. * * @remarks * To be used only by `Chunk::ParseParameters()`. */ static ZeroChecksumAcceptableParameter* ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding); static const std::unordered_map AlternateErrorDetectionMethod2String; private: /** * Only used by Parse(), ParseStrict() and Factory() static methods. */ ZeroChecksumAcceptableParameter(uint8_t* buffer, size_t bufferLength); public: ~ZeroChecksumAcceptableParameter() override; void Dump(int indentation = 0) const final; ZeroChecksumAcceptableParameter* Clone(uint8_t* buffer, size_t bufferLength) const final; AlternateErrorDetectionMethod GetAlternateErrorDetectionMethod() const { const auto method = Utils::Byte::Get4Bytes(GetBuffer(), 4); if ( method == static_cast(AlternateErrorDetectionMethod::NONE) || method == static_cast(AlternateErrorDetectionMethod::SCTP_OVER_DTLS)) { return static_cast(method); } else { return AlternateErrorDetectionMethod::NONE; } } void SetAlternateErrorDetectionMethod(AlternateErrorDetectionMethod alternateErrorDetectionMethod); protected: ZeroChecksumAcceptableParameter* SoftClone(const uint8_t* buffer) const final; /** * We don't really need to override this method since this Parameter * doesn't have variable-length value (despite the fixed header doesn't * have default length). */ size_t GetHeaderLength() const final { return ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength; } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/public/AssociationInterface.hpp ================================================ #ifndef MS_RTC_SCTP_ASSOCIATION_INTERFACE_HPP #define MS_RTC_SCTP_ASSOCIATION_INTERFACE_HPP #include "common.hpp" #include "RTC/SCTP/public/AssociationMetrics.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include #include #include namespace RTC { namespace SCTP { /** * The SCTP Association class represents the mediasoup side of an SCTP * association with a remote peer. * * It manages all Packet and Chunk dispatching and the connection flow. */ class AssociationInterface { public: virtual ~AssociationInterface() = default; virtual void Dump(int indentation = 0) const = 0; virtual flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const = 0; virtual Types::AssociationState GetAssociationState() const = 0; /** * May invoke `Connect()` but only if the parent transport is ready for * SCTP transmission (e.g. the WebRtcTransport has ICE and DTLS connected). */ virtual void MayConnect() = 0; /** * Initiate the SCTP association with the remote peer. It sends an INIT * Chunk. * * @remarks * - The SCTP association must be in New state. */ virtual void Connect() = 0; /** * Gracefully shutdowns the Association and sends all outstanding data. * This is an asynchronous operation and `OnAssociationClosed()` will be * called on success. * * @remarks * - libwebrtc never calls the corresponding DcSctpSocket::Shutdown() * method due to a bug and hence we shouldn't either. * * @see https://issues.webrtc.org/issues/42222897 */ virtual void Shutdown() = 0; /** * Closes the Association non-gracefully. Will send ABORT if the connection * is not already closed. No callbacks will be made after Close() has * returned. However, before Close() returns, it may have called * `OnAssociationClosed()` or `OnAssociationAborted()` callbacks. */ virtual void Close() = 0; /** * Retrieves the latest metrics. If the Association is not fully connected, * `std::nullopt` will be returned. */ virtual std::optional GetMetrics() const = 0; /** * Returns the currently set priority for an outgoing stream. The initial * value, when not set, is `SctpOptions::defaultStreamPriority`. */ virtual uint16_t GetStreamPriority(uint16_t streamId) const = 0; /** * Sets the priority of an outgoing stream. The initial value, when not * set, is `SctpOptions::defaultStreamPriority`. */ virtual void SetStreamPriority(uint16_t streamId, uint16_t priority) = 0; /** * Sets the maximum size of sent messages. The initial value, when not * set, is `SctpOptions::maxSendMessageSize`. */ virtual void SetMaxSendMessageSize(size_t maxMessageSize) = 0; /** * Returns the number of bytes of data currently queued to be sent on a * given stream. */ virtual size_t GetStreamBufferedAmount(uint16_t streamId) const = 0; /** * Returns the number of buffered outgoing bytes that is considered "low" * for a given stream. See `SetStreamBufferedAmountLowThreshold()`. */ virtual size_t GetStreamBufferedAmountLowThreshold(uint16_t streamId) const = 0; /** * Specifies the number of bytes of buffered outgoing data that is * considered "low" for a given stream, which will trigger * `OnAssociationStreamBufferedAmountLow()` event. The default value is 0. */ virtual void SetBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) = 0; /** * Resetting streams is an asynchronous operation and the results will be * notified using `OnAssociationStreamsResetPerformed()` on success and * `OnAssociationStreamsResetFailed()` on failure. * * When it's known that the peer has reset its own outgoing streams, * `OnAssociationInboundStreamsReset()` is called. * * Resetting streams can only be done on an established association that * supports stream resetting. Calling this method on e.g. a closed SCTP * association or streams that don't support resetting will not perform * any operation. * * @remarks * - Only outbound streams can be reset. * - Resetting a stream will also remove all queued messages on those * streams, but will ensure that the currently sent message (if any) is * fully sent before closing the stream. */ virtual Types::ResetStreamsStatus ResetStreams(std::span outboundStreamIds) = 0; /** * Sends an SCTP message using the provided send options. Sending a message * is an asynchronous operation, and the `OnAssociationError()` callback * may be invoked to indicate any errors in sending the message. * * The association does not have to be established before calling this * method. If it's called before there is an established association, the * message will be queued. * * @remarks * - Copy constructor is disabled and there is move constructor. That's why * we don't pass a reference here. We could pass `Message&&` but that's * worse opens the door to bugs. */ virtual Types::SendMessageStatus SendMessage( Message message, const SendMessageOptions& sendMessageOptions) = 0; /** * Sends SCTP messages using the provided send options. Sending a message * is an asynchronous operation, and the `OnAssociationError()` callback * may be invoked to indicate any errors in sending a message. * * The association does not have to be established before calling this * method. If it's called before there is an established association, the * message will be queued. * * This has identical semantics to `SendMessage()', except that it may * coalesce many messages into a single SCTP Packet if they would fit. * * @remarks * - Same as in `SendMessage()`. */ virtual std::vector SendManyMessages( std::span messages, const SendMessageOptions& sendMessageOptions) = 0; /** * Receives SCTP data (hopefully an SCTP Packet) from the remote peer. */ virtual void ReceiveSctpData(const uint8_t* data, size_t len) = 0; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/public/AssociationListenerInterface.hpp ================================================ #ifndef MS_RTC_SCTP_ASSOCIATION_LISTENER_INTERFACE_HPP #define MS_RTC_SCTP_ASSOCIATION_LISTENER_INTERFACE_HPP #include "common.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include #include namespace RTC { namespace SCTP { class AssociationListenerInterface { public: virtual ~AssociationListenerInterface() = default; public: /** * Called when an SCTP Packet must be sent to the remote endpoint. * * @return * - `true` if the packet was successfully sent. However, since * sending is unreliable, there are no guarantees that the Packet was * actually delivered. * - `false` if the Packet failed to be sent. * * @remarks * - It is NOT allowed to call methods in Association within this callback. */ virtual bool OnAssociationSendData(const uint8_t* data, size_t len) = 0; /** * Called when calling Connect(). * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationConnecting() = 0; /** * Called when calling Connect() succeeds and also for incoming successful * connection attempts. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationConnected() = 0; /** * Called when calling Connect() and also for incoming connection attempts * in case the association fails. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationFailed(Types::ErrorKind errorKind, std::string_view errorMessage) = 0; /** * Called when the Association is closed in a controlled way or when the * Association has aborted - either as decided by this Association due to * e.g. too many retransmission attempts or by the peer when receiving an * ABORT command. No other callbacks will be done after this callback, * unless reconnecting. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationClosed(Types::ErrorKind errorKind, std::string_view errorMessage) = 0; /** * Called on connection restarted (by peer). This is just a notification, * and the association is expected to work fine after this call, but there * could have been packet loss as a result of restarting the association. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationRestarted() = 0; /** * Triggered when an non-fatal error is reported by either this library or * from the other peer (by sending an ERROR command). These should be * logged, but no other action need to be taken as the association is still * viable. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationError(Types::ErrorKind errorKind, std::string_view errorMessage) = 0; /** * Called when an SCTP message in full has been received. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationMessageReceived(Message message) = 0; /** * Indicates that a stream reset request has been performed. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationStreamsResetPerformed(std::span outboundStreamIds) = 0; /** * Indicates that a stream reset request has failed. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationStreamsResetFailed( std::span outboundStreamIds, std::string_view errorMessage) = 0; /** * When a peer has reset some of its outbound streams, this will be * called. An empty list indicates that all streams have been reset. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationInboundStreamsReset(std::span inboundStreamIds) = 0; /** * Called when the amount of data buffered to be sent falls to or below * the threshold set when calling SetStreamBufferedAmountLowThreshold(). * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationStreamBufferedAmountLow(uint16_t streamId) = 0; /** * Called when the total amount of data buffered (in the entire send * buffer, for all streams) falls to or below the threshold specified in * SctpOptions::totalBufferedAmountLowThreshold`. * * @remarks * - It is allowed to call methods in Association within this callback. */ virtual void OnAssociationTotalBufferedAmountLow() = 0; /** * Called when the Association needs to know if the parent transport is * ready for SCTP traffic (e.g. whether the WebRtcTransport has ICE and * DTLS connected and at least a DataProducer or DataConsumer has been * created). Returned boolean indicates it. * * @remarks * - It is NOT allowed to call methods in Association within this callback. */ virtual bool OnAssociationIsTransportReadyForSctp() = 0; /** * SCTP message lifecycle events. * * If a `lifecycleId` is provided as `MessageSendOptions`, lifecycle * callbacks will be triggered as the message is processed by the library. * * The possible transitions are shown in the graph below: * * Association::SendMessage() ────────────────────────┐ * │ │ * │ │ * v v * OnAssociationLifecycleMessageFullySent ──> OnAssociationLifecycleMessageExpired * │ │ * │ │ * v v * OnAssociationLifeCycleMessageDelivered ──> OnAssociationLifecycleEnd */ /** * Called when a message has been fully sent, meaning that the last * fragment has been produced from the send queue and sent on the network. * Note that this will trigger at most once per message even if the * message was retransmitted due to packet loss. * * @remarks * - This is a message lifecycle event. * - It is NOT allowed to call methods in Association within this callback. */ virtual void OnAssociationLifecycleMessageFullySent(uint64_t lifecycleId) {}; /** * Called when a message has expired. If it was expired with data * remaining in the send queue that had not been sent ever, * `maybeDelivered` will be set to false. If `maybeDelivered` is true, * the message has at least once been sent and may have been correctly * received by the peer, but it has expired before the receiver managed * to acknowledge it. This means that if `maybeDelivered` is true, it's * unknown if the message was lost or was delivered, and if * `maybeDelivered` is false, it's guaranteed to not be delivered. * * @remarks * - This is a message lifecycle event. * - It's guaranteed that OnAssociationLifecycleMessageDelivered() is not called * if this callback has triggered. * - It is NOT allowed to call methods in Association within this callback. */ virtual void OnAssociationLifecycleMessageExpired(uint64_t lifecycleId, bool maybeDelivered) { } /** * Called whena non-expired message has been acknowledged by the peer as * delivered. * * Note that this will trigger only when the peer moves its cumulative * TSN ack beyond this message, and will not fire for messages acked using * gap-ack-blocks as those are renegable. This means that this may fire a * bit later than the message was actually first "acked" by the peer, as * according to the protocol, those acks may be unacked later by the * client. * * @remarks * - This is a message lifecycle event. * - It's guaranteed that OnAssociationLifecycleMessageEnd() is not called if * this callback has triggered. * - It is NOT allowed to call methods in Association within this callback. */ virtual void OnAssociationLifecycleMessageDelivered(uint64_t lifecycleId) { } /** * Called when a lifecycle event has reached its end. It will be called * when processing of a message is complete, no matter how it completed. * It will be called after all other lifecycle events, if any. * * Note that it's possible that this callback triggers without any other * lifecycle callbacks having been called before in case of errors, such * as attempting to send an empty message or failing to enqueue a message * if the send queue is full. * * @remarks: * - This is a message lifecycle event. * - When the Association is deallocated, there will be no * OnAssociationLifecycleMessageEnd() callbacks sent for messages that were * enqueued. But as long as the Association is alive, these callbacks are * guaranteed to be sent as messages are either expired or successfully * acknowledged. * - It is NOT allowed to call methods in Association within this callback. */ virtual void OnAssociationLifecycleMessageEnd(uint64_t lifecycleId) { } }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/public/AssociationMetrics.hpp ================================================ #ifndef MS_RTC_SCTP_ASSOCIATION_METRICS_HPP #define MS_RTC_SCTP_ASSOCIATION_METRICS_HPP #include "common.hpp" #include "RTC/SCTP/association/StateCookie.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" namespace RTC { namespace SCTP { /** * SCTP Association metrics. */ struct AssociationMetrics { /** * Number of SCTP Packets sent. */ uint64_t txPacketsCount{ 0 }; /** * Number of messages requested to be sent. */ uint64_t txMessagesCount{ 0 }; /** * Number of SCTP Packets received. */ uint64_t rxPacketsCount{ 0 }; /** * Number of messages received. */ uint64_t rxMessagesCount{ 0 }; /** * Number of Packets retransmitted. Since SCTP Packets can contain both * retransmitted DATA or I-DATA Chunks and Chunks that are transmitted for * the first time, this represents an upper bound as it's incremented * every time a Packet contains a retransmitted DATA or I-DATA chunk. */ uint64_t rtxPacketsCount{ 0 }; /** * Total number of bytes retransmitted. This includes the payload and * DATA/I-DATA headers, but not SCTP packet headers. */ uint64_t rtxBytesCount{ 0 }; /** * The current congestion window (cwnd) in bytes, corresponding to * `spinfo_cwnd` defined in RFC 6458. */ size_t cwndBytes{ 0 }; /** * Smoothed round trip time (in ms), corresponding to `spinfo_srtt` * defined in RFC 6458. */ uint64_t srttMs{ 0 }; /** * Number of data items in the retransmission queue that haven’t been * acked/nacked yet and are in-flight. Corresponding to `sstat_unackdata` * defined in RFC 6458. This may be an approximation when there are * messages in the send queue that haven't been fragmented/packetized yet. */ size_t unackDataCount{ 0 }; /** * The peer’s last announced receiver window size, corresponding to * `sstat_rwnd` defined in RFC 6458. */ uint32_t peerRwndBytes{ 0 }; /** * SCTP implementation of the peer. Only detected when the peer sends an * INIT_ACK Chunk to us with a State Cookie. */ Types::SctpImplementation peerImplementation{ Types::SctpImplementation::UNKNOWN }; /** * The number of negotiated outbound streams, which is configured locally * as `SctpOptions::maxOutboundStreams`, and which will be signaled by the * remote during connection. */ uint16_t negotiatedMaxOutboundStreams{ 0 }; /** * The number of negotiated inbound streams, which is configured locally * as `SctpOptions::maxInboundStreams`, and which will be signaled by the * remote during connection. */ uint16_t negotiatedMaxInboundStreams{ 0 }; /** * Whether Partial Reliability has been negotiated. * * @see RFC 3758. */ bool usesPartialReliability{ false }; /** * Whether Stream Schedulers and User Message Interleaving (I-DATA Chunks) * have been negotiated. * * @see RFC 8260. */ bool usesMessageInterleaving{ false }; /** * Whether Stream Re-Configuration has been negotiated. * * @see RFC 6525. */ bool usesReConfig{ false }; /** * Whether Alternate Error Detection Method for Zero Checksum has been * negotiated. * * @remarks * - This feature is only enabled if both peers signal their wish to use * the same (non-zero) Zero Checksum Alternate Error Detection Method. * * @see RFC 9653. */ bool usesZeroChecksum{ false }; void Dump(int indentation = 0) const; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/public/Message.hpp ================================================ #ifndef MS_RTC_SCTP_MESSAGE_HPP #define MS_RTC_SCTP_MESSAGE_HPP #include "common.hpp" #include #include namespace RTC { namespace SCTP { /** * An SCTP message is a group of bytes sent or received as a whole on a * specified stream identifier (`streamId`) and with a payload protocol * identifier (`ppid`). */ class Message { public: Message(uint16_t streamId, uint32_t ppid, std::vector payload); /** * Move constructor. No need to do anything special since std::vector * already implements move. */ Message(Message&& other) = default; /** * Move assignment. No need to do anything special since std::vector * already implements move. */ Message& operator=(Message&& other) = default; /** * Copy constructor disabled. */ Message(const Message&) = delete; /** * Copy assignment disabled. */ Message& operator=(const Message&) = delete; ~Message(); public: void Dump(int indentation = 0) const; uint16_t GetStreamId() const { return this->streamId; } void SetStreamId(uint16_t streamId); uint32_t GetPayloadProtocolId() const { return this->ppid; } std::span GetPayload() const { return this->payload; } size_t GetPayloadLength() const { return this->payload.size(); } /** * Useful to extract the payload and its ownership when destructing the * Message. * * @remarks * - && at the end means that it can only be called from a rvalue. * * @usage * ```c++ * const auto payload = std::move(message).ReleasePayload(); * ``` */ std::vector ReleasePayload() && { // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move) return std::move(this->payload); } Message Clone() const { return Message(this->streamId, this->ppid, this->payload); } private: uint16_t streamId; uint32_t ppid; std::vector payload; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/public/SctpOptions.hpp ================================================ #ifndef MS_RTC_SCTP_OPTIONS_HPP #define MS_RTC_SCTP_OPTIONS_HPP #include "common.hpp" #include "RTC/Consts.hpp" #include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp" namespace RTC { namespace SCTP { /** * SCTP options. */ struct SctpOptions { /** * Signaled source port. */ uint16_t sourcePort{ 5000 }; /** * Signaled destination port. */ uint16_t destinationPort{ 5000 }; /** * Announced maximum number of outbound streams (OS). * * @remarks * - We use maximum value by default. */ uint16_t announcedMaxOutboundStreams{ 65535 }; /** * Announced maximum number of inbound streams (MIS). * * @remarks * - We use maximum value by default. */ uint16_t announcedMaxInboundStreams{ 65535 }; /** * Maximum size of an SCTP Packet. It doesn't include any overhead of * DTLS, TURN, UDP or IP headers. */ size_t mtu{ RTC::Consts::MaxSafeMtuSizeForSctp }; /** * The largest allowed message payload to be sent. Messages will be rejected * if their payload is larger than this value. Note that this doesn't affect * incoming messages, which may larger than this value (but smaller than * `maxReceiverWindowBufferSize`). */ size_t maxSendMessageSize{ 256 * 1024 }; /** * The default stream priority, if not overridden by * `Association::SetStreamPriority()`. The default value is selected to be * compatible with https://www.w3.org/TR/webrtc-priority/, section 4.2-4.3. */ uint16_t defaultStreamPriority{ 256 }; /** * Maximum received window buffer size. This should be a bit larger than * the largest sized message you want to be able to receive. This * essentially limits the memory usage on the receive side. Note that * memory is allocated dynamically, and this represents the maximum amount * of buffered data. The actual memory usage of the library will be * smaller in normal operation, and will be larger than this due to other * allocations and overhead if the buffer is fully utilized. */ size_t maxReceiverWindowBufferSize{ 5 * 1024 * 1024 }; /** * Send queue total size limit. It will not be possible to queue more data * if the queue size is larger than this number. */ size_t maxSendBufferSize{ 2000000 }; /** * Per stream send queue size limit. Similar to `maxSendBufferSize`, but * limiting the size of individual streams. */ size_t perStreamSendQueueLimit{ 2000000 }; /** * A threshold that, when the amount of data in the send buffer goes below * this value, will trigger `Association::OnAssociationTotalBufferedAmountLow()`. */ size_t totalBufferedAmountLowThreshold{ 1800000 }; /** * Max allowed RTT value. When the RTT is measured and it's found to be * larger than this value, it will be discarded and not used for e.g. any * RTO calculation. The default value is an extreme maximum but can be * adapted to better match the environment. */ uint64_t maxRttMs{ 60000 }; /** * Initial RTO value. */ uint64_t initialRtoMs{ 500 }; /** * Minimum RTO value. */ uint64_t minRtoMs{ 400 }; /** * Minimum RTO value. */ uint64_t maxRtoMs{ 60000 }; /** * T1-init timeout (ms). */ uint64_t t1InitTimeoutMs{ 1000 }; /** * T1-cookie timeout (ms). */ uint64_t t1CookieTimeoutMs{ 1000 }; /** * T2-shutdown timeout (ms). */ uint64_t t2ShutdownTimeoutMs{ 1000 }; /** * Maximum duration of the backoff timeout. If no value is given, no * limit is set. */ std::optional timerMaxBackoffTimeoutMs{ std::nullopt }; /** * Hearbeat interval (on idle connections only). Set to zero to disable. */ uint64_t heartbeatIntervalMs{ 30000 }; /** * The maximum time when a SACK will be sent from the arrival of an * unacknowledged Packet. Whatever is smallest of RTO/2 and this will be * used. */ uint64_t delayedAckMaxTimeoutMs{ 200 }; /** * The minimum limit for the measured RTT variance. * * Setting this below the expected delayed ack timeout (+ margin) of the * peer might result in unnecessary retransmissions, as the maximum time * it takes to ACK a DATA chunk is typically RTT + ATO (delayed ack * timeout), and when the SCTP channel is quite idle, and heartbeats * dominate the source of RTT measurement, the RTO would converge with the * smoothed RTT (SRTT). The default ATO is 200ms in usrsctp, and a 20ms * (10%) margin would include the processing time of received packets and * the clock granularity when setting the delayed ack timer on the peer. * * This is defined as "G" in the algorithm for TCP in * https://datatracker.ietf.org/doc/html/rfc6298#section-4. */ uint64_t minRttVarianceMs{ 220 }; /** * The initial congestion window size, in number of MTUs. * * @see https://tools.ietf.org/html/rfc4960#section-7.2.1 which defaults * at ~3 and https://research.google/pubs/pub36640/ which argues for at * least ten segments. */ size_t initialCwndMtus{ 10 }; /** * The minimum congestion window size, in number of MTUs, upon detection * of/ packet loss by SACK. Note that if the retransmission timer expires, * the congestion window will be as small as one MTU. * * @see https://tools.ietf.org/html/rfc4960#section-7.2.3. */ size_t minCwndMtus{ 4 }; /** * When the congestion window is at or above this number of MTUs, the * congestion control algorithm will avoid filling the congestion window * fully, if that results in fragmenting large messages into quite small * packets. When the congestion window is smaller than this option, it * will aim to fill the congestion window as much as it can, even if it * results in creating small fragmented packets. */ size_t avoidFragmentationCwndMtus{ 6 }; /** * When the congestion window is below this number of MTUs, sent data * chunks will have the "I" (Immediate SACK - RFC7053) bit set. That will * prevent the receiver from delaying the SACK, which result in shorter * time until the sender can send the next packet as its driven by SACKs. * This can reduce latency for low utilized and lossy connections. * * Default value set to be same as initial congestion window. Set to zero * to disable. */ size_t immediateSackUnderCwndMtus{ 10 }; /** * The number of packets that may be sent at once. This is limited to * avoid bursts that too quickly fill the send buffer. Typically in a * connection in its "slow start" phase (when it sends as much as it can), * it will send up to three packets for every SACK received, so the default * limit is set just above that, and then mostly applicable for (but not * limited to) fast retransmission scenarios. */ size_t maxBurst{ 4 }; /** * Maximum data retransmit attempts (for DATA, I_DATA and other Chunks). * Set to std::nullopt for no limit. */ std::optional maxRetransmissions{ 10 }; /** * Max.Init.Retransmits. Set to std::nullopt for no limit. * * @see https://datatracker.ietf.org/doc/html/rfc9260#section-16 */ std::optional maxInitRetransmissions{ 8 }; /** * Enable Partial Reliability Extension. * @see RFC 3758. */ bool enablePartialReliability{ true }; /** * Enable Stream Schedulers and User Message Interleaving (I-DATA Chunks). * * @see RFC 8260. */ bool enableMessageInterleaving{ true }; /** * Whether RTO should be added to heartbeat interval. */ bool heartbeatIntervalIncludeRtt{ true }; /** * Alternate Error Detection Method for Zero Checksum. * * @remarks * - This feature is only enabled if both peers signal their wish to use * the same (non-zero) Zero Checksum Alternate Error Detection Method. * * @see RFC 9653. */ ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod zeroChecksumAlternateErrorDetectionMethod{ ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE }; }; /** * Send options given when sending SCTP messages. */ struct SendMessageOptions { /** * Whether the message should be sent with unordered message delivery. */ bool unordered{ false }; /** * If set, will discard messages that haven't been correctly sent and * received before the lifetime has expired. This is only available if * the peer supports Partial Reliability Extension (RFC 3758). */ std::optional lifetimeMs{ std::nullopt }; /** * If set, limits the number of retransmissions. This is only available * if the peer supports Partial Reliability Extension (RFC 3758). */ std::optional maxRetransmissions{ std::nullopt }; /** * If set, will generate lifecycle events for this message. See e.g. * `AssociationListener::OnAssociationLifecycleMessageFullySent()`. This * value is decided by the application and the library will provide it to * all lifecycle callbacks. */ std::optional lifecycleId{ std::nullopt }; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/public/SctpTypes.hpp ================================================ #ifndef MS_RTC_SCTP_TYPES_HPP #define MS_RTC_SCTP_TYPES_HPP #include "common.hpp" #include "Utils/UnwrappedSequenceNumber.hpp" #include #include namespace RTC { namespace SCTP { namespace Types { /** * Publicly exposed SCTP Association state. */ enum class AssociationState : uint8_t { /** * Initial state. * * @remarks * - Once state changes it will never transition to NEW again. */ NEW, /** * The Association is closed. */ CLOSED, /** * The Association has initiated a connection, which is not yet * established. * * @remarks * - For incoming connections and for reconnections when the Association * is already connected, the Association will not transition to this * state. */ CONNECTING, /** * The Association is connected and the connection is established. */ CONNECTED, /** * The Association is shutting down, and the connection is not yet closed. */ SHUTTING_DOWN }; constexpr std::string_view AssociationStateToString(AssociationState associationState) { switch (associationState) { case AssociationState::NEW: { return "NEW"; } case AssociationState::CLOSED: { return "CLOSED"; } case AssociationState::CONNECTING: { return "CONNECTING"; } case AssociationState::CONNECTED: { return "CONNECTED"; } case AssociationState::SHUTTING_DOWN: { return "SHUTTING_DOWN"; } NO_DEFAULT_GCC(); } } /** * Kinds of errors that are exposed in the API. */ enum class ErrorKind : uint8_t { /** * Indicates that no error has occurred. This will never be the case when * OnError() or OnAborted() is called. */ SUCCESS, /** * There have been too many retries or timeouts, and the library has given * up. */ TOO_MANY_RETRIES, /** * A command was received that is only possible to execute when the * Association is connected, which it is not. */ NOT_CONNECTED, /** * Parsing of the command or its parameters failed. */ PARSE_FAILED, /** * Commands are received in the wrong sequence, which indicates a * synchronisation mismatch between the peers. */ WRONG_SEQUENCE, /** * The peer has reported an issue using ERROR or ABORT command. */ PEER_REPORTED, /** * The peer has performed a protocol violation. */ PROTOCOL_VIOLATION, /** * The receive or send buffers have been exhausted. */ RESOURCE_EXHAUSTION, /** * The application has performed an invalid operation. */ UNSUPPORTED_OPERATION }; constexpr std::string_view ErrorKindToString(ErrorKind errorKind) { switch (errorKind) { // NOTE: In dcsctp this is `NO_ERROR` but it fails on MSVC because // some Windows headers define `NO_ERROR` macro. case ErrorKind::SUCCESS: { return "SUCCESS"; } case ErrorKind::TOO_MANY_RETRIES: { return "TOO_MANY_RETRIES"; } case ErrorKind::NOT_CONNECTED: { return "NOT_CONNECTED"; } case ErrorKind::PARSE_FAILED: { return "PARSE_FAILED"; } case ErrorKind::WRONG_SEQUENCE: { return "WRONG_SEQUENCE"; } case ErrorKind::PEER_REPORTED: { return "PEER_REPORTED"; } case ErrorKind::PROTOCOL_VIOLATION: { return "PROTOCOL_VIOLATION"; } case ErrorKind::RESOURCE_EXHAUSTION: { return "RESOURCE_EXHAUSTION"; } case ErrorKind::UNSUPPORTED_OPERATION: { return "UNSUPPORTED_OPERATION"; } NO_DEFAULT_GCC(); } } /** * SCTP implementation determined by first 8 bytes of the State Cookie * sent by the remote peer. */ enum class SctpImplementation : uint8_t { UNKNOWN, MEDIASOUP, DCSCTP, USRSCTP }; constexpr std::string_view SctpImplementationToString(SctpImplementation sctpImplementation) { switch (sctpImplementation) { case SctpImplementation::UNKNOWN: { return "unknown"; } case SctpImplementation::MEDIASOUP: { return "mediasoup"; } case SctpImplementation::DCSCTP: { return "dcsctp"; } case SctpImplementation::USRSCTP: { return "usrsctp"; } NO_DEFAULT_GCC(); } } /** * Return value of Association::ResetStreams(). */ enum class ResetStreamsStatus : uint8_t { /** * If the connection is not yet established, this will be returned. */ NOT_CONNECTED, /** * Indicates that ResetStreams operation has been successfully * initiated. */ PERFORMED, /** * Indicates that resetting streams has failed as it's not supported by * the peer. */ NOT_SUPPORTED }; constexpr std::string_view ResetStreamsStatusToString(ResetStreamsStatus status) { switch (status) { case ResetStreamsStatus::NOT_CONNECTED: { return "NOT_CONNECTED"; } case ResetStreamsStatus::PERFORMED: { return "PERFORMED"; } case ResetStreamsStatus::NOT_SUPPORTED: { return "NOT_SUPPORTED"; } } } /** * Return value of Association::SendMessage() and * Association::SendManyMessages(). */ enum class SendMessageStatus : uint8_t { /** * The message was enqueued successfully. As sending the message is done * asynchronously, this is no guarantee that the message has been * actually sent. */ SUCCESS, /** * The message was rejected as the payload was empty (which is not * allowed in SCTP). */ ERROR_MESSAGE_EMPTY, /** * The message was rejected as the payload was larger than what has been * set as `SctpOptions.maxMessageSize`. */ ERROR_MESSAGE_TOO_LARGE, /** * The message could not be enqueued as the Association is out of * resources. This mainly indicates that the send queue is full. */ ERROR_RESOURCE_EXHAUSTION, /** * The message could not be sent as the Association is shutting down. */ ERROR_SHUTTING_DOWN }; constexpr std::string_view SendMessageStatusToString(SendMessageStatus status) { switch (status) { case SendMessageStatus::SUCCESS: { return "SUCCESS"; } case SendMessageStatus::ERROR_MESSAGE_EMPTY: { return "ERROR_MESSAGE_EMPTY"; } case SendMessageStatus::ERROR_MESSAGE_TOO_LARGE: { return "ERROR_MESSAGE_TOO_LARGE"; } case SendMessageStatus::ERROR_RESOURCE_EXHAUSTION: { return "ERROR_RESOURCE_EXHAUSTION"; } case SendMessageStatus::ERROR_SHUTTING_DOWN: { return "ERROR_SHUTTING_DOWN"; } } } constexpr uint16_t MaxRetransmitsNoLimit{ std::numeric_limits::max() }; constexpr uint64_t ExpiresAtMsInfinite{ std::numeric_limits::max() }; /** * Unwrapped Transmission Sequence Numbers (TSN). */ using UnwrappedTsn = Utils::UnwrappedSequenceNumber; /** * Unwrapped Stream Sequence Numbers (SSN). */ using UnwrappedSsn = Utils::UnwrappedSequenceNumber; /** * Unwrapped Message Identifier (MID). */ using UnwrappedMid = Utils::UnwrappedSequenceNumber; } // namespace Types } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/rx/DataTracker.hpp ================================================ #ifndef MS_RTC_SCTP_DATA_TRACKER_HPP #define MS_RTC_SCTP_DATA_TRACKER_HPP #include "common.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include "Utils/UnwrappedSequenceNumber.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include #include #include namespace RTC { namespace SCTP { /** * Keeps track of received DATA and I-DATA chunks and handles all logic for * when_to create SACKs and also how_to generate them. * * It only uses TSNs to track delivery and doesn't need to be aware of * streams. * * SACKs are optimally sent every second packet on association with no packet * loss. When packet loss is detected, it's sent for every packet. When SACKs * are not sent directly, a timer is used to send a SACK delayed (by RTO/2, * or 200ms, whatever is smallest). */ class DataTracker { public: /** * The maximum number of duplicate TSNs that will be reported in a SACK. */ static constexpr size_t MaxDuplicateTsnReported{ 20 }; /** * The maximum number of gap-ack-blocks that will be reported in a SACK. */ static constexpr size_t MaxGapAckBlocksReported{ 20 }; /** * The maximum number of accepted in-flight DATA / I-DATA chunks. This * indicates the maximum difference from this buffer's last cumulative ack * TSN, and any received data. Data received beyond this limit will be * dropped, which will force the transmitter to send data that actually * increases the last cumulative acked TSN. */ static constexpr uint32_t MaxAcceptedOutstandingFragments{ 100000 }; private: enum class AckState : uint8_t { /** * No need to send an ACK. */ IDLE, /** * Has received data chunks (but not yet end of packet). */ BECOMING_DELAYED, /** * Has received data chunks and the end of a packet. Delayed ack timer is * running and a SACK will be sent on expiry, or if DATA / I-DATA is sent, * or after next packet with data. */ DELAYED, /** * Send a SACK immediately after handling this packet. */ IMMEDIATE, }; private: static constexpr std::string_view AckStateToString(AckState ackState) { // NOTE: We cannot use MS_TRACE() here because clang in Linux will // complain about "read of non-constexpr variable 'configuration' is not // allowed in a constant expression". switch (ackState) { case AckState::IDLE: { return "IDLE"; } case AckState::BECOMING_DELAYED: { return "BECOMING_DELAYED"; } case AckState::DELAYED: { return "DELAYED"; } case AckState::IMMEDIATE: { return "IMMEDIATE"; } NO_DEFAULT_GCC(); } } private: /** * Represents ranges of TSNs that have been received that are not directly * following the last cumulative acked TSN. This information is returned * to the sender in the "gap-ack-blocks" in the SACK chunk. The blocks are * always non-overlapping and non-adjacent. */ class AdditionalTsnBlocks { public: /** * Represents an inclusive range of received TSNs, i.e. [firstTsn, lastTsn]. */ struct TsnRange { TsnRange(Types::UnwrappedTsn firstTsn, Types::UnwrappedTsn lastTsn) : firstTsn(firstTsn), lastTsn(lastTsn) { } Types::UnwrappedTsn firstTsn; Types::UnwrappedTsn lastTsn; }; /** * Adds a TSN to the set. This will try to expand any existing block and * might merge blocks to ensure that all blocks are non-adjacent. If a * current block can't be expanded, a new block is created. * * The return value indicates if `tsn` was added. If false is returned, * the `tsn` was already represented in one of the blocks. */ bool Add(Types::UnwrappedTsn tsn); /** * Erases all TSNs up to, and including `tsn`. This will remove all * blocks that are completely below `tsn` and may truncate a block where * `tsn` is within that block. In that case, the frontmost block's start * TSN will be the next following tsn after `tsn`. */ void EraseTo(Types::UnwrappedTsn tsn); /** * Removes the first block. Must not be called on an empty set. */ void PopFront(); const std::vector& GetBlocks() const { return this->blocks; } bool IsEmpty() const { return this->blocks.empty(); } const TsnRange& Front() const { return this->blocks.front(); } private: // A sorted vector of non-overlapping and non-adjacent blocks. std::vector blocks; }; public: DataTracker(BackoffTimerHandleInterface* delayedAckTimer, uint32_t remoteInitialTsn); public: /** * Indicates if the provided TSN is valid. If this return false, the data * should be dropped and not added to any other buffers, which essentially * means that there is intentional packet loss. */ bool IsTsnValid(uint32_t tsn) const; /** * Called for every incoming data chunk. Returns `true` if `tsn` was seen * for the first time, and `false` if it has been seen before (a duplicate * `tsn`). * * @remarks * - `IsTsnValid()` must be called prior to calling this method. */ bool Observe(uint32_t tsn, bool immediateAck = false); /** * Called at the end of processing an SCTP packet. */ void ObservePacketEnd(); /** * Called for incoming FORWARD-TSN/I-FORWARD-TSN chunks. Indicates if the * chunk had any effect. */ bool HandleForwardTsn(uint32_t newCumulativeTsn); /** * Indicates if a SACK should be sent. There may be other reasons to send * a SACK, but if this function indicates so, it should be sent as soon as * possible. Calling this function will make it clear a flag so that if * it's called again, it will probably return `false`. * * If the delayed ack timer is running, this method will return `false` * unless `alsoIfDelayed` is set to `true`. Then it will return true as * well. */ bool ShouldSendAck(bool alsoIfDelayed = false); /** * Forces `ShouldSendSack()` to return `true`. */ void ForceImmediateSack(); /** * Returns the last cumulative ack TSN - the last seen data chunk's TSN * value before any packet loss was detected. */ uint32_t GetLastCumulativeAckedTsn() const { return this->lastCumulativeAckedTsn.Wrap(); } bool IsLaterThanCumulativeAckedTsn(uint32_t tsn) const { return this->tsnUnwrapper.PeekUnwrap(tsn) > this->lastCumulativeAckedTsn; } /** * Returns `true` if the received `tsn` would increase the cumulative ack * TSN. */ bool WillIncreaseCumAckTsn(uint32_t tsn) const; /** * Adds a SACK chunk with selective ack to the given Packet. * * @remarks * - It will clear `this->duplicates`, so every SACK chunk that is * consumed must be sent. */ void AddSackSelectiveAck(Packet* packet, size_t aRwnd); void HandleDelayedAckTimerExpiry(); private: void UpdateAckState(AckState newAckState, std::string_view reason); private: // If a packet has ever been seen. BackoffTimerHandleInterface* delayedAckTimer; bool packetSeen{ false }; AckState ackState{ AckState::IDLE }; Types::UnwrappedTsn::Unwrapper tsnUnwrapper; // All TSNs up until (and including) this value have been seen. Types::UnwrappedTsn lastCumulativeAckedTsn; // Received TSNs that are not directly following `lastCumulativeAckedTsn`. AdditionalTsnBlocks additionalTsnBlocks; std::set duplicateTsns; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/rx/InterleavedReassemblyStreams.hpp ================================================ #ifndef MS_RTC_SCTP_INTERLEAVED_REASSEMBLY_STREAMS_HPP #define MS_RTC_SCTP_INTERLEAVED_REASSEMBLY_STREAMS_HPP #include "common.hpp" #include "RTC/SCTP/rx/ReassemblyStreamsInterface.hpp" #include #include #include // std::tie() namespace RTC { namespace SCTP { /** * Handles reassembly of incoming data when interleaved message sending is * enabled on the association, i.e. when RFC 8260 is in use. * * In other words, this class handles data received via I-DATA chunks. */ class InterleavedReassemblyStreams : public ReassemblyStreamsInterface { private: struct FullStreamId { FullStreamId(bool unordered, uint16_t streamId) : unordered(unordered), streamId(streamId) { } friend bool operator<(FullStreamId a, FullStreamId b) { return std::tie(a.unordered, a.streamId) < std::tie(b.unordered, b.streamId); } const bool unordered; const uint16_t streamId; }; private: class Stream { private: using ChunkMap = std::map>; public: Stream(FullStreamId fullStreamId, InterleavedReassemblyStreams* parent, uint32_t nextMid = 0) : fullStreamId(fullStreamId), parent(*parent), nextMid(midUnwrapper.Unwrap(nextMid)) { } public: int32_t AddData(Types::UnwrappedTsn tsn, UserData data); size_t EraseTo(uint32_t mid); void Reset() { this->midUnwrapper.Reset(); this->nextMid = this->midUnwrapper.Unwrap(0); } bool HasUnassembledChunks() const { return !this->chunksByMid.empty(); } private: /** * Try to assemble one message identified by `mid`. Returns the number * of bytes assembled if a message was assembled. */ size_t TryToAssembleMessage(Types::UnwrappedMid mid); /** * Try to assemble several messages in order from the stream. Returns * the number of bytes assembled if a message was assembled. */ size_t TryToAssembleMessages(); size_t AssembleMessage(ChunkMap& tsnChunks); size_t AssembleMessage(Types::UnwrappedTsn tsn, UserData data); private: const FullStreamId fullStreamId; InterleavedReassemblyStreams& parent; std::map chunksByMid; Types::UnwrappedMid::Unwrapper midUnwrapper; Types::UnwrappedMid nextMid; }; public: explicit InterleavedReassemblyStreams( ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage); public: int32_t AddData(Types::UnwrappedTsn tsn, UserData data) override; size_t HandleForwardTsn( Types::UnwrappedTsn newCumulativeTsn, std::span skippedStreams) override; void ResetStreams(std::span streamIds) override; private: Stream& GetOrCreateStream(const FullStreamId& streamId); private: // Callback for when a message has been assembled. const OnAssembledMessage onAssembledMessage; // All unordered and ordered streams, managing not-yet-assembled data. std::map streams; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/rx/ReassemblyQueue.hpp ================================================ #ifndef MS_RTC_SCTP_REASSEMBLY_QUEUE_HPP #define MS_RTC_SCTP_REASSEMBLY_QUEUE_HPP #include "common.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include "RTC/SCTP/rx/ReassemblyStreamsInterface.hpp" #include #include #include #include namespace RTC { namespace SCTP { /** * Contains the received DATA / I-DATA chunks that haven't yet been * reassembled, and reassembles chunks when possible. * * The actual assembly is handled by an implementation of the * `ReassemblyStreamsInterface` interface. * * Except for reassembling fragmented messages, this class will also handle * two less common operations. To handle the receiver-side of partial * reliability (limited number of retransmissions or limited message * lifetime) as well as stream resetting, which is used when a sender wishes * to close SCTP streams. * * Partial reliability is handled when a FORWARD-TSN or I-FORWARD-TSN chunk * is received, and it will simply delete any chunks matching the parameters * in that chunk. This is mainly implemented in ReassemblyStreams classes. * * Resetting streams is handled when a RECONFIG chunks is received with an * "Outgoing SSN Reset Request" parameter. That parameter will contain a list * of streams to reset, and a `senderLastAssignedTsn`. If this TSN is not yet * seen, the stream cannot be directly reset, and this class will respond * that the reset is "deferred". But if this TSN provided is known, the * stream can be immediately be reset. * * The reassembly queue has a maximum size, as it would otherwise be an DoS * attack vector where a peer could consume all memory of the other peer by * sending a lot of ordered chunks, but carefully withholding an early one. * It also has a watermark limit, which the caller can query is the number * of bytes is above that limit. This is used by the caller to be selective * in what to add to the reassembly queue, so that it's not exhausted. The * caller is expected to call `IsFull()` prior to adding data to the queue * and to act accordingly if the queue is full. */ class ReassemblyQueue { public: /** * When the queue is filled over this fraction (of its maximum size), the * SCTP association should restrict incoming data to avoid filling up the * queue. */ static constexpr float HighWatermarkLimit{ 0.9 }; private: struct DeferredResetStreams { DeferredResetStreams(Types::UnwrappedTsn senderLastAssignedTsn, std::set streamIds) : senderLastAssignedTsn(senderLastAssignedTsn), streamIds(std::move(streamIds)) { } Types::UnwrappedTsn senderLastAssignedTsn; std::set streamIds; std::vector> deferredActions; // TODO: Once we upgrade to C++23, replace with: // std::vector> deferredActions; }; public: explicit ReassemblyQueue(size_t maxLengthBytes, bool useMessageInterleaving = false); ~ReassemblyQueue(); public: /** * Adds a data chunk to the queue, with a `tsn` and other parameters in * `data`. */ void AddData(uint32_t tsn, UserData data); /** * Indicates if the reassembly queue has any reassembled messages that can * be retrieved by calling `GetNextMessage()`. */ bool HasMessages() const { return !this->reassembledMessages.empty(); } /** * Returns the number of reassembled messages that are ready to be * retrieved by calling `GetNextMessage()`. */ size_t GetMessagesReadyCount() const { return this->reassembledMessages.size(); } /** * Returns the next reassembled message or `std::nullopt` if there are no * messages ready. */ std::optional GetNextMessage(); /** * Handles a FORWARD-TSN/I-FORWARD-TSN chunk, when the sender has indicated * that the received (this class) should forget about some chunks. This is * used to implement partial reliability. */ void HandleForwardTsn( uint32_t newCumulativeTsn, std::span skippedStreams); /** * Resets the provided streams and leaves deferred reset processing, if * enabled. */ void ResetStreamsAndLeaveDeferredReset(std::span streamIds); /** * Enters deferred reset processing. */ void EnterDeferredReset(uint32_t senderLastAssignedTsn, std::span streamIds); /** * The number of payload bytes that have been queued. Note that the actual * memory usage is higher due to additional overhead of tracking received * data. */ size_t GetQueuedBytes() const { return this->queuedBytes; } /** * The remaining bytes until the queue has reached the watermark limit. */ size_t GetRemainingBytes() const { return this->watermarkBytes - this->queuedBytes; } /** * Indicates if the queue is full. Data should not be added to the queue * when it's full. */ bool IsFull() const { return this->queuedBytes >= this->maxLengthBytes; } /** * Indicates if the queue is above the watermark limit, which is a certain * percentage of its size. */ bool IsAboveWatermark() const { return this->queuedBytes >= this->watermarkBytes; } /** * Returns the watermark limit, in bytes. */ size_t GetWatermarkBytes() const { return this->watermarkBytes; } private: std::unique_ptr CreateReassemblyStreams( ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage, bool useMessageInterleaving); void AddReassembledMessage(std::span tsns, Message message); void AssertIsConsistent() const; private: const size_t maxLengthBytes; const size_t watermarkBytes; Types::UnwrappedTsn::Unwrapper tsnUnwrapper; // Messages that have been reassembled, and will be consumed from by // `GetNextMessage()`. std::deque reassembledMessages; // If present, "deferred reset processing" mode is active. std::optional deferredResetStreams; // The number of "payload bytes" that are in this queue, in total. size_t queuedBytes = 0; // The actual implementation of ReassemblyStreams. std::unique_ptr reassemblyStreams; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/rx/ReassemblyStreamsInterface.hpp ================================================ #ifndef MS_RTC_SCTP_REASSEMBLY_STREAMS_INTERFACE_HPP #define MS_RTC_SCTP_REASSEMBLY_STREAMS_INTERFACE_HPP #include "common.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include namespace RTC { namespace SCTP { /** * Implementations of this interface will be called when data is received, * when data should be skipped/forgotten or when sequence number should be * reset. * * As a result of these operations - mainly when data is received - the * implementations of this interface should notify when a message has been * assembled, by calling the provided callback of type `OnAssembledMessage()`. * How it assembles messages will depend on e.g. if a message was sent on an * ordered or unordered stream. * * Implementations will - for each operation - indicate how much additional * memory that has been used as a result of performing the operation. This * is used to limit the maximum amount of memory used, to prevent * out-of-memory situations. */ class ReassemblyStreamsInterface { public: /** * This callback will be provided as an argument to the constructor of the * concrete class implementing this interface and should be called when a * message has been assembled as well as indicating from which TSNs this * message was assembled from. */ using OnAssembledMessage = std::function tsns, Message message)>; public: virtual ~ReassemblyStreamsInterface() = default; public: /** * Adds a data chunk to a stream as identified in `data`. If it was the * last remaining chunk in a message, reassemble one (or several, in case * of ordered chunks) messages. * * Returns the additional number of bytes added to the queue as a result * of performing this operation. If this addition resulted in messages * being assembled and delivered, this may be negative. */ virtual int32_t AddData(Types::UnwrappedTsn tsn, UserData data) = 0; /** * Called for incoming FORWARD-TSN/I-FORWARD-TSN chunks - when the sender * wishes the received to skip/forget about data up until the provided * TSN. This is used to implement partial reliability, such as limiting * the number of retransmissions or the an expiration duration. As a * result of skipping data, this may result in the implementation being * able to assemble messages in ordered streams. * * Returns the number of bytes removed from the queue as a result of this * operation. */ virtual size_t HandleForwardTsn( Types::UnwrappedTsn newCumulativeTsn, std::span skippedStreams) = 0; /** * Called for incoming (possibly deferred) RE-CONFIG chunks asking for * either a few streams, or all streams (when the list is empty) to be * reset - to have their next SSN or Message ID to be zero. */ virtual void ResetStreams(std::span streamIds) = 0; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/rx/TraditionalReassemblyStreams.hpp ================================================ #ifndef MS_RTC_SCTP_TRADITIONAL_REASSEMBLY_STREAMS_HPP #define MS_RTC_SCTP_TRADITIONAL_REASSEMBLY_STREAMS_HPP #include "common.hpp" #include "RTC/SCTP/rx/ReassemblyStreamsInterface.hpp" #include namespace RTC { namespace SCTP { /** * Handles reassembly of incoming data when interleaved message sending is * not enabled on the association, i.e. when RFC 8260 is not in use and * RFC 9260 is to be followed. * * In other words, this class handles data received via DATA chunks. */ class TraditionalReassemblyStreams : public ReassemblyStreamsInterface { private: using ChunkMap = std::map; private: /** * Base class for `UnorderedStream` and `OrderedStream` classes. */ class StreamBase { protected: explicit StreamBase(TraditionalReassemblyStreams* parent) : parent(*parent) { } size_t AssembleMessage(ChunkMap::iterator start, ChunkMap::iterator end); size_t AssembleMessage(Types::UnwrappedTsn tsn, UserData data); protected: TraditionalReassemblyStreams& parent; }; private: /** * Manages all received data for a specific ordered stream, and assembles * messages when possible. */ class OrderedStream : StreamBase { public: explicit OrderedStream(TraditionalReassemblyStreams* parent, uint16_t nextSsn = 0) : StreamBase(parent), nextSsn(ssnUnwrapper.Unwrap(nextSsn)) { } int32_t AddData(Types::UnwrappedTsn tsn, UserData data); size_t EraseTo(uint16_t ssn); void Reset() { this->ssnUnwrapper.Reset(); this->nextSsn = this->ssnUnwrapper.Unwrap(uint16_t(0)); } uint16_t GetNextSsn() const { return this->nextSsn.Wrap(); } bool HasUnassembledChunks() const { return !chunksBySsn.empty(); } private: /** * Try to assemble one message in order from the stream. Returns the * number of bytes assembled if a message was assembled. */ size_t TryToAssembleMessage(); /** * Try to assemble several messages in order from the stream. Returns * the number of bytes assembled if a message was assembled. */ size_t TryToAssembleMessages(); /** * Same as above but when inserting the first complete message avoid * insertion into the map. */ size_t TryToAssembleMessagesFastpath( Types::UnwrappedSsn ssn, Types::UnwrappedTsn tsn, UserData data); private: // NOTE: This must be an ordered container to be able to iterate in SSN // order. std::map chunksBySsn; Types::UnwrappedSsn::Unwrapper ssnUnwrapper; Types::UnwrappedSsn nextSsn; }; private: /** * Manages all received data for a specific unordered stream, and assembles * messages when possible. */ class UnorderedStream : StreamBase { public: explicit UnorderedStream(TraditionalReassemblyStreams* parent) : StreamBase(parent) { } int32_t AddData(Types::UnwrappedTsn tsn, UserData data); /** * Returns the number of bytes removed from the queue. */ size_t EraseTo(Types::UnwrappedTsn tsn); bool HasUnassembledChunks() const { return !this->chunks.empty(); } private: /** * Given an iterator to any chunk within the map, try to assemble a * message containing it and - if successful - erase those chunks from * the stream chunks map. * * Returns the number of bytes that were assembled. */ size_t TryToAssembleMessage(ChunkMap::iterator it); /** * Given a map (`chunks`) and an iterator to within that map (`it`), * this method will return an iterator to the first chunk in that * message, which has the `isBeginning` flag set. If there are any gaps, * or if the beginning can't be found, `std::nullopt` is returned. */ std::optional::iterator> FindBeginning( std::map::iterator it); /** * Given a map (`chunks`) and an iterator to within that map (`it`), * this method will return an iterator to the chunk after the last chunk * in that message, which has the `isEnd` flag set. If there are any * gaps, or if the end can't be found, `std::nullopt` is returned. */ std::optional::iterator> FindEnd( std::map::iterator it); private: ChunkMap chunks; }; public: explicit TraditionalReassemblyStreams( ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage); public: int32_t AddData(Types::UnwrappedTsn tsn, UserData data) override; size_t HandleForwardTsn( Types::UnwrappedTsn newCumulativeTsn, std::span skippedStreams) override; void ResetStreams(std::span streamIds) override; private: // Callback for when a message has been assembled. const OnAssembledMessage onAssembledMessage; // All unordered and ordered streams, managing not-yet-assembled data. std::map unorderedStreams; std::map orderedStreams; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/tx/OutstandingData.hpp ================================================ #ifndef MS_RTC_SCTP_OUTSTANDING_DATA_HPP #define MS_RTC_SCTP_OUTSTANDING_DATA_HPP #include "common.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include #include #include #include #include namespace RTC { namespace SCTP { /** * This class keeps track of outstanding data Chunks (sent, not yet acked) * and handles acking, nacking, rescheduling and abandoning. * * Items are added to this queue as they are sent and will be removed when * the peer acks them using the cumulative TSN ack. */ class OutstandingData { #ifdef MS_TEST public: /** * State for DATA Chunks (message fragments) in the queue. * * @remarks * - Used in tests. */ enum class State : uint8_t { /** * The Chunk has been sent but not received yet (from the sender's point * of view, as no SACK has been received yet that reference this Chunk). */ IN_FLIGHT, /** * A SACK has been received which explicitly marked this Chunk as missing. * It's now NACKED and may be retransmitted if NACKED enough times. */ NACKED, /** * A Chunk that will be retransmitted when possible. */ TO_BE_RETRANSMITTED, /** * A SACK has been received which explicitly marked this Chunk as * received. */ ACKED, /** * A Chunk whose message has expired or has been retransmitted too many * times (RFC3758). It will not be retransmitted anymore. */ ABANDONED, }; #endif public: /** * Contains variables scoped to a processing of an incoming SACK. */ struct AckInfo { explicit AckInfo(Types::UnwrappedTsn cumulativeTsnAck) : highestTsnAcked(cumulativeTsnAck) { } /** * Bytes acked by increasing `cumulativeTsnAck` and `gapAckBlocks`. */ size_t bytesAcked{ 0 }; /** * Indicates if this SACK indicates that packet loss has occurred. Just * because a packet is missing in the SACK doesn't necessarily mean that * there is packet loss as that packet might be in-flight and received * out-of-order. But when it has been reported missing consecutive * times, it will eventually be considered "lost" and this will be set. */ bool hasPacketLoss{ false }; /** * Highest TSN Newly Acknowledged, an SCTP variable. */ Types::UnwrappedTsn highestTsnAcked; /** * The set of lifecycle IDs that were acked using `cumulativeTsnAck`. */ std::vector ackedLifecycleIds; /** * The set of lifecycle IDs that were acked, but had been abandoned. */ std::vector abandonedLifecycleIds; }; private: /** * A fragmented message's DATA Chunk while in the retransmission queue, * and its associated metadata. * * @remarks * - This data structure has been optimized for size, by ordering fields * to avoid unnecessary padding. */ class Item { public: enum class NackAction : uint8_t { NOTHING, RETRANSMIT, ABANDON, }; private: private: enum class Lifecycle : uint8_t { /** * The Chunk is alive (sent, received, etc). */ ACTIVE, /** * The Chunk is scheduled to be retransmitted, and will then * transition to become active. */ TO_BE_RETRANSMITTED, /** * The Chunk has been abandoned. This is a terminal state. */ ABANDONED }; enum class AckState : uint8_t { /** * The Chunk is in-flight. */ UNACKED, /** * The Chunk has been received and acknowledged. */ ACKED, /** * The Chunk has been nacked and is possibly lost. */ NACKED }; public: Item( uint32_t outgoingMessageId, UserData data, uint64_t timeSentMs, uint16_t maxRetransmissions, uint64_t expiresAtMs, std::optional lifecycleId); Item(const Item&) = delete; Item& operator=(const Item&) = delete; public: uint32_t GetOutgoingMessageId() const { return this->outgoingMessageId; } uint64_t GetTimeSentMs() const { return this->timeSentMs; } const UserData& GetData() const { return this->data; } /** * Acks an item. */ void Ack(); /** * Nacks an item. If it has been nacked enough times, or if * `retransmitNow` is set, it might be marked for retransmission. If * the item has reached its max retransmission value, it will instead * be abandoned. The action performed is indicated as return value. */ NackAction Nack(bool retransmitNow); /** * Prepares the item to be retransmitted. Sets it as outstanding and * clears all nack counters. */ void MarkAsRetransmitted(); /** * Marks this item as abandoned. */ void Abandon(); bool IsOutstanding() const { return this->ackState != AckState::ACKED && this->lifecycle == Lifecycle::ACTIVE; } bool IsAcked() const { return this->ackState == AckState::ACKED; } bool IsNacked() const { return this->ackState == AckState::NACKED; } bool IsAbandoned() const { return this->lifecycle == Lifecycle::ABANDONED; } /** * Indicates if this Chunk should be retransmitted. */ bool ShouldBeRetransmitted() const { return this->lifecycle == Lifecycle::TO_BE_RETRANSMITTED; } /** * Indicates if this Chunk has ever been retransmitted. */ bool HasBeenRetransmitted() const { return this->numRetransmissions > 0; } /** * Given the current time, and the current state of this DATA Chunk, it * will indicate if it has expired (SCTP Partial Reliability Extension). */ bool HasExpired(uint64_t nowMs) const { return (this->expiresAtMs != Types::ExpiresAtMsInfinite && this->expiresAtMs <= nowMs); } std::optional GetLifecycleId() const { return this->lifecycleId; } const uint32_t outgoingMessageId; // The actual data to send/retransmit. const UserData data; // When the packet was sent, and placed in this queue. const uint64_t timeSentMs; // If the message was sent with a maximum number of retransmissions, // this is set to that number. The value zero (0) means that it will // never be retransmitted. const uint16_t maxRetransmissions; // At this exact millisecond, the item is considered expired. If the // message is not to be expired, this is set to the infinite future. // NOTE: If 0 it means infinite time. const uint64_t expiresAtMs; // An optional lifecycle id, which may only be set for the last // fragment. const std::optional lifecycleId; // Indicates the life cycle status of this Chunk. Lifecycle lifecycle{ Lifecycle::ACTIVE }; // Indicates the presence of this Chunk, if it's in flight (UNACKED), // has been received (ACKED) or is possibly lost (NACKED). AckState ackState{ AckState::UNACKED }; // The number of times the DATA Chunk has been nacked (by having // received a SACK which doesn't include it). Will be cleared on // retransmissions. uint8_t nackCount{ 0 }; // The number of times the DATA Chunk has been retransmitted. uint16_t numRetransmissions{ 0 }; }; public: OutstandingData( size_t dataChunkHeaderLength, Types::UnwrappedTsn lastCumulativeTsnAck, std::function discardFromSendQueue); public: AckInfo HandleSack( Types::UnwrappedTsn cumulativeTsnAck, std::span gapAckBlocks, bool isInFastRecovery); /** * Returns as many of the Chunks that are eligible for fast retransmissions * and that would fit in a single packet of `maxLength`. The eligible * Chunks that didn't fit will be marked for (normal) retransmission and * will not be returned if this method is called again. */ std::vector> GetChunksToBeFastRetransmitted(size_t maxLength); /** * Given `maxLength` of space left in a packet, which Chunks can be added * to it? */ std::vector> GetChunksToBeRetransmitted(size_t maxLength); /** * How many inflight bytes there are, as sent on the wire as packets. */ size_t GetUnackedPacketBytes() const { return this->unackedPacketBytes; } /** * How many inflight bytes there are, counting only the payload. */ size_t GetUnackedPayloadBytes() const { return this->unackedPayloadBytes; } /** * Returns the number of DATA Chunks that are in-flight (not acked or * nacked). */ size_t GetUnackedItems() const { return this->unackedItems; } /** * Given the current time `nowMs`, expire and abandon outstanding (sent * at least once) Chunks that have a limited lifetime. */ void ExpireOutstandingChunks(uint64_t nowMs); bool IsEmpty() const { return this->outstandingData.empty(); } bool HasDataToBeFastRetransmitted() const { return !this->toBeFastRetransmitted.empty(); } bool HasDataToBeRetransmitted() const { return !this->toBeRetransmitted.empty() || !this->toBeFastRetransmitted.empty(); } Types::UnwrappedTsn GetLastCumulativeTsnAck() const { return this->lastCumulativeTsnAck; } Types::UnwrappedTsn GetNextTsn() const { return this->GetHighestOutstandingTsn().GetNextValue(); } Types::UnwrappedTsn GetHighestOutstandingTsn() const; /** * Schedules `data` to be sent, with the provided partial reliability * parameters. Returns the TSN if the item was actually added and * scheduled to be sent, and std::nullopt if it shouldn't be sent. */ std::optional Insert( uint32_t outgoingMessageId, const UserData& data, uint64_t timeSentMs, uint16_t maxRetransmissions = Types::MaxRetransmitsNoLimit, uint64_t expiresAtMs = Types::ExpiresAtMsInfinite, std::optional lifecycleId = std::nullopt); /** * Nacks all outstanding data. */ void NackAll(); /** * Adds a FORWARD-TSN Chunk to the given Packet and returns it. */ const ForwardTsnChunk* AddForwardTsn(Packet* packet) const; /** * Adds an I-FORWARD-TSN Chunk to the given Packet and returns it. */ const IForwardTsnChunk* AddIForwardTsn(Packet* packet) const; /** * Given the current time and a TSN, it returns the measured RTT between * when the Chunk was sent and now. It takes into acccount Karn's * algorithm, so if the Chunk has ever been retransmitted, it will return * `std::nullopt`. */ std::optional MeasureRtt(uint64_t nowMs, Types::UnwrappedTsn tsn) const; /** * Returns true if the next Chunk that is not acked by the peer has been * abandoned, which means that a FORWARD-TSN should be sent. */ bool ShouldSendForwardTsn() const; /** * Called when an outgoing stream reset is sent, marking the last assigned * TSN as a breakpoint that a FORWARD-TSN shouldn't cross. */ void BeginResetStreams(); #ifdef MS_TEST /** * Returns the internal state of all queued Chunks. * * @remarks * - Used in tests. */ std::vector> GetChunkStatesForTesting() const; #endif private: /** * Returns how large a Chunk will be, serialized, carrying the data. */ size_t GetSerializedChunkLength(const UserData& data) const; Item& GetItem(Types::UnwrappedTsn tsn); const Item& GetItem(Types::UnwrappedTsn tsn) const; /** * Given a `cumulativeTsnAck` from an incoming SACK, will remove those * items in the retransmission queue up until this value and will update * `ackInfo` by setting `this->lastCumulativeTsnAck`. */ void RemoveAcked(Types::UnwrappedTsn cumulativeTsnAck, AckInfo& ackInfo); /** * Will mark the Chunks covered by the `gapAckBlocks` from an incoming * SACK as "acked" and update `ackInfo` by adding new TSNs to * `this->cumulativeTsnAck`. */ void AckGapBlocks( Types::UnwrappedTsn cumulativeTsnAck, std::span gapAckBlocks, AckInfo& ackInfo); /** * Mark Chunks reported as "missing", as "nacked" or "to be retransmitted" * depending how many times this has happened. Only packets up until * `ackInfo.highestTsnAcked` (highest TSN newly acknowledged) are * nacked/retransmitted. The method will set `ackInfo.hasPacketLoss`. */ void NackBetweenAckBlocks( Types::UnwrappedTsn cumulativeTsnAck, std::span gapAckBlocks, bool isInFastRecovery, bool cumulativeTsnAckedAdvanced, AckInfo& ackInfo); /** * Process the acknowledgement of the Chunk referenced by `item` and * updates state in `ackInfo` and the object's state. */ void AckChunk(AckInfo& ackInfo, Types::UnwrappedTsn tsn, Item& item); /** * Helper method to process an incoming nack of an item and perform the * correct operations given the action indicated when nacking an item * (e.g. retransmitting or abandoning). The return value indicate if an * action was performed, meaning that packet loss was detected and acted * upon. If `doFastRetransmit` is set and if the item has been nacked * sufficiently many times so that it should be retransmitted, this will * schedule it to be "fast retransmitted". This is only done just before * going into fast recovery. * * @remarks * - Note that since nacking an item may result in it becoming abandoned, * which in turn could alter `this->outstandingData`, any iterators are * invalidated after having called this method. */ bool NackItem(Types::UnwrappedTsn tsn, bool retransmitNow, bool doFastRetransmit); /** * Given that a message fragment, `item` has been abandoned, abandon all * other fragments that share the same message - both never-before-sent * fragments that are still in the SendQueue and outstanding Chunks. */ void AbandonAllFor(const OutstandingData::Item& item); std::vector> ExtractChunksThatCanFit( std::set& chunks, size_t maxLength); void AssertIsConsistent() const; private: // The size of the data Chunk (DATA/I-DATA) header that is used. const size_t dataChunkHeaderLength; // The last cumulative TSN ack number. Types::UnwrappedTsn lastCumulativeTsnAck; // Callback when to discard items from the send queue. std::function discardFromSendQueue; // Outstanding items. If non-empty, the first element has // `TSN=this->lastCumulativeTsnAck_ + 1` and the following items are in // strict increasing TSN order. The last item has // `TSN=GetHighestOutstandingTsn()`. std::deque outstandingData; // The number of bytes that are in-flight, counting only the payload. size_t unackedPayloadBytes{ 0 }; // The number of bytes that are in-flight, as sent on the wire (as // packets). size_t unackedPacketBytes{ 0 }; // The number of DATA Chunks that are in-flight (sent but not yet acked // or nacked). size_t unackedItems{ 0 }; // Data Chunks that are eligible for fast retransmission. std::set toBeFastRetransmitted; // Data Chunks that are to be retransmitted. std::set toBeRetransmitted; // Wben a stream reset has begun, the "next TSN to assign" is added to // this set, and removed when the cum-ack TSN reaches it. This is used // to limit a FORWARD-TSN to reset streams past a "stream reset last // assigned TSN". // NOTE: dcsctp uses `webrtc::flat_set` type which is more // efficient in read operations. std::set streamResetBreakpointTsns; }; #ifdef MS_TEST /** * For logging purposes in Catch2 tests. */ inline std::ostream& operator<<(std::ostream& os, OutstandingData::State state) { switch (state) { case OutstandingData::State::IN_FLIGHT: { return os << "IN_FLIGHT"; } case OutstandingData::State::NACKED: { return os << "NACKED"; } case OutstandingData::State::TO_BE_RETRANSMITTED: { return os << "TO_BE_RETRANSMITTED"; } case OutstandingData::State::ACKED: { return os << "ACKED"; } case OutstandingData::State::ABANDONED: { return os << "ABANDONED"; } default: { return os << "UNKNOWN(" << static_cast(state) << ")"; } } } /** * For Catch2 to print it nicely. */ inline std::ostream& operator<<( std::ostream& os, const std::pair& s) { return os << "{tsn:" << s.first << ", state:" << s.second << "}"; } #endif } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/tx/RetransmissionErrorCounter.hpp ================================================ #ifndef MS_RTC_SCTP_RETRANSMISSION_ERROR_COUNTER_HPP #define MS_RTC_SCTP_RETRANSMISSION_ERROR_COUNTER_HPP #include "common.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include namespace RTC { namespace SCTP { /** * The RetransmissionErrorCounter is a simple counter with a limit, and when * the limit is exceeded, the counter is exhausted and the Association will * be closed. It's incremented on retransmission errors, such as the T3-RTX * timer expiring, but also missing heartbeats and stream reset requests. */ class RetransmissionErrorCounter { public: RetransmissionErrorCounter(const SctpOptions& sctpOptions); ~RetransmissionErrorCounter(); public: void Dump(int indentation = 0) const; /** * Increments the retransmission timer. Returns `true` if the maximum * error count has been reached, `false` will be returned. */ bool Increment(std::string_view reason); /** * Whether maximum error count has been reached. * @return [description] */ bool IsExhausted() const { return this->limit.has_value() && this->counter > this->limit.value(); } /** * Clears the retransmission errors. */ void Clear(); /** * Returns its current counter value. */ size_t GetCounter() const { return this->counter; } private: std::optional limit; size_t counter{ 0 }; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/tx/RetransmissionQueue.hpp ================================================ #ifndef MS_RTC_SCTP_RETRANSMISSION_QUEUE_HPP #define MS_RTC_SCTP_RETRANSMISSION_QUEUE_HPP #include "common.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SCTP/tx/OutstandingData.hpp" #include "RTC/SCTP/tx/SendQueueInterface.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include namespace RTC { namespace SCTP { /** * The RetransmissionQueue manages all DATA/I-DATA chunks that are in-flight * and schedules them to be retransmitted if necessary. Chunks are * retransmitted when they have been lost for a number of consecutive SACKs, * or when the retransmission timer expires. * * As congestion control is tightly connected with the state of transmitted * packets, that's also managed here to limit the amount of data that is * in-flight (sent, but not yet acknowledged). */ class RetransmissionQueue { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnRetransmissionQueueNewRttMs(uint64_t newRttMs) = 0; virtual void OnRetransmissionQueueClearRetransmissionCounter() = 0; }; private: enum class CongestionAlgorithmPhase : uint8_t { SLOW_START, CONGESTION_AVOIDANCE, }; public: /** * Creates a RetransmissionQueue which will send data using * `localInitialTsn` as the first TSN to use for sent fragments. It will * poll data from `sendQueue`. When SACKs are received, it will estimate * the RTT and call `listener->OnRetransmissionQueueNewRttMs()`. When an * outstanding chunk has been acked, it will call * `listener->OnRetransmissionQueueClearRetransmissionCounter() and will * also use `t3RtxTimer`, which is the SCTP retransmission timer to manage * retransmissions. */ RetransmissionQueue( Listener* listener, AssociationListenerInterface& associationListener, uint32_t localInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, SendQueueInterface& sendQueue, BackoffTimerHandleInterface* t3RtxTimer, const SctpOptions& sctpOptions, bool supportsPartialReliability, bool useMessageInterleaving); ~RetransmissionQueue(); public: /** * Handles a received SACK. Returns true if the SACK was processed and * false if it was discarded due to received out-of-order and not relevant. */ bool HandleReceivedSackChunk(uint64_t nowMs, const SackChunk* receivedSackChunk); /** * Handles an expired retransmission timer. */ void HandleT3RtxTimerExpiry(); bool HasDataToBeFastRetransmitted() const { return this->outstandingData.HasDataToBeFastRetransmitted(); } /** * Returns a list of Chunks to "fast retransmit" that would fit in * `maxLength` (bytes). The current value of `cwnd` is ignored. */ std::vector> GetChunksForFastRetransmit(size_t maxLength); /** * Returns a list of Chunks to send that would fit in `maxLength` * (bytes). This may be further limited by the congestion control windows. * Note that `ShouldSendForwardTsn()` must be called prior to this method, * to abandon expired Chunks, as this method will not expire any Chunks. */ std::vector> GetChunksToSend( uint64_t nowMs, size_t maxLength); #ifdef MS_TEST /** * Returns the internal state of all queued Chunks. * * @remarks * - Used in tests. */ std::vector> GetChunkStatesForTesting() const { return this->outstandingData.GetChunkStatesForTesting(); } #endif /** * Returns the next TSN that will be allocated for sent DATA Chunks. */ uint32_t GetNextTsn() const { return this->outstandingData.GetNextTsn().Wrap(); } uint32_t GetLastAssignedTsn() const { return Types::UnwrappedTsn::AddTo(this->outstandingData.GetNextTsn(), -1).Wrap(); } /** * Returns the size of the congestion window, in bytes. This is the number * of bytes that may be in-flight. */ size_t GetCwnd() const { return this->cwnd; } /** * Overrides the current congestion window size. */ void SetCwnd(size_t cwnd) { this->cwnd = cwnd; } /** * Returns the current receiver window size. */ size_t GetRwnd() const { return this->rwnd; } size_t GetRtxPacketsCount() const { return this->rtxPacketsCount; } uint64_t GetRtxBytesCount() const { return this->rtxBytesCount; } /** * How many inflight bytes there are, as sent on the wire as packets. */ size_t GetUnackedPacketBytes() const { return this->outstandingData.GetUnackedPacketBytes(); } /** * Returns the number of DATA/I-DATA chunks that are in-flight. */ size_t GetUnackedItems() const { return this->outstandingData.GetUnackedItems(); } /** * Given the current time `nowMs`, it will evaluate if there are Chunks * that have expired and that need to be discarded. It returns true if a * FORWARD-TSN should be sent. */ bool ShouldSendForwardTsn(uint64_t nowMs); /** * Adds a FORWARD-TSN Chunk to the given Packet and returns it. */ const ForwardTsnChunk* AddForwardTsn(Packet* packet) const { return this->outstandingData.AddForwardTsn(packet); } /** * Adds an I-FORWARD-TSN Chunk to the given Packet and returns it. */ const IForwardTsnChunk* AddIForwardTsn(Packet* packet) const { return this->outstandingData.AddIForwardTsn(packet); } /** * @see SendQueueInterface for a longer description of these methods * related to stream resetting. */ void PrepareResetStream(uint16_t streamId); bool HasStreamsReadyToBeReset() const; std::vector BeginResetStreams(); void CommitResetStreams(); void RollbackResetStreams(); private: /** * Returns how large a chunk will be, serialized, carrying the data. */ size_t GetSerializedChunkLength(const UserData& data) const; /** * Indicates if the congestion control algorithm is in "fast recovery". */ bool IsInFastRecovery() const { return this->fastRecoveryExitTsn.has_value(); } /** * Indicates if the provided SACK Chunk is valid given what has previously * been received. If it returns false, the SACK is most likely a duplicate * of something already seen, so this returning false doesn't necessarily * mean that the SACK is illegal. */ bool IsSackChunkValid(const SackChunk* sackChunk) const; /** * When a SACK Chunk is received, this method will be called which may * call into the `RetransmissionTimeout` to update the RTO. */ void UpdateRttMs(uint64_t nowMs, Types::UnwrappedTsn cumulativeTsnAck); /** * If the congestion control is in "fast recovery mode", this may be * exited now. */ void MayExitFastRecovery(Types::UnwrappedTsn cumulativeTsnAck); /** * If Chunks have been ACKed, stop the retransmission timer. * * @remarks * - This method is NOT defined in dcsctp! See bug report: * https://issues.webrtc.org/issues/505751236 */ void StopT3RtxTimerOnIncreasedCumulativeTsnAck(Types::UnwrappedTsn cumulativeTsnAck); /** * Update the congestion control algorithm given as the cumulative ack TSN * value has increased, as reported in an incoming SACK Chunk. */ void HandleIncreasedCumulativeTsnAck(size_t unackedPacketBytes, size_t totalBytesAcked); /** * Update the congestion control algorithm, given as packet loss has been * detected, as reported in an incoming SACK Chunk. */ void HandlePacketLoss(Types::UnwrappedTsn highestTsnAcked); /** * Update the view of the receiver window size. */ void UpdateReceiverWindow(uint32_t aRwnd); /** * If there is data sent and not acked, ensure that the retransmission * timer is running. */ void StartT3RtxTimerIfOutstandingData(); /** * Returns the current congestion control algorithm phase. */ CongestionAlgorithmPhase GetCongestionAlgorithmPhase() const { return (this->cwnd <= this->ssthresh) ? CongestionAlgorithmPhase::SLOW_START : CongestionAlgorithmPhase::CONGESTION_AVOIDANCE; } private: Listener* listener; AssociationListenerInterface& associationListener; const SctpOptions sctpOptions; // If the peer supports RFC3758 "SCTP Partial Reliability Extension". bool supportsPartialReliability; // The length of the data chunk (DATA/I-DATA) header that is used. const size_t dataChunkHeaderLength; // The retransmission timer. BackoffTimerHandleInterface* t3RtxTimer; // Unwraps TSNs. Types::UnwrappedTsn::Unwrapper tsnUnwrapper; // Congestion Window. Number of bytes that may be in-flight (sent, not // acked). size_t cwnd; // Receive Window. Number of bytes available in the receiver's RX buffer. size_t rwnd; // Slow Start Threshold. See RFC 9260. size_t ssthresh; // Partial Bytes Acked. See RFC 9260. size_t partialBytesAcked{ 0 }; // See `AssociationMetrics`. size_t rtxPacketsCount{ 0 }; uint64_t rtxBytesCount{ 0 }; // If set, fast recovery is enabled until this TSN has been cumulative // acked. std::optional fastRecoveryExitTsn{ std::nullopt }; // The send queue. SendQueueInterface& sendQueue; // All the outstanding data Chunks that are in-flight and that have not // been cumulative acked. Note that it also contains chunks that have been // acked in gap-ack-blocks. OutstandingData outstandingData; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/tx/RetransmissionTimeout.hpp ================================================ #ifndef MS_RTC_SCTP_RETRANSMISSION_TIMEOUT_HPP #define MS_RTC_SCTP_RETRANSMISSION_TIMEOUT_HPP #include "common.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" namespace RTC { namespace SCTP { /** * Manages updating of the Retransmission Timeout (RTO) SCTP variable, which * is used directly as the base timeout for T3-RTX and for other timers, such * as delayed ack. * * When a round-trip-time (RTT) is calculated (outside this class), the * `ObserveRttMs()` method is called, which calculates the retransmission * timeout (RTO) value. The RTO value will become larger if the RTT is high * and/or the RTT values are varying a lot, which is an indicator of a bad * connection. */ class RetransmissionTimeout { public: explicit RetransmissionTimeout(const SctpOptions& sctpOptions); ~RetransmissionTimeout(); public: void Dump(int indentation = 0) const; /** * To be called when a RTT (ms) has been measured, to update the RTO * value. */ void ObserveRttMs(uint64_t rttMs); /** * Returns the Retransmission Timeout (RTO) value. */ uint64_t GetRtoMs() const { return this->rtoMs; } /** * Returns the smoothed RTT value (ms).. */ uint64_t GetSrttMs() const { return this->srttMs; } private: uint64_t minRtoMs; uint64_t maxRtoMs; uint64_t maxRttMs; uint64_t minRttVarianceMs; double srttMs; double rtoMs; double rttVarMs{ 0 }; bool firstMeasurement{ false }; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/tx/RoundRobinSendQueue.hpp ================================================ #ifndef MS_RTC_SCTP_ROUND_ROBIN_SEND_QUEUE_HPP #define MS_RTC_SCTP_ROUND_ROBIN_SEND_QUEUE_HPP #include "common.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SCTP/tx/SendQueueInterface.hpp" #include "RTC/SCTP/tx/StreamScheduler.hpp" #include #include #include namespace RTC { namespace SCTP { /** * The Round Robin send queue holds all messages that the client wants to * send, but that haven't yet been split into chunks and fully sent on the * wire. * * As defined in https://datatracker.ietf.org/doc/html/rfc8260#section-3.2, * it will cycle to send messages from different streams. It will send all * fragments from one message before continuing with a different message on * possibly a different stream, until support for message interleaving has * been implemented. * * As messages can be (requested to be) sent before the connection is * properly established, this send queue is always present - even for closed * connections. * * The send queue may trigger callbacks. `OnAssociationStreamBufferedAmountLow()` * and `OnAssociationTotalBufferedAmountLow()` will be triggered as defined in * their documentation. `OnAssociationLifecycleMessageExpired()` with * `maybeDelivered=false` and `OnAssociationLifecycleMessageEnd()` will be * triggered when messages have been expired, abandoned or discarded from the * send queue. If a message is fully produced, meaning that the last fragment * has been produced, the responsibility to send lifecycle events is then * transferred to the retransmission queue, which is the one asking to * produce the message. */ class RoundRobinSendQueue : public SendQueueInterface { private: struct MessageAttributes { bool isUnordered; uint16_t maxRetransmissions; uint64_t expiresAtMs; std::optional lifecycleId; }; private: /** * Represents a value and a "low threshold" that when the value reaches or * goes under the "low threshold", will trigger `onThresholdReached()` * callback. */ class ThresholdWatcher { public: explicit ThresholdWatcher(std::function onThresholdReached) : onThresholdReached(std::move(onThresholdReached)) { } /** * Increases the value. */ void Increase(size_t bytes) { this->value += bytes; } /** * Decreases the value and triggers `onThresholdReached()` if it's at * or below `this->lowThreshold`. */ void Decrease(size_t bytes); size_t GetValue() const { return this->value; } size_t GetLowThreshold() const { return this->lowThreshold; } void SetLowThreshold(size_t lowThreshold); private: const std::function onThresholdReached; size_t value{ 0 }; size_t lowThreshold{ 0 }; }; private: /** * Per-stream information. */ class OutgoingStream : public StreamScheduler::StreamProducer { public: OutgoingStream( RoundRobinSendQueue* parent, StreamScheduler* scheduler, uint16_t streamId, uint16_t priority, std::function onBufferedAmountLow) : parent(*parent), schedulerStream(scheduler->CreateStream(this, streamId, priority)), bufferedAmountThresholdWatcher(std::move(onBufferedAmountLow)) { } uint16_t GetStreamId() const { return this->schedulerStream->GetStreamId(); } /** * Enqueues a message to this stream. */ void AddMessage(Message message, MessageAttributes attributes); // Implementing `StreamScheduler::StreamProducer`. std::optional Produce(uint64_t nowMs, size_t maxLength) override; size_t GetBytesToSendInNextMessage() const override; const ThresholdWatcher& GetBufferedAmount() const { return bufferedAmountThresholdWatcher; } ThresholdWatcher& GetBufferedAmount() { return bufferedAmountThresholdWatcher; } /** * Discards a partially sent message, see `SendQueue::Discard()`. */ bool Discard(uint32_t outgoingMessageId); /** * Pauses this stream, which is used before resetting it. */ void Pause(); /** * Resumes a paused stream. */ void Resume(); bool IsReadyToBeReset() const { return this->pauseState == PauseState::PAUSED; } bool IsResetting() const { return this->pauseState == PauseState::RESETTING; } void SetAsResetting(); /** * Resets this stream, meaning MIDs and SSNs are set to zero. */ void Reset(); /** * Indicates if this stream has a partially sent message in it. */ bool HasPartiallySentMessage() const; uint16_t GetPriority() const { return this->schedulerStream->GetPriority(); } void SetPriority(uint16_t priority) { this->schedulerStream->SetPriority(priority); } private: /** * Streams are paused before they can be reset. To reset a stream, the * socket sends an outgoing stream reset command with the TSN of the last * fragment of the last message, so that receivers and senders can agree * on when it stopped. And if the send queue is in the middle of sending * a message, and without fragments not yet sent and without TSNs * allocated to them, it will keep sending data until that message has * ended. */ enum class PauseState : uint8_t { /** * The stream is not paused, and not scheduled to be reset. */ NOT_PAUSED, /** * The stream has requested to be reset/paused but is still producing * fragments of a message that hasn't ended yet. When it does, it will * transition to the `PAUSED` state. */ PENDING, /** * The stream is fully paused and can be reset. */ PAUSED, /** * The stream has been added to an outgoing stream reset request and a * response from the peer hasn't been received yet. */ RESETTING, }; // An enqueued message and metadata. struct Item { explicit Item(uint32_t outgoingMessageId, Message msg, MessageAttributes attributes) : outgoingMessageId(outgoingMessageId), message(std::move(msg)), attributes(attributes), remainingLength(message.GetPayloadLength()) { } uint32_t outgoingMessageId; Message message; MessageAttributes attributes; // The remaining payload (offset and length) to be sent, when it has // been fragmented. size_t remainingOffset{ 0 }; size_t remainingLength; // If set, an allocated Message ID and SSN. Will be allocated when the // first fragment is sent. std::optional mid{ std::nullopt }; std::optional ssn{ std::nullopt }; // The current Fragment Sequence Number, incremented for each fragment. uint32_t currentFsn{ 0 }; }; void HandleMessageExpired(OutgoingStream::Item& item); void AssertIsConsistent() const; private: RoundRobinSendQueue& parent; const std::unique_ptr schedulerStream; // The current amount of buffered data. ThresholdWatcher bufferedAmountThresholdWatcher; PauseState pauseState = PauseState::NOT_PAUSED; // MIDs are different for unordered and ordered messages sent on a // stream. uint32_t nextUnorderedMid{ 0 }; uint32_t nextOrderedMid{ 0 }; uint16_t nextSsn{ 0 }; // Enqueued messages, and metadata. std::deque items; }; public: RoundRobinSendQueue( AssociationListenerInterface& associationListener, size_t mtu, uint16_t defaultPriority, size_t totalBufferedAmountLowThreshold); ~RoundRobinSendQueue() override; public: /** * Indicates if the buffer is empty. */ bool IsEmpty() const { return GetTotalBufferedAmount() == 0; } /** * Adds the message to be sent using the `sendMessageOptions` provided. * The current time should be in `nowMs`. Note that it's the responsibility * of the caller to ensure that the buffer is not full (by calling * `IsFull()`) before adding messages to it. */ void AddMessage( uint64_t nowMs, Message message, const SendMessageOptions& sendMessageOptions = {}); uint16_t GetStreamPriority(uint16_t streamId) const; void SetStreamPriority(uint16_t streamId, uint16_t priority); // Methods implementing `SendQueueInterface`. public: void EnableMessageInterleaving(bool enabled) override { this->scheduler.EnableMessageInterleaving(enabled); } std::optional Produce(uint64_t nowMs, size_t maxLength) override; bool Discard(uint16_t streamId, uint32_t outgoingMessageId) override; void PrepareResetStream(uint16_t streamId) override; bool HasStreamsReadyToBeReset() const override; std::vector GetStreamsReadyToBeReset() override; void CommitResetStreams() override; void RollbackResetStreams() override; void Reset() override; size_t GetStreamBufferedAmount(uint16_t streamId) const override; size_t GetTotalBufferedAmount() const override { return this->totalBufferedAmountThresholdWatcher.GetValue(); } size_t GetStreamBufferedAmountLowThreshold(uint16_t streamId) const override; void SetStreamBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) override; private: OutgoingStream& GetOrCreateStreamInfo(uint16_t streamId); void AssertIsConsistent() const; private: AssociationListenerInterface& associationListener; const uint16_t defaultPriority; StreamScheduler scheduler; // The total amount of buffer data, for all streams. ThresholdWatcher totalBufferedAmountThresholdWatcher; uint32_t currentOutgoingMessageId{ 0 }; // All streams, and messages added to those. std::map streams; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/tx/SendQueueInterface.hpp ================================================ #ifndef MS_RTC_SCTP_SEND_QUEUE_INTERFACE_HPP #define MS_RTC_SCTP_SEND_QUEUE_INTERFACE_HPP #include "common.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include namespace RTC { namespace SCTP { class SendQueueInterface { public: /** * Container for a data chunk that is produced by the send queue. */ struct DataToSend { DataToSend(uint32_t outgoingMessageId, UserData data) : outgoingMessageId(outgoingMessageId), data(std::move(data)) { } uint32_t outgoingMessageId; /** * The data to send, including all parameters. */ UserData data; /** * Partial reliability (RFC 3758). */ uint16_t maxRetransmissions{ Types::MaxRetransmitsNoLimit }; /** * Time when it expires. */ uint64_t expiresAtMs{ Types::ExpiresAtMsInfinite }; /** * Lifecycle. Set for the last fragment and `std::nullopt` for all * other fragments. */ std::optional lifecycleId; }; public: virtual ~SendQueueInterface() = default; /** * Configures the send queue to support interleaved message sending as * described in RFC 8260. Every send queue starts with this value set as * disabled, but can later change it when the capabilities of the * connection have been negotiated. This affects the behavior of the * `Produce()` method. */ virtual void EnableMessageInterleaving(bool enabled) = 0; /** * Produce a chunk to be sent. `maxLength` refers to how many payload bytes * that may be produced, not including any headers. * * @todo * - As in dcsctp, this interface is obviously missing an "AddMessage()" * method, but that is postponed a bit until the story around how to * model message prioritization, which is important for any advanced * stream scheduler, is further clarified. */ virtual std::optional Produce(uint64_t nowMs, size_t maxLength) = 0; /** * Discards a partially sent message identified by the parameters `streamId` * and `outgoingMessageId`. The `outgoingMessageId` comes from the returned * information when having called `Produce()`. A partially sent message * means that it has had at least one fragment of it returned when * `Produce()` was called prior to calling this method. * * This is used when a message has been found to be expired (by the partial * reliability extension), and the retransmission queue will signal the * receiver that any partially received message fragments should be * skipped. This means that any remaining fragments in the send queue must * be removed as well so that they are not sent. * * This method returns true if this message had unsent fragments still in * the queue that were discarded, and false if there were no such * fragments. */ virtual bool Discard(uint16_t streamId, uint32_t outgoingMessageId) = 0; /** * Prepares the stream to be reset. This is used to close a SCTP stream * and will be signaled to the other side. * * Concretely, it discards all whole (not partly sent) messages in the * given stream and pauses that stream so that future added messages are * not produced until resumed. * * This method can be called multiple times to add more streams to be * reset, and paused while they are resetting. This is the first part of * the two-phase commit protocol to reset streams, where the caller * completes the procedure by either calling `CommitResetStreams()` or * `RollbackResetStreams()`. * * @todo * - As in dcsctp, investigate if it really should discard any message at * all. RFC 8831 only mentions that "RFC 6525 also guarantees that all * the messages are delivered (or abandoned) before the stream is reset". */ virtual void PrepareResetStream(uint16_t streamId) = 0; /** * Indicates if there are any streams that are ready to be reset. */ virtual bool HasStreamsReadyToBeReset() const = 0; /** * Returns a list of streams that are ready to be included in an outgoing * stream reset request. Any streams that are returned here must be * included in an outgoing stream reset request, and there must not be * concurrent requests. */ virtual std::vector GetStreamsReadyToBeReset() = 0; /** * Called to commit to reset the streams returned by * `GetStreamsReadyToBeReset()`. It will reset the stream sequence numbers * (SSNs) and message identifiers (MIDs) and resume the paused streams. */ virtual void CommitResetStreams() = 0; /** * Called to abort the resetting of streams returned by * `GetStreamsReadyToBeReset()`. Will resume the paused streams without * resetting the stream sequence numbers (SSNs) or message identifiers * (MIDs). Note that the non-partial messages that were discarded when * calling `PrepareResetStreams()` will not be recovered, to better match * the intention from the sender to "close the channel". */ virtual void RollbackResetStreams() = 0; /** * Resets all message identifier counters (MID, SSN) and makes all * partially messages be ready to be re-sent in full. This is used when * the peer has been detected to have restarted and is used to try to * minimize the amount of data loss. However, data loss cannot be * completely guaranteed when a peer restarts. */ virtual void Reset() = 0; /** * Returns the amount of buffered data. This doesn't include packets that * are e.g. inflight. */ virtual size_t GetStreamBufferedAmount(uint16_t streamId) const = 0; /** * Returns the total amount of buffer data, for all streams. */ virtual size_t GetTotalBufferedAmount() const = 0; /** * Returns the limit for the `OnAssociationStreamBufferedAmountLow()` * event. Default value is 0. */ virtual size_t GetStreamBufferedAmountLowThreshold(uint16_t streamId) const = 0; /** * Sets a limit for the `OnAssociationStreamBufferedAmountLow()` event. * @param streamId [description] * @param bytes [description] */ virtual void SetStreamBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) = 0; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SCTP/tx/StreamScheduler.hpp ================================================ #ifndef MS_RTC_SCTP_STREAM_SCHEDULER_HPP #define MS_RTC_SCTP_STREAM_SCHEDULER_HPP #include "common.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/chunks/IDataChunk.hpp" #include "RTC/SCTP/tx/SendQueueInterface.hpp" #include namespace RTC { namespace SCTP { /** * A parameterized stream scheduler. Currently, it implements the round robin * scheduling algorithm using virtual finish time. It is to be used as a * part of a send queue and will track all active streams (streams that have * any data that can be sent). * * The stream scheduler works with the concept of associating active streams * with a "virtual finish time", which is the time when a stream is allowed * to produce data. Streams are ordered by their virtual finish time, and * the "current virtual time" will advance to the next following virtual * finish time whenever a chunk is to be produced. * * In the round robin scheduling algorithm, a stream's virtual finish time * will just increment by one (1) after having produced a chunk, which * results in a round-robin scheduling. * * In WFQ scheduling algorithm, a stream's virtual finish time will be * defined as the number of bytes in the next fragment to be sent, multiplied * by theinverse of the stream's priority, meaning that a high priority - or * a smaller fragment - results in a closer virtual finish time, compared to * a stream with either a lower priority or a larger fragment to be sent. * * @remarks * - When message interleaving is enabled, the WFQ (Weighted Fair Queueing) * scheduling algorithm will be used. And when it's not, round-robin * scheduling will be used instead. */ class StreamScheduler { public: class StreamProducer { public: virtual ~StreamProducer() = default; public: /** * Produces a fragment of data to send. The current wall time is specified * as `nowMs` and should be used to skip chunks with expired limited * lifetime. The parameter `maxLength` specifies the maximum amount of * actual payload that may be returned. If these constraints prevents the * stream from sending some data, `std::nullopt` should be returned. */ virtual std::optional Produce( uint64_t nowMs, size_t maxLength) = 0; /** * Returns the number of payload bytes that is scheduled to be sent in the * next enqueued message, or zero if there are no enqueued messages or if * the stream has been actively paused. */ virtual size_t GetBytesToSendInNextMessage() const = 0; }; public: class Stream { private: friend class StreamScheduler; private: Stream(StreamScheduler* parent, StreamProducer* producer, uint16_t streamId, uint16_t priority) : parent(*parent), producer(*producer), streamId(streamId), priority(priority), inverseWeight(1.0 / std::max(static_cast(priority), 1e-6)) { } public: uint16_t GetStreamId() const { return this->streamId; } uint16_t GetPriority() const { return this->priority; } void SetPriority(uint16_t priority); /** * Will activate the stream if it has any data to send. That is, if the * callback to `GetBytesToSendInNextMessage()` returns non-zero. If the * callback returns zero, the stream will not be made active. */ void MayMakeActive(); /** * Will remove the stream from the list of active streams, and will not * try to produce data from it. To make it active again, call * `MayMakeActive()`. */ void MakeInactive(); /** * Make the scheduler move to another message, or another stream. This * is used to abort the scheduler from continuing producing fragments * for the current message in case it's deleted. */ void ForceReschedule() { this->parent.ForceReschedule(); } private: /** * Produces a message from this stream. This will only be called on * streams that have data. */ std::optional Produce(uint64_t nowMs, size_t maxLength); void MakeActive(size_t bytesToSendNext); void ForceMarkInactive(); double GetCurrentTime() const { return this->currentVirtualTime; } double GetNextFinishTime() const { return this->nextFinishTime; } size_t GetBytesToSendInNextMessage() const { return this->producer.GetBytesToSendInNextMessage(); } double CalculateFinishTime(size_t bytesToSendNext) const; private: StreamScheduler& parent; StreamProducer& producer; const uint16_t streamId; uint16_t priority; double inverseWeight; // This outgoing stream's "current" virtual time. double currentVirtualTime{ 0.0 }; double nextFinishTime{ 0.0 }; }; private: struct ActiveStreamComparator { // Ordered by virtual finish time (primary), stream-id (secondary). bool operator()(Stream* a, Stream* b) const { const double aVft = a->GetNextFinishTime(); const double bVft = b->GetNextFinishTime(); if (aVft == bVft) { return a->GetStreamId() < b->GetStreamId(); } return aVft < bVft; } }; public: explicit StreamScheduler(size_t mtu) : maxPayloadBytes(mtu - Packet::CommonHeaderLength - IDataChunk::IDataChunkHeaderLength) { } public: std::unique_ptr CreateStream(StreamProducer* producer, uint16_t streamId, uint16_t priority) { return std::unique_ptr(new Stream(this, producer, streamId, priority)); } void EnableMessageInterleaving(bool enabled) { this->enableMessageInterleaving = enabled; } /** * Makes the scheduler stop producing message from the current stream and * re-evaluates which stream to produce from. */ void ForceReschedule() { this->currentlySendingAMessage = false; } /** * Produces a fragment of data to send. The current wall time is specified * as `nowMs` and will be used to skip chunks with expired limited * lifetime. The parameter `maxLength` specifies the maximum amount of * actual payload that may be returned. If no data can be produced, * `std::nullopt` is returned. */ std::optional Produce(uint64_t nowMs, size_t maxLength); std::set GetActiveStreamsForTesting() const; private: void AssertIsConsistent() const; private: const size_t maxPayloadBytes; // The current virtual time, as defined in the WFQ algorithm. double virtualTime{ 0.0 }; // The current stream to send chunks from. Stream* currentStream{ nullptr }; bool enableMessageInterleaving{ false }; // Indicates if the streams is currently sending a message, and should // then (if message interleaving is not enabled) continue sending from // this stream until that message has been sent in full. bool currentlySendingAMessage{ false }; // The currently active streams, ordered by virtual finish time. std::set activeStreams; }; } // namespace SCTP } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SctpAssociation.hpp ================================================ #ifndef MS_RTC_OLD_SCTP_ASSOCIATION_HPP #define MS_RTC_OLD_SCTP_ASSOCIATION_HPP #include "common.hpp" #include "Utils.hpp" #include "RTC/DataConsumer.hpp" #include "RTC/DataProducer.hpp" #include namespace RTC { class SctpAssociation { public: enum class SctpState : uint8_t { NEW = 1, CONNECTING, CONNECTED, FAILED, CLOSED }; private: enum class StreamDirection : uint8_t { INCOMING = 1, OUTGOING }; protected: using onQueuedCallback = const std::function; public: class Listener { public: virtual ~Listener() = default; public: virtual void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) = 0; virtual void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) = 0; virtual void OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation) = 0; virtual void OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation) = 0; virtual void OnSctpAssociationSendData( RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) = 0; virtual void OnSctpAssociationMessageReceived( RTC::SctpAssociation* sctpAssociation, uint16_t streamId, const uint8_t* msg, size_t len, uint32_t ppid) = 0; virtual void OnSctpAssociationBufferedAmount( RTC::SctpAssociation* sctpAssociation, uint32_t len) = 0; }; public: static bool IsSctp(const uint8_t* data, size_t len) { // clang-format off return ( (len >= 12) && // Must have Source Port Number and Destination Port Number set to 5000 (hack). (Utils::Byte::Get2Bytes(data, 0) == 5000) && (Utils::Byte::Get2Bytes(data, 2) == 5000) ); // clang-format on } public: SctpAssociation( Listener* listener, uint16_t os, uint16_t mis, size_t maxSctpMessageSize, size_t sctpSendBufferSize, bool isDataChannel); ~SctpAssociation(); public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; void TransportConnected(); void TransportDisconnected(); SctpState GetState() const { return this->state; } size_t GetSctpBufferedAmount() const { return this->sctpBufferedAmount; } void ProcessSctpData(const uint8_t* data, size_t len); void SendSctpMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb = nullptr); void HandleDataProducer(RTC::DataProducer* dataProducer); void HandleDataConsumer(RTC::DataConsumer* dataConsumer); void DataProducerClosed(RTC::DataProducer* dataProducer); void DataConsumerClosed(RTC::DataConsumer* dataConsumer); private: void MayConnect(); void ResetSctpStream(uint16_t streamId, StreamDirection direction) const; void AddOutgoingStreams(bool force = false); /* Callbacks fired by usrsctp events. */ public: void OnUsrSctpSendSctpData(void* buffer, size_t len); void OnUsrSctpReceiveSctpData( uint16_t streamId, uint16_t ssn, uint32_t ppid, int flags, const uint8_t* data, size_t len); void OnUsrSctpReceiveSctpNotification(union sctp_notification* notification, size_t len); void OnUsrSctpSentData(uint32_t freeBuffer); public: uintptr_t id{ 0u }; private: // Passed by argument. Listener* listener{ nullptr }; uint16_t os{ 1024u }; uint16_t mis{ 1024u }; size_t maxSctpMessageSize{ 262144u }; size_t sctpSendBufferSize{ 262144u }; size_t sctpBufferedAmount{ 0u }; bool isDataChannel{ false }; // Allocated by this. uint8_t* messageBuffer{ nullptr }; // Others. SctpState state{ SctpState::NEW }; struct socket* socket{ nullptr }; uint16_t desiredOs{ 0u }; size_t messageBufferLen{ 0u }; bool transportConnected{ false }; // Whether at least one SCTP stream (AKA DataProducer or DataConsumer) has // already been created, no matter it's still alive. bool firstStreamCreated{ false }; // Whether we have received STCP data from the remote peer. bool sctpDataReceived{ false }; uint16_t lastSsnReceived{ 0u }; // Valid for us since no SCTP I-DATA support. }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SctpDictionaries.hpp ================================================ #ifndef MS_RTC_SCTP_DICTIONARIES_HPP #define MS_RTC_SCTP_DICTIONARIES_HPP #include "common.hpp" #include namespace RTC { class SctpStreamParameters { public: SctpStreamParameters() = default; explicit SctpStreamParameters(const FBS::SctpParameters::SctpStreamParameters* data); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; public: uint16_t streamId{ 0u }; bool ordered{ true }; uint16_t maxPacketLifeTime{ 0u }; uint16_t maxRetransmits{ 0u }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SctpListener.hpp ================================================ #ifndef MS_RTC_SCTP_LISTENER_HPP #define MS_RTC_SCTP_LISTENER_HPP #include "common.hpp" #include "RTC/DataProducer.hpp" #include namespace RTC { class SctpListener { public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; void AddDataProducer(RTC::DataProducer* dataProducer); void RemoveDataProducer(RTC::DataProducer* dataProducer); RTC::DataProducer* GetDataProducer(uint16_t streamId); public: // Table of streamId / DataProducer pairs. std::unordered_map streamIdTable; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SenderBandwidthEstimator.hpp ================================================ #ifndef MS_RTC_SENDER_BANDWIDTH_ESTIMATOR_HPP #define MS_RTC_SENDER_BANDWIDTH_ESTIMATOR_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include "RTC/RateCalculator.hpp" #include "RTC/SeqManager.hpp" #include "RTC/TrendCalculator.hpp" #include namespace RTC { class SenderBandwidthEstimator { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnSenderBandwidthEstimatorAvailableBitrate( RTC::SenderBandwidthEstimator* senderBwe, uint32_t availableBitrate, uint32_t previousAvailableBitrate) = 0; }; public: struct SentInfo { uint16_t wideSeq{ 0u }; size_t size{ 0u }; bool isProbation{ false }; uint64_t sendingAtMs{ 0u }; uint64_t sentAtMs{ 0u }; }; private: class CummulativeResult { public: CummulativeResult() = default; public: uint64_t GetStartedAtMs() const { return this->firstPacketSentAtMs; } size_t GetNumPackets() const { return this->numPackets; } size_t GetTotalSize() const { return this->totalSize; } uint32_t GetSendBitrate() const { auto sendIntervalMs = std::max(this->lastPacketSentAtMs - this->firstPacketSentAtMs, 1u); return static_cast(this->totalSize / sendIntervalMs) * 8 * 1000; } uint32_t GetReceiveBitrate() const { auto recvIntervalMs = std::max(this->lastPacketReceivedAtMs - this->firstPacketReceivedAtMs, 1u); return static_cast(this->totalSize / recvIntervalMs) * 8 * 1000; } void AddPacket(size_t size, int64_t sentAtMs, int64_t receivedAtMs); void Reset(); private: size_t numPackets{ 0u }; size_t totalSize{ 0u }; int64_t firstPacketSentAtMs{ 0u }; int64_t lastPacketSentAtMs{ 0u }; int64_t firstPacketReceivedAtMs{ 0u }; int64_t lastPacketReceivedAtMs{ 0u }; }; public: SenderBandwidthEstimator( RTC::SenderBandwidthEstimator::Listener* listener, SharedInterface* shared, uint32_t initialAvailableBitrate); virtual ~SenderBandwidthEstimator(); public: void TransportConnected(); void TransportDisconnected(); void RtpPacketSent(const SentInfo& sentInfo); void ReceiveRtcpTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket* feedback); void EstimateAvailableBitrate(CummulativeResult& cummulativeResult); void UpdateRtt(float rtt); uint32_t GetAvailableBitrate() const; void RescheduleNextAvailableBitrateEvent(); private: // Passed by argument. Listener* listener{ nullptr }; SharedInterface* shared{ nullptr }; // Others. uint32_t initialAvailableBitrate{ 0u }; uint32_t availableBitrate{ 0u }; uint64_t lastAvailableBitrateEventAtMs{ 0u }; std::map::SeqLowerThan> sentInfos; float rtt{ 0 }; // Round trip time in ms. CummulativeResult cummulativeResult; CummulativeResult probationCummulativeResult; RTC::RateCalculator sendTransmission; RTC::TrendCalculator sendTransmissionTrend; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SeqManager.hpp ================================================ #ifndef RTC_SEQ_MANAGER_HPP #define RTC_SEQ_MANAGER_HPP #include "common.hpp" #include // std::numeric_limits #include namespace RTC { // T is the base type (uint16_t, uint32_t, ...). // N is the max number of bits used in T. template class SeqManager { public: static constexpr T MaxValue = (N == 0) ? std::numeric_limits::max() : ((1 << N) - 1); public: struct SeqLowerThan { bool operator()(T lhs, T rhs) const; }; struct SeqHigherThan { bool operator()(T lhs, T rhs) const; }; public: static bool IsSeqHigherThan(T lhs, T rhs); static bool IsSeqLowerThan(T lhs, T rhs); public: SeqManager() = default; explicit SeqManager(T initialOutput); public: void Sync(T input); void Drop(T input); bool Input(T input, T& output); T GetMaxInput() const; T GetMaxOutput() const; private: void ClearDropped(); private: // Whether at least a sequence number has been inserted. bool started{ false }; T initialOutput{ 0 }; T base{ 0 }; T maxOutput{ 0 }; T maxInput{ 0 }; T maxDropped{ 0 }; T maxForwarded{ 0 }; std::set dropped; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/Serializable.hpp ================================================ #ifndef MS_RTC_SERIALIZABLE_HPP #define MS_RTC_SERIALIZABLE_HPP #include "common.hpp" namespace RTC { /** * Class holding serializable content. * * @remarks * - ICE, RTP, RTCP, SCTP packets and some items in those packets inherit * from this class (or will inherit). * - Typically many of those packets and items may include padding bytes to * be multiple of 4 bytes. However it's up to each packet or item to deal * with padding. From the point of view of the Serializable class, the * length of a Serializable packet or item must include its padding bytes * (if any). */ class Serializable { public: using BufferReleasedListener = std::function; protected: using ConsolidatedListener = std::function; public: /** * @param buffer - The buffer holding the packet. * @param bufferLength - Buffer length. * * @remarks * - In same cases, `bufferLength` is the exact length of the Serializable * and in other cases `bufferLength` is the maximum length that the * Serializable can take. * - Always use `GetLength()` to obtain the exact length of the * Serializable. */ Serializable(const uint8_t* buffer, size_t bufferLength); virtual ~Serializable(); public: /** * Print Serializable state with given indentation. */ virtual void Dump(int indentation = 0) const = 0; /** * Get a buffer containing the serialized content. Combined with the * `GetLength()` method, the application can obtain the full sequence of * bytes of the Serializable in network byte order. */ virtual const uint8_t* GetBuffer() const final { return this->buffer; } /** * Maximum length the Serializable can take. It's guaranteed to be equal or * greater than value returned by `GetLength()`. */ virtual size_t GetBufferLength() const final { return this->bufferLength; } /** * Current exact length of the Serializable, including padding bytes (if * any). */ virtual size_t GetLength() const final { return this->length; } /** * Current available length of the Serializable. */ virtual size_t GetAvailableLength() const final { return this->bufferLength - this->length; } /** * Serialize the Serializable into a new buffer. This method copies the * bytes of the internal buffer into the new buffer and makes `GetBuffer()` * point to the new one. * * @param buffer - The new buffer in which the Serializable will be * serialized. * @param bufferLength - New buffer length. * * @remarks * - In addition to call this method in Serializable parent class, the * `Serialize()` implementation in the subclass must also reassign any * pointers it holds and make them point to the proper position in the * new buffer. * * @throw MediaSoupError - If given `bufferLength` is lower than the * current exact length of the Serializable. */ virtual void Serialize(uint8_t* buffer, size_t bufferLength); /** * Clone the Serializable into a new buffer. This method returns a new * instance of the Serializable subclass which doesn't share any memory * with the original one. * * @param buffer - The buffer for the cloned Serializable. * @param bufferLength - Buffer length. * * @throw MediaSoupError - If given `bufferLength` is lower than the * current exact length of the Serializable. */ virtual Serializable* Clone(uint8_t* buffer, size_t bufferLength) const = 0; /** * Set a listener that will be invoked when the current buffer is released, * meaning that this Serializable no longer uses it. * * @remarks * - The caller should call this method with `nullptr` as argument in case * the lifetime of the previously passed `listener` ends before the * Serializable is destroyed or serialized. Otherwise the Serializable * will invoke a listener that was already destroyed. */ virtual void SetBufferReleasedListener(BufferReleasedListener* listener) final; /** * The application must call this method on a Serializable when it's been * constructed within a parent Serializable object that needs to know when * this Serializable is done to recompute its total length and internal * pointers. * * @throw MediaSoupError - If `SetConsolidatedListener()` was not called * first. * * @see SetConsolidatedListener() */ virtual void Consolidate() const final; /** * Methods to be used by classes inheriting from Serializable. */ protected: /** * Change the buffer of the Serializable. */ virtual void SetBuffer(uint8_t* buffer) final; /** * Update the buffer length of the Serializable. ** * @remarks * - The child class must invoke this method after parsing completes in * case it couldn't anticipate its expected exact length. Specially * useful when parsing variable-length items within a packet. * * @throw * - MediaSoupError - If given `bufferLength` is lower than the current * exact length of the Serializable. * - MediaSoupError - If 0 is given. */ virtual void SetBufferLength(size_t bufferLength) final; /** * Method to be called by the child class to update the current exact * length of the Serializable. * * @remarks * - The child class must invoke this method after parsing completes and * after every change in the Serializable content that affects its * current length. * * @throw * - MediaSoupError - If given `length` is larger than the buffer length of * the Serializable. * - MediaSoupError - If 0 is given. */ virtual void SetLength(size_t length) final; /** * Clone the Serializable into the given Serializable. * * @remarks * - If this method throws (due to the buffer length of the given * Serializable being too small), then this method deletes the given * `serializable` pointer and throws, meaning that the subclass must not * delete it in case it captured the error. * * @throw MediaSoupError - If the buffer length of the given `serializable` * is too small. */ virtual void CloneInto(Serializable* serializable) const; /** * Fill the last given `padding` number of bytes of the buffer with zeros. * * @remarks * - This method does NOT add bytes to the buffer. */ virtual void FillPadding(uint8_t padding) final; /** * Set a listener that will be invoked when calling `Consolidate()` on this * Serializable. * * @see Consolidate() */ virtual void SetConsolidatedListener(ConsolidatedListener&& listener) final; private: // Buffer holding the Serializable content. uint8_t* buffer; // Length of the buffer. This is the maximum length the Serializable can // take. size_t bufferLength; // Serializable current exact length (includes padding bytes). size_t length{ 0u }; // Event listener invoked when the current buffer is released (no longer // used by this Serializable)- BufferReleasedListener* bufferReleasedListener{ nullptr }; // Event listener invoked when the Serializable is consolidated. ConsolidatedListener consolidatedListener{ nullptr }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SimpleConsumer.hpp ================================================ #ifndef MS_RTC_SIMPLE_CONSUMER_HPP #define MS_RTC_SIMPLE_CONSUMER_HPP #include "RTC/Consumer.hpp" #include "RTC/SeqManager.hpp" #include namespace RTC { class SimpleConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener { public: SimpleConsumer( SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, const FBS::Transport::ConsumeRequest* data); ~SimpleConsumer() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder) override; flatbuffers::Offset FillBufferScore( flatbuffers::FlatBufferBuilder& builder) const override; bool IsActive() const override { // clang-format off return ( RTC::Consumer::IsActive() && this->producerRtpStream && // If there is no RTP inactivity check do not consider the stream // inactive despite it has score 0. (this->producerRtpStream->GetScore() > 0u || !this->producerRtpStream->HasRtpInactivityCheckEnabled()) ); // clang-format on } void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerRtpStreamScore( RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; uint8_t GetBitratePriority() const override; uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) override; void ApplyLayers() override; uint32_t GetDesiredBitrate() const override; void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override; const std::vector& GetRtpStreams() const override { return this->rtpStreams; } bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override; void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override; void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override; void ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) override; void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) override; void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) override; uint32_t GetTransmissionRate(uint64_t nowMs) override; float GetRtt() const override; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; private: void UserOnTransportConnected() override; void UserOnTransportDisconnected() override; void UserOnPaused() override; void UserOnResumed() override; void CreateRtpStream(); void RequestKeyFrame(); void StorePacketInTargetLayerRetransmissionBuffer( RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket); void EmitScore() const; /* Pure virtual methods inherited from RtpStreamSend::Listener. */ public: void OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override; void OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) override; private: // Allocated by this. RTC::RTP::RtpStreamSend* rtpStream{ nullptr }; // Others. std::vector rtpStreams; RTC::RTP::RtpStreamRecv* producerRtpStream{ nullptr }; bool keyFrameSupported{ false }; bool syncRequired{ false }; RTC::SeqManager rtpSeqManager; bool managingBitrate{ false }; std::unique_ptr encodingContext; // Buffer to store packets that arrive earlier than the first packet of the // video key frame. std::map::SeqLowerThan> targetLayerRetransmissionBuffer; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SimulcastConsumer.hpp ================================================ #ifndef MS_RTC_SIMULCAST_CONSUMER_HPP #define MS_RTC_SIMULCAST_CONSUMER_HPP #include "RTC/Consumer.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/SeqManager.hpp" #include namespace RTC { class SimulcastConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener { public: SimulcastConsumer( SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, const FBS::Transport::ConsumeRequest* data); ~SimulcastConsumer() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder) override; flatbuffers::Offset FillBufferScore( flatbuffers::FlatBufferBuilder& builder) const override; RTC::ConsumerTypes::VideoLayers GetPreferredLayers() const override { RTC::ConsumerTypes::VideoLayers layers; layers.spatial = this->preferredLayers.spatial; layers.temporal = this->preferredLayers.temporal; return layers; } bool IsActive() const override { // clang-format off return ( RTC::Consumer::IsActive() && std::any_of( this->producerRtpStreams.begin(), this->producerRtpStreams.end(), [](const RTC::RTP::RtpStreamRecv* rtpStream) { // If there is no RTP inactivity check do not consider the stream // inactive despite it has score 0. return (rtpStream != nullptr && (rtpStream->GetScore() > 0u || !rtpStream->HasRtpInactivityCheckEnabled())); } ) ); // clang-format on } void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerRtpStreamScore( RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; uint8_t GetBitratePriority() const override; uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) override; void ApplyLayers() override; uint32_t GetDesiredBitrate() const override; void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override; bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override; const std::vector& GetRtpStreams() const override { return this->rtpStreams; } void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override; void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override; void ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) override; void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) override; void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) override; uint32_t GetTransmissionRate(uint64_t nowMs) override; float GetRtt() const override; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; private: void UserOnTransportConnected() override; void UserOnTransportDisconnected() override; void UserOnPaused() override; void UserOnResumed() override; void CreateRtpStream(); void RequestKeyFrames(); void RequestKeyFrameForTargetSpatialLayer(); void RequestKeyFrameForCurrentSpatialLayer(); void MayChangeLayers(bool force = false); bool RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const; void UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer); bool CanSwitchToSpatialLayer(int16_t spatialLayer) const; void EmitScore() const; void StorePacketInTargetLayerRetransmissionBuffer( RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket); void EmitLayersChange() const; RTC::RTP::RtpStreamRecv* GetProducerCurrentRtpStream() const; RTC::RTP::RtpStreamRecv* GetProducerTargetRtpStream() const; RTC::RTP::RtpStreamRecv* GetProducerTsReferenceRtpStream() const; /* Pure virtual methods inherited from RtpStreamSend::Listener. */ public: void OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override; void OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) override; private: // Allocated by this. RTC::RTP::RtpStreamSend* rtpStream{ nullptr }; // Others. absl::flat_hash_map mapMappedSsrcSpatialLayer; std::vector rtpStreams; std::vector producerRtpStreams; // Indexed by spatial layer. bool syncRequired{ false }; int16_t spatialLayerToSync{ -1 }; bool lastSentPacketHasMarker{ false }; RTC::SeqManager rtpSeqManager; RTC::ConsumerTypes::VideoLayers preferredLayers; RTC::ConsumerTypes::VideoLayers provisionalTargetLayers; RTC::ConsumerTypes::VideoLayers targetLayers; int16_t currentSpatialLayer{ -1 }; int16_t tsReferenceSpatialLayer{ -1 }; // Used for RTP TS sync. uint16_t snReferenceSpatialLayer{ 0 }; bool checkingForOldPacketsInSpatialLayer{ false }; std::unique_ptr encodingContext; uint32_t tsOffset{ 0u }; // RTP Timestamp offset. bool keyFrameForTsOffsetRequested{ false }; // Last time we moved to lower spatial layer due to BWE. uint64_t lastBweDowngradeAtMs{ 0u }; // Buffer to store packets that arrive earlier than the first packet of the // video key frame. std::map::SeqLowerThan> targetLayerRetransmissionBuffer; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SrtpSession.hpp ================================================ #ifndef MS_RTC_SRTP_SESSION_HPP #define MS_RTC_SRTP_SESSION_HPP #include "common.hpp" #include "FBS/srtpParameters.h" #include namespace RTC { class SrtpSession { public: enum class CryptoSuite : uint8_t { AEAD_AES_256_GCM = 0, AEAD_AES_128_GCM, AES_CM_128_HMAC_SHA1_80, AES_CM_128_HMAC_SHA1_32, }; public: enum class Type : uint8_t { INBOUND = 1, OUTBOUND }; public: static void ClassInit(); static FBS::SrtpParameters::SrtpCryptoSuite CryptoSuiteToFbs(CryptoSuite cryptoSuite); static CryptoSuite CryptoSuiteFromFbs(FBS::SrtpParameters::SrtpCryptoSuite cryptoSuite); private: static void OnSrtpEvent(srtp_event_data_t* data); public: SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t* key, size_t keyLen); ~SrtpSession(); public: bool EncryptRtp(const uint8_t** data, size_t* len); bool DecryptSrtp(uint8_t* data, size_t* len); bool EncryptRtcp(const uint8_t** data, size_t* len); bool DecryptSrtcp(uint8_t* data, size_t* len); void RemoveStream(uint32_t ssrc) { srtp_stream_remove(this->session, htonl(ssrc)); } private: // Allocated by this. srtp_t session{ nullptr }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/SvcConsumer.hpp ================================================ #ifndef MS_RTC_SVC_CONSUMER_HPP #define MS_RTC_SVC_CONSUMER_HPP #include "RTC/Consumer.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/SeqManager.hpp" #include namespace RTC { class SvcConsumer : public RTC::Consumer, public RTC::RTP::RtpStreamSend::Listener { public: SvcConsumer( SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, const FBS::Transport::ConsumeRequest* data); ~SvcConsumer() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder) override; flatbuffers::Offset FillBufferScore( flatbuffers::FlatBufferBuilder& builder) const override; RTC::ConsumerTypes::VideoLayers GetPreferredLayers() const override { RTC::ConsumerTypes::VideoLayers layers; layers.spatial = this->preferredLayers.spatial; layers.temporal = this->preferredLayers.temporal; return layers; } bool IsActive() const override { // clang-format off return ( RTC::Consumer::IsActive() && this->producerRtpStream && // If there is no RTP inactivity check do not consider the stream // inactive despite it has score 0. (this->producerRtpStream->GetScore() > 0u || !this->producerRtpStream->HasRtpInactivityCheckEnabled()) ); // clang-format on } void ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void ProducerRtpStreamScore( RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; void ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; uint8_t GetBitratePriority() const override; uint32_t IncreaseLayer(uint32_t bitrate, bool considerLoss) override; void ApplyLayers() override; uint32_t GetDesiredBitrate() const override; void SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) override; bool GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) override; const std::vector& GetRtpStreams() const override { return this->rtpStreams; } void NeedWorstRemoteFractionLost(uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override; void ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) override; void ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) override; void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) override; void ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) override; uint32_t GetTransmissionRate(uint64_t nowMs) override; float GetRtt() const override; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; private: void UserOnTransportConnected() override; void UserOnTransportDisconnected() override; void UserOnPaused() override; void UserOnResumed() override; void CreateRtpStream(); void RequestKeyFrame(); void MayChangeLayers(bool force = false); bool RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const; void UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer); void EmitScore() const; void StorePacketInTargetLayerRetransmissionBuffer( RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket); void EmitLayersChange() const; /* Pure virtual methods inherited from RtpStreamSend::Listener. */ public: void OnRtpStreamScore(RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) override; void OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) override; private: // Allocated by this. RTC::RTP::RtpStreamSend* rtpStream{ nullptr }; // Others. std::vector rtpStreams; RTC::RTP::RtpStreamRecv* producerRtpStream{ nullptr }; bool syncRequired{ false }; RTC::SeqManager rtpSeqManager; RTC::ConsumerTypes::VideoLayers preferredLayers; RTC::ConsumerTypes::VideoLayers provisionalTargetLayers; std::unique_ptr encodingContext; // Last time we moved to lower spatial layer due to BWE. uint64_t lastBweDowngradeAtMs{ 0u }; // Buffer to store packets that arrive earlier than the first packet of the // video key frame. std::map::SeqLowerThan> targetLayerRetransmissionBuffer; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/TcpConnection.hpp ================================================ #ifndef MS_RTC_TCP_CONNECTION_HPP #define MS_RTC_TCP_CONNECTION_HPP #include "common.hpp" #include "handles/TcpConnectionHandle.hpp" namespace RTC { class TcpConnection : public ::TcpConnectionHandle { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnTcpConnectionPacketReceived( RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen) = 0; }; public: TcpConnection(Listener* listener, size_t bufferSize); ~TcpConnection() override; public: void Send(const uint8_t* data, size_t len, ::TcpConnectionHandle::onSendCallback* cb); /* Pure virtual methods inherited from ::TcpConnectionHandle. */ public: void UserOnTcpConnectionRead() override; private: // Passed by argument. Listener* listener{ nullptr }; // Others. size_t frameStart{ 0u }; // Where the latest frame starts. }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/TcpServer.hpp ================================================ #ifndef MS_RTC_TCP_SERVER_HPP #define MS_RTC_TCP_SERVER_HPP #include "common.hpp" #include "RTC/TcpConnection.hpp" #include "RTC/Transport.hpp" #include "handles/TcpConnectionHandle.hpp" #include "handles/TcpServerHandle.hpp" #include namespace RTC { class TcpServer : public ::TcpServerHandle { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnRtcTcpConnectionClosed( RTC::TcpServer* tcpServer, RTC::TcpConnection* connection) = 0; }; public: TcpServer( Listener* listener, RTC::TcpConnection::Listener* connListener, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags); TcpServer( Listener* listener, RTC::TcpConnection::Listener* connListener, std::string& ip, uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, uint64_t& portRangeHash); ~TcpServer() override; /* Pure virtual methods inherited from ::TcpServerHandle. */ public: void UserOnTcpConnectionAlloc() override; void UserOnTcpConnectionClosed(::TcpConnectionHandle* connection) override; private: // Passed by argument. Listener* listener{ nullptr }; RTC::TcpConnection::Listener* connListener{ nullptr }; bool fixedPort{ false }; uint64_t portRangeHash{ 0u }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/Transport.hpp ================================================ #ifndef MS_RTC_TRANSPORT_HPP #define MS_RTC_TRANSPORT_HPP // #define ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR #include "common.hpp" #include "Channel/ChannelNotification.hpp" #include "Channel/ChannelRequest.hpp" #include "Channel/ChannelSocket.hpp" #include "FBS/transport.h" #include "RTC/Consumer.hpp" #include "RTC/DataConsumer.hpp" #include "RTC/DataProducer.hpp" #include "RTC/Producer.hpp" #include "RTC/RTCP/CompoundPacket.hpp" #include "RTC/RTCP/Packet.hpp" #include "RTC/RTP/HeaderExtensionIds.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RateCalculator.hpp" #include "RTC/RtpListener.hpp" #include "RTC/SCTP/public/AssociationInterface.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" // TODO: SCTP: Remove once we only use built-in SCTP stack. #include "SharedInterface.hpp" #include "RTC/SctpAssociation.hpp" #include "RTC/SctpListener.hpp" #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR #include "RTC/SenderBandwidthEstimator.hpp" #endif #include "RTC/TransportCongestionControlClient.hpp" #include "RTC/TransportCongestionControlServer.hpp" #include "handles/TimerHandleInterface.hpp" #include #include #include namespace RTC { class Transport : public RTC::Producer::Listener, public RTC::Consumer::Listener, public RTC::DataProducer::Listener, public RTC::DataConsumer::Listener, public RTC::SCTP::AssociationListenerInterface, // TODO: SCTP: Remove once we only use built-in SCTP stack. public RTC::SctpAssociation::Listener, public RTC::TransportCongestionControlClient::Listener, public RTC::TransportCongestionControlServer::Listener, public Channel::ChannelSocket::RequestHandler, public Channel::ChannelSocket::NotificationHandler, #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR public RTC::SenderBandwidthEstimator::Listener, #endif public TimerHandleInterface::Listener { protected: using onSendCallback = const std::function; using onQueuedCallback = const std::function; public: class Listener { public: virtual ~Listener() = default; public: virtual void OnTransportNewProducer(RTC::Transport* transport, RTC::Producer* producer) = 0; virtual void OnTransportProducerClosed(RTC::Transport* transport, RTC::Producer* producer) = 0; virtual void OnTransportProducerPaused(RTC::Transport* transport, RTC::Producer* producer) = 0; virtual void OnTransportProducerResumed(RTC::Transport* transport, RTC::Producer* producer) = 0; virtual void OnTransportProducerNewRtpStream( RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) = 0; virtual void OnTransportProducerRtpStreamScore( RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) = 0; virtual void OnTransportProducerRtcpSenderReport( RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first) = 0; virtual void OnTransportProducerRtpPacketReceived( RTC::Transport* transport, RTC::Producer* producer, RTC::RTP::Packet* packet) = 0; virtual void OnTransportNeedWorstRemoteFractionLost( RTC::Transport* transport, RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) = 0; virtual void OnTransportNewConsumer( RTC::Transport* transport, RTC::Consumer* consumer, const std::string& producerId) = 0; virtual void OnTransportConsumerClosed(RTC::Transport* transport, RTC::Consumer* consumer) = 0; virtual void OnTransportConsumerProducerClosed( RTC::Transport* transport, RTC::Consumer* consumer) = 0; virtual void OnTransportDataProducerPaused( RTC::Transport* transport, RTC::DataProducer* dataProducer) = 0; virtual void OnTransportDataProducerResumed( RTC::Transport* transport, RTC::DataProducer* dataProducer) = 0; virtual void OnTransportConsumerKeyFrameRequested( RTC::Transport* transport, RTC::Consumer* consumer, uint32_t mappedSsrc) = 0; virtual void OnTransportNewDataProducer( RTC::Transport* transport, RTC::DataProducer* dataProducer) = 0; virtual void OnTransportDataProducerClosed( RTC::Transport* transport, RTC::DataProducer* dataProducer) = 0; // TODO: SCTP: Remove when we migrate to the new SCTP stack. virtual void OnTransportDataProducerMessageReceived( RTC::Transport* transport, RTC::DataProducer* dataProducer, const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel) = 0; virtual void OnTransportDataProducerMessageReceived( RTC::Transport* transport, RTC::DataProducer* dataProducer, RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel) = 0; virtual void OnTransportNewDataConsumer( RTC::Transport* transport, RTC::DataConsumer* dataConsumer, std::string& dataProducerId) = 0; virtual void OnTransportDataConsumerClosed( RTC::Transport* transport, RTC::DataConsumer* dataConsumer) = 0; virtual void OnTransportDataConsumerDataProducerClosed( RTC::Transport* transport, RTC::DataConsumer* dataConsumer) = 0; virtual void OnTransportListenServerClosed(RTC::Transport* transport) = 0; }; public: struct SocketFlags { bool ipv6Only{ false }; bool udpReusePort{ false }; }; struct PortRange { uint16_t min{ 0u }; uint16_t max{ 0u }; }; struct ListenInfo { std::string ip; std::string announcedAddress; uint16_t port{ 0u }; PortRange portRange; SocketFlags flags; uint32_t sendBufferSize{ 0u }; uint32_t recvBufferSize{ 0u }; }; private: struct TraceEventTypes { bool probation{ false }; bool bwe{ false }; }; public: Transport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::Transport::Options* options); ~Transport() override; public: void CloseProducersAndConsumers(); void ListenServerClosed(); // Subclasses must also invoke the parent Close(). flatbuffers::Offset FillBufferStats(flatbuffers::FlatBufferBuilder& builder); flatbuffers::Offset FillBuffer(flatbuffers::FlatBufferBuilder& builder) const; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; /* Methods inherited from Channel::ChannelSocket::NotificationHandler. */ public: void HandleNotification(Channel::ChannelNotification* notification) override; protected: // Must be called from the subclass. virtual void SetDestroying() final; virtual void Connected() final; virtual void Disconnected() final; virtual void DataReceived(size_t len) final { this->recvTransmission.Update(len, this->shared->GetTimeMs()); } virtual void DataSent(size_t len) final { this->sendTransmission.Update(len, this->shared->GetTimeMs()); } virtual void ReceiveRtpPacket(RTC::RTP::Packet* packet) final; virtual void ReceiveRtcpPacket(RTC::RTCP::Packet* packet) final; virtual void ReceiveSctpData(const uint8_t* data, size_t len) final; // TODO: SCTP: Remove once we only use built-in SCTP stack. virtual void SendSctpMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb = nullptr) final; virtual void SendSctpMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb = nullptr) final; virtual RTC::Producer* GetProducerById(const std::string& producerId) const final; virtual RTC::Consumer* GetConsumerById(const std::string& consumerId) const final; virtual RTC::Consumer* GetConsumerByMediaSsrc(uint32_t ssrc) const final; virtual RTC::Consumer* GetConsumerByRtxSsrc(uint32_t ssrc) const final; virtual RTC::DataProducer* GetDataProducerById(const std::string& dataProducerId) const final; virtual RTC::DataConsumer* GetDataConsumerById(const std::string& dataConsumerId) const final; private: virtual bool IsConnected() const = 0; virtual void SendRtpPacket( RTC::Consumer* consumer, RTC::RTP::Packet* packet, const onSendCallback* cb = nullptr) = 0; virtual void HandleRtcpPacket(RTC::RTCP::Packet* packet) final; virtual void SendRtcp(uint64_t nowMs) final; virtual void SendRtcpPacket(RTC::RTCP::Packet* packet) = 0; virtual void SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) = 0; // TODO: SCTP: Remove once we only use built-in SCTP stack. virtual void SendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb = nullptr) = 0; virtual void SendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb = nullptr) = 0; virtual bool SendData(const uint8_t* data, size_t len) = 0; virtual void RecvStreamClosed(uint32_t ssrc) = 0; virtual void SendStreamClosed(uint32_t ssrc) = 0; virtual void DistributeAvailableOutgoingBitrate() final; virtual void ComputeOutgoingDesiredBitrate(bool forceBitrate = false) final; virtual void EmitTraceEventProbationType(RTC::RTP::Packet* packet) const final; virtual void EmitTraceEventBweType( RTC::TransportCongestionControlClient::Bitrates& bitrates) const final; virtual void CheckNoDataProducer(const std::string& dataProducerId) const final; virtual void CheckNoDataConsumer(const std::string& dataConsumerId) const final; /* Pure virtual methods inherited from RTC::Producer::Listener. */ public: void OnProducerReceiveData(RTC::Producer* /*producer*/, size_t len) override { this->DataReceived(len); } void OnProducerReceiveRtpPacket(RTC::Producer* /*producer*/, RTC::RTP::Packet* packet) override { this->ReceiveRtpPacket(packet); } void OnProducerPaused(RTC::Producer* producer) override; void OnProducerResumed(RTC::Producer* producer) override; void OnProducerNewRtpStream( RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) override; void OnProducerRtpStreamScore( RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) override; void OnProducerRtcpSenderReport( RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first) override; void OnProducerRtpPacketReceived(RTC::Producer* producer, RTC::RTP::Packet* packet) override; void OnProducerSendRtcpPacket(RTC::Producer* producer, RTC::RTCP::Packet* packet) override; void OnProducerNeedWorstRemoteFractionLost( RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) override; /* Pure virtual methods inherited from RTC::Consumer::Listener. */ public: void OnConsumerSendRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) override; void OnConsumerRetransmitRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) override; void OnConsumerKeyFrameRequested(RTC::Consumer* consumer, uint32_t mappedSsrc) override; void OnConsumerNeedBitrateChange(RTC::Consumer* consumer) override; void OnConsumerNeedZeroBitrate(RTC::Consumer* consumer) override; void OnConsumerProducerClosed(RTC::Consumer* consumer) override; /* Pure virtual methods inherited from RTC::DataProducer::Listener. */ public: void OnDataProducerReceiveData(RTC::DataProducer* /*dataProducer*/, size_t len) override { this->DataReceived(len); } void OnDataProducerMessageReceived( RTC::DataProducer* dataProducer, const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel) override; void OnDataProducerMessageReceived( RTC::DataProducer* dataProducer, RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel) override; void OnDataProducerPaused(RTC::DataProducer* dataProducer) override; void OnDataProducerResumed(RTC::DataProducer* dataProducer) override; /* Pure virtual methods inherited from RTC::DataConsumer::Listener. */ public: // TODO: SCTP: Remove when we migrate to the new SCTP stack. void OnDataConsumerSendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb = nullptr) override; void OnDataConsumerSendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) override; void OnDataConsumerNeedBufferedAmount( RTC::DataConsumer* dataConsumer, uint32_t& bufferedAmount) override; void OnDataConsumerDataProducerClosed(RTC::DataConsumer* dataConsumer) override; /* Pure virtual methods inherited from RTC::SCTP::AssociationListenerInterface. */ public: bool OnAssociationSendData(const uint8_t* data, size_t len) override; void OnAssociationConnecting() override; void OnAssociationConnected() override; void OnAssociationFailed(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override; void OnAssociationClosed(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override; void OnAssociationRestarted() override; void OnAssociationError(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override; void OnAssociationMessageReceived(RTC::SCTP::Message message) override; void OnAssociationStreamsResetPerformed(std::span outboundStreamIds) override; void OnAssociationStreamsResetFailed( std::span outboundStreamIds, std::string_view errorMessage) override; void OnAssociationInboundStreamsReset(std::span inboundStreamIds) override; void OnAssociationStreamBufferedAmountLow(uint16_t streamId) override; void OnAssociationTotalBufferedAmountLow() override; bool OnAssociationIsTransportReadyForSctp() override; // TODO: SCTP: Add OnAssociationLifecycleMessageXxxxxx() methods. /* Pure virtual methods inherited from RTC::SctpAssociation::Listener. */ // TODO: SCTP: Remove once we only use built-in SCTP stack. public: void OnSctpAssociationConnecting(RTC::SctpAssociation* sctpAssociation) override; void OnSctpAssociationConnected(RTC::SctpAssociation* sctpAssociation) override; void OnSctpAssociationFailed(RTC::SctpAssociation* sctpAssociation) override; void OnSctpAssociationClosed(RTC::SctpAssociation* sctpAssociation) override; void OnSctpAssociationSendData( RTC::SctpAssociation* sctpAssociation, const uint8_t* data, size_t len) override; void OnSctpAssociationMessageReceived( RTC::SctpAssociation* sctpAssociation, uint16_t streamId, const uint8_t* msg, size_t len, uint32_t ppid) override; void OnSctpAssociationBufferedAmount( RTC::SctpAssociation* sctpAssociation, uint32_t bufferedAmount) override; /* Pure virtual methods inherited from RTC::TransportCongestionControlClient::Listener. */ public: void OnTransportCongestionControlClientBitrates( RTC::TransportCongestionControlClient* tccClient, RTC::TransportCongestionControlClient::Bitrates& bitrates) override; void OnTransportCongestionControlClientSendRtpPacket( RTC::TransportCongestionControlClient* tccClient, RTC::RTP::Packet* packet, const webrtc::PacedPacketInfo& pacingInfo) override; /* Pure virtual methods inherited from RTC::TransportCongestionControlServer::Listener. */ public: void OnTransportCongestionControlServerSendRtcpPacket( RTC::TransportCongestionControlServer* tccServer, RTC::RTCP::Packet* packet) override; #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR /* Pure virtual methods inherited from RTC::SenderBandwidthEstimator::Listener. */ public: void OnSenderBandwidthEstimatorAvailableBitrate( RTC::SenderBandwidthEstimator* senderBwe, uint32_t availableBitrate, uint32_t previousAvailableBitrate) override; #endif /* Pure virtual methods inherited from TimerHandleInterface::Listener. */ public: void OnTimer(TimerHandleInterface* timer) override; public: // Passed by argument. std::string id; protected: SharedInterface* shared{ nullptr }; private: // Passed by argument. Listener* listener{ nullptr }; // Allocated by this. absl::flat_hash_map mapProducers; absl::flat_hash_map mapConsumers; absl::flat_hash_map mapDataProducers; absl::flat_hash_map mapDataConsumers; absl::flat_hash_map mapSsrcConsumer; absl::flat_hash_map mapRtxSsrcConsumer; TimerHandleInterface* rtcpTimer{ nullptr }; // Allocated by this. std::unique_ptr sctpAssociation{ nullptr }; // TODO: SCTP: Remove once we only use built-in SCTP stack. RTC::SctpAssociation* oldSctpAssociation{ nullptr }; std::shared_ptr tccClient{ nullptr }; std::shared_ptr tccServer{ nullptr }; #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR std::shared_ptr senderBwe{ nullptr }; #endif // Others. bool direct{ false }; // Whether this Transport allows direct communication. bool isDestroying{ false }; struct RTC::RTP::HeaderExtensionIds recvRtpHeaderExtensionIds; RTC::RtpListener rtpListener; RTC::SctpListener sctpListener; RTC::RateCalculator recvTransmission; RTC::RateCalculator sendTransmission; RTC::RtpDataCounter recvRtpTransmission; RTC::RtpDataCounter sendRtpTransmission; RTC::RtpDataCounter recvRtxTransmission; RTC::RtpDataCounter sendRtxTransmission; RTC::RtpDataCounter sendProbationTransmission; uint16_t transportWideCcSeq{ 0u }; uint32_t initialAvailableOutgoingBitrate{ 600000u }; uint32_t maxIncomingBitrate{ 0u }; uint32_t maxOutgoingBitrate{ 0u }; uint32_t minOutgoingBitrate{ 0u }; size_t maxMessageSize{ 262144u }; struct TraceEventTypes traceEventTypes; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/TransportCongestionControlClient.hpp ================================================ #ifndef MS_RTC_TRANSPORT_CONGESTION_CONTROL_CLIENT_HPP #define MS_RTC_TRANSPORT_CONGESTION_CONTROL_CLIENT_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/BweType.hpp" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include "RTC/RTCP/ReceiverReport.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/ProbationGenerator.hpp" #include "RTC/TrendCalculator.hpp" #include "handles/TimerHandleInterface.hpp" #include #include #include #include #include namespace RTC { constexpr uint32_t TransportCongestionControlMinOutgoingBitrate{ 30000u }; class TransportCongestionControlClient : public webrtc::PacketRouter, public webrtc::TargetTransferRateObserver, public TimerHandleInterface::Listener { public: struct Bitrates { uint32_t desiredBitrate{ 0u }; uint32_t effectiveDesiredBitrate{ 0u }; uint32_t minBitrate{ 0u }; uint32_t maxBitrate{ 0u }; uint32_t startBitrate{ 0u }; uint32_t maxPaddingBitrate{ 0u }; uint32_t availableBitrate{ 0u }; }; public: class Listener { public: virtual ~Listener() = default; public: virtual void OnTransportCongestionControlClientBitrates( RTC::TransportCongestionControlClient* tccClient, RTC::TransportCongestionControlClient::Bitrates& bitrates) = 0; virtual void OnTransportCongestionControlClientSendRtpPacket( RTC::TransportCongestionControlClient* tccClient, RTC::RTP::Packet* packet, const webrtc::PacedPacketInfo& pacingInfo) = 0; }; public: TransportCongestionControlClient( RTC::TransportCongestionControlClient::Listener* listener, SharedInterface* shared, RTC::BweType bweType, uint32_t initialAvailableBitrate, uint32_t maxOutgoingBitrate, uint32_t minOutgoingBitrate); ~TransportCongestionControlClient() override; public: RTC::BweType GetBweType() const { return this->bweType; } void TransportConnected(); void TransportDisconnected(); void InsertPacket(webrtc::RtpPacketSendInfo& packetInfo); webrtc::PacedPacketInfo GetPacingInfo(); void PacketSent(const webrtc::RtpPacketSendInfo& packetInfo, int64_t nowMs); void ReceiveEstimatedBitrate(uint32_t bitrate); void ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReportPacket* packet, float rtt, int64_t nowMs); void ReceiveRtcpTransportFeedback(const RTC::RTCP::FeedbackRtpTransportPacket* feedback); void SetDesiredBitrate(uint32_t desiredBitrate, bool force); void SetMaxOutgoingBitrate(uint32_t maxBitrate); void SetMinOutgoingBitrate(uint32_t minBitrate); const Bitrates& GetBitrates() const { return this->bitrates; } uint32_t GetAvailableBitrate() const; double GetPacketLoss() const; void RescheduleNextAvailableBitrateEvent(); private: void MayEmitAvailableBitrateEvent(uint32_t previousAvailableBitrate); void UpdatePacketLoss(double packetLoss); void ApplyBitrateUpdates(); void InitializeController(); void DestroyController(); // jmillan: missing. // void OnRemoteNetworkEstimate(NetworkStateEstimate estimate) override; /* Pure virtual methods inherited from webrtc::TargetTransferRateObserver. */ public: void OnTargetTransferRate(webrtc::TargetTransferRate targetTransferRate) override; /* Pure virtual methods inherited from webrtc::PacketRouter. */ public: void SendPacket(RTC::RTP::Packet* packet, const webrtc::PacedPacketInfo& pacingInfo) override; RTC::RTP::Packet* GeneratePadding(size_t size) override; /* Pure virtual methods inherited from RTC::TimerHandleInterface. */ public: void OnTimer(TimerHandleInterface* timer) override; private: // Passed by argument. Listener* listener{ nullptr }; SharedInterface* shared{ nullptr }; // Allocated by this. webrtc::NetworkControllerFactoryInterface* controllerFactory{ nullptr }; webrtc::RtpTransportControllerSend* rtpTransportControllerSend{ nullptr }; RTC::RTP::ProbationGenerator* probationGenerator{ nullptr }; TimerHandleInterface* processTimer{ nullptr }; // Others. RTC::BweType bweType; uint32_t initialAvailableBitrate{ 0u }; uint32_t maxOutgoingBitrate{ 0u }; uint32_t minOutgoingBitrate{ 0u }; Bitrates bitrates; bool availableBitrateEventCalled{ false }; uint64_t lastAvailableBitrateEventAtMs{ 0u }; RTC::TrendCalculator desiredBitrateTrend; std::deque packetLossHistory; double packetLoss{ 0 }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/TransportCongestionControlServer.hpp ================================================ #ifndef MS_RTC_TRANSPORT_CONGESTION_CONTROL_SERVER_HPP #define MS_RTC_TRANSPORT_CONGESTION_CONTROL_SERVER_HPP #include "common.hpp" #include "SharedInterface.hpp" #include "RTC/BweType.hpp" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include "RTC/RTCP/Packet.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/SeqManager.hpp" #include "handles/TimerHandleInterface.hpp" #include #include namespace RTC { class TransportCongestionControlServer : public webrtc::RemoteBitrateEstimator::Listener, public TimerHandleInterface::Listener { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnTransportCongestionControlServerSendRtcpPacket( RTC::TransportCongestionControlServer* tccServer, RTC::RTCP::Packet* packet) = 0; }; public: TransportCongestionControlServer( RTC::TransportCongestionControlServer::Listener* listener, SharedInterface* shared, RTC::BweType bweType, size_t maxRtcpPacketLen); ~TransportCongestionControlServer() override; public: RTC::BweType GetBweType() const { return this->bweType; } void TransportConnected(); void TransportDisconnected(); uint32_t GetAvailableBitrate() const { switch (this->bweType) { case RTC::BweType::REMB: return this->rembServer->GetAvailableBitrate(); default: return 0u; } } double GetPacketLoss() const; void IncomingPacket(uint64_t nowMs, const RTC::RTP::Packet* packet); void SetMaxIncomingBitrate(uint32_t bitrate); void FillAndSendTransportCcFeedback(); private: // Returns true if a feedback packet was sent. bool SendTransportCcFeedback(); void MayDropOldPacketArrivalTimes(uint16_t seqNum, uint64_t nowMs); void MaySendLimitationRembFeedback(uint64_t nowMs); void UpdatePacketLoss(double packetLoss); void ResetTransportCcFeedback(uint8_t feedbackPacketCount); /* Pure virtual methods inherited from webrtc::RemoteBitrateEstimator::Listener. */ public: void OnRembServerAvailableBitrate( const webrtc::RemoteBitrateEstimator* remoteBitrateEstimator, const std::vector& ssrcs, uint32_t availableBitrate) override; /* Pure virtual methods inherited from TimerHandleInterface::Listener. */ public: void OnTimer(TimerHandleInterface* timer) override; private: // Passed by argument. Listener* listener{ nullptr }; SharedInterface* shared{ nullptr }; // Allocated by this. TimerHandleInterface* transportCcFeedbackSendPeriodicTimer{ nullptr }; std::unique_ptr transportCcFeedbackPacket; webrtc::RemoteBitrateEstimatorAbsSendTime* rembServer{ nullptr }; // Others. RTC::BweType bweType; size_t maxRtcpPacketLen{ 0u }; uint8_t transportCcFeedbackPacketCount{ 0u }; uint32_t transportCcFeedbackSenderSsrc{ 0u }; uint32_t transportCcFeedbackMediaSsrc{ 0u }; uint32_t maxIncomingBitrate{ 0u }; uint64_t limitationRembSentAtMs{ 0u }; uint8_t unlimitedRembCounter{ 0u }; std::deque packetLossHistory; double packetLoss{ 0 }; // Whether any packet with transport wide sequence number was received. bool transportWideSeqNumberReceived{ false }; uint16_t transportCcFeedbackWideSeqNumStart{ 0u }; // Map of arrival timestamp (ms) indexed by wide seq number. std::map::SeqLowerThan> mapPacketArrivalTimes; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/TransportTuple.hpp ================================================ #ifndef MS_RTC_TRANSPORT_TUPLE_HPP #define MS_RTC_TRANSPORT_TUPLE_HPP #include "common.hpp" #include "Utils.hpp" #include "FBS/transport.h" #include "RTC/TcpConnection.hpp" #include "RTC/UdpSocket.hpp" #include #include namespace RTC { class TransportTuple { protected: using onSendCallback = const std::function; public: enum class Protocol : uint8_t { UDP = 1, TCP = 2 }; static Protocol ProtocolFromFbs(FBS::Transport::Protocol protocol); static FBS::Transport::Protocol ProtocolToFbs(Protocol protocol); static uint64_t GenerateFnv1aHash(const uint8_t* data, size_t size); public: TransportTuple(RTC::UdpSocket* udpSocket, const struct sockaddr* udpRemoteAddr) : udpSocket(udpSocket), udpRemoteAddr(const_cast(udpRemoteAddr)), protocol(Protocol::UDP) { GenerateHash(); } explicit TransportTuple(RTC::TcpConnection* tcpConnection) : tcpConnection(tcpConnection), protocol(Protocol::TCP) { GenerateHash(); } explicit TransportTuple(const TransportTuple* tuple) : hash(tuple->hash), udpSocket(tuple->udpSocket), udpRemoteAddr(tuple->udpRemoteAddr), tcpConnection(tuple->tcpConnection), localAnnouncedAddress(tuple->localAnnouncedAddress), protocol(tuple->protocol) { if (protocol == TransportTuple::Protocol::UDP) { StoreUdpRemoteAddress(); } } public: void CloseTcpConnection(); flatbuffers::Offset FillBuffer(flatbuffers::FlatBufferBuilder& builder) const; void Dump(int indentation = 0) const; void StoreUdpRemoteAddress() { // Clone the given address into our address storage and make the sockaddr // pointer point to it. this->udpRemoteAddrStorage = Utils::IP::CopyAddress(this->udpRemoteAddr); this->udpRemoteAddr = reinterpret_cast(std::addressof(this->udpRemoteAddrStorage)); } bool Compare(const TransportTuple* tuple) const { return this->hash == tuple->hash; } void SetLocalAnnouncedAddress(std::string& localAnnouncedAddress) { this->localAnnouncedAddress = localAnnouncedAddress; } void Send(const uint8_t* data, size_t len, RTC::TransportTuple::onSendCallback* cb = nullptr) { if (this->protocol == Protocol::UDP) { this->udpSocket->Send(data, len, this->udpRemoteAddr, cb); } else { this->tcpConnection->Send(data, len, cb); } } Protocol GetProtocol() const { return this->protocol; } const struct sockaddr* GetLocalAddress() const { if (this->protocol == Protocol::UDP) { return this->udpSocket->GetLocalAddress(); } else { return this->tcpConnection->GetLocalAddress(); } } const struct sockaddr* GetRemoteAddress() const { if (this->protocol == Protocol::UDP) { return static_cast(this->udpRemoteAddr); } else { return this->tcpConnection->GetPeerAddress(); } } size_t GetRecvBytes() const { if (this->protocol == Protocol::UDP) { return this->udpSocket->GetRecvBytes(); } else { return this->tcpConnection->GetRecvBytes(); } } size_t GetSentBytes() const { if (this->protocol == Protocol::UDP) { return this->udpSocket->GetSentBytes(); } else { return this->tcpConnection->GetSentBytes(); } } private: void SetHash(); void GenerateHash(); public: uint64_t hash{ 0u }; private: // Passed by argument. RTC::UdpSocket* udpSocket{ nullptr }; struct sockaddr* udpRemoteAddr{ nullptr }; RTC::TcpConnection* tcpConnection{ nullptr }; std::string localAnnouncedAddress; // Others. struct sockaddr_storage udpRemoteAddrStorage{}; Protocol protocol; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/TrendCalculator.hpp ================================================ #ifndef TREND_CALCULATOR_HPP #define TREND_CALCULATOR_HPP #include "common.hpp" namespace RTC { class TrendCalculator { public: static constexpr float DecreaseFactor{ 0.05f }; // per second. public: explicit TrendCalculator(float decreaseFactor = DecreaseFactor); public: uint32_t GetValue() const { return this->value; } void Update(uint32_t value, uint64_t nowMs); void ForceUpdate(uint32_t value, uint64_t nowMs); private: float decreaseFactor{ DecreaseFactor }; uint32_t value{ 0u }; uint32_t highestValue{ 0u }; uint64_t highestValueUpdatedAtMs{ 0u }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/UdpSocket.hpp ================================================ #ifndef MS_RTC_UDP_SOCKET_HPP #define MS_RTC_UDP_SOCKET_HPP #include "common.hpp" #include "RTC/Transport.hpp" #include "handles/UdpSocketHandle.hpp" #include namespace RTC { class UdpSocket : public ::UdpSocketHandle { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnUdpSocketPacketReceived( RTC::UdpSocket* socket, const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* remoteAddr) = 0; }; public: UdpSocket(Listener* listener, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags); UdpSocket( Listener* listener, std::string& ip, uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, uint64_t& portRangeHash); ~UdpSocket() override; /* Pure virtual methods inherited from ::UdpSocketHandle. */ public: void UserOnUdpDatagramReceived( const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* addr) override; private: // Passed by argument. Listener* listener{ nullptr }; bool fixedPort{ false }; uint64_t portRangeHash{ 0u }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/WebRtcServer.hpp ================================================ #ifndef MS_RTC_WEBRTC_SERVER_HPP #define MS_RTC_WEBRTC_SERVER_HPP #include "SharedInterface.hpp" #include "Channel/ChannelRequest.hpp" #include "RTC/ICE/IceCandidate.hpp" #include "RTC/ICE/StunPacket.hpp" #include "RTC/TcpConnection.hpp" #include "RTC/TcpServer.hpp" #include "RTC/TransportTuple.hpp" #include "RTC/UdpSocket.hpp" #include "RTC/WebRtcTransport.hpp" #include #include #include #include #include namespace RTC { class WebRtcServer : public RTC::UdpSocket::Listener, public RTC::TcpServer::Listener, public RTC::TcpConnection::Listener, public RTC::WebRtcTransport::WebRtcTransportListener, public Channel::ChannelSocket::RequestHandler { private: struct UdpSocketOrTcpServer { // Expose a constructor to use vector.emplace_back(). UdpSocketOrTcpServer( RTC::UdpSocket* udpSocket, RTC::TcpServer* tcpServer, std::string& announcedAddress, bool exposeInternalIp) : udpSocket(udpSocket), tcpServer(tcpServer), announcedAddress(announcedAddress), exposeInternalIp(exposeInternalIp) { } RTC::UdpSocket* udpSocket; RTC::TcpServer* tcpServer; std::string announcedAddress; bool exposeInternalIp; }; private: static std::string GetLocalIceUsernameFragmentFromReceivedStunPacket( const RTC::ICE::StunPacket* packet); public: WebRtcServer( SharedInterface* shared, const std::string& id, const flatbuffers::Vector>* listenInfos); ~WebRtcServer() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; std::vector GetIceCandidates( bool enableUdp, bool enableTcp, bool preferUdp, bool preferTcp) const; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; private: void OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen); void OnStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); void OnNonStunDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen); /* Pure virtual methods inherited from RTC::WebRtcTransport::WebRtcTransportListener. */ public: void OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) override; void OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport) override; void OnWebRtcTransportLocalIceUsernameFragmentAdded( RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) override; void OnWebRtcTransportLocalIceUsernameFragmentRemoved( RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) override; void OnWebRtcTransportTransportTupleAdded( RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) override; void OnWebRtcTransportTransportTupleRemoved( RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) override; /* Pure virtual methods inherited from RTC::UdpSocket::Listener. */ public: void OnUdpSocketPacketReceived( RTC::UdpSocket* socket, const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* remoteAddr) override; /* Pure virtual methods inherited from RTC::TcpServer::Listener. */ public: void OnRtcTcpConnectionClosed(RTC::TcpServer* tcpServer, RTC::TcpConnection* connection) override; /* Pure virtual methods inherited from RTC::TcpConnection::Listener. */ public: void OnTcpConnectionPacketReceived( RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen) override; const std::string& GetId() const { return this->id; } private: // Passed by argument. std::string id; // Passed by argument. SharedInterface* shared{ nullptr }; // Vector of UdpSockets and TcpServers in the user given order. std::vector udpSocketOrTcpServers; // Set of WebRtcTransports. absl::flat_hash_set webRtcTransports; // Map of WebRtcTransports indexed by local ICE usernameFragment. absl::flat_hash_map mapLocalIceUsernameFragmentWebRtcTransport; // Map of WebRtcTransports indexed by TransportTuple.hash. absl::flat_hash_map mapTupleWebRtcTransport; // Whether the destructor has been called. bool closing{ false }; }; } // namespace RTC #endif ================================================ FILE: worker/include/RTC/WebRtcTransport.hpp ================================================ #ifndef MS_RTC_WEBRTC_TRANSPORT_HPP #define MS_RTC_WEBRTC_TRANSPORT_HPP #include "SharedInterface.hpp" #include "RTC/DtlsTransport.hpp" #include "RTC/ICE/IceCandidate.hpp" #include "RTC/ICE/IceServer.hpp" #include "RTC/ICE/StunPacket.hpp" #include "RTC/SrtpSession.hpp" #include "RTC/TcpConnection.hpp" #include "RTC/TcpServer.hpp" #include "RTC/Transport.hpp" #include "RTC/TransportTuple.hpp" #include "RTC/UdpSocket.hpp" #include namespace RTC { class WebRtcTransport : public RTC::Transport, public RTC::UdpSocket::Listener, public RTC::TcpServer::Listener, public RTC::TcpConnection::Listener, public RTC::ICE::IceServer::Listener, public RTC::DtlsTransport::Listener { public: class WebRtcTransportListener { public: virtual ~WebRtcTransportListener() = default; public: virtual void OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) = 0; virtual void OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport) = 0; virtual void OnWebRtcTransportLocalIceUsernameFragmentAdded( RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) = 0; virtual void OnWebRtcTransportLocalIceUsernameFragmentRemoved( RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) = 0; virtual void OnWebRtcTransportTransportTupleAdded( RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) = 0; virtual void OnWebRtcTransportTransportTupleRemoved( RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) = 0; }; public: WebRtcTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::WebRtcTransport::WebRtcTransportOptions* options); WebRtcTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, WebRtcTransportListener* webRtcTransportListener, const std::vector& iceCandidates, const FBS::WebRtcTransport::WebRtcTransportOptions* options); ~WebRtcTransport() override; public: flatbuffers::Offset FillBufferStats( flatbuffers::FlatBufferBuilder& builder); flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) const; void ProcessStunPacketFromWebRtcServer( RTC::TransportTuple* tuple, const RTC::ICE::StunPacket* packet); void ProcessNonStunPacketFromWebRtcServer( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen); void RemoveTuple(RTC::TransportTuple* tuple); /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; /* Methods inherited from Channel::ChannelSocket::NotificationHandler. */ public: void HandleNotification(Channel::ChannelNotification* notification) override; private: bool IsConnected() const override; void MayRunDtlsTransport(); void SendRtpPacket( RTC::Consumer* consumer, RTC::RTP::Packet* packet, RTC::Transport::onSendCallback* cb = nullptr) override; void SendRtcpPacket(RTC::RTCP::Packet* packet) override; void SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) override; // TODO: SCTP: Remove once we only use built-in SCTP stack. void SendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb = nullptr) override; void SendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb = nullptr) override; bool SendData(const uint8_t* data, size_t len) override; void RecvStreamClosed(uint32_t ssrc) override; void SendStreamClosed(uint32_t ssrc) override; void OnPacketReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen); void OnStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); void OnDtlsDataReceived(const RTC::TransportTuple* tuple, const uint8_t* data, size_t len); void OnRtpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen); void OnRtcpDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len); /* Pure virtual methods inherited from RTC::UdpSocket::Listener. */ public: void OnUdpSocketPacketReceived( RTC::UdpSocket* socket, const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* remoteAddr) override; /* Pure virtual methods inherited from RTC::TcpServer::Listener. */ public: void OnRtcTcpConnectionClosed(RTC::TcpServer* tcpServer, RTC::TcpConnection* connection) override; /* Pure virtual methods inherited from RTC::TcpConnection::Listener. */ public: void OnTcpConnectionPacketReceived( RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen) override; /* Pure virtual methods inherited from RTC::ICE::IceServer::Listener. */ public: void OnIceServerSendStunPacket( const RTC::ICE::IceServer* iceServer, const RTC::ICE::StunPacket* packet, RTC::TransportTuple* tuple) override; void OnIceServerLocalUsernameFragmentAdded( const RTC::ICE::IceServer* iceServer, const std::string& usernameFragment) override; void OnIceServerLocalUsernameFragmentRemoved( const RTC::ICE::IceServer* iceServer, const std::string& usernameFragment) override; void OnIceServerTupleAdded(const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) override; void OnIceServerTupleRemoved(const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) override; void OnIceServerSelectedTuple( const RTC::ICE::IceServer* iceServer, RTC::TransportTuple* tuple) override; void OnIceServerConnected(const RTC::ICE::IceServer* iceServer) override; void OnIceServerCompleted(const RTC::ICE::IceServer* iceServer) override; void OnIceServerDisconnected(const RTC::ICE::IceServer* iceServer) override; /* Pure virtual methods inherited from RTC::DtlsTransport::Listener. */ public: void OnDtlsTransportConnecting(const RTC::DtlsTransport* dtlsTransport) override; void OnDtlsTransportConnected( const RTC::DtlsTransport* dtlsTransport, RTC::SrtpSession::CryptoSuite srtpCryptoSuite, uint8_t* srtpLocalKey, size_t srtpLocalKeyLen, uint8_t* srtpRemoteKey, size_t srtpRemoteKeyLen, std::string& remoteCert) override; void OnDtlsTransportFailed(const RTC::DtlsTransport* dtlsTransport) override; void OnDtlsTransportClosed(const RTC::DtlsTransport* dtlsTransport) override; void OnDtlsTransportSendData( const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) override; void OnDtlsTransportApplicationDataReceived( const RTC::DtlsTransport* dtlsTransport, const uint8_t* data, size_t len) override; private: // Passed by argument. WebRtcTransportListener* webRtcTransportListener{ nullptr }; // Allocated by this. RTC::ICE::IceServer* iceServer{ nullptr }; // Map of UdpSocket/TcpServer and local announced address (if any). absl::flat_hash_map udpSockets; absl::flat_hash_map tcpServers; RTC::DtlsTransport* dtlsTransport{ nullptr }; RTC::SrtpSession* srtpRecvSession{ nullptr }; RTC::SrtpSession* srtpSendSession{ nullptr }; // Others. // Whether connect() was succesfully called. bool connectCalled{ false }; std::vector iceCandidates; RTC::DtlsTransport::Role dtlsRole{ RTC::DtlsTransport::Role::AUTO }; }; } // namespace RTC #endif ================================================ FILE: worker/include/Settings.hpp ================================================ #ifndef MS_SETTINGS_HPP #define MS_SETTINGS_HPP #include "common.hpp" #include "LogLevel.hpp" #include "Channel/ChannelRequest.hpp" #include #include #include class Settings { public: struct LogTags { bool info{ false }; bool ice{ false }; bool dtls{ false }; bool rtp{ false }; bool srtp{ false }; bool rtcp{ false }; bool rtx{ false }; bool bwe{ false }; bool score{ false }; bool simulcast{ false }; bool svc{ false }; bool sctp{ false }; bool message{ false }; }; public: // Struct holding the configuration. struct Configuration { LogLevel logLevel{ LogLevel::LOG_ERROR }; struct LogTags logTags; uint16_t rtcMinPort{ 10000u }; uint16_t rtcMaxPort{ 59999u }; std::string dtlsCertificateFile; std::string dtlsPrivateKeyFile; std::string libwebrtcFieldTrials{ "WebRTC-Bwe-AlrLimitedBackoff/Enabled/" }; bool disableLiburing{ false }; bool useBuiltInSctpStack{ false }; }; public: static void SetConfiguration(int argc, char* argv[]); static void SetLogLevel(std::string& level); static void SetLogTags(const std::vector& tags); static void PrintConfiguration(); static void HandleRequest(Channel::ChannelRequest* request); private: static void SetDtlsCertificateAndPrivateKeyFiles(); public: static thread_local struct Configuration configuration; private: static const absl::flat_hash_map String2LogLevel; static const absl::flat_hash_map LogLevel2String; }; #endif ================================================ FILE: worker/include/Shared.hpp ================================================ #ifndef MS_SHARED_HPP #define MS_SHARED_HPP #include "DepLibUV.hpp" #include "SharedInterface.hpp" #include "Channel/ChannelMessageRegistrator.hpp" #include "Channel/ChannelNotifier.hpp" class Shared : public SharedInterface { public: explicit Shared( Channel::ChannelMessageRegistrator* channelMessageRegistrator, Channel::ChannelNotifier* channelNotifier); ~Shared() override; public: Channel::ChannelMessageRegistratorInterface* GetChannelMessageRegistrator() override { return this->channelMessageRegistrator.get(); } Channel::ChannelNotifier* GetChannelNotifier() override { return this->channelNotifier.get(); } TimerHandleInterface* CreateTimer(TimerHandleInterface::Listener* listener) override; BackoffTimerHandleInterface* CreateBackoffTimer( const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options) override; uint64_t GetTimeMs() override { return DepLibUV::GetTimeMs(); } uint64_t GetTimeUs() override { return DepLibUV::GetTimeUs(); } uint64_t GetTimeNs() override { return DepLibUV::GetTimeNs(); } int64_t GetTimeMsInt64() override { return DepLibUV::GetTimeMsInt64(); } int64_t GetTimeUsInt64() override { return DepLibUV::GetTimeUsInt64(); } private: std::unique_ptr channelMessageRegistrator; std::unique_ptr channelNotifier; }; #endif ================================================ FILE: worker/include/SharedInterface.hpp ================================================ #ifndef MS_SHARED_INTERFACE_HPP #define MS_SHARED_INTERFACE_HPP #include "Channel/ChannelMessageRegistratorInterface.hpp" // TODO: We should have a ChannelNotifierInterface class instead. #include "Channel/ChannelNotifier.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include "handles/TimerHandleInterface.hpp" class SharedInterface { public: virtual ~SharedInterface() = default; public: /** * @todo We should have a ChannelMessageRegistratorInterface class instead. */ virtual Channel::ChannelMessageRegistratorInterface* GetChannelMessageRegistrator() = 0; /** * @todo We should have a ChannelNotifierInterface class instead. */ virtual Channel::ChannelNotifier* GetChannelNotifier() = 0; /** * Creates a TimerHandle timer. * * @remarks * - The caller is responsible for freeing it. */ virtual TimerHandleInterface* CreateTimer(TimerHandleInterface::Listener* listener) = 0; /** * Creates a BackoffTimerHandle timer. * * @remarks * - The caller is responsible for freeing it. */ virtual BackoffTimerHandleInterface* CreateBackoffTimer( const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options) = 0; /** * Get current time in milliseconds. */ virtual uint64_t GetTimeMs() = 0; /** * Get current time in microseconds. */ virtual uint64_t GetTimeUs() = 0; /** * Get current time in nanoseconds. */ virtual uint64_t GetTimeNs() = 0; /** * @remarks * - Used within libwebrtc dependency which uses int64_t values for time * representation. * * @todo Remove once not needed. */ virtual int64_t GetTimeMsInt64() = 0; /** * @remarks * - Used within libwebrtc dependency which uses int64_t values for time * representation. * * @todo Remove once not needed. */ virtual int64_t GetTimeUsInt64() = 0; }; #endif ================================================ FILE: worker/include/Utils/UnwrappedSequenceNumber.hpp ================================================ #ifndef MS_UTILS_UNWRAPPED_SEQUENCE_NUMBER_HPP #define MS_UTILS_UNWRAPPED_SEQUENCE_NUMBER_HPP #include "common.hpp" #include #include #include namespace Utils { /** * UnwrappedSequenceNumber handles wrapping sequence numbers and unwraps * them to an int64_t value space, to allow wrapped sequence numbers to be * easily compared for ordering. * * Sequence numbers are expected to be monotonically increasing, but they * do not need to be unwrapped in order, as long as the difference to the * previous one is not larger than half the range of the wrapped sequence * number. */ template class UnwrappedSequenceNumber { public: static_assert(!std::numeric_limits::is_signed, "the wrapped type must be unsigned"); static_assert( std::numeric_limits::max() < std::numeric_limits::max(), "the wrapped type must be less than the int64_t value space"); /** * The unwrapper is a sort of factory and converts wrapped sequence * numbers to unwrapped ones. */ class Unwrapper { public: Unwrapper() = default; Unwrapper(const Unwrapper&) = default; Unwrapper& operator=(const Unwrapper&) = default; public: /** * Given a wrapped `value`, and with knowledge of its current last seen * largest number, will return a value that can be compared using normal * operators, such as less-than, greater-than etc. * * This will also update the Unwrapper's state, to track the last seen * largest value. */ UnwrappedSequenceNumber Unwrap(T value) { if (!this->lastValue) { this->lastUnwrapped = value; } else { const uint64_t prev = this->lastValue->GetValue(); const uint64_t curr = value; auto delta = static_cast(curr - prev); const auto half = static_cast(1) << (sizeof(T) * 8 - 1); if (delta < -half) { delta += static_cast(1) << (sizeof(T) * 8); } else if (delta > half) { delta -= static_cast(1) << (sizeof(T) * 8); } this->lastUnwrapped += delta; } this->lastValue = UnwrappedSequenceNumber(value); return UnwrappedSequenceNumber(this->lastUnwrapped); } /** * Similar to `Unwrap()`, but will not update the Unwrappers's internal * state. */ UnwrappedSequenceNumber PeekUnwrap(T value) const { if (!this->lastValue) { return UnwrappedSequenceNumber(value); } const uint64_t prev = this->lastValue->GetValue(); const uint64_t curr = value; auto delta = static_cast(curr - prev); const auto half = static_cast(1) << (sizeof(T) * 8 - 1); if (delta < -half) { delta += static_cast(1) << (sizeof(T) * 8); } else if (delta > half) { delta -= static_cast(1) << (sizeof(T) * 8); } return UnwrappedSequenceNumber(this->lastUnwrapped + delta); } /** * Resets the Unwrapper to its pristine state. Used when a sequence number * is to be reset to zero. */ void Reset() { this->lastUnwrapped = 0; this->lastValue.reset(); } private: int64_t lastUnwrapped{ 0 }; std::optional> lastValue; }; public: /** * Returns a new sequence number based on `value`, and adding `delta` * (which may be negative). */ static UnwrappedSequenceNumber AddTo(UnwrappedSequenceNumber value, int64_t delta) { return UnwrappedSequenceNumber(value.value + delta); } /** * Returns the absolute difference between `lhs` and `rhs`. */ static T Difference(UnwrappedSequenceNumber lhs, UnwrappedSequenceNumber rhs) { return (lhs.value > rhs.value) ? (lhs.value - rhs.value) : (rhs.value - lhs.value); } private: static constexpr auto ValueLimit = static_cast(1) << std::numeric_limits::digits; public: explicit UnwrappedSequenceNumber(int64_t value) : value(value) { } public: /** * Returns the wrapped value this type represents. */ T Wrap() const { return static_cast(this->value % UnwrappedSequenceNumber::ValueLimit); } bool operator==(const UnwrappedSequenceNumber& other) const { return this->value == other.value; } bool operator!=(const UnwrappedSequenceNumber& other) const { return this->value != other.value; } bool operator<(const UnwrappedSequenceNumber& other) const { return this->value < other.value; } bool operator>(const UnwrappedSequenceNumber& other) const { return this->value > other.value; } bool operator>=(const UnwrappedSequenceNumber& other) const { return this->value >= other.value; } bool operator<=(const UnwrappedSequenceNumber& other) const { return this->value <= other.value; } // Const accessors for underlying value. constexpr const int64_t* operator->() const { return std::addressof(this->value); } constexpr const int64_t& operator*() const& { return this->value; } constexpr const int64_t&& operator*() const&& { return std::move(this->value); } constexpr const int64_t& GetValue() const& { return this->value; } constexpr const int64_t&& GetValue() const&& { return std::move(this->value); } constexpr explicit operator const int64_t&() const& { return this->value; } /** * Increments the value. */ void Increment() { ++this->value; } /** * Returns the next value relative to this sequence number. */ UnwrappedSequenceNumber GetNextValue() const { return UnwrappedSequenceNumber(this->value + 1); } private: int64_t value{ 0 }; }; /** * For logging purposes in Catch2 tests. */ template inline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber& s) { return os << "{T:" << typeid(T).name() << ", wrapped:" << s.Wrap(); } template<> inline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber& s) { return os << "{T:uint8_t, wrapped:" << s.Wrap() << "}"; } template<> inline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber& s) { return os << "{T:uint16_t, wrapped:" << s.Wrap() << "}"; } template<> inline std::ostream& operator<<(std::ostream& os, const UnwrappedSequenceNumber& s) { return os << "{T:uint32_t, wrapped:" << s.Wrap() << "}"; } } // namespace Utils #endif ================================================ FILE: worker/include/Utils.hpp ================================================ #ifndef MS_UTILS_HPP #define MS_UTILS_HPP #include "common.hpp" #include "RTC/Consts.hpp" #include #include #include #include // std::memcmp(), std::memcpy() #include // std::numeric_limits #include // std::mt19937_64, std::uniform_int_distribution, std::random_device #include #include // std::enable_if, std::is_same_v, std::is_unsigned #ifdef _WIN32 #include // https://stackoverflow.com/a/24550632/2085408 #include #define __builtin_popcount __popcnt #endif namespace Utils { class IP { public: static int GetFamily(const std::string& ip); static void GetAddressInfo(const struct sockaddr* addr, int& family, std::string& ip, uint16_t& port); static size_t GetAddressLen(const struct sockaddr* addr); static bool CompareAddresses(const struct sockaddr* addr1, const struct sockaddr* addr2) { // Compare family. if ( addr1->sa_family != addr2->sa_family || (addr1->sa_family != AF_INET && addr1->sa_family != AF_INET6) || (addr2->sa_family != AF_INET && addr2->sa_family != AF_INET6)) { return false; } // Compare port. if ( reinterpret_cast(addr1)->sin_port != reinterpret_cast(addr2)->sin_port) { return false; } // Compare IP. switch (addr1->sa_family) { case AF_INET: { return ( reinterpret_cast(addr1)->sin_addr.s_addr == reinterpret_cast(addr2)->sin_addr.s_addr); } case AF_INET6: { return ( std::memcmp( std::addressof(reinterpret_cast(addr1)->sin6_addr), std::addressof(reinterpret_cast(addr2)->sin6_addr), 16) == 0); } default: { return false; } } } static struct sockaddr_storage CopyAddress(const struct sockaddr* addr) { struct sockaddr_storage copiedAddr{}; switch (addr->sa_family) { case AF_INET: std::memcpy(std::addressof(copiedAddr), addr, sizeof(struct sockaddr_in)); break; case AF_INET6: std::memcpy(std::addressof(copiedAddr), addr, sizeof(struct sockaddr_in6)); break; default:; } return copiedAddr; } static std::string NormalizeIp(std::string& ip); }; class File { public: static void CheckFile(const char* file); }; class Byte { public: /** * Getters below get value in Host Byte Order. * Setters below set value in Network Byte Order. */ static uint8_t Get1Byte(const uint8_t* data, size_t i) { return data[i]; } static uint16_t Get2Bytes(const uint8_t* data, size_t i) { return uint16_t{ data[i + 1] } | uint16_t{ data[i] } << 8; } static uint32_t Get3Bytes(const uint8_t* data, size_t i) { return uint32_t{ data[i + 2] } | uint32_t{ data[i + 1] } << 8 | uint32_t{ data[i] } << 16; } static int32_t Get3BytesSigned(const uint8_t* data, size_t i) { auto byte2 = data[i]; // The most significant byte. auto byte1 = data[i + 1]; auto byte0 = data[i + 2]; // The less significant byte. // Check bit 7 (sign). const uint8_t extension = byte2 & 0b10000000 ? 0b11111111 : 0b00000000; return int32_t{ byte0 } | (int32_t{ byte1 } << 8) | (int32_t{ byte2 } << 16) | (int32_t{ extension } << 24); } static uint32_t Get4Bytes(const uint8_t* data, size_t i) { return uint32_t{ data[i + 3] } | uint32_t{ data[i + 2] } << 8 | uint32_t{ data[i + 1] } << 16 | uint32_t{ data[i] } << 24; } static uint64_t Get8Bytes(const uint8_t* data, size_t i) { return uint64_t{ Byte::Get4Bytes(data, i) } << 32 | Byte::Get4Bytes(data, i + 4); } static void Set1Byte(uint8_t* data, size_t i, uint8_t value) { data[i] = value; } static void Set2Bytes(uint8_t* data, size_t i, uint16_t value) { data[i + 1] = static_cast(value); data[i] = static_cast(value >> 8); } static void Set3Bytes(uint8_t* data, size_t i, uint32_t value) { data[i + 2] = static_cast(value); data[i + 1] = static_cast(value >> 8); data[i] = static_cast(value >> 16); } static void Set3BytesSigned(uint8_t* data, size_t i, int32_t value) { data[i + 2] = static_cast(value); data[i + 1] = static_cast(value >> 8); data[i] = static_cast(value >> 16); } static void Set4Bytes(uint8_t* data, size_t i, uint32_t value) { data[i + 3] = static_cast(value); data[i + 2] = static_cast(value >> 8); data[i + 1] = static_cast(value >> 16); data[i] = static_cast(value >> 24); } static void Set8Bytes(uint8_t* data, size_t i, uint64_t value) { data[i + 7] = static_cast(value); data[i + 6] = static_cast(value >> 8); data[i + 5] = static_cast(value >> 16); data[i + 4] = static_cast(value >> 24); data[i + 3] = static_cast(value >> 32); data[i + 2] = static_cast(value >> 40); data[i + 1] = static_cast(value >> 48); data[i] = static_cast(value >> 56); } template typename std::enable_if::value, bool>::type static IsPaddedTo4Bytes(T size) { return (size & 0x03) == 0u; } template typename std::enable_if::value, bool>::type static IsPaddedTo8Bytes(T size) { return (size & 0x07) == 0u; } template typename std::enable_if::value, T>::type static PadTo4Bytes(T size) { return (size + 3) & ~static_cast(0x03); } template typename std::enable_if::value, T>::type static PadDownTo4Bytes(T size) { return size & ~static_cast(0x03); } template typename std::enable_if::value, T>::type static PadTo8Bytes(T size) { return (size + 7) & ~static_cast(0x07); } }; class Bits { public: static size_t CountSetBits(const uint16_t mask) { return static_cast(__builtin_popcount(mask)); } }; class Crypto { public: static void ClassInit(); static void ClassDestroy(); template static T GetRandomUInt(T min, T max) { static_assert( std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "T must be uint16_t, uint32_t, uint64_t, size_t"); std::uniform_int_distribution dist(min, max); return dist(Crypto::rng); } static std::string GetRandomString(size_t len); static uint32_t GetCRC32(const uint8_t* data, size_t size); static uint32_t GetCRC32c(const uint8_t* data, size_t size); static const uint8_t* GetHmacSha1(const char* key, size_t keyLen, const uint8_t* data, size_t len); static void WriteRandomBytes(uint8_t* buffer, size_t len); private: static thread_local std::mt19937_64 rng; static thread_local EVP_MAC* mac; static thread_local EVP_MAC_CTX* hmacSha1Ctx; static thread_local uint8_t hmacSha1Buffer[]; static const uint32_t Crc32Table[256]; static const uint32_t Crc32cTable[256]; }; class String { public: static void ToLowerCase(std::string& str) { std::transform(str.begin(), str.end(), str.begin(), ::tolower); } static std::string Base64Encode(const uint8_t* data, size_t len); static std::string Base64Encode(const std::string& str); static uint8_t* Base64Decode(const uint8_t* data, size_t len, size_t& outLen); static uint8_t* Base64Decode(const std::string& str, size_t& outLen); }; class Number { public: // T is the base type (uint16_t, uint32_t, ...). // N is the max number of bits used in T. template static bool IsEqualThan(T lhs, T rhs) { static_assert( std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "T must be uint8_t, uint16_t, uint32_t or uint64_t"); constexpr T MaxValue = (N == 0) ? std::numeric_limits::max() : ((1 << N) - 1); lhs &= MaxValue; rhs &= MaxValue; return (lhs == rhs); } // T is the base type (uint16_t, uint32_t, ...). // N is the max number of bits used in T. template static bool IsHigherThan(T lhs, T rhs) { static_assert( std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "T must be uint8_t, uint16_t, uint32_t or uint64_t"); constexpr T MaxValue = (N == 0) ? std::numeric_limits::max() : ((1 << N) - 1); lhs &= MaxValue; rhs &= MaxValue; return ((lhs > rhs) && (lhs - rhs <= MaxValue / 2)) || ((rhs > lhs) && (rhs - lhs > MaxValue / 2)); } // T is the base type (uint16_t, uint32_t, ...). // N is the max number of bits used in T. template static bool IsLowerThan(T lhs, T rhs) { static_assert( std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "T must be uint8_t, uint16_t, uint32_t or uint64_t"); constexpr T MaxValue = (N == 0) ? std::numeric_limits::max() : ((1 << N) - 1); lhs &= MaxValue; rhs &= MaxValue; return ((rhs > lhs) && (rhs - lhs <= MaxValue / 2)) || ((lhs > rhs) && (lhs - rhs > MaxValue / 2)); } // T is the base type (uint16_t, uint32_t, ...). // N is the max number of bits used in T. template static bool IsHigherOrEqualThan(T lhs, T rhs) { static_assert( std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "T must be uint8_t, uint16_t, uint32_t or uint64_t"); constexpr T MaxValue = (N == 0) ? std::numeric_limits::max() : ((1 << N) - 1); lhs &= MaxValue; rhs &= MaxValue; return (lhs == rhs) || ((lhs > rhs) && (lhs - rhs <= MaxValue / 2)) || ((rhs > lhs) && (rhs - lhs > MaxValue / 2)); } // T is the base type (uint16_t, uint32_t, ...). // N is the max number of bits used in T. template static bool IsLowerOrEqualThan(T lhs, T rhs) { static_assert( std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v, "T must be uint8_t, uint16_t, uint32_t or uint64_t"); constexpr T MaxValue = (N == 0) ? std::numeric_limits::max() : ((1 << N) - 1); lhs &= MaxValue; rhs &= MaxValue; return (lhs == rhs) || ((rhs > lhs) && (rhs - lhs <= MaxValue / 2)) || ((lhs > rhs) && (lhs - rhs > MaxValue / 2)); } /** * Calculates the forward difference between two wrapping numbers. * * Example: * ```c++ * uint8_t x = 253; * uint8_t y = 2; * * ForwardDiff(x, y) == 5 * ``` * * 252 253 254 255 0 1 2 3 * ################################################# * | | x | | | | | y | | * ################################################# * |----->----->----->----->-----> * * ForwardDiff(y, x) == 251 * * 252 253 254 255 0 1 2 3 * ################################################# * | | x | | | | | y | | * ################################################# * -->-----> |----->--- * * If M > 0 then wrapping occurs at M, if M == 0 then wrapping occurs at the * largest value representable by T. */ template static T ForwardDiff(T a, T b) requires(M > 0) { static_assert(std::is_unsigned::value, "type must be an unsigned integer"); assert(a < M); assert(b < M); return a <= b ? b - a : M - (a - b); } template static T ForwardDiff(T a, T b) requires(M == 0) { static_assert(std::is_unsigned::value, "type must be an unsigned integer"); return b - a; } template static T ForwardDiff(T a, T b) { return ForwardDiff(a, b); } /** * Calculates the reverse difference between two wrapping numbers. * * Example: * ```c++ * uint8_t x = 253; * uint8_t y = 2; * * ReverseDiff(y, x) == 5 * * 252 253 254 255 0 1 2 3 * ################################################# * | | x | | | | | y | | * ################################################# * <-----<-----<-----<-----<-----| * * ReverseDiff(x, y) == 251 * * 252 253 254 255 0 1 2 3 * ################################################# * | | x | | | | | y | | * ################################################# * ---<-----| |<-----<-- * * If M > 0 then wrapping occurs at M, if M == 0 then wrapping occurs at the * largest value representable by T. */ template static T ReverseDiff(T a, T b) requires(M > 0) { static_assert(std::is_unsigned::value, "type must be an unsigned integer"); assert(a < M); assert(b < M); return b <= a ? a - b : M - (b - a); } template static T ReverseDiff(T a, T b) requires(M == 0) { static_assert(std::is_unsigned::value, "type must be an unsigned integer"); return a - b; } template static T ReverseDiff(T a, T b) { return ReverseDiff(a, b); } }; class Time { private: // Seconds from Jan 1, 1900 to Jan 1, 1970. static constexpr uint32_t UnixNtpOffset{ 0x83AA7E80 }; // NTP fractional unit. static constexpr uint64_t NtpFractionalUnit{ 1LL << 32 }; public: struct Ntp { uint32_t seconds; uint32_t fractions; }; static Time::Ntp TimeMs2Ntp(uint64_t ms) { Time::Ntp ntp{}; // NOLINT(cppcoreguidelines-pro-type-member-init) ntp.seconds = ms / 1000; ntp.fractions = static_cast((static_cast(ms % 1000) / 1000) * NtpFractionalUnit); return ntp; } static uint64_t Ntp2TimeMs(Time::Ntp ntp) { return ( (static_cast(ntp.seconds) * 1000) + static_cast( std::round((static_cast(ntp.fractions) * 1000) / NtpFractionalUnit))); } static uint32_t TimeMsToAbsSendTime(uint64_t ms) { return static_cast(((ms << 18) + 500) / 1000) & 0x00FFFFFF; } }; class BitStream { public: BitStream(uint8_t* data, size_t len); ~BitStream() = default; const uint8_t* GetData() const; size_t GetLength() const; uint32_t GetOffset() const; void Reset(); uint8_t GetBit(); uint32_t GetBits(size_t count); uint32_t GetLeftBits() const; uint32_t GetNumBits(uint32_t n) const; std::optional ReadNs(uint32_t n); void SkipBits(size_t count); void Write(uint32_t offset, uint32_t n, uint32_t v); void PutBit(uint8_t bit); void PutBits(uint32_t count, uint32_t bits); private: void PutBit(uint32_t offset, uint8_t bit); void PutBits(uint32_t offset, uint32_t count, uint32_t bits); private: uint8_t data[RTC::Consts::TwoBytesRtpExtensionMaxLength]; uint32_t len{ 0 }; uint32_t offset{ 0 }; }; } // namespace Utils #endif ================================================ FILE: worker/include/Worker.hpp ================================================ #ifndef MS_WORKER_HPP #define MS_WORKER_HPP #include "SharedInterface.hpp" #include "Channel/ChannelRequest.hpp" #include "Channel/ChannelSocket.hpp" #include "FBS/worker.h" #include "RTC/Router.hpp" #include "RTC/WebRtcServer.hpp" #include "handles/SignalHandle.hpp" #include #include #include class Worker : public Channel::ChannelSocket::Listener, public SignalHandle::Listener, public RTC::Router::Listener { public: explicit Worker(Channel::ChannelSocket* channel, SharedInterface* shared); ~Worker() override; private: void Close(); flatbuffers::Offset FillBuffer(flatbuffers::FlatBufferBuilder& builder) const; flatbuffers::Offset FillBufferResourceUsage( flatbuffers::FlatBufferBuilder& builder) const; void SetNewRouterId(std::string& routerId) const; RTC::WebRtcServer* GetWebRtcServer(const std::string& webRtcServerId) const; RTC::Router* GetRouter(const std::string& routerId) const; void CheckNoWebRtcServer(const std::string& webRtcServerId) const; void CheckNoRouter(const std::string& routerId) const; /* Methods inherited from Channel::ChannelSocket::RequestHandler. */ public: void HandleRequest(Channel::ChannelRequest* request) override; void HandleNotification(Channel::ChannelNotification* notification) override; /* Methods inherited from Channel::ChannelSocket::Listener. */ public: void OnChannelClosed(Channel::ChannelSocket* channel) override; /* Methods inherited from SignalHandle::Listener. */ public: void OnSignal(SignalHandle* signalsHandler, int signum) override; /* Pure virtual methods inherited from RTC::Router::Listener. */ public: RTC::WebRtcServer* OnRouterNeedWebRtcServer(RTC::Router* router, std::string& webRtcServerId) override; private: // Passed by argument. Channel::ChannelSocket* channel{ nullptr }; // Allocated by this. SignalHandle* signalHandle{ nullptr }; SharedInterface* shared{ nullptr }; absl::flat_hash_map mapWebRtcServers; absl::flat_hash_map mapRouters; // Others. bool closed{ false }; }; #endif ================================================ FILE: worker/include/common.hpp ================================================ #ifndef MS_COMMON_HPP #define MS_COMMON_HPP #include // std::transform(), std::find(), std::min(), std::max(), std::copy(), std::clamp(), std::ranges #include // PRIu64, etc #include // size_t #include // uint8_t, etc #include // std::function #include // std::addressof(), std::unique_ptr(), etc #include #include // std::pair, std::move(), std::piecewise_construct #ifdef _WIN32 #include // Avoid uv/win.h: error C2628 'intptr_t' followed by 'int' is illegal. #if !defined(_SSIZE_T_) && !defined(_SSIZE_T_DEFINED) #include typedef SSIZE_T ssize_t; #define SSIZE_MAX INTPTR_MAX #define _SSIZE_T_ #define _SSIZE_T_DEFINED #endif #else #include // htonl(), htons(), ntohl(), ntohs() #include // sockaddr_in, sockaddr_in6 #include // struct sockaddr, struct sockaddr_storage, AF_INET, AF_INET6 #endif // This is a macro to silence false warnings in GCC in switch() blocks with FBS // types. #if defined(__GNUC__) && !defined(__clang__) #define NO_DEFAULT_GCC() \ default: \ __builtin_unreachable() #else #define NO_DEFAULT_GCC() #endif using ChannelReadCtx = void*; using ChannelReadFreeFn = void (*)(uint8_t*, uint32_t, size_t); // Returns `ChannelReadFree` on successful read that must be used to free // `message`. using ChannelReadFn = ChannelReadFreeFn (*)( uint8_t** /*message*/, uint32_t* /*messageLen*/, size_t* /*messageCtx*/, // This is `uv_async_t` handle that can be called later with `uv_async_send()` // when there is more data to read. const void* /*handle*/, ChannelReadCtx /*ctx*/ ); using ChannelWriteCtx = void*; using ChannelWriteFn = void (*)(const uint8_t* /*message*/, uint32_t /*messageLen*/, ChannelWriteCtx /*ctx*/); #endif ================================================ FILE: worker/include/handles/BackoffTimerHandle.hpp ================================================ #ifndef MS_BACKOFF_TIMER_HANDLE_HPP #define MS_BACKOFF_TIMER_HANDLE_HPP #include "common.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include "handles/TimerHandle.hpp" #include "handles/TimerHandleInterface.hpp" // Forward declaration. class Shared; class BackoffTimerHandle : public BackoffTimerHandleInterface, public TimerHandleInterface::Listener { // Only Shared class can invoke the constructor. friend class Shared; private: explicit BackoffTimerHandle(BackoffTimerHandleOptions options); public: BackoffTimerHandle& operator=(const BackoffTimerHandle&) = delete; BackoffTimerHandle(const BackoffTimerHandle&) = delete; ~BackoffTimerHandle() override; public: /** * Start the BackoffTimer (if it's stopped) or restart it (if already * running). It will reset the timeout count. */ void Start() override; /** * Stop the BackoffTimer. It will reset the timeout count. */ void Stop() override; /** * Set the base timeout duration. It will be applied after the next timeout * and effective duration can be larger if backoff algorithm is exponential. */ void SetBaseTimeoutMs(uint64_t baseTimeoutMs) override; /** * Whether the BackoffTimer is running. Useful to check if this BackoffTimer * will timeout again within the OnTimer() callback. */ bool IsRunning() const override { return this->running; } const std::string GetLabel() const override { return this->label; } /** * Maximum number of restarts. * * @remarks * - If `maxRestarts` was not given in the constructor, this method returns * `std::nullopt`. */ std::optional GetMaxRestarts() const override { return this->maxRestarts; } /** * Number of times the timer has expired. */ size_t GetExpirationCount() const override { return this->expirationCount; } private: uint64_t ComputeNextTimeoutMs() const; /* Pure virtual methods inherited from TimerHandleInterface::Listener. */ public: void OnTimer(TimerHandleInterface* timer) override; private: // Passed by argument. BackoffTimerHandleInterface::Listener* listener{ nullptr }; const std::string label; uint64_t baseTimeoutMs; BackoffAlgorithm backoffAlgorithm; std::optional maxBackoffTimeoutMs; std::optional maxRestarts; // Allocated by this. TimerHandle* timer{ nullptr }; // Others. bool running{ false }; size_t expirationCount{ 0 }; }; #endif ================================================ FILE: worker/include/handles/BackoffTimerHandleInterface.hpp ================================================ #ifndef MS_BACKOFF_TIMER_HANDLE_INTERFACE_HPP #define MS_BACKOFF_TIMER_HANDLE_INTERFACE_HPP #include "common.hpp" #include // std::numeric_limits() #include class BackoffTimerHandleInterface { public: class Listener { public: virtual ~Listener() = default; public: /** * Invoked on timeout expiration. The parent can modify the base * timeout given as reference and affect the next timeout duration. * * @remarks * - If the caller deletes this BackoffTimer instance within the callback * it must signal it be setting `stop` to true. */ virtual void OnBackoffTimer( BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) = 0; }; public: enum class BackoffAlgorithm : uint8_t { // The base duration will be used for any restart. FIXED, // An exponential backoff is used for restarts, with a 2x multiplier, // meaning that every restart will use a duration that is twice as long as // the previous. EXPONENTIAL, }; public: struct BackoffTimerHandleOptions { /** * Listener on which `OnBackoffTimer()` callback will be invoked. */ BackoffTimerHandleInterface::Listener* listener{ nullptr }; /** * Label. */ std::string label; /** * Base timeout duration (ms). */ uint64_t baseTimeoutMs; /** * Backoff algorithm. */ BackoffAlgorithm backoffAlgorithm; /** * Maximum duration of the backoff timeout (ms). If no value is given, no * limit is set. */ std::optional maxBackoffTimeoutMs; /** * Maximum number of restarts. If no value is given, it will restart * forever until stopped. */ std::optional maxRestarts; }; public: static constexpr uint64_t MaxTimeoutMs{ std::numeric_limits::max() / 2 }; public: BackoffTimerHandleInterface() = default; BackoffTimerHandleInterface& operator=(const BackoffTimerHandleInterface&) = delete; BackoffTimerHandleInterface(const BackoffTimerHandleInterface&) = delete; virtual ~BackoffTimerHandleInterface() = default; public: /** * Start the BackoffTimer (if it's stopped) or restart it (if already * running). It will reset the timeout count. */ virtual void Start() = 0; /** * Stop the BackoffTimer. It will reset the timeout count. */ virtual void Stop() = 0; /** * Set the base timeout duration. It will be applied after the next timeout * and effective duration can be larger if backoff algorithm is exponential. */ virtual void SetBaseTimeoutMs(uint64_t baseTimeoutMs) = 0; /** * Whether the BackoffTimer is running. Useful to check if this BackoffTimer * will timeout again within the OnTimer() callback. */ virtual bool IsRunning() const = 0; virtual const std::string GetLabel() const = 0; /** * Maximum number of restarts. * * @remarks * - If `maxRestarts` was not given in the constructor, this method returns 0. */ virtual std::optional GetMaxRestarts() const = 0; /** * Number of times the timer has expired. */ virtual size_t GetExpirationCount() const = 0; }; #endif ================================================ FILE: worker/include/handles/SignalHandle.hpp ================================================ #ifndef MS_SIGNAL_HANDLE_HPP #define MS_SIGNAL_HANDLE_HPP #include #include #include class SignalHandle { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnSignal(SignalHandle* signalsHandler, int signum) = 0; }; public: explicit SignalHandle(Listener* listener); ~SignalHandle(); public: void AddSignal(int signum, const std::string& name); private: void InternalClose(); /* Callbacks fired by UV events. */ public: void OnUvSignal(int signum); private: // Passed by argument. Listener* listener{ nullptr }; // Allocated by this. std::vector uvHandles; // Others. bool closed{ false }; }; #endif ================================================ FILE: worker/include/handles/TcpConnectionHandle.hpp ================================================ #ifndef MS_TCP_CONNECTION_HANDLE_HPP #define MS_TCP_CONNECTION_HANDLE_HPP #include "common.hpp" #include #include class TcpConnectionHandle { protected: using onSendCallback = const std::function; public: class Listener { public: virtual ~Listener() = default; public: virtual void OnTcpConnectionClosed(TcpConnectionHandle* connection) = 0; }; public: /* Struct for the data field of uv_req_t when writing into the connection. */ struct UvWriteData { explicit UvWriteData(size_t storeSize) : store(new uint8_t[storeSize]) { } // Disable copy constructor because of the dynamically allocated data (store). UvWriteData(const UvWriteData&) = delete; ~UvWriteData() { delete[] this->store; delete this->cb; } uv_write_t req{}; uint8_t* store{ nullptr }; TcpConnectionHandle::onSendCallback* cb{ nullptr }; }; public: explicit TcpConnectionHandle(size_t bufferSize); TcpConnectionHandle& operator=(const TcpConnectionHandle&) = delete; TcpConnectionHandle(const TcpConnectionHandle&) = delete; virtual ~TcpConnectionHandle(); public: void TriggerClose(); bool IsClosed() const { return this->closed; } void Dump(int indentation = 0) const; void Setup( Listener* listener, struct sockaddr_storage* localAddr, const std::string& localIp, uint16_t localPort); uv_tcp_t* GetUvHandle() const { return this->uvHandle; } void Start(); void Write( const uint8_t* data1, size_t len1, const uint8_t* data2, size_t len2, TcpConnectionHandle::onSendCallback* cb); void ErrorReceiving(); const struct sockaddr* GetLocalAddress() const { return reinterpret_cast(this->localAddr); } int GetLocalFamily() const { return reinterpret_cast(this->localAddr)->sa_family; } const std::string& GetLocalIp() const { return this->localIp; } uint16_t GetLocalPort() const { return this->localPort; } const struct sockaddr* GetPeerAddress() const { return reinterpret_cast(&this->peerAddr); } const std::string& GetPeerIp() const { return this->peerIp; } uint16_t GetPeerPort() const { return this->peerPort; } size_t GetRecvBytes() const { return this->recvBytes; } size_t GetSentBytes() const { return this->sentBytes; } private: void InternalClose(); bool SetPeerAddress(); /* Callbacks fired by UV events. */ public: void OnUvReadAlloc(size_t suggestedSize, uv_buf_t* buf); void OnUvRead(ssize_t nread, const uv_buf_t* buf); void OnUvWrite(int status, onSendCallback* cb); /* Pure virtual methods that must be implemented by the subclass. */ protected: virtual void UserOnTcpConnectionRead() = 0; protected: // Passed by argument. size_t bufferSize{ 0u }; // Allocated by this. uint8_t* buffer{ nullptr }; // Others. size_t bufferDataLen{ 0u }; std::string localIp; uint16_t localPort{ 0u }; struct sockaddr_storage peerAddr{}; std::string peerIp; uint16_t peerPort{ 0u }; private: // Passed by argument. Listener* listener{ nullptr }; // Allocated by this. uv_tcp_t* uvHandle{ nullptr }; // Others. struct sockaddr_storage* localAddr{ nullptr }; #ifdef MS_LIBURING_SUPPORTED // Local file descriptor for io_uring. uv_os_fd_t fd{ 0u }; #endif bool closed{ false }; size_t recvBytes{ 0u }; size_t sentBytes{ 0u }; bool isClosedByPeer{ false }; bool hasError{ false }; }; #endif ================================================ FILE: worker/include/handles/TcpServerHandle.hpp ================================================ #ifndef MS_TCP_SERVER_HANDLE_HPP #define MS_TCP_SERVER_HANDLE_HPP #include "common.hpp" #include "handles/TcpConnectionHandle.hpp" #include #include #include class TcpServerHandle : public TcpConnectionHandle::Listener { public: /** * uvHandle must be an already initialized and binded uv_tcp_t pointer. */ explicit TcpServerHandle(uv_tcp_t* uvHandle); ~TcpServerHandle() override; public: void Dump(int indentation = 0) const; const struct sockaddr* GetLocalAddress() const { return reinterpret_cast(&this->localAddr); } int GetLocalFamily() const { return reinterpret_cast(&this->localAddr)->sa_family; } const std::string& GetLocalIp() const { return this->localIp; } uint16_t GetLocalPort() const { return this->localPort; } size_t GetNumConnections() const { return this->connections.size(); } uint32_t GetSendBufferSize() const; void SetSendBufferSize(uint32_t size); uint32_t GetRecvBufferSize() const; void SetRecvBufferSize(uint32_t size); protected: void AcceptTcpConnection(TcpConnectionHandle* connection); private: void InternalClose(); bool SetLocalAddress(); /* Pure virtual methods that must be implemented by the subclass. */ protected: virtual void UserOnTcpConnectionAlloc() = 0; virtual void UserOnTcpConnectionClosed(TcpConnectionHandle* connection) = 0; /* Callbacks fired by UV events. */ public: void OnUvConnection(int status); /* Methods inherited from TcpConnectionHandle::Listener. */ public: void OnTcpConnectionClosed(TcpConnectionHandle* connection) override; protected: struct sockaddr_storage localAddr{}; std::string localIp; uint16_t localPort{ 0u }; private: // Allocated by this (may be passed by argument). uv_tcp_t* uvHandle{ nullptr }; // Others. absl::flat_hash_set connections; bool closed{ false }; }; #endif ================================================ FILE: worker/include/handles/TimerHandle.hpp ================================================ #ifndef MS_TIMER_HANDLE_HPP #define MS_TIMER_HANDLE_HPP #include "common.hpp" #include "handles/TimerHandleInterface.hpp" #include // Forward declaration. class Shared; class BackoffTimerHandle; class TimerHandle : public TimerHandleInterface { // Only Shared and BackoffTimerHandle classes can invoke the constructor. friend class Shared; friend class BackoffTimerHandle; private: explicit TimerHandle(TimerHandleInterface::Listener* listener); public: TimerHandle& operator=(const TimerHandle&) = delete; TimerHandle(const TimerHandle&) = delete; ~TimerHandle() override; public: void Start(uint64_t timeout, uint64_t repeat = 0) override; void Stop() override; void Restart() override; void Restart(uint64_t timeout, uint64_t repeat = 0) override; uint64_t GetTimeout() const override { return this->timeout; } uint64_t GetRepeat() const override { return this->repeat; } bool IsActive() const override { return uv_is_active(reinterpret_cast(this->uvHandle)) != 0; } private: void InternalClose(); /* Callbacks fired by UV events. */ public: void OnUvTimer(); private: // Passed by argument. TimerHandleInterface::Listener* listener{ nullptr }; // Allocated by this. uv_timer_t* uvHandle{ nullptr }; // Others. bool closed{ false }; uint64_t timeout{ 0u }; uint64_t repeat{ 0u }; }; #endif ================================================ FILE: worker/include/handles/TimerHandleInterface.hpp ================================================ #ifndef MS_TIMER_HANDLE_INTERFACE_HPP #define MS_TIMER_HANDLE_INTERFACE_HPP #include "common.hpp" class TimerHandleInterface { public: class Listener { public: virtual ~Listener() = default; public: virtual void OnTimer(TimerHandleInterface* timer) = 0; }; public: TimerHandleInterface() = default; TimerHandleInterface& operator=(const TimerHandleInterface&) = delete; TimerHandleInterface(const TimerHandleInterface&) = delete; virtual ~TimerHandleInterface() = default; public: virtual void Start(uint64_t timeout, uint64_t repeat = 0) = 0; virtual void Stop() = 0; virtual void Restart() = 0; virtual void Restart(uint64_t timeout, uint64_t repeat = 0) = 0; virtual uint64_t GetTimeout() const = 0; virtual uint64_t GetRepeat() const = 0; virtual bool IsActive() const = 0; }; #endif ================================================ FILE: worker/include/handles/UdpSocketHandle.hpp ================================================ #ifndef MS_UDP_SOCKET_HANDLE_HPP #define MS_UDP_SOCKET_HANDLE_HPP #include "common.hpp" #include #include class UdpSocketHandle { protected: using onSendCallback = const std::function; public: /* Struct for the data field of uv_req_t when sending a datagram. */ struct UvSendData { explicit UvSendData(size_t storeSize) : store(new uint8_t[storeSize]) { } // Disable copy constructor because of the dynamically allocated data (store). UvSendData(const UvSendData&) = delete; ~UvSendData() { delete[] this->store; delete this->cb; } uv_udp_send_t req{}; uint8_t* store{ nullptr }; UdpSocketHandle::onSendCallback* cb{ nullptr }; }; public: /** * uvHandle must be an already initialized and binded uv_udp_t pointer. */ explicit UdpSocketHandle(uv_udp_t* uvHandle); UdpSocketHandle& operator=(const UdpSocketHandle&) = delete; UdpSocketHandle(const UdpSocketHandle&) = delete; virtual ~UdpSocketHandle(); public: bool IsClosed() const { return this->closed; } void Dump(int indentation = 0) const; void Send( const uint8_t* data, size_t len, const struct sockaddr* addr, UdpSocketHandle::onSendCallback* cb); const struct sockaddr* GetLocalAddress() const { return reinterpret_cast(&this->localAddr); } int GetLocalFamily() const { return reinterpret_cast(&this->localAddr)->sa_family; } const std::string& GetLocalIp() const { return this->localIp; } uint16_t GetLocalPort() const { return this->localPort; } size_t GetRecvBytes() const { return this->recvBytes; } size_t GetSentBytes() const { return this->sentBytes; } uint32_t GetSendBufferSize() const; void SetSendBufferSize(uint32_t size); uint32_t GetRecvBufferSize() const; void SetRecvBufferSize(uint32_t size); private: void InternalClose(); bool SetLocalAddress(); /* Callbacks fired by UV events. */ public: void OnUvRecvAlloc(size_t suggestedSize, uv_buf_t* buf); void OnUvRecv(ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags); void OnUvSend(int status, UdpSocketHandle::onSendCallback* cb); /* Pure virtual methods that must be implemented by the subclass. */ protected: virtual void UserOnUdpDatagramReceived( const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* addr) = 0; protected: struct sockaddr_storage localAddr{}; std::string localIp; uint16_t localPort{ 0u }; private: // Allocated by this (may be passed by argument). uv_udp_t* uvHandle{ nullptr }; // Others. #ifdef MS_LIBURING_SUPPORTED // Local file descriptor for io_uring. uv_os_fd_t fd{ 0u }; #endif bool closed{ false }; size_t recvBytes{ 0u }; size_t sentBytes{ 0u }; }; #endif ================================================ FILE: worker/include/handles/UnixStreamSocketHandle.hpp ================================================ #ifndef MS_UNIX_STREAM_SOCKET_HANDLE_HPP #define MS_UNIX_STREAM_SOCKET_HANDLE_HPP #include "common.hpp" #include class UnixStreamSocketHandle { public: /* Struct for the data field of uv_req_t when writing data. */ struct UvWriteData { explicit UvWriteData(size_t storeSize) : store(new uint8_t[storeSize]) { } // Disable copy constructor because of the dynamically allocated data (store). UvWriteData(const UvWriteData&) = delete; ~UvWriteData() { delete[] this->store; } uv_write_t req{}; uint8_t* store{ nullptr }; }; enum class Role : uint8_t { PRODUCER = 1, CONSUMER }; public: UnixStreamSocketHandle(int fd, size_t bufferSize, UnixStreamSocketHandle::Role role); UnixStreamSocketHandle& operator=(const UnixStreamSocketHandle&) = delete; UnixStreamSocketHandle(const UnixStreamSocketHandle&) = delete; virtual ~UnixStreamSocketHandle(); public: void Close(); bool IsClosed() const { return this->closed; } void Write(const uint8_t* data, size_t len); uint32_t GetSendBufferSize() const; void SetSendBufferSize(uint32_t size); uint32_t GetRecvBufferSize() const; void SetRecvBufferSize(uint32_t size); /* Callbacks fired by UV events. */ public: void OnUvReadAlloc(size_t suggestedSize, uv_buf_t* buf); void OnUvRead(ssize_t nread, const uv_buf_t* buf); void OnUvWriteError(int error); /* Pure virtual methods that must be implemented by the subclass. */ protected: virtual void UserOnUnixStreamRead() = 0; virtual void UserOnUnixStreamSocketClosed() = 0; private: // Allocated by this. uv_pipe_t* uvHandle{ nullptr }; // Others. bool closed{ false }; bool isClosedByPeer{ false }; bool hasError{ false }; protected: // Passed by argument. size_t bufferSize{ 0u }; UnixStreamSocketHandle::Role role; // Allocated by this. uint8_t* buffer{ nullptr }; // Others. size_t bufferDataLen{ 0u }; }; #endif ================================================ FILE: worker/include/lib.hpp ================================================ #include "common.hpp" // NOLINTNEXTLINE(readability-identifier-naming) extern "C" int mediasoup_worker_run( int argc, char* argv[], const char* version, int consumerChannelFd, int producerChannelFd, ChannelReadFn channelReadFn, ChannelReadCtx channelReadCtx, ChannelWriteFn channelWriteFn, ChannelWriteCtx channelWriteCtx); ================================================ FILE: worker/meson.build ================================================ project( 'mediasoup-worker', ['c', 'cpp'], default_options : [ 'cpp_std=c++20', 'warning_level=1', 'default_library=static', ], meson_version: '>= 1.1.0', ) cpp_args = [ host_machine.endian() == 'little' ? '-DMS_LITTLE_ENDIAN' : '-DMS_BIG_ENDIAN', ] if host_machine.system() == 'windows' cpp_args += [ '-DNOMINMAX', # Don't define min and max macros (windows.h) # Don't bloat namespace with incompatible winsock versions. '-DWIN32_LEAN_AND_MEAN', # Don't warn about usage of insecure C functions. '-D_CRT_SECURE_NO_WARNINGS', '-D_SCL_SECURE_NO_WARNINGS', # Introduced in VS 2017 15.8, allow overaligned types in aligned_storage. '-D_ENABLE_EXTENDED_ALIGNED_STORAGE', ] endif if get_option('ms_build_tests') cpp_args += [ '-DMS_TEST', '-DMS_LOG_STD' ] endif if get_option('ms_build_fuzzer') cpp_args += [ '-DMS_FUZZER', '-DMS_LOG_STD' ] endif if get_option('ms_log_trace') cpp_args += [ '-DMS_LOG_TRACE', ] endif if get_option('ms_log_file_line') cpp_args += [ '-DMS_LOG_FILE_LINE', ] endif if get_option('ms_rtc_logger_rtp') cpp_args += [ '-DMS_RTC_LOGGER_RTP', ] endif if get_option('ms_dump_rtp_payload_descriptor') cpp_args += [ '-DMS_DUMP_RTP_PAYLOAD_DESCRIPTOR', ] endif if get_option('ms_dump_rtp_shared_packet_memory_usage') cpp_args += [ '-DMS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE', ] endif cpp = meson.get_compiler('cpp') # This is a workaround to define FLATBUFFERS_LOCALE_INDEPENDENT=0 outside # flatbuffers project, which is needed in case the current arch doesn't support # strtoll_l or strtoull_l (such as musl in Alpine Linux). Problem is that # flatbuffers build system not only defines it for its own usage but also relies # on it in its include/flatbuffers/util.h file, and if such a file is included # by mediasoup source files (and it is included) build fails with "error: # 'strtoull_l' was not declared in this scope". # See issue: https://github.com/versatica/mediasoup/issues/1223 add_project_arguments('-DFLATBUFFERS_LOCALE_INDEPENDENT=@0@'.format(cpp.has_function('strtoull_l')), language: 'cpp') common_sources = [ 'src/lib.cpp', 'src/DepLibSRTP.cpp', 'src/DepLibUV.cpp', 'src/DepLibWebRTC.cpp', 'src/DepOpenSSL.cpp', # TODO: Remove once we only use built-in SCTP stack. 'src/DepUsrSCTP.cpp', 'src/Logger.cpp', 'src/MediaSoupErrors.cpp', 'src/Settings.cpp', 'src/Shared.cpp', 'src/Worker.cpp', 'src/Utils/BitStream.cpp', 'src/Utils/Crypto.cpp', 'src/Utils/File.cpp', 'src/Utils/IP.cpp', 'src/Utils/String.cpp', 'src/handles/BackoffTimerHandle.cpp', 'src/handles/SignalHandle.cpp', 'src/handles/TcpConnectionHandle.cpp', 'src/handles/TcpServerHandle.cpp', 'src/handles/TimerHandle.cpp', 'src/handles/UdpSocketHandle.cpp', 'src/handles/UnixStreamSocketHandle.cpp', 'src/Channel/ChannelNotifier.cpp', 'src/Channel/ChannelRequest.cpp', 'src/Channel/ChannelMessageRegistrator.cpp', 'src/Channel/ChannelNotification.cpp', 'src/Channel/ChannelSocket.cpp', 'src/RTC/ActiveSpeakerObserver.cpp', 'src/RTC/AudioLevelObserver.cpp', 'src/RTC/Consumer.cpp', 'src/RTC/DataConsumer.cpp', 'src/RTC/DataProducer.cpp', 'src/RTC/DirectTransport.cpp', 'src/RTC/DtlsTransport.cpp', 'src/RTC/KeyFrameRequestManager.cpp', 'src/RTC/NackGenerator.cpp', 'src/RTC/PipeConsumer.cpp', 'src/RTC/PipeTransport.cpp', 'src/RTC/PlainTransport.cpp', 'src/RTC/PortManager.cpp', 'src/RTC/Producer.cpp', 'src/RTC/RateCalculator.cpp', 'src/RTC/Router.cpp', 'src/RTC/RtcLogger.cpp', 'src/RTC/RtpListener.cpp', 'src/RTC/RtpObserver.cpp', # TODO: Remove once we only use built-in SCTP stack. 'src/RTC/SctpAssociation.cpp', 'src/RTC/SctpListener.cpp', 'src/RTC/SenderBandwidthEstimator.cpp', 'src/RTC/SeqManager.cpp', 'src/RTC/Serializable.cpp', 'src/RTC/SimpleConsumer.cpp', 'src/RTC/SimulcastConsumer.cpp', 'src/RTC/SrtpSession.cpp', 'src/RTC/SvcConsumer.cpp', 'src/RTC/TcpConnection.cpp', 'src/RTC/TcpServer.cpp', 'src/RTC/Transport.cpp', 'src/RTC/TransportCongestionControlClient.cpp', 'src/RTC/TransportCongestionControlServer.cpp', 'src/RTC/TransportTuple.cpp', 'src/RTC/TrendCalculator.cpp', 'src/RTC/UdpSocket.cpp', 'src/RTC/WebRtcServer.cpp', 'src/RTC/WebRtcTransport.cpp', 'src/RTC/RtpDictionaries/Parameters.cpp', 'src/RTC/RtpDictionaries/RtcpFeedback.cpp', 'src/RTC/RtpDictionaries/RtcpParameters.cpp', 'src/RTC/RtpDictionaries/RtpCodecMimeType.cpp', 'src/RTC/RtpDictionaries/RtpCodecParameters.cpp', 'src/RTC/RtpDictionaries/RtpEncodingParameters.cpp', 'src/RTC/RtpDictionaries/RtpHeaderExtensionParameters.cpp', 'src/RTC/RtpDictionaries/RtpHeaderExtensionUri.cpp', 'src/RTC/RtpDictionaries/RtpParameters.cpp', 'src/RTC/RtpDictionaries/RtpRtxParameters.cpp', 'src/RTC/SctpDictionaries/SctpStreamParameters.cpp', 'src/RTC/ICE/IceCandidate.cpp', 'src/RTC/ICE/IceServer.cpp', 'src/RTC/ICE/StunPacket.cpp', 'src/RTC/RTP/Packet.cpp', 'src/RTC/RTP/ProbationGenerator.cpp', 'src/RTC/RTP/RetransmissionBuffer.cpp', 'src/RTC/RTP/RtpStream.cpp', 'src/RTC/RTP/RtpStreamRecv.cpp', 'src/RTC/RTP/RtpStreamSend.cpp', 'src/RTC/RTP/RtxStream.cpp', 'src/RTC/RTP/SharedPacket.cpp', 'src/RTC/RTP/Codecs/AV1.cpp', 'src/RTC/RTP/Codecs/H264.cpp', 'src/RTC/RTP/Codecs/Opus.cpp', 'src/RTC/RTP/Codecs/VP8.cpp', 'src/RTC/RTP/Codecs/VP9.cpp', 'src/RTC/RTP/Codecs/DependencyDescriptor.cpp', 'src/RTC/RTCP/Packet.cpp', 'src/RTC/RTCP/CompoundPacket.cpp', 'src/RTC/RTCP/SenderReport.cpp', 'src/RTC/RTCP/ReceiverReport.cpp', 'src/RTC/RTCP/Sdes.cpp', 'src/RTC/RTCP/Bye.cpp', 'src/RTC/RTCP/Feedback.cpp', 'src/RTC/RTCP/FeedbackPs.cpp', 'src/RTC/RTCP/FeedbackRtp.cpp', 'src/RTC/RTCP/FeedbackRtpNack.cpp', 'src/RTC/RTCP/FeedbackRtpTmmb.cpp', 'src/RTC/RTCP/FeedbackRtpSrReq.cpp', 'src/RTC/RTCP/FeedbackRtpTllei.cpp', 'src/RTC/RTCP/FeedbackRtpEcn.cpp', 'src/RTC/RTCP/FeedbackRtpTransport.cpp', 'src/RTC/RTCP/FeedbackPsPli.cpp', 'src/RTC/RTCP/FeedbackPsSli.cpp', 'src/RTC/RTCP/FeedbackPsRpsi.cpp', 'src/RTC/RTCP/FeedbackPsFir.cpp', 'src/RTC/RTCP/FeedbackPsTst.cpp', 'src/RTC/RTCP/FeedbackPsVbcm.cpp', 'src/RTC/RTCP/FeedbackPsLei.cpp', 'src/RTC/RTCP/FeedbackPsAfb.cpp', 'src/RTC/RTCP/FeedbackPsRemb.cpp', 'src/RTC/RTCP/XR.cpp', 'src/RTC/RTCP/XrDelaySinceLastRr.cpp', 'src/RTC/RTCP/XrReceiverReferenceTime.cpp', 'src/RTC/SCTP/association/Association.cpp', 'src/RTC/SCTP/association/AssociationListenerDeferrer.cpp', 'src/RTC/SCTP/association/HeartbeatHandler.cpp', 'src/RTC/SCTP/association/NegotiatedCapabilities.cpp', 'src/RTC/SCTP/association/PacketSender.cpp', 'src/RTC/SCTP/association/StateCookie.cpp', 'src/RTC/SCTP/association/StreamResetHandler.cpp', 'src/RTC/SCTP/association/TransmissionControlBlock.cpp', 'src/RTC/SCTP/rx/DataTracker.cpp', 'src/RTC/SCTP/rx/InterleavedReassemblyStreams.cpp', 'src/RTC/SCTP/rx/ReassemblyQueue.cpp', 'src/RTC/SCTP/rx/TraditionalReassemblyStreams.cpp', 'src/RTC/SCTP/tx/OutstandingData.cpp', 'src/RTC/SCTP/tx/RetransmissionErrorCounter.cpp', 'src/RTC/SCTP/tx/RetransmissionQueue.cpp', 'src/RTC/SCTP/tx/RetransmissionTimeout.cpp', 'src/RTC/SCTP/tx/RoundRobinSendQueue.cpp', 'src/RTC/SCTP/tx/StreamScheduler.cpp', 'src/RTC/SCTP/packet/Packet.cpp', 'src/RTC/SCTP/packet/TLV.cpp', 'src/RTC/SCTP/packet/UserData.cpp', 'src/RTC/SCTP/packet/Chunk.cpp', 'src/RTC/SCTP/packet/chunks/DataChunk.cpp', 'src/RTC/SCTP/packet/chunks/InitChunk.cpp', 'src/RTC/SCTP/packet/chunks/InitAckChunk.cpp', 'src/RTC/SCTP/packet/chunks/SackChunk.cpp', 'src/RTC/SCTP/packet/chunks/HeartbeatRequestChunk.cpp', 'src/RTC/SCTP/packet/chunks/HeartbeatAckChunk.cpp', 'src/RTC/SCTP/packet/chunks/AbortAssociationChunk.cpp', 'src/RTC/SCTP/packet/chunks/ShutdownChunk.cpp', 'src/RTC/SCTP/packet/chunks/ShutdownAckChunk.cpp', 'src/RTC/SCTP/packet/chunks/OperationErrorChunk.cpp', 'src/RTC/SCTP/packet/chunks/CookieEchoChunk.cpp', 'src/RTC/SCTP/packet/chunks/CookieAckChunk.cpp', 'src/RTC/SCTP/packet/chunks/ShutdownCompleteChunk.cpp', 'src/RTC/SCTP/packet/chunks/ForwardTsnChunk.cpp', 'src/RTC/SCTP/packet/chunks/ReConfigChunk.cpp', 'src/RTC/SCTP/packet/chunks/IDataChunk.cpp', 'src/RTC/SCTP/packet/chunks/IForwardTsnChunk.cpp', 'src/RTC/SCTP/packet/chunks/UnknownChunk.cpp', 'src/RTC/SCTP/packet/Parameter.cpp', 'src/RTC/SCTP/packet/parameters/HeartbeatInfoParameter.cpp', 'src/RTC/SCTP/packet/parameters/IPv4AddressParameter.cpp', 'src/RTC/SCTP/packet/parameters/IPv6AddressParameter.cpp', 'src/RTC/SCTP/packet/parameters/StateCookieParameter.cpp', 'src/RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.cpp', 'src/RTC/SCTP/packet/parameters/CookiePreservativeParameter.cpp', 'src/RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.cpp', 'src/RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.cpp', 'src/RTC/SCTP/packet/parameters/SupportedExtensionsParameter.cpp', 'src/RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.cpp', 'src/RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.cpp', 'src/RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.cpp', 'src/RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.cpp', 'src/RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.cpp', 'src/RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.cpp', 'src/RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.cpp', 'src/RTC/SCTP/packet/parameters/UnknownParameter.cpp', 'src/RTC/SCTP/packet/ErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.cpp', 'src/RTC/SCTP/packet/errorCauses/UnknownErrorCause.cpp', 'src/RTC/SCTP/public/AssociationMetrics.cpp', 'src/RTC/SCTP/public/Message.cpp', ] libuv_proj = subproject( 'libuv', default_options: [ 'warning_level=0', 'build_tests=false', 'build_benchmarks=false', ], ) openssl_proj = subproject( 'openssl', default_options: [ 'warning_level=0', ], ) libsrtp3_proj = subproject( 'libsrtp3', default_options: [ 'warning_level=0', 'crypto-library=openssl', 'crypto-library-kdf=disabled', 'tests=disabled', ], ) usrsctp_proj = subproject( 'usrsctp', default_options: [ 'warning_level=0', 'sctp_build_programs=false', ], ) abseil_cpp_proj = subproject( 'abseil-cpp', default_options: [ 'warning_level=0', 'cpp_std=c++17', ], ) flatbuffers_proj = subproject( 'flatbuffers', default_options: [ 'warning_level=0', ], ) # flatbuffers schemas subdirectory. subdir('fbs') # Add current build directory so libwebrtc has access to FBS folder. libwebrtc_include_directories = include_directories('include', 'fbs') subdir('deps/libwebrtc') dependencies = [ abseil_cpp_proj.get_variable('absl_container_dep'), openssl_proj.get_variable('openssl_dep'), libuv_proj.get_variable('libuv_dep'), libsrtp3_proj.get_variable('libsrtp3_dep'), usrsctp_proj.get_variable('usrsctp_dep'), flatbuffers_proj.get_variable('flatbuffers_dep'), flatbuffers_generator_dep, libwebrtc_dep, ] link_whole = [ abseil_cpp_proj.get_variable('absl_container_lib'), openssl_proj.get_variable('libcrypto_lib'), openssl_proj.get_variable('libssl_lib'), libuv_proj.get_variable('libuv'), libsrtp3_proj.get_variable('libsrtp3'), usrsctp_proj.get_variable('usrsctp'), flatbuffers_proj.get_variable('flatbuffers_lib'), libwebrtc, ] if host_machine.system() == 'windows' wingetopt_proj = subproject( 'wingetopt', default_options: [ 'warning_level=0', ], ) dependencies += [ wingetopt_proj.get_variable('wingetopt_dep'), ] link_whole += [ wingetopt_proj.get_variable('wingetopt_lib'), ] endif if host_machine.system() == 'linux' and not get_option('ms_disable_liburing') kernel_version = run_command('uname', '-r', check: true).stdout().strip() # Enable liburing for kernel versions greather than or equal to 6. if kernel_version[0].to_int() >= 6 liburing_proj = subproject( 'liburing', default_options: [ 'default_library=static', # NOTE: We need to add this to disable ASAN in liburing, otherwise when # building `mediasoup-test-asan-undefined` binary, liburing receives # `use_ubsan: true` and tries to compile its `sanitize.c` file # that contains references to `__asan_*` functions, but we don't enable # ASAN (`-fsanitize=address) in that binaries so liburing fails to # compile. 'b_sanitize=none', ], ) dependencies += [ liburing_proj.get_variable('uring'), ] link_whole += [ liburing_proj.get_variable('liburing') ] common_sources += [ 'src/DepLibUring.cpp', ] cpp_args += [ '-DMS_LIBURING_SUPPORTED', ] endif endif if get_option('ms_build_tests') catch2_proj = subproject( 'catch2', default_options: [ 'warning_level=0', ], ) dependencies += [ catch2_proj.get_variable('catch2_dep'), ] endif libmediasoup_worker = library( 'libmediasoup-worker', name_prefix: '', build_by_default: false, install: true, install_tag: 'libmediasoup-worker', dependencies: dependencies, sources: common_sources, include_directories: include_directories('include'), cpp_args: cpp_args, link_whole: link_whole, ) executable( 'mediasoup-worker', build_by_default: true, install: true, install_tag: 'mediasoup-worker', dependencies: dependencies, sources: common_sources + ['src/main.cpp'], include_directories: include_directories('include'), cpp_args: cpp_args + ['-DMS_EXECUTABLE'], ) mock_sources = [ 'mocks/src/MockShared.cpp', 'mocks/src/handles/MockBackoffTimerHandle.cpp', 'mocks/src/Channel/MockChannelMessageRegistrator.cpp', 'mocks/src/RTC/SCTP/association/MockTransmissionControlBlockContext.cpp', ] test_sources = [ 'test/src/tests.cpp', 'test/src/testHelpers.cpp', 'test/src/RTC/TestKeyFrameRequestManager.cpp', 'test/src/RTC/TestNackGenerator.cpp', 'test/src/RTC/TestRateCalculator.cpp', 'test/src/RTC/TestRtpEncodingParameters.cpp', 'test/src/RTC/TestSeqManager.cpp', 'test/src/RTC/TestSimpleConsumer.cpp', 'test/src/RTC/TestTransportCongestionControlServer.cpp', 'test/src/RTC/TestTransportTuple.cpp', 'test/src/RTC/TestTrendCalculator.cpp', 'test/src/RTC/ICE/iceCommon.cpp', 'test/src/RTC/ICE/TestStunPacket.cpp', 'test/src/RTC/RTP/rtpCommon.cpp', 'test/src/RTC/RTP/TestPacket.cpp', 'test/src/RTC/RTP/TestProbationGenerator.cpp', 'test/src/RTC/RTP/TestRetransmissionBuffer.cpp', 'test/src/RTC/RTP/TestRtpStreamSend.cpp', 'test/src/RTC/RTP/TestRtpStreamRecv.cpp', 'test/src/RTC/RTP/TestSharedPacket.cpp', 'test/src/RTC/RTP/Codecs/TestH264.cpp', 'test/src/RTC/RTP/Codecs/TestVP8.cpp', 'test/src/RTC/RTP/Codecs/TestVP9.cpp', 'test/src/RTC/RTP/Codecs/TestDependencyDescriptor.cpp', 'test/src/RTC/RTCP/TestFeedbackPsAfb.cpp', 'test/src/RTC/RTCP/TestFeedbackPsFir.cpp', 'test/src/RTC/RTCP/TestFeedbackPsLei.cpp', 'test/src/RTC/RTCP/TestFeedbackPsPli.cpp', 'test/src/RTC/RTCP/TestFeedbackPsRemb.cpp', 'test/src/RTC/RTCP/TestFeedbackPsRpsi.cpp', 'test/src/RTC/RTCP/TestFeedbackPsSli.cpp', 'test/src/RTC/RTCP/TestFeedbackPsTst.cpp', 'test/src/RTC/RTCP/TestFeedbackPsVbcm.cpp', 'test/src/RTC/RTCP/TestFeedbackRtpEcn.cpp', 'test/src/RTC/RTCP/TestFeedbackRtpNack.cpp', 'test/src/RTC/RTCP/TestFeedbackRtpSrReq.cpp', 'test/src/RTC/RTCP/TestFeedbackRtpTllei.cpp', 'test/src/RTC/RTCP/TestFeedbackRtpTmmb.cpp', 'test/src/RTC/RTCP/TestFeedbackRtpTransport.cpp', 'test/src/RTC/RTCP/TestBye.cpp', 'test/src/RTC/RTCP/TestReceiverReport.cpp', 'test/src/RTC/RTCP/TestSdes.cpp', 'test/src/RTC/RTCP/TestSenderReport.cpp', 'test/src/RTC/RTCP/TestPacket.cpp', 'test/src/RTC/RTCP/TestXr.cpp', 'test/src/RTC/SCTP/sctpCommon.cpp', 'test/src/RTC/SCTP/association/TestHeartbeatHandler.cpp', 'test/src/RTC/SCTP/association/TestNegotiatedCapabilities.cpp', 'test/src/RTC/SCTP/association/TestStateCookie.cpp', 'test/src/RTC/SCTP/rx/TestDataTracker.cpp', 'test/src/RTC/SCTP/rx/TestInterleavedReassemblyStreams.cpp', 'test/src/RTC/SCTP/rx/TestReassemblyQueue.cpp', 'test/src/RTC/SCTP/rx/TestTraditionalReassemblyStreams.cpp', 'test/src/RTC/SCTP/tx/TestOutstandingData.cpp', 'test/src/RTC/SCTP/tx/TestRetransmissionErrorCounter.cpp', 'test/src/RTC/SCTP/tx/TestRetransmissionQueue.cpp', 'test/src/RTC/SCTP/tx/TestRetransmissionTimeout.cpp', 'test/src/RTC/SCTP/tx/TestRoundRobinSendQueue.cpp', 'test/src/RTC/SCTP/tx/TestOutstandingData.cpp', 'test/src/RTC/SCTP/tx/TestStreamScheduler.cpp', 'test/src/RTC/SCTP/packet/TestPacket.cpp', 'test/src/RTC/SCTP/packet/TestChunk.cpp', 'test/src/RTC/SCTP/packet/TestParameter.cpp', 'test/src/RTC/SCTP/packet/TestErrorCause.cpp', 'test/src/RTC/SCTP/packet/chunks/TestDataChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestInitChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestInitAckChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestSackChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestHeartbeatRequestChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestHeartbeatAckChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestAbortAssociationChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestShutdownChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestShutdownAckChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestOperationErrorChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestCookieEchoChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestCookieAckChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestShutdownCompleteChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestForwardTsnChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestReConfigChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestIDataChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestIForwardTsnChunk.cpp', 'test/src/RTC/SCTP/packet/chunks/TestUnknownChunk.cpp', 'test/src/RTC/SCTP/packet/parameters/TestHeartbeatInfoParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestIPv4AddressParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestIPv6AddressParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestStateCookieParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestUnrecognizedParameterParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestCookiePreservativeParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestSupportedAddressTypesParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestForwardTsnSupportedParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestSupportedExtensionsParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestOutgoingSsnResetRequestParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestIncomingSsnResetRequestParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestSsnTsnResetRequestParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestReconfigurationResponseParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestAddOutgoingStreamsRequestParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestAddIncomingStreamsRequestParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestZeroChecksumAcceptableParameter.cpp', 'test/src/RTC/SCTP/packet/parameters/TestUnknownParameter.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestInvalidStreamIdentifierErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestMissingMandatoryParameterErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestStaleCookieErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestOutOfResourceErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestUnresolvableAddressErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestUnrecognizedChunkTypeErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestInvalidMandatoryParameterErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestUnrecognizedParametersErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestNoUserDataErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestCookieReceivedWhileShuttingDownErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestRestartOfAnAssociationWithNewAddressesErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestUserInitiatedAbortErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestProtocolViolationErrorCause.cpp', 'test/src/RTC/SCTP/packet/errorCauses/TestUnknownErrorCause.cpp', 'test/src/Utils/TestBits.cpp', 'test/src/Utils/TestByte.cpp', 'test/src/Utils/TestCrypto.cpp', 'test/src/Utils/TestIP.cpp', 'test/src/Utils/TestNumber.cpp', 'test/src/Utils/TestString.cpp', 'test/src/Utils/TestTime.cpp', 'test/src/Utils/TestUnwrappedSequenceNumber.cpp', ] mediasoup_worker_test = executable( 'mediasoup-worker-test', build_by_default: false, install: true, install_tag: 'mediasoup-worker-test', dependencies: dependencies, sources: common_sources + test_sources + mock_sources, include_directories: include_directories( 'include', 'test/include', 'mocks/include', ), cpp_args: cpp_args, ) test( 'mediasoup-worker-test', mediasoup_worker_test, workdir: meson.project_source_root(), ) mediasoup_worker_test_asan_address = executable( 'mediasoup-worker-test-asan-address', build_by_default: false, install: true, install_tag: 'mediasoup-worker-test-asan-address', dependencies: dependencies, sources: common_sources + test_sources + mock_sources, include_directories: include_directories( 'include', 'test/include', 'mocks/include', ), cpp_args: cpp_args + [ '-g', '-O3', '-fno-omit-frame-pointer', '-fsanitize=address', ], link_args: [ '-fsanitize=address', ], ) test( 'mediasoup-worker-test-asan-address', mediasoup_worker_test_asan_address, workdir: meson.project_source_root(), ) mediasoup_worker_test_asan_undefined = executable( 'mediasoup-worker-test-asan-undefined', build_by_default: false, install: true, install_tag: 'mediasoup-worker-test-asan-undefined', dependencies: dependencies, sources: common_sources + test_sources + mock_sources, include_directories: include_directories( 'include', 'test/include', 'mocks/include', ), cpp_args: cpp_args + [ '-g', '-O3', '-fno-omit-frame-pointer', '-fsanitize=undefined', ], link_args: [ '-fsanitize=undefined', ], ) test( 'mediasoup-worker-test-asan-undefined', mediasoup_worker_test_asan_undefined, workdir: meson.project_source_root(), ) fuzzer_sources = [ 'fuzzer/src/fuzzer.cpp', 'fuzzer/src/FuzzerUtils.cpp', 'fuzzer/src/RTC/FuzzerDtlsTransport.cpp', 'fuzzer/src/RTC/FuzzerRateCalculator.cpp', 'fuzzer/src/RTC/FuzzerSeqManager.cpp', 'fuzzer/src/RTC/FuzzerTrendCalculator.cpp', 'fuzzer/src/RTC/ICE/FuzzerStunPacket.cpp', 'fuzzer/src/RTC/RTP/FuzzerPacket.cpp', 'fuzzer/src/RTC/RTP/FuzzerProbationGenerator.cpp', 'fuzzer/src/RTC/RTP/FuzzerRetransmissionBuffer.cpp', 'fuzzer/src/RTC/RTP/FuzzerRtpStreamSend.cpp', 'fuzzer/src/RTC/RTP/Codecs/FuzzerOpus.cpp', 'fuzzer/src/RTC/RTP/Codecs/FuzzerAV1.cpp', 'fuzzer/src/RTC/RTP/Codecs/FuzzerH264.cpp', 'fuzzer/src/RTC/RTP/Codecs/FuzzerVP8.cpp', 'fuzzer/src/RTC/RTP/Codecs/FuzzerVP9.cpp', 'fuzzer/src/RTC/RTP/Codecs/FuzzerDependencyDescriptor.cpp', 'fuzzer/src/RTC/RTCP/FuzzerBye.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPs.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsAfb.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsFir.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsLei.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsPli.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsRemb.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsRpsi.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsSli.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsTst.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackPsVbcm.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtp.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpEcn.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpNack.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpSrReq.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTllei.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTmmb.cpp', 'fuzzer/src/RTC/RTCP/FuzzerFeedbackRtpTransport.cpp', 'fuzzer/src/RTC/RTCP/FuzzerPacket.cpp', 'fuzzer/src/RTC/RTCP/FuzzerReceiverReport.cpp', 'fuzzer/src/RTC/RTCP/FuzzerSdes.cpp', 'fuzzer/src/RTC/RTCP/FuzzerSenderReport.cpp', 'fuzzer/src/RTC/RTCP/FuzzerXr.cpp', 'fuzzer/src/RTC/SCTP/association/FuzzerStateCookie.cpp', 'fuzzer/src/RTC/SCTP/packet/FuzzerPacket.cpp', ] executable( 'mediasoup-worker-fuzzer', build_by_default: false, install: true, install_tag: 'mediasoup-worker-fuzzer', dependencies: dependencies, sources: common_sources + fuzzer_sources + mock_sources, include_directories: include_directories( 'include', 'fuzzer/include', 'mocks/include', ), cpp_args: cpp_args + [ '-g', '-O3', '-fno-omit-frame-pointer', '-fsanitize=address,undefined,fuzzer', ], link_args: [ '-fsanitize=address,undefined,fuzzer', ], ) ================================================ FILE: worker/meson_options.txt ================================================ option('ms_log_trace', type: 'boolean', value: false, description: 'Logs the current method/function if current log level is "debug"') option('ms_log_file_line', type: 'boolean', value: false, description: 'Logging macros print more verbose information, including current file and line') option('ms_rtc_logger_rtp', type: 'boolean', value: false, description: 'Prints a line with information for each processed RTP packet') option('ms_dump_rtp_payload_descriptor', type: 'boolean', value: false, description: 'RTC::RTP::Packet::Dump() method also dumps payload descriptor details') option('ms_dump_rtp_shared_packet_memory_usage', type: 'boolean', value: false, description: 'prints information about total memory allocated by RTC::RTP::SharedPacket instances') option('ms_disable_liburing', type: 'boolean', value: false, description: 'When set to true, disables liburing integration despite current host supports it') option('ms_build_tests', type: 'boolean', value: false, description: 'Must be enabled when building mediasoup worker tests') option('ms_build_fuzzer', type: 'boolean', value: false, description: 'Must be enabled when building mediasoup worker fuzzer') ================================================ FILE: worker/mocks/include/Channel/MockChannelMessageRegistrator.hpp ================================================ #ifndef MS_MOCKS_MOCK_CHANNEL_MESSAGE_REGISTRATOR_HPP #define MS_MOCKS_MOCK_CHANNEL_MESSAGE_REGISTRATOR_HPP #include "Channel/ChannelMessageRegistratorInterface.hpp" // TODO: We should have a ChannelSocketInterface class instead. #include "Channel/ChannelSocket.hpp" #include #include namespace mocks { namespace Channel { class MockChannelMessageRegistrator : public ::Channel::ChannelMessageRegistratorInterface { public: explicit MockChannelMessageRegistrator(); ~MockChannelMessageRegistrator() override; public: flatbuffers::Offset FillBuffer( flatbuffers::FlatBufferBuilder& builder) override; void RegisterHandler( const std::string& id, ::Channel::ChannelSocket::RequestHandler* channelRequestHandler, ::Channel::ChannelSocket::NotificationHandler* channelNotificationHandler) override; void UnregisterHandler(const std::string& id) override; ::Channel::ChannelSocket::RequestHandler* GetChannelRequestHandler(const std::string& id) override; ::Channel::ChannelSocket::NotificationHandler* GetChannelNotificationHandler( const std::string& id) override; private: std::unordered_map mapChannelRequestHandlers; std::unordered_map mapChannelNotificationHandlers; }; } // namespace Channel } // namespace mocks #endif ================================================ FILE: worker/mocks/include/MockShared.hpp ================================================ #ifndef MS_MOCKS_MOCK_SHARED_HPP #define MS_MOCKS_MOCK_SHARED_HPP #include "SharedInterface.hpp" #include "mocks/include/Channel/MockChannelMessageRegistrator.hpp" #include "mocks/include/handles/MockBackoffTimerHandle.hpp" #include "Channel/ChannelNotifier.hpp" #include "Channel/ChannelSocket.hpp" #include #include #include namespace mocks { class MockShared : public SharedInterface { public: explicit MockShared(std::function getTimeMs); ~MockShared() override = default; public: ::Channel::ChannelMessageRegistratorInterface* GetChannelMessageRegistrator() override { return this->channelMessageRegistrator.get(); } ::Channel::ChannelNotifier* GetChannelNotifier() override { return this->channelNotifier.get(); } TimerHandleInterface* CreateTimer(TimerHandleInterface::Listener* listener) override; BackoffTimerHandleInterface* CreateBackoffTimer( const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options) override; uint64_t GetTimeMs() override { return this->getTimeMs(); } uint64_t GetTimeUs() override { return GetTimeMs() * 1000; } uint64_t GetTimeNs() override { return GetTimeMs() * 1000 * 1000; } int64_t GetTimeMsInt64() override { return static_cast(GetTimeMs()); } int64_t GetTimeUsInt64() override { return static_cast(GetTimeUs()); } // Methods for testing. public: MockBackoffTimerHandle* GetBackoffTimer(const std::string_view label) const { const auto it = this->backoffTimers.find(std::string(label)); if (it != this->backoffTimers.end()) { return it->second; } else { return nullptr; } } private: // Given by argument. const std::function getTimeMs; // Others. std::unique_ptr<::Channel::ChannelSocket> channelSocket; std::unique_ptr channelMessageRegistrator; std::unique_ptr<::Channel::ChannelNotifier> channelNotifier; std::map backoffTimers; }; } // namespace mocks #endif ================================================ FILE: worker/mocks/include/RTC/SCTP/association/MockAssociationListener.hpp ================================================ #ifndef MS_MOCKS_RTC_SCTP_MOCK_ASSOCIATION_LISTENER_HPP #define MS_MOCKS_RTC_SCTP_MOCK_ASSOCIATION_LISTENER_HPP #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include #include #include #include #include namespace mocks { namespace RTC { namespace SCTP { class MockAssociationListener : public ::RTC::SCTP::AssociationListenerInterface { public: bool OnAssociationSendData(const uint8_t* data, size_t len) override { this->sentPackets.emplace_back(data, data + len); return true; } void OnAssociationConnecting() override { this->connecting = true; } void OnAssociationConnected() override { this->connecting = false; this->connected = true; } void OnAssociationFailed( ::RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override { this->connecting = false; this->connected = false; this->failed = true; this->failedErrorKind = errorKind; this->failedErrorMessage = errorMessage; } void OnAssociationClosed( ::RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override { this->connecting = false; this->connected = false; this->closed = true; this->closedErrorKind = errorKind; this->closedErrorMessage = errorMessage; } void OnAssociationRestarted() override { this->restarted = true; } void OnAssociationError( ::RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) override { this->connecting = false; this->connected = false; this->errored = true; this->erroredErrorKind = errorKind; this->erroredErrorMessage = errorMessage; } void OnAssociationMessageReceived(::RTC::SCTP::Message message) override { this->receivedMessages.emplace_back(std::move(message)); } void OnAssociationStreamsResetPerformed(std::span /*outboundStreamIds*/) override { // TODO: Do something here for tests. } void OnAssociationStreamsResetFailed( std::span /*outboundStreamIds*/, std::string_view /*errorMessage*/) override { // TODO: Do something here for tests. } void OnAssociationInboundStreamsReset(std::span /*inboundStreamIds*/) override { // TODO: Do something here for tests. } void OnAssociationStreamBufferedAmountLow(uint16_t streamId) override { ++this->onStreamBufferedAmountLowCalls[streamId]; } void OnAssociationTotalBufferedAmountLow() override { ++this->onTotalBufferedAmountLowCalls; } bool OnAssociationIsTransportReadyForSctp() override { return this->transportReady; } void OnAssociationLifecycleMessageFullySent(uint64_t lifecycleId) override { this->onAssociationLifecycleMessageFullySentCalls.insert(lifecycleId); } void OnAssociationLifecycleMessageExpired(uint64_t lifecycleId, bool maybeDelivered) override { this->onAssociationLifecycleMessageExpiredCalls[lifecycleId] = maybeDelivered; } void OnAssociationLifecycleMessageDelivered(uint64_t lifecycleId) override { this->onAssociationLifecycleMessageDeliveredCalls.insert(lifecycleId); } void OnAssociationLifecycleMessageEnd(uint64_t lifecycleId) override { this->onAssociationLifecycleMessageEndCalls.insert(lifecycleId); } // Methods for testing. public: bool IsConnecting() const { return this->connecting; } bool IsConnected() const { return this->connected; } bool HasRestarted() const { return this->restarted; } bool HasFailed() const { return this->failed; } ::RTC::SCTP::Types::ErrorKind GetFailedErrorKind() const { return this->failedErrorKind; } const std::string& GetFailedErrorMessage() const { return this->failedErrorMessage; } bool IsClosed() const { return this->closed; } ::RTC::SCTP::Types::ErrorKind GetClosedErrorKind() const { return this->closedErrorKind; } const std::string& GetClosedErrorMessage() const { return this->closedErrorMessage; } bool HasErrored() const { return this->errored; } ::RTC::SCTP::Types::ErrorKind GetErroredErrorKind() const { return this->erroredErrorKind; } const std::string& GetErroredErrorMessage() const { return this->erroredErrorMessage; } bool HasOnStreamBufferedAmountLowBeenCalledWithStreamId(uint64_t streamId) const { return this->onStreamBufferedAmountLowCalls.contains(streamId); } size_t CountOnStreamBufferedAmountLowCallsWithStreamId(uint64_t streamId) const { if (!this->onStreamBufferedAmountLowCalls.contains(streamId)) { return 0; } return this->onStreamBufferedAmountLowCalls.at(streamId); } size_t CountOnTotalBufferedAmountLowCalls() const { return this->onTotalBufferedAmountLowCalls; } bool HasSentPackets() const { return !this->sentPackets.empty(); } bool HasReceivedMessages() const { return !this->receivedMessages.empty(); } std::vector ConsumeFirstSentPacket() { if (this->sentPackets.empty()) { return {}; } const auto packet = std::move(this->sentPackets.front()); this->sentPackets.pop_front(); return packet; } std::optional<::RTC::SCTP::Message> ConsumeFirstReceivedMessage() { if (this->receivedMessages.empty()) { return std::nullopt; } auto message = std::move(this->receivedMessages.front()); this->receivedMessages.pop_front(); return message; } bool IsTransportReady() const { return this->transportReady; } bool HasOnAssociationLifecycleMessageFullySentBeenCalledWithLifecycleId(uint64_t lifecycleId) const { return this->onAssociationLifecycleMessageFullySentCalls.contains(lifecycleId); } bool HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId( uint64_t lifecycleId, bool maybeDelivered) const { if (!this->onAssociationLifecycleMessageExpiredCalls.contains(lifecycleId)) { return false; } return this->onAssociationLifecycleMessageExpiredCalls.at(lifecycleId) == maybeDelivered; } bool HasOnAssociationLifecycleMessageDeliveredBeenCalledWithLifecycleId(uint64_t lifecycleId) const { return this->onAssociationLifecycleMessageDeliveredCalls.contains(lifecycleId); } bool HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(uint64_t lifecycleId) const { return this->onAssociationLifecycleMessageEndCalls.contains(lifecycleId); } private: // Observable state for tests. bool connecting{ false }; bool connected{ false }; bool restarted{ false }; bool failed{ false }; ::RTC::SCTP::Types::ErrorKind failedErrorKind; std::string failedErrorMessage; bool closed{ false }; ::RTC::SCTP::Types::ErrorKind closedErrorKind; std::string closedErrorMessage; bool errored{ false }; ::RTC::SCTP::Types::ErrorKind erroredErrorKind; std::string erroredErrorMessage; std::map onStreamBufferedAmountLowCalls; size_t onTotalBufferedAmountLowCalls{ 0 }; std::deque> sentPackets; std::deque<::RTC::SCTP::Message> receivedMessages; bool transportReady{ true }; std::set onAssociationLifecycleMessageFullySentCalls; std::map onAssociationLifecycleMessageExpiredCalls; std::set onAssociationLifecycleMessageDeliveredCalls; std::set onAssociationLifecycleMessageEndCalls; }; } // namespace SCTP } // namespace RTC } // namespace mocks #endif ================================================ FILE: worker/mocks/include/RTC/SCTP/association/MockTransmissionControlBlockContext.hpp ================================================ #ifndef MS_MOCKS_RTC_SCTP_MOCK_TRANSMISSION_CONTROL_BLOCK_HPP #define MS_MOCKS_RTC_SCTP_MOCK_TRANSMISSION_CONTROL_BLOCK_HPP #include "common.hpp" #include "mocks/include/mockTypes.hpp" #include "RTC/SCTP/association/TransmissionControlBlockContextInterface.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/public/AssociationListenerInterface.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include #include #include namespace mocks { namespace RTC { namespace SCTP { class MockTransmissionControlBlockContext : public ::RTC::SCTP::TransmissionControlBlockContextInterface { public: using GetCurrentRtoMsAction = std::function; public: explicit MockTransmissionControlBlockContext( ::RTC::SCTP::AssociationListenerInterface& associationListener, const ::RTC::SCTP::SctpOptions& sctpOptions) : associationListener(associationListener), sctpOptions(sctpOptions) { } public: bool IsAssociationEstablished() const override { return this->associationEstablished; } uint32_t GetLocalInitialTsn() const override { // TODO: Implement this. return 0; } uint32_t GetRemoteInitialTsn() const override { // TODO: Implement this. return 0; } void ObserveRttMs(uint64_t rttMs) override { this->observeRttMsCallCount++; } uint64_t GetCurrentRtoMs() const override { if (!this->getCurrentRtoMsOnceActions.empty()) { auto action = std::move(this->getCurrentRtoMsOnceActions.front()); this->getCurrentRtoMsOnceActions.pop(); return action(); } else { return 0; } } bool IncrementTxErrorCounter(std::string_view /*reason*/) override { this->incrementTxErrorCounterCallCount++; return false; } void ClearTxErrorCounter() override { this->incrementTxErrorCounterCallCount = 0; } bool HasTooManyTxErrors() const override { // TODO: Implement this. return false; } std::unique_ptr<::RTC::SCTP::Packet> CreatePacket() const override; bool SendPacket(::RTC::SCTP::Packet* packet) override; // Methods for testing. public: void SetAssociationEstablished(bool associationEstablished) { this->associationEstablished = associationEstablished; } /** * @remarks * - Must be called before expecting calls to `ObserveRttMs()`. */ MockTransmissionControlBlockContext& ExpectObserveRttMsCalledTimes(size_t times) { this->observeRttMsCallCount = 0; this->expectedObserveRttMsCallCount = times; return *this; } /** * @remarks * - Must be called before expecting calls to `IncrementTxErrorCounter()`. */ MockTransmissionControlBlockContext& ExpectIncrementTxErrorCounterCalledTimes(size_t times) { this->incrementTxErrorCounterCallCount = 0; this->expectedIncrementTxErrorCounterCallCount = times; return *this; } MockTransmissionControlBlockContext& WillGetCurrentRtoMsOnce(GetCurrentRtoMsAction action) { this->getCurrentRtoMsOnceActions.push(std::move(action)); return *this; } mocks::VerificationResult VerifyExpectations() const { if ( this->expectedObserveRttMsCallCount.has_value() && this->observeRttMsCallCount != this->expectedObserveRttMsCallCount.value()) { return { .ok = false, .errorMessage = "ObserveRttMs() call count mismatch [expected:" + std::to_string(this->expectedObserveRttMsCallCount.value()) + ", got:" + std::to_string(this->observeRttMsCallCount) + "]" }; } if ( this->expectedIncrementTxErrorCounterCallCount.has_value() && this->incrementTxErrorCounterCallCount != this->expectedIncrementTxErrorCounterCallCount.value()) { return { .ok = false, .errorMessage = "IncrementTxErrorCounter() call count mismatch [expected:" + std::to_string(this->expectedIncrementTxErrorCounterCallCount.value()) + ", got:" + std::to_string(this->incrementTxErrorCounterCallCount) + "]" }; } return { .ok = true, .errorMessage = "" }; } private: // Passed by argument. ::RTC::SCTP::AssociationListenerInterface& associationListener; const ::RTC::SCTP::SctpOptions sctpOptions; // ObserveRttMs(). size_t observeRttMsCallCount{ 0 }; std::optional expectedObserveRttMsCallCount; // GetCurrentRtoMs(). mutable std::queue getCurrentRtoMsOnceActions; // IncrementTxErrorCounter(). size_t incrementTxErrorCounterCallCount{ 0 }; std::optional expectedIncrementTxErrorCounterCallCount; // Others. bool associationEstablished{ false }; }; } // namespace SCTP } // namespace RTC } // namespace mocks #endif ================================================ FILE: worker/mocks/include/RTC/SCTP/tx/MockSendQueue.hpp ================================================ #ifndef MS_MOCKS_RTC_SCTP_MOCK_SEND_QUEUE_HPP #define MS_MOCKS_RTC_SCTP_MOCK_SEND_QUEUE_HPP #include "common.hpp" #include "mocks/include/mockTypes.hpp" #include "RTC/SCTP/tx/SendQueueInterface.hpp" #include #include #include namespace mocks { namespace RTC { namespace SCTP { class MockSendQueue : public ::RTC::SCTP::SendQueueInterface { public: using ProduceAction = std::function(uint64_t /*nowMs*/, size_t /*maxLength*/)>; struct DiscardExpectation { // If std::nullopt, don't check it. std::optional streamId; // If std::nullopt, don't check it. std::optional outgoingMessageId; bool returnValue{ false }; }; public: MockSendQueue() = default; ~MockSendQueue() override = default; public: void EnableMessageInterleaving(bool /*enabled*/) override { } std::optional Produce(uint64_t nowMs, size_t maxLength) override { this->produceCallCount++; if (!this->produceOnceActions.empty()) { auto action = std::move(this->produceOnceActions.front()); this->produceOnceActions.pop(); return action(nowMs, maxLength); } else if (this->produceRepeatedlyAction) { return this->produceRepeatedlyAction(nowMs, maxLength); } else { return std::nullopt; } } bool Discard(uint16_t streamId, uint32_t outgoingMessageId) override { this->discardCallCount++; if (this->discardExpectations.empty()) { throw std::logic_error("Discard() called but no expectation was set"); } auto discardExpectation = this->discardExpectations.front(); this->discardExpectations.pop(); if (discardExpectation.streamId.has_value() && discardExpectation.streamId.value() != streamId) { throw std::logic_error( "Discard() called with unexpected streamId [expected:" + std::to_string(discardExpectation.streamId.value()) + ", got:" + std::to_string(streamId) + "]"); } if ( discardExpectation.outgoingMessageId.has_value() && discardExpectation.outgoingMessageId.value() != outgoingMessageId) { throw std::logic_error( "Discard() called with unexpected outgoingMessageId [expected:" + std::to_string(discardExpectation.outgoingMessageId.value()) + ", got:" + std::to_string(outgoingMessageId) + "]"); } return discardExpectation.returnValue; } void PrepareResetStream(uint16_t /*streamId*/) override { } bool HasStreamsReadyToBeReset() const override { return false; } std::vector GetStreamsReadyToBeReset() override { return {}; } void CommitResetStreams() override { } void RollbackResetStreams() override { } void Reset() override { } size_t GetStreamBufferedAmount(uint16_t /*streamId*/) const override { return 0; } size_t GetTotalBufferedAmount() const override { return 0; } size_t GetStreamBufferedAmountLowThreshold(uint16_t /*streamId*/) const override { return 0; } void SetStreamBufferedAmountLowThreshold(uint16_t /*streamId*/, size_t /*bytes*/) override { } // Methods for testing. public: MockSendQueue& WillProduceOnce(ProduceAction action) { this->produceOnceActions.push(std::move(action)); return *this; } MockSendQueue& WillProduceRepeatedly(ProduceAction action) { this->produceRepeatedlyAction = std::move(action); return *this; } /** * @remarks * - Must be called before expecting calls to `Produce()`. */ MockSendQueue& ExpectProduceCalledTimes(size_t times) { this->produceCallCount = 0; this->expectedProduceCallCount = times; return *this; } MockSendQueue& WillDiscardOnce(uint16_t streamId, uint32_t outgoingMessageId, bool returnValue) { this->discardExpectations.push({ streamId, outgoingMessageId, returnValue }); return *this; } /** * @remarks * - Must be called before expecting calls to `Discard()`. */ MockSendQueue& ExpectDiscardCalledTimes(size_t times) { this->discardCallCount = 0; this->expectedDiscardCallCount = times; return *this; } mocks::VerificationResult VerifyExpectations() const { if ( this->expectedProduceCallCount.has_value() && this->produceCallCount != this->expectedProduceCallCount.value()) { return { .ok = false, .errorMessage = "Produce() call count mismatch [expected:" + std::to_string(this->expectedProduceCallCount.value()) + ", got:" + std::to_string(this->produceCallCount) + "]" }; } if ( this->expectedDiscardCallCount.has_value() && this->discardCallCount != this->expectedDiscardCallCount.value()) { return { .ok = false, .errorMessage = "Discard() call count mismatch [expected:" + std::to_string(this->expectedDiscardCallCount.value()) + ", got:" + std::to_string(this->discardCallCount) + "]" }; } if (!this->discardExpectations.empty()) { return { .ok = false, .errorMessage = "Discard() has " + std::to_string(this->discardExpectations.size()) + " unconsumed expectation(s)" }; } return { .ok = true, .errorMessage = "" }; } private: // Produce(). std::queue produceOnceActions; ProduceAction produceRepeatedlyAction; size_t produceCallCount{ 0 }; std::optional expectedProduceCallCount; // Discard(). std::queue discardExpectations; size_t discardCallCount{ 0 }; std::optional expectedDiscardCallCount; }; } // namespace SCTP } // namespace RTC } // namespace mocks #endif ================================================ FILE: worker/mocks/include/handles/MockBackoffTimerHandle.hpp ================================================ #ifndef MS_MOCKS_MOCK_BACKOFF_TIMER_HANDLE_HPP #define MS_MOCKS_MOCK_BACKOFF_TIMER_HANDLE_HPP #include "common.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include namespace mocks { // Forward declaration. class MockShared; class MockBackoffTimerHandle : public BackoffTimerHandleInterface { // Only MockShared class can invoke the constructor. friend class mocks::MockShared; private: explicit MockBackoffTimerHandle( BackoffTimerHandleOptions options, std::function getTimeMs, std::function onDelete); public: MockBackoffTimerHandle& operator=(const MockBackoffTimerHandle&) = delete; MockBackoffTimerHandle(const MockBackoffTimerHandle&) = delete; ~MockBackoffTimerHandle() override { this->onDelete(); } public: void Dump(int indentation = 0) const; void Start() override { this->running = true; this->expiresAtMs = this->getTimeMs() + ComputeNextTimeoutMs(); } void Stop() override { this->running = false; this->expiresAtMs = std::numeric_limits::max(); } /** * @remarks * - This method must be defined in the .cpp since it's called by the * constructor. Otherwise clang-tidy complains: * "warning: Call to virtual method 'BackoffTimerHandle::SetBaseTimeoutMs' * during construction bypasses virtual dispatch * [clang-analyzer-optin.cplusplus.VirtualCall]" */ void SetBaseTimeoutMs(uint64_t baseTimeoutMs) override; bool IsRunning() const override { return this->running; } const std::string GetLabel() const override { return this->label; } std::optional GetMaxRestarts() const override { return this->maxRestarts; } size_t GetExpirationCount() const override { return this->expirationCount; } // Methods for testing. public: uint64_t GetExpiresAtMs() const { return this->expiresAtMs; } bool EvaluateHasExpired() { if (this->getTimeMs() >= this->expiresAtMs) { TriggerExpire(); return true; } else { return false; } } private: uint64_t ComputeNextTimeoutMs() const { auto expirationCount = this->expirationCount; switch (this->backoffAlgorithm) { case BackoffAlgorithm::FIXED: { return this->baseTimeoutMs; } case BackoffAlgorithm::EXPONENTIAL: { auto timeoutMs = this->baseTimeoutMs; while (expirationCount > 0 && timeoutMs < BackoffTimerHandleInterface::MaxTimeoutMs) { timeoutMs *= 2; --expirationCount; if (this->maxBackoffTimeoutMs.has_value() && timeoutMs > this->maxBackoffTimeoutMs.value()) { return this->maxBackoffTimeoutMs.value(); } } return std::min(timeoutMs, BackoffTimerHandleInterface::MaxTimeoutMs); } NO_DEFAULT_GCC(); } } void TriggerExpire(); private: // Passed by argument. BackoffTimerHandleInterface::Listener* listener{ nullptr }; const std::string label; uint64_t baseTimeoutMs; BackoffAlgorithm backoffAlgorithm; std::optional maxBackoffTimeoutMs; std::optional maxRestarts; std::function getTimeMs; const std::function onDelete; // Others. bool running{ false }; size_t expirationCount{ 0 }; uint64_t expiresAtMs{ std::numeric_limits::max() }; }; } // namespace mocks #endif ================================================ FILE: worker/mocks/include/handles/MockTimerHandle.hpp ================================================ #ifndef MS_MOCKS_MOCK_TIMER_HANDLE_HPP #define MS_MOCKS_MOCK_TIMER_HANDLE_HPP #include "common.hpp" #include "handles/TimerHandleInterface.hpp" namespace mocks { // Forward declaration. class MockShared; class MockTimerHandle : public TimerHandleInterface { // Only MockShared class can invoke the constructor. friend class mocks::MockShared; private: explicit MockTimerHandle() = default; public: MockTimerHandle& operator=(const MockTimerHandle&) = delete; MockTimerHandle(const MockTimerHandle&) = delete; ~MockTimerHandle() override = default; public: void Start(uint64_t timeout, uint64_t repeat = 0) override { this->running = true; this->timeout = timeout; this->repeat = repeat; } void Stop() override { this->running = false; } void Restart() override { this->running = true; } void Restart(uint64_t timeout, uint64_t repeat = 0) override { this->running = true; this->timeout = timeout; this->repeat = repeat; } uint64_t GetTimeout() const override { return this->timeout; } uint64_t GetRepeat() const override { return this->repeat; } bool IsActive() const override { return this->running; } private: bool running{ false }; uint64_t timeout{ 0u }; uint64_t repeat{ 0u }; }; } // namespace mocks #endif ================================================ FILE: worker/mocks/include/mockTypes.hpp ================================================ #ifndef MS_MOCKS_TYPES_HPP #define MS_MOCKS_TYPES_HPP #include namespace mocks { struct VerificationResult { bool ok; std::string errorMessage; }; } // namespace mocks #endif ================================================ FILE: worker/mocks/src/Channel/MockChannelMessageRegistrator.cpp ================================================ #define MS_CLASS "ChannelMessageRegistrator" // #define MS_LOG_DEV_LEVEL 3 #include "mocks/include/Channel/MockChannelMessageRegistrator.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include namespace mocks { namespace Channel { MockChannelMessageRegistrator::MockChannelMessageRegistrator() { MS_TRACE(); } MockChannelMessageRegistrator::~MockChannelMessageRegistrator() { MS_TRACE(); this->mapChannelRequestHandlers.clear(); this->mapChannelNotificationHandlers.clear(); } flatbuffers::Offset MockChannelMessageRegistrator::FillBuffer( flatbuffers::FlatBufferBuilder& builder) { // Add channelRequestHandlerIds. std::vector> channelRequestHandlerIds; for (const auto& kv : this->mapChannelRequestHandlers) { const auto& handlerId = kv.first; channelRequestHandlerIds.push_back(builder.CreateString(handlerId)); } // Add channelNotificationHandlerIds. std::vector> channelNotificationHandlerIds; for (const auto& kv : this->mapChannelNotificationHandlers) { const auto& handlerId = kv.first; channelNotificationHandlerIds.push_back(builder.CreateString(handlerId)); } return FBS::Worker::CreateChannelMessageHandlersDirect( builder, &channelRequestHandlerIds, &channelNotificationHandlerIds); } void MockChannelMessageRegistrator::RegisterHandler( const std::string& id, ::Channel::ChannelSocket::RequestHandler* channelRequestHandler, ::Channel::ChannelSocket::NotificationHandler* channelNotificationHandler) { MS_TRACE(); if (channelRequestHandler) { if (this->mapChannelRequestHandlers.contains(id)) { MS_THROW_ERROR("Channel request handler with ID %s already exists", id.c_str()); } this->mapChannelRequestHandlers[id] = channelRequestHandler; } if (channelNotificationHandler) { if (this->mapChannelNotificationHandlers.contains(id)) { if (channelRequestHandler) { this->mapChannelRequestHandlers.erase(id); } MS_THROW_ERROR("Channel notification handler with ID %s already exists", id.c_str()); } this->mapChannelNotificationHandlers[id] = channelNotificationHandler; } } void MockChannelMessageRegistrator::UnregisterHandler(const std::string& id) { MS_TRACE(); this->mapChannelRequestHandlers.erase(id); this->mapChannelNotificationHandlers.erase(id); } ::Channel::ChannelSocket::RequestHandler* MockChannelMessageRegistrator::GetChannelRequestHandler( const std::string& id) { MS_TRACE(); auto it = this->mapChannelRequestHandlers.find(id); if (it != this->mapChannelRequestHandlers.end()) { return it->second; } else { return nullptr; } } ::Channel::ChannelSocket::NotificationHandler* MockChannelMessageRegistrator::GetChannelNotificationHandler( const std::string& id) { MS_TRACE(); auto it = this->mapChannelNotificationHandlers.find(id); if (it != this->mapChannelNotificationHandlers.end()) { return it->second; } else { return nullptr; } } } // namespace Channel } // namespace mocks ================================================ FILE: worker/mocks/src/MockShared.cpp ================================================ #define MS_CLASS "mocks::MockShared" // #define MS_LOG_DEV_LEVEL 3 #include "mocks/include/MockShared.hpp" #include "Logger.hpp" #include "mocks/include/handles/MockTimerHandle.hpp" namespace mocks { MockShared::MockShared(std::function getTimeMs) : getTimeMs(std::move(getTimeMs)), channelSocket(new ::Channel::ChannelSocket()), channelMessageRegistrator(new mocks::Channel::MockChannelMessageRegistrator()), channelNotifier(new ::Channel::ChannelNotifier(this->channelSocket.get())) { MS_TRACE(); } TimerHandleInterface* MockShared::CreateTimer(TimerHandleInterface::Listener* /*listener*/) { MS_TRACE(); return new MockTimerHandle(); } BackoffTimerHandleInterface* MockShared::CreateBackoffTimer( const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options) { MS_TRACE(); const auto& label = options.label; auto* backoffTimer = new MockBackoffTimerHandle( options, /*getTimeMs*/ this->getTimeMs, /*onDelete*/ [this, label]() { this->backoffTimers.erase(label); }); this->backoffTimers[options.label] = backoffTimer; return backoffTimer; } } // namespace mocks ================================================ FILE: worker/mocks/src/RTC/SCTP/association/MockTransmissionControlBlockContext.cpp ================================================ #define MS_CLASS "mocks::RTC::SCTP::MockTransmissionControlBlockContext" // #define MS_LOG_DEV_LEVEL 3 #include "mocks/include/RTC/SCTP/association/MockTransmissionControlBlockContext.hpp" #include "Logger.hpp" alignas(4) static thread_local uint8_t FactoryBuffer[65536]; namespace mocks { namespace RTC { namespace SCTP { std::unique_ptr<::RTC::SCTP::Packet> MockTransmissionControlBlockContext::CreatePacket() const { MS_TRACE(); auto packet = std::unique_ptr<::RTC::SCTP::Packet>{ ::RTC::SCTP::Packet::Factory( FactoryBuffer, this->sctpOptions.mtu) }; packet->SetSourcePort(this->sctpOptions.sourcePort); packet->SetDestinationPort(this->sctpOptions.destinationPort); return packet; } bool MockTransmissionControlBlockContext::SendPacket(::RTC::SCTP::Packet* packet) { MS_TRACE(); const bool sent = this->associationListener.OnAssociationSendData(packet->GetBuffer(), packet->GetLength()); return sent; } } // namespace SCTP } // namespace RTC } // namespace mocks ================================================ FILE: worker/mocks/src/handles/MockBackoffTimerHandle.cpp ================================================ #define MS_CLASS "mocks::MockBackoffTimerHandle" // #define MS_LOG_DEV_LEVEL 3 #include "mocks/include/handles/MockBackoffTimerHandle.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include namespace mocks { MockBackoffTimerHandle::MockBackoffTimerHandle( BackoffTimerHandleOptions options, std::function getTimeMs, std::function onDelete) : listener(options.listener), label(std::move(options.label)), baseTimeoutMs(options.baseTimeoutMs), backoffAlgorithm(options.backoffAlgorithm), maxBackoffTimeoutMs(options.maxBackoffTimeoutMs), maxRestarts(options.maxRestarts), getTimeMs(std::move(getTimeMs)), onDelete(std::move(onDelete)) { MS_TRACE(); if (!this->listener) { MS_THROW_TYPE_ERROR("options.listener must be given"); } if (this->label.empty()) { MS_THROW_TYPE_ERROR("options.label must be given"); } if (this->baseTimeoutMs > BackoffTimerHandleInterface::MaxTimeoutMs) { MS_THROW_ERROR( "[%s] base timeout (%" PRIu64 " ms) cannot be greater than %" PRIu64 " ms", this->label.c_str(), this->baseTimeoutMs, BackoffTimerHandleInterface::MaxTimeoutMs); } } void MockBackoffTimerHandle::Dump(int indentation) const { MS_TRACE(); const uint64_t nowMs = this->getTimeMs(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " label: %s", this->label.c_str()); MS_DUMP_CLEAN(indentation, " base timeout (ms): %" PRIu64, this->baseTimeoutMs); MS_DUMP_CLEAN( indentation, " max backoff timeout (ms): %s", this->maxBackoffTimeoutMs.has_value() ? std::to_string(this->maxBackoffTimeoutMs.value()).c_str() : "(unset)"); MS_DUMP_CLEAN( indentation, " max restarts (ms): %s", this->maxRestarts.has_value() ? std::to_string(this->maxRestarts.value()).c_str() : "(unset)"); MS_DUMP_CLEAN(indentation, " running: %s", this->running ? "yes" : "no"); MS_DUMP_CLEAN(indentation, " expiration count: %zu", this->expirationCount); MS_DUMP_CLEAN(indentation, " now (ms): %" PRIu64, nowMs); MS_DUMP_CLEAN(indentation, " expires at (ms): %" PRIu64, this->expiresAtMs); MS_DUMP_CLEAN(indentation, " expires in (ms): %" PRIu64, this->expiresAtMs - nowMs); MS_DUMP_CLEAN(indentation, ""); } void MockBackoffTimerHandle::SetBaseTimeoutMs(uint64_t baseTimeoutMs) { MS_TRACE(); if (baseTimeoutMs > BackoffTimerHandleInterface::MaxTimeoutMs) { MS_THROW_ERROR( "[%s] base timeout (%" PRIu64 " ms) cannot be greater than %" PRIu64 " ms", this->label.c_str(), baseTimeoutMs, BackoffTimerHandleInterface::MaxTimeoutMs); } this->baseTimeoutMs = baseTimeoutMs; } void MockBackoffTimerHandle::TriggerExpire() { MS_TRACE(); this->expirationCount++; // Compute whether the BackoffTimer should still be running after this timeout // expiration so the parent can check IsRunning() within the `OnBackoffTimer()` // callback. this->running = !this->maxRestarts.has_value() || this->expirationCount <= this->maxRestarts.value(); uint64_t baseTimeoutMs{ this->baseTimeoutMs }; bool stop{ false }; // Call the listener by passing base timeout as reference so the parent has // a chance to change it and affect the next timeout. this->listener->OnBackoffTimer(this, baseTimeoutMs, stop); // If the parent has set `stop` to true it means that it has deleted the // instance, so stop here. if (stop) { return; } // NOTE: This may throw. SetBaseTimeoutMs(baseTimeoutMs); // The caller may have called Stop() within the callback so we must check // the `running` flag. if (this->running) { this->expiresAtMs = this->getTimeMs() + ComputeNextTimeoutMs(); } } } // namespace mocks ================================================ FILE: worker/scripts/.npmrc ================================================ package-lock=true ================================================ FILE: worker/scripts/clang-scripts.mjs ================================================ import * as process from 'node:process'; import * as os from 'node:os'; import * as fs from 'node:fs'; import * as path from 'node:path'; import { execSync } from 'node:child_process'; import { globSync } from 'glob'; const PYTHON = getPython(); const ROOT_DIR = getRootDir(); const BUILD_DIR = getBuildDir(); const NUM_CORES = getNumCores(); const CLANG_FORMAT_VERSION = 22; const CLANG_TIDY_VERSION = 21; const CLANG_FORMAT_PATHS = [ '../src/**/*.cpp', '../include/**/*.hpp', '../test/src/**/*.cpp', '../test/include/**/**.hpp', '../fuzzer/src/**/*.cpp', '../fuzzer/include/**/*.hpp', '../mocks/src/**/*.cpp', '../mocks/include/**/*.hpp', ]; const CLANG_TIDY_PATHS = [ '../src/**/*.cpp', '../test/src/**/*.cpp', '../fuzzer/src/**/*.cpp', '../mocks/src/**/*.cpp', ]; const task = process.argv.slice(2).join(' '); void run(); async function run() { switch (task) { case 'lint': { lint(); break; } case 'format': { format(); break; } case 'tidy': { tidy({ fix: false }); break; } case 'tidy:fix': { tidy({ fix: true }); break; } case 'normalize-compile-commands': { normalizeCompileCommands(); break; } default: { logError('unknown task'); exitWithError(); } } } function lint() { logInfo('lint()'); const clangFormat = getClangToolBinary({ clangToolName: 'clang-format', version: CLANG_FORMAT_VERSION, checkRequireVersion: true, }); const clangFormatFiles = globSync(CLANG_FORMAT_PATHS).join(' '); executeCmd(`"${clangFormat}" --Werror --dry-run ${clangFormatFiles}`); } function format() { logInfo('format()'); const clangFormat = getClangToolBinary({ clangToolName: 'clang-format', version: CLANG_FORMAT_VERSION, checkRequireVersion: true, }); const clangFormatFiles = globSync(CLANG_FORMAT_PATHS).join(' '); executeCmd(`"${clangFormat}" --Werror -i ${clangFormatFiles}`); } function tidy({ fix }) { logInfo(`tidy() [fix:${fix}]`); const clangTidy = getClangToolBinary({ clangToolName: 'clang-tidy', version: CLANG_TIDY_VERSION, checkRequireVersion: true, }); const runClangTidy = getClangToolBinary({ clangToolName: 'run-clang-tidy', version: CLANG_TIDY_VERSION, // Don't check version because this command does not provide --version // option. checkRequireVersion: false, }); const clangApplyReplacements = getClangToolBinary({ clangToolName: 'clang-apply-replacements', version: CLANG_TIDY_VERSION, checkRequireVersion: true, }); const tidyChecksArg = process.env.MEDIASOUP_TIDY_CHECKS ? `-checks=-*,${process.env.MEDIASOUP_TIDY_CHECKS}` : ''; const runClangTidyFilesArgs = process.env.MEDIASOUP_TIDY_FILES ? globSync( process.env.MEDIASOUP_TIDY_FILES.split(/\s/) .filter(Boolean) .map(filePath => `../${filePath}`) ).join(' ') : globSync(CLANG_TIDY_PATHS).join(' '); if (!runClangTidyFilesArgs) { logError('tidy() | no files found'); exitWithError(); } const fixArg = fix ? '-fix' : ''; // Check .clang-tidy config file and fail if some option is invalid/unknown. executeCmd(`"${clangTidy}" --verify-config`); executeCmd( `"${PYTHON}" "${runClangTidy}" -clang-tidy-binary="${clangTidy}" -clang-apply-replacements-binary="${clangApplyReplacements}" -p="${BUILD_DIR}" -j=${NUM_CORES} -quiet ${fixArg} -format ${tidyChecksArg} ${runClangTidyFilesArgs}` ); } function normalizeCompileCommands() { logInfo('normalizeCompileCommands()'); const compileCommandsFile = `${BUILD_DIR}/compile_commands.json`; try { const commands = JSON.parse(fs.readFileSync(compileCommandsFile, 'utf8')); for (const entry of commands) { if (entry.file && entry.directory) { // Resolve to absolute path first. const absolutePath = path.resolve(entry.directory, entry.file); // Convert to relative path from repo root. entry.file = path.relative(ROOT_DIR, absolutePath); } } fs.writeFileSync(compileCommandsFile, JSON.stringify(commands, null, 2)); } catch (error) { logError( `normalizeCompileCommands() | failed to clean up compile_commands.json: ${error}` ); exitWithError(); } } function getClangToolBinary({ clangToolName, version, checkRequireVersion }) { logInfo( `getClangToolBinary() [clangToolName:${clangToolName}, version:${version}]` ); let clangToolBinary; // Try `clangTool-version` first, otherwise try `clangTool`. try { clangToolBinary = `${clangToolName}-${version}`; logInfo(`getClangToolBinary() | trying ${clangToolBinary}...`); execSync(`${clangToolBinary} --help`, { stdio: ['ignore', 'ignore', 'ignore'], }); } catch (error) { clangToolBinary = clangToolName; logInfo(`getClangToolBinary() | trying ${clangToolBinary}...`); try { execSync(`${clangToolBinary} --help`, { stdio: ['ignore', 'ignore', 'ignore'], }); } catch { logError(`getClangToolBinary() | ${clangToolName} binary not found`); exitWithError(); } } logInfo(`getClangToolBinary() | using ${clangToolBinary}`); if (checkRequireVersion) { try { checkClangToolVersion(clangToolBinary, version); } catch (error) { logError(`getClangToolBinary() | failed: ${error.message}`); exitWithError(); } } const clangToolBinaryAbsolutePath = execSync(`which ${clangToolBinary}`, { encoding: 'utf8', }).trim(); return clangToolBinaryAbsolutePath; } function checkClangToolVersion(clangToolBinary, requiredVersion) { try { let version; // Run the command and capture the output. const output = execSync(`${clangToolBinary} --version`, { encoding: 'utf-8', }); // Extract the mayor version number from the output. const match = output.match(/version (\d+)/); if (match && match[1]) { version = parseInt(match[1], 10); } else { throw new Error( `checkClangToolVersion() | unable to parse output of '${clangToolBinary} --version': ${output}` ); } if (version === requiredVersion) { logInfo( `checkClangToolVersion() | ${clangToolBinary} version is the required one (${requiredVersion})` ); } else { throw new Error( `checkClangToolVersion() | ${clangToolBinary} version (${version}) is not the required one (${requiredVersion})` ); } } catch (error) { throw new Error( `checkClangToolVersion() | failed to check ${clangToolBinary} version: ${error.message}`, { cause: error } ); } } function getPython() { let python = process.env.PYTHON; if (!python) { try { execSync('python3 --version', { stdio: ['ignore', 'ignore', 'ignore'] }); python = 'python3'; } catch (error) { python = 'python'; } } return python; } function getRootDir() { return path.resolve(path.join('../../')); } function getBuildDir() { const workerDir = path.join(ROOT_DIR, 'worker'); const workerOutDir = process.env.MEDIASOUP_OUT_DIR ?? `${workerDir}/out`; const mediasoupBuildtype = process.env.MEDIASOUP_BUILDTYPE ?? 'Release'; const workerInstallDir = process.env.MEDIASOUP_INSTALL_DIR ?? `${workerOutDir}/${mediasoupBuildtype}`; const buildDir = process.envBUILD_DIR ?? `${workerInstallDir}/build`; return buildDir; } function getNumCores() { return Object.keys(os.cpus()).length; } function executeCmd(command) { logInfo(`executeCmd(): ${command}`); try { execSync(command, { stdio: ['ignore', process.stdout, process.stderr] }); } catch (error) { logError('executeCmd() failed'); exitWithError(); } } function logInfo(message) { // eslint-disable-next-line no-console console.log(`clang-scripts.mjs \x1b[36m[INFO] [${task}]\x1b[0m`, message); } // eslint-disable-next-line no-unused-vars function logWarn(message) { // eslint-disable-next-line no-console console.warn(`clang-scripts.mjs \x1b[33m[WARN] [${task}]\x1b[0m`, message); } function logError(message) { // eslint-disable-next-line no-console console.error(`clang-scripts.mjs \x1b[31m[ERROR] [${task}]\x1b[0m`, message); } function exitWithError() { process.exit(1); } ================================================ FILE: worker/scripts/get-dep.sh ================================================ #!/usr/bin/env bash set -e WORKER_PWD=${PWD} DEP=$1 current_dir_name=${WORKER_PWD##*/} if [ "${current_dir_name}" != "worker" ] ; then echo ">>> [ERROR] $(basename $0) must be called from mediasoup/worker directory" >&2 exit 1 fi function get_dep() { GIT_REPO="$1" GIT_TAG="$2" DEST="$3" echo ">>> [INFO] getting dep '${DEP}'..." if [ -d "${DEST}" ] ; then echo ">>> [INFO] deleting ${DEST}..." git rm -rf --ignore-unmatch ${DEST} > /dev/null rm -rf ${DEST} fi echo ">>> [INFO] cloning ${GIT_REPO}..." git clone ${GIT_REPO} ${DEST} cd ${DEST} echo ">>> [INFO] setting '${GIT_TAG}' git tag..." git checkout --quiet ${GIT_TAG} set -e echo ">>> [INFO] adding dep source code to the repository..." rm -rf .git git add . echo ">>> [INFO] got dep '${DEP}'" cd ${WORKER_PWD} } function get_fuzzer_corpora() { GIT_REPO="https://github.com/RTC-Cartel/webrtc-fuzzer-corpora.git" GIT_TAG="master" DEST="deps/webrtc-fuzzer-corpora" get_dep "${GIT_REPO}" "${GIT_TAG}" "${DEST}" } case "${DEP}" in '-h') echo "Usage:" echo " ./scripts/$(basename $0) [fuzzer-corpora]" echo ;; fuzzer-corpora) get_fuzzer_corpora ;; *) echo ">>> [ERROR] unknown dep '${DEP}'" >&2 exit 1 esac if [ $? -eq 0 ] ; then echo ">>> [INFO] done" else echo ">>> [ERROR] failed" >&2 exit 1 fi ================================================ FILE: worker/scripts/package.json ================================================ { "name": "mediasoup-worker-dev-tools", "version": "0.0.1", "description": "mediasoup worker dev tools", "scripts": { "lint": "node clang-scripts.mjs lint", "format": "node clang-scripts.mjs format", "tidy": "node clang-scripts.mjs tidy", "tidy:fix": "node clang-scripts.mjs tidy:fix", "normalize-compile-commands": "node clang-scripts.mjs normalize-compile-commands" }, "dependencies": { "glob": "^13.0.6" } } ================================================ FILE: worker/scripts/run-fuzzer.sh ================================================ #!/usr/bin/env bash WORKER_PWD=${PWD} DURATION_SEC=$1 current_dir_name=${WORKER_PWD##*/} if [ "${current_dir_name}" != "worker" ] ; then echo "run-fuzzer.sh [ERROR] $(basename $0) must be called from mediasoup/worker directory" >&2 exit 1 fi if [ "$#" -eq 0 ] ; then echo "run-fuzzer.sh [ERROR] duration (in seconds) must be given as argument" >&2 exit 1 fi invoke fuzzer-run-all & MEDIASOUP_WORKER_FUZZER_PID=$! i=${DURATION_SEC} until [ ${i} -eq 0 ] do echo "run-fuzzer.sh [INFO] ${i} seconds left" if ! kill -0 ${MEDIASOUP_WORKER_FUZZER_PID} &> /dev/null ; then echo "run-fuzzer.sh [ERROR] mediasoup-worker-fuzzer died" >&2 exit 1 else ((i=i-1)) sleep 1 fi done echo "run-fuzzer.sh [INFO] mediasoup-worker-fuzzer is still running after given ${DURATION_SEC} seconds so no fuzzer issues so far" kill -SIGTERM ${MEDIASOUP_WORKER_FUZZER_PID} &> /dev/null exit 0 ================================================ FILE: worker/src/Channel/ChannelMessageRegistrator.cpp ================================================ #define MS_CLASS "ChannelMessageRegistrator" // #define MS_LOG_DEV_LEVEL 3 #include "Channel/ChannelMessageRegistrator.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include namespace Channel { ChannelMessageRegistrator::ChannelMessageRegistrator() { MS_TRACE(); } ChannelMessageRegistrator::~ChannelMessageRegistrator() { MS_TRACE(); this->mapChannelRequestHandlers.clear(); this->mapChannelNotificationHandlers.clear(); } flatbuffers::Offset ChannelMessageRegistrator::FillBuffer( flatbuffers::FlatBufferBuilder& builder) { // Add channelRequestHandlerIds. std::vector> channelRequestHandlerIds; for (const auto& kv : this->mapChannelRequestHandlers) { const auto& handlerId = kv.first; channelRequestHandlerIds.push_back(builder.CreateString(handlerId)); } // Add channelNotificationHandlerIds. std::vector> channelNotificationHandlerIds; for (const auto& kv : this->mapChannelNotificationHandlers) { const auto& handlerId = kv.first; channelNotificationHandlerIds.push_back(builder.CreateString(handlerId)); } return FBS::Worker::CreateChannelMessageHandlersDirect( builder, &channelRequestHandlerIds, &channelNotificationHandlerIds); } void ChannelMessageRegistrator::RegisterHandler( const std::string& id, ChannelSocket::RequestHandler* channelRequestHandler, ChannelSocket::NotificationHandler* channelNotificationHandler) { MS_TRACE(); if (channelRequestHandler) { if (this->mapChannelRequestHandlers.contains(id)) { MS_THROW_ERROR("Channel request handler with ID %s already exists", id.c_str()); } this->mapChannelRequestHandlers[id] = channelRequestHandler; } if (channelNotificationHandler) { if (this->mapChannelNotificationHandlers.contains(id)) { if (channelRequestHandler) { this->mapChannelRequestHandlers.erase(id); } MS_THROW_ERROR("Channel notification handler with ID %s already exists", id.c_str()); } this->mapChannelNotificationHandlers[id] = channelNotificationHandler; } } void ChannelMessageRegistrator::UnregisterHandler(const std::string& id) { MS_TRACE(); this->mapChannelRequestHandlers.erase(id); this->mapChannelNotificationHandlers.erase(id); } ChannelSocket::RequestHandler* ChannelMessageRegistrator::GetChannelRequestHandler( const std::string& id) { MS_TRACE(); auto it = this->mapChannelRequestHandlers.find(id); if (it != this->mapChannelRequestHandlers.end()) { return it->second; } else { return nullptr; } } ChannelSocket::NotificationHandler* ChannelMessageRegistrator::GetChannelNotificationHandler( const std::string& id) { MS_TRACE(); auto it = this->mapChannelNotificationHandlers.find(id); if (it != this->mapChannelNotificationHandlers.end()) { return it->second; } else { return nullptr; } } } // namespace Channel ================================================ FILE: worker/src/Channel/ChannelNotification.cpp ================================================ #define MS_CLASS "Channel::ChannelNotification" // #define MS_LOG_DEV_LEVEL 3 #include "Channel/ChannelNotification.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace Channel { /* Class variables. */ // clang-format off const absl::flat_hash_map ChannelNotification::Event2String = { { FBS::Notification::Event::WORKER_CLOSE, "worker.close" }, { FBS::Notification::Event::TRANSPORT_SEND_RTCP, "transport.sendRtcp" }, { FBS::Notification::Event::PRODUCER_SEND, "producer.send" }, { FBS::Notification::Event::DATAPRODUCER_SEND, "dataProducer.send" }, }; // clang-format on /* Instance methods. */ ChannelNotification::ChannelNotification(const FBS::Notification::Notification* notification) { MS_TRACE(); this->data = notification; this->event = notification->event(); auto eventCStrIt = ChannelNotification::Event2String.find(this->event); if (eventCStrIt == ChannelNotification::Event2String.end()) { MS_THROW_ERROR("unknown event '%" PRIu8 "'", static_cast(this->event)); } this->eventCStr = eventCStrIt->second; this->handlerId = this->data->handlerId()->str(); } } // namespace Channel ================================================ FILE: worker/src/Channel/ChannelNotifier.cpp ================================================ #define MS_CLASS "Channel::Notifier" // #define MS_LOG_DEV_LEVEL 3 #include "Channel/ChannelNotifier.hpp" #include "Logger.hpp" namespace Channel { /* Instance methods. */ ChannelNotifier::ChannelNotifier(Channel::ChannelSocket* channel) : channel(channel) { MS_TRACE(); } void ChannelNotifier::Emit(const std::string& targetId, FBS::Notification::Event event) { MS_TRACE(); auto& builder = this->bufferBuilder; auto notification = FBS::Notification::CreateNotificationDirect(builder, targetId.c_str(), event); auto message = FBS::Message::CreateMessage(builder, FBS::Message::Body::Notification, notification.Union()); builder.FinishSizePrefixed(message); this->channel->Send(builder.GetBufferPointer(), builder.GetSize()); builder.Clear(); } } // namespace Channel ================================================ FILE: worker/src/Channel/ChannelRequest.cpp ================================================ #define MS_CLASS "Channel::ChannelRequest" // #define MS_LOG_DEV_LEVEL 3 #include "Channel/ChannelRequest.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace Channel { /* Class variables. */ thread_local flatbuffers::FlatBufferBuilder ChannelRequest::bufferBuilder{}; // clang-format off const absl::flat_hash_map ChannelRequest::Method2String = { { FBS::Request::Method::WORKER_DUMP, "worker.dump" }, { FBS::Request::Method::WORKER_GET_RESOURCE_USAGE, "worker.getResourceUsage" }, { FBS::Request::Method::WORKER_UPDATE_SETTINGS, "worker.updateSettings" }, { FBS::Request::Method::WORKER_CREATE_WEBRTCSERVER, "worker.createWebRtcServer" }, { FBS::Request::Method::WORKER_CREATE_ROUTER, "worker.createRouter" }, { FBS::Request::Method::WORKER_WEBRTCSERVER_CLOSE, "worker.closeWebRtcServer" }, { FBS::Request::Method::WORKER_CLOSE_ROUTER, "worker.closeRouter" }, { FBS::Request::Method::WEBRTCSERVER_DUMP, "webRtcServer.dump" }, { FBS::Request::Method::ROUTER_DUMP, "router.dump" }, { FBS::Request::Method::ROUTER_CREATE_WEBRTCTRANSPORT, "router.createWebRtcTransport" }, { FBS::Request::Method::ROUTER_CREATE_WEBRTCTRANSPORT_WITH_SERVER, "router.createWebRtcTransportWithServer" }, { FBS::Request::Method::ROUTER_CREATE_PLAINTRANSPORT, "router.createPlainTransport" }, { FBS::Request::Method::ROUTER_CREATE_PIPETRANSPORT, "router.createPipeTransport" }, { FBS::Request::Method::ROUTER_CREATE_DIRECTTRANSPORT, "router.createDirectTransport" }, { FBS::Request::Method::ROUTER_CLOSE_TRANSPORT, "router.closeTransport" }, { FBS::Request::Method::ROUTER_CREATE_ACTIVESPEAKEROBSERVER, "router.createActiveSpeakerObserver" }, { FBS::Request::Method::ROUTER_CREATE_AUDIOLEVELOBSERVER, "router.createAudioLevelObserver" }, { FBS::Request::Method::ROUTER_CLOSE_RTPOBSERVER, "router.closeRtpObserver" }, { FBS::Request::Method::TRANSPORT_DUMP, "transport.dump" }, { FBS::Request::Method::TRANSPORT_GET_STATS, "transport.getStats" }, { FBS::Request::Method::TRANSPORT_CONNECT, "transport.connect" }, { FBS::Request::Method::TRANSPORT_SET_MAX_INCOMING_BITRATE, "transport.setMaxIncomingBitrate" }, { FBS::Request::Method::TRANSPORT_SET_MAX_OUTGOING_BITRATE, "transport.setMaxOutgoingBitrate" }, { FBS::Request::Method::TRANSPORT_SET_MIN_OUTGOING_BITRATE, "transport.setMinOutgoingBitrate" }, { FBS::Request::Method::TRANSPORT_RESTART_ICE, "transport.restartIce" }, { FBS::Request::Method::TRANSPORT_PRODUCE, "transport.produce" }, { FBS::Request::Method::TRANSPORT_PRODUCE_DATA, "transport.produceData" }, { FBS::Request::Method::TRANSPORT_CONSUME, "transport.consume" }, { FBS::Request::Method::TRANSPORT_CONSUME_DATA, "transport.consumeData" }, { FBS::Request::Method::TRANSPORT_ENABLE_TRACE_EVENT, "transport.enableTraceEvent" }, { FBS::Request::Method::TRANSPORT_CLOSE_PRODUCER, "transport.closeProducer" }, { FBS::Request::Method::TRANSPORT_CLOSE_CONSUMER, "transport.closeConsumer" }, { FBS::Request::Method::TRANSPORT_CLOSE_DATAPRODUCER, "transport.closeDataProducer" }, { FBS::Request::Method::TRANSPORT_CLOSE_DATACONSUMER, "transport.closeDataConsumer" }, { FBS::Request::Method::PLAINTRANSPORT_CONNECT, "plainTransport.connect" }, { FBS::Request::Method::PIPETRANSPORT_CONNECT, "pipeTransport.connect" }, { FBS::Request::Method::WEBRTCTRANSPORT_CONNECT, "webRtcTransport.connect" }, { FBS::Request::Method::PRODUCER_DUMP, "producer.dump" }, { FBS::Request::Method::PRODUCER_GET_STATS, "producer.getStats" }, { FBS::Request::Method::PRODUCER_PAUSE, "producer.pause" }, { FBS::Request::Method::PRODUCER_RESUME, "producer.resume" }, { FBS::Request::Method::PRODUCER_ENABLE_TRACE_EVENT, "producer.enableTraceEvent" }, { FBS::Request::Method::CONSUMER_DUMP, "consumer.dump" }, { FBS::Request::Method::CONSUMER_GET_STATS, "consumer.getStats" }, { FBS::Request::Method::CONSUMER_PAUSE, "consumer.pause" }, { FBS::Request::Method::CONSUMER_RESUME, "consumer.resume" }, { FBS::Request::Method::CONSUMER_SET_PREFERRED_LAYERS, "consumer.setPreferredLayers" }, { FBS::Request::Method::CONSUMER_SET_PRIORITY, "consumer.setPriority" }, { FBS::Request::Method::CONSUMER_REQUEST_KEY_FRAME, "consumer.requestKeyFrame" }, { FBS::Request::Method::CONSUMER_ENABLE_TRACE_EVENT, "consumer.enableTraceEvent" }, { FBS::Request::Method::DATAPRODUCER_DUMP, "dataProducer.dump" }, { FBS::Request::Method::DATAPRODUCER_GET_STATS, "dataProducer.getStats" }, { FBS::Request::Method::DATAPRODUCER_PAUSE, "dataProducer.pause" }, { FBS::Request::Method::DATAPRODUCER_RESUME, "dataProducer.resume" }, { FBS::Request::Method::DATACONSUMER_DUMP, "dataConsumer.dump" }, { FBS::Request::Method::DATACONSUMER_GET_STATS, "dataConsumer.getStats" }, { FBS::Request::Method::DATACONSUMER_PAUSE, "dataConsumer.pause" }, { FBS::Request::Method::DATACONSUMER_RESUME, "dataConsumer.resume" }, { FBS::Request::Method::DATACONSUMER_GET_BUFFERED_AMOUNT, "dataConsumer.getBufferedAmount" }, { FBS::Request::Method::DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD, "dataConsumer.setBufferedAmountLowThreshold" }, { FBS::Request::Method::DATACONSUMER_SEND, "dataConsumer.send" }, { FBS::Request::Method::DATACONSUMER_SET_SUBCHANNELS, "dataConsumer.setSubchannels" }, { FBS::Request::Method::DATACONSUMER_ADD_SUBCHANNEL, "dataConsumer.addSubchannel" }, { FBS::Request::Method::DATACONSUMER_REMOVE_SUBCHANNEL, "dataConsumer.removeSubchannel" }, { FBS::Request::Method::RTPOBSERVER_PAUSE, "rtpObserver.pause" }, { FBS::Request::Method::RTPOBSERVER_RESUME, "rtpObserver.resume" }, { FBS::Request::Method::RTPOBSERVER_ADD_PRODUCER, "rtpObserver.addProducer" }, { FBS::Request::Method::RTPOBSERVER_REMOVE_PRODUCER, "rtpObserver.removeProducer" }, }; // clang-format on /* Instance methods. */ /** * msg contains the request flatbuffer. */ ChannelRequest::ChannelRequest(Channel::ChannelSocket* channel, const FBS::Request::Request* request) : channel(channel) { MS_TRACE(); this->data = request; this->id = request->id(); this->method = request->method(); const auto methodCStrIt = ChannelRequest::Method2String.find(this->method); if (methodCStrIt == ChannelRequest::Method2String.end()) { Error("unknown method"); MS_THROW_ERROR("unknown method '%" PRIu8 "'", static_cast(this->method)); } this->methodCStr = methodCStrIt->second; this->handlerId = this->data->handlerId()->str(); } void ChannelRequest::Accept() { MS_TRACE(); MS_ASSERT(!this->replied, "request already replied"); this->replied = true; auto& builder = ChannelRequest::bufferBuilder; auto response = FBS::Response::CreateResponse(builder, this->id, true, FBS::Response::Body::NONE, 0); SendResponse(response); } void ChannelRequest::Error(const char* reason) { MS_TRACE(); MS_ASSERT(!this->replied, "request already replied"); this->replied = true; auto& builder = ChannelRequest::bufferBuilder; auto response = FBS::Response::CreateResponseDirect( builder, this->id, /*accepted*/ false, FBS::Response::Body::NONE, 0, /*error*/ "Error", reason); SendResponse(response); } void ChannelRequest::TypeError(const char* reason) { MS_TRACE(); MS_ASSERT(!this->replied, "request already replied"); this->replied = true; auto& builder = ChannelRequest::bufferBuilder; auto response = FBS::Response::CreateResponseDirect( builder, this->id, /*accepted*/ false, FBS::Response::Body::NONE, 0, /*error*/ "TypeError", reason); SendResponse(response); } void ChannelRequest::Send(const uint8_t* buffer, size_t size) const { this->channel->Send(buffer, size); } void ChannelRequest::SendResponse(const flatbuffers::Offset& response) const { MS_TRACE(); auto& builder = ChannelRequest::bufferBuilder; auto message = FBS::Message::CreateMessage(builder, FBS::Message::Body::Response, response.Union()); builder.FinishSizePrefixed(message); Send(builder.GetBufferPointer(), builder.GetSize()); builder.Clear(); } } // namespace Channel ================================================ FILE: worker/src/Channel/ChannelSocket.cpp ================================================ #define MS_CLASS "Channel::ChannelSocket" // #define MS_LOG_DEV_LEVEL 3 #include "Channel/ChannelSocket.hpp" #include "DepLibUV.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::memcpy(), std::memmove() namespace Channel { // Binary length for a 4194304 bytes payload. static constexpr size_t MessageMaxLen{ 4194308 }; static constexpr size_t PayloadMaxLen{ 4194304 }; /* Static methods for UV callbacks. */ inline static void onAsync(uv_handle_t* handle) { while (static_cast(handle->data)->CallbackRead()) { // Read while there are new messages. } } inline static void onCloseAsync(uv_handle_t* handle) { delete reinterpret_cast(handle); } /* Instance methods. */ #if defined(MS_TEST) || defined(MS_FUZZER) ChannelSocket::ChannelSocket() { MS_TRACE_STD(); } #endif ChannelSocket::ChannelSocket(int consumerFd, int producerFd) : consumerSocket(new ConsumerSocket(consumerFd, MessageMaxLen, this)), producerSocket(new ProducerSocket(producerFd, MessageMaxLen)) { MS_TRACE_STD(); } ChannelSocket::ChannelSocket( ChannelReadFn channelReadFn, ChannelReadCtx channelReadCtx, ChannelWriteFn channelWriteFn, ChannelWriteCtx channelWriteCtx) : channelReadFn(channelReadFn), channelReadCtx(channelReadCtx), channelWriteFn(channelWriteFn), channelWriteCtx(channelWriteCtx), uvReadHandle(new uv_async_t) { MS_TRACE_STD(); int err; this->uvReadHandle->data = static_cast(this); err = uv_async_init(DepLibUV::GetLoop(), this->uvReadHandle, reinterpret_cast(onAsync)); if (err != 0) { delete this->uvReadHandle; this->uvReadHandle = nullptr; MS_THROW_ERROR_STD("uv_async_init() failed: %s", uv_strerror(err)); } err = uv_async_send(this->uvReadHandle); if (err != 0) { delete this->uvReadHandle; this->uvReadHandle = nullptr; MS_THROW_ERROR_STD("uv_async_send() failed: %s", uv_strerror(err)); } } ChannelSocket::~ChannelSocket() { MS_TRACE_STD(); if (!this->closed) { Close(); } delete this->consumerSocket; delete this->producerSocket; } void ChannelSocket::Close() { MS_TRACE_STD(); if (this->closed) { return; } this->closed = true; if (this->uvReadHandle) { uv_close( reinterpret_cast(this->uvReadHandle), static_cast(onCloseAsync)); } if (this->consumerSocket) { this->consumerSocket->Close(); } if (this->producerSocket) { this->producerSocket->Close(); } } void ChannelSocket::SetListener(Listener* listener) { MS_TRACE_STD(); this->listener = listener; } void ChannelSocket::Send(const uint8_t* data, uint32_t dataLen) { MS_TRACE_STD(); if (this->closed) { return; } if (dataLen > PayloadMaxLen) { MS_ERROR_STD("message too big"); return; } SendImpl(data, dataLen); } void ChannelSocket::SendLog(const char* data, uint32_t dataLen) { MS_TRACE_STD(); if (this->closed) { return; } if (dataLen > PayloadMaxLen) { MS_ERROR_STD("message too big"); return; } auto& builder = this->bufferBuilder; auto log = FBS::Log::CreateLogDirect(builder, data); auto message = FBS::Message::CreateMessage(builder, FBS::Message::Body::Log, log.Union()); builder.FinishSizePrefixed(message); this->Send(builder.GetBufferPointer(), builder.GetSize()); builder.Clear(); } bool ChannelSocket::CallbackRead() { MS_TRACE_STD(); if (this->closed) { return false; } uint8_t* msg{ nullptr }; uint32_t msgLen; size_t msgCtx; // Try to read next message using `channelReadFn`, message, its length and // context will be stored in provided arguments. auto free = this->channelReadFn( std::addressof(msg), std::addressof(msgLen), std::addressof(msgCtx), this->uvReadHandle, this->channelReadCtx); // Non-null free function pointer means message was successfully read above // and will need to be freed later. if (free) { const auto* message = FBS::Message::GetMessage(msg); #if MS_LOG_DEV_LEVEL == 3 auto s = flatbuffers::FlatBufferToString( reinterpret_cast(msg), FBS::Message::MessageTypeTable()); MS_DUMP("%s", s.c_str()); #endif if (message->data_type() == FBS::Message::Body::Request) { ChannelRequest* request{ nullptr }; try { request = new ChannelRequest(this, message->data_as()); // Notify the listener. this->listener->HandleRequest(request); } catch (const MediaSoupTypeError& error) { request->TypeError(error.what()); } catch (const MediaSoupError& error) { request->Error(error.what()); } delete request; } else if (message->data_type() == FBS::Message::Body::Notification) { ChannelNotification* notification{ nullptr }; try { notification = new ChannelNotification(message->data_as()); // Notify the listener. this->listener->HandleNotification(notification); } catch (const MediaSoupError& error) { MS_ERROR("notification failed: %s", error.what()); } delete notification; } else { MS_ERROR("discarding wrong Channel data"); } // Message needs to be freed using stored function pointer. free(msg, msgLen, msgCtx); } // Return `true` if something was processed. return free != nullptr; } void ChannelSocket::SendImpl(const uint8_t* payload, uint32_t payloadLen) { MS_TRACE_STD(); // Write using function call if provided. if (this->channelWriteFn) { this->channelWriteFn(payload, payloadLen, this->channelWriteCtx); } else if (this->producerSocket) { this->producerSocket->Write(payload, payloadLen); } } void ChannelSocket::OnConsumerSocketMessage( const ConsumerSocket* /*consumerSocket*/, char* msg, size_t /*msgLen*/) { MS_TRACE(); const auto* message = FBS::Message::GetMessage(msg); #if MS_LOG_DEV_LEVEL == 3 auto s = flatbuffers::FlatBufferToString( reinterpret_cast(msg), FBS::Message::MessageTypeTable()); MS_DUMP("%s", s.c_str()); #endif if (message->data_type() == FBS::Message::Body::Request) { ChannelRequest* request{ nullptr }; try { request = new ChannelRequest(this, message->data_as()); // Notify the listener. this->listener->HandleRequest(request); } catch (const MediaSoupTypeError& error) { request->TypeError(error.what()); } catch (const MediaSoupError& error) { request->Error(error.what()); } delete request; } else if (message->data_type() == FBS::Message::Body::Notification) { ChannelNotification* notification{ nullptr }; try { notification = new ChannelNotification(message->data_as()); // Notify the listener. this->listener->HandleNotification(notification); } catch (const MediaSoupError& error) { MS_ERROR("notification failed: %s", error.what()); } delete notification; } else { MS_ERROR("discarding wrong Channel data"); } } void ChannelSocket::OnConsumerSocketClosed(const ConsumerSocket* /*consumerSocket*/) { MS_TRACE_STD(); this->listener->OnChannelClosed(this); } /* Instance methods. */ ConsumerSocket::ConsumerSocket(int fd, size_t bufferSize, Listener* listener) : ::UnixStreamSocketHandle(fd, bufferSize, ::UnixStreamSocketHandle::Role::CONSUMER), listener(listener) { MS_TRACE_STD(); } ConsumerSocket::~ConsumerSocket() { MS_TRACE_STD(); } void ConsumerSocket::UserOnUnixStreamRead() { MS_TRACE_STD(); size_t msgStart{ 0 }; // Be ready to parse more than a single message in a single chunk. while (true) { if (IsClosed()) { return; } const size_t readLen = this->bufferDataLen - msgStart; if (readLen < sizeof(uint32_t)) { // Incomplete data. break; } uint32_t msgLen; // Read message length. std::memcpy(std::addressof(msgLen), this->buffer + msgStart, sizeof(uint32_t)); if (readLen < sizeof(uint32_t) + static_cast(msgLen)) { // Incomplete data. break; } this->listener->OnConsumerSocketMessage( this, reinterpret_cast(this->buffer + msgStart + sizeof(uint32_t)), static_cast(msgLen)); msgStart += sizeof(uint32_t) + static_cast(msgLen); } if (msgStart != 0) { this->bufferDataLen = this->bufferDataLen - msgStart; if (this->bufferDataLen != 0) { std::memmove(this->buffer, this->buffer + msgStart, this->bufferDataLen); } } } void ConsumerSocket::UserOnUnixStreamSocketClosed() { MS_TRACE_STD(); // Notify the listener. this->listener->OnConsumerSocketClosed(this); } /* Instance methods. */ ProducerSocket::ProducerSocket(int fd, size_t bufferSize) : ::UnixStreamSocketHandle(fd, bufferSize, ::UnixStreamSocketHandle::Role::PRODUCER) { MS_TRACE_STD(); } } // namespace Channel ================================================ FILE: worker/src/DepLibSRTP.cpp ================================================ #define MS_CLASS "DepLibSRTP" // #define MS_LOG_DEV_LEVEL 3 #include "DepLibSRTP.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include /* Static variables. */ static std::mutex GlobalSyncMutex; static size_t GlobalInstances = 0; // NOTE: This map must always be in sync with the srtp_err_status_t in srtp.h // in libsrtp library: // https://github.com/cisco/libsrtp/blob/main/include/srtp.h // // clang-format off const std::unordered_map DepLibSRTP::ErrorCode2String = { { srtp_err_status_ok, "nothing to report" }, { srtp_err_status_fail, "unspecified failure" }, { srtp_err_status_bad_param, "unsupported parameter" }, { srtp_err_status_alloc_fail, "couldn't allocate memory" }, { srtp_err_status_dealloc_fail, "couldn't deallocate properly" }, { srtp_err_status_init_fail, "couldn't initialize" }, { srtp_err_status_terminus, "can't process as much data as requested" }, { srtp_err_status_auth_fail, "authentication failure" }, { srtp_err_status_cipher_fail, "cipher failure" }, { srtp_err_status_replay_fail, "replay check failed (bad index)" }, { srtp_err_status_replay_old, "replay check failed (index too old)" }, { srtp_err_status_algo_fail, "algorithm failed test routine" }, { srtp_err_status_no_such_op, "unsupported operation" }, { srtp_err_status_no_ctx, "no appropriate context found" }, { srtp_err_status_cant_check, "unable to perform desired validation" }, { srtp_err_status_key_expired, "can't use key any more" }, { srtp_err_status_socket_err, "error in use of socket" }, { srtp_err_status_signal_err, "error in use POSIX signals" }, { srtp_err_status_nonce_bad, "nonce check failed" }, { srtp_err_status_read_fail, "couldn't read data" }, { srtp_err_status_write_fail, "couldn't write data" }, { srtp_err_status_parse_err, "error parsing data" }, { srtp_err_status_encode_err, "error encoding data" }, { srtp_err_status_semaphore_err, "error while using semaphores" }, { srtp_err_status_pfkey_err, "error while using pfkey" }, { srtp_err_status_bad_mki, "error MKI present in packet is invalid" }, { srtp_err_status_pkt_idx_old, "packet index is too old to consider" }, { srtp_err_status_pkt_idx_adv, "packet index advanced, reset needed" }, { srtp_err_status_buffer_small, "out buffer is too small" }, { srtp_err_status_cryptex_err, "unsupported cryptex operation" } }; // clang-format on /* Static methods. */ void DepLibSRTP::ClassInit() { MS_TRACE(); { const std::lock_guard lock(GlobalSyncMutex); if (GlobalInstances == 0) { MS_DEBUG_TAG(info, "libsrtp version: \"%s\"", srtp_get_version_string()); const srtp_err_status_t err = srtp_init(); if (DepLibSRTP::IsError(err)) { MS_THROW_ERROR("srtp_init() failed: %s", DepLibSRTP::GetErrorString(err).c_str()); } } ++GlobalInstances; } } void DepLibSRTP::ClassDestroy() { MS_TRACE(); { const std::lock_guard lock(GlobalSyncMutex); --GlobalInstances; if (GlobalInstances == 0) { srtp_shutdown(); } } } const std::string& DepLibSRTP::GetErrorString(srtp_err_status_t code) { MS_TRACE(); static const std::string UnknownError("unknown libsrtp error"); auto it = DepLibSRTP::ErrorCode2String.find(code); if (it == DepLibSRTP::ErrorCode2String.end()) { return UnknownError; } return it->second; } ================================================ FILE: worker/src/DepLibUV.cpp ================================================ #define MS_CLASS "DepLibUV" // #define MS_LOG_DEV_LEVEL 3 #include "DepLibUV.hpp" #include "Logger.hpp" /* Class variables. */ thread_local uv_loop_t* DepLibUV::loop{ nullptr }; /* Static methods for UV callbacks. */ inline static void onCloseLoop(uv_handle_t* handle) { delete reinterpret_cast(handle); } inline static void onWalk(uv_handle_t* handle, void* /*arg*/) { // Must use MS_ERROR_STD since at this point the Channel is already closed. MS_ERROR_STD( "alive UV handle found (this shouldn't happen) [type:%s, active:%d, closing:%d, has_ref:%d]", uv_handle_type_name(handle->type), uv_is_active(handle), uv_is_closing(handle), uv_has_ref(handle)); if (!uv_is_closing(handle)) { uv_close(handle, onCloseLoop); } } /* Static methods. */ void DepLibUV::ClassInit() { // NOTE: Logger depends on this so we cannot log anything here. DepLibUV::loop = new uv_loop_t; const int err = uv_loop_init(DepLibUV::loop); if (err != 0) { MS_ABORT("libuv loop initialization failed"); } } void DepLibUV::ClassDestroy() { MS_TRACE(); // Here we should not have any UV handle left. All them should have been // already closed+freed. However, in order to not introduce regressions // in the future, we check this anyway. // More context: https://github.com/versatica/mediasoup/pull/576 int err; uv_stop(DepLibUV::loop); uv_walk(DepLibUV::loop, onWalk, nullptr); while (true) { err = uv_loop_close(DepLibUV::loop); if (err != UV_EBUSY) { break; } uv_run(DepLibUV::loop, UV_RUN_NOWAIT); } if (err != 0) { MS_ERROR_STD("failed to close libuv loop: %s", uv_err_name(err)); } delete DepLibUV::loop; } void DepLibUV::PrintVersion() { MS_TRACE(); MS_DEBUG_TAG(info, "libuv version: \"%s\"", uv_version_string()); } void DepLibUV::RunLoop() { MS_TRACE(); // This should never happen. MS_ASSERT(DepLibUV::loop != nullptr, "loop unset"); const int ret = uv_run(DepLibUV::loop, UV_RUN_DEFAULT); MS_ASSERT(ret == 0, "uv_run() returned %s", uv_err_name(ret)); } ================================================ FILE: worker/src/DepLibUring.cpp ================================================ #define MS_CLASS "DepLibUring" // #define MS_LOG_DEV_LEVEL 3 #include "DepLibUring.hpp" #include "DepLibUV.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "Utils.hpp" #include #include #include #include /* Class variables. */ thread_local bool DepLibUring::enabled{ false }; // liburing instance per thread. thread_local DepLibUring::LibUring* DepLibUring::liburing{ nullptr }; // Completion queue entry array used to retrieve processes tasks. thread_local struct io_uring_cqe* Cqes[DepLibUring::QueueDepth]; /* Static methods for UV callbacks. */ inline static void onCloseFd(uv_handle_t* handle) { delete reinterpret_cast(handle); } inline static void onFdEvent(uv_poll_t* handle, int /*status*/, int /*events*/) { auto* liburing = static_cast(handle->data); auto count = io_uring_peek_batch_cqe(liburing->GetRing(), Cqes, DepLibUring::QueueDepth); // libuv uses level triggering, so we need to read from the socket to reset // the counter in order to avoid libuv calling this callback indefinitely. eventfd_t v; const int err = eventfd_read(liburing->GetEventFd(), std::addressof(v)); if (err < 0) { // Get positive errno. const int error = -err; MS_ABORT("eventfd_read() failed: %s", std::strerror(error)); }; for (unsigned int i{ 0 }; i < count; ++i) { struct io_uring_cqe* cqe = Cqes[i]; auto* userData = static_cast(io_uring_cqe_get_data(cqe)); if (liburing->IsZeroCopyEnabled()) { // CQE notification for a zero-copy submission. if (cqe->flags & IORING_CQE_F_NOTIF) { // The send buffer is now in the network card, run the send callback. if (userData->cb) { (*userData->cb)(true); delete userData->cb; userData->cb = nullptr; } liburing->ReleaseUserDataEntry(userData->idx); io_uring_cqe_seen(liburing->GetRing(), cqe); continue; } // CQE for a zero-copy submission, a CQE notification will follow. if (cqe->flags & IORING_CQE_F_MORE) { if (cqe->res < 0) { if (userData->cb) { (*userData->cb)(false); delete userData->cb; userData->cb = nullptr; } } // NOTE: Do not release the user data as it will be done upon reception // of CQE notification. io_uring_cqe_seen(liburing->GetRing(), cqe); continue; } } // Successfull SQE. if (cqe->res >= 0) { if (userData->cb) { (*userData->cb)(true); delete userData->cb; userData->cb = nullptr; } } // Failed SQE. else { if (userData->cb) { (*userData->cb)(false); delete userData->cb; userData->cb = nullptr; } } liburing->ReleaseUserDataEntry(userData->idx); io_uring_cqe_seen(liburing->GetRing(), cqe); } } /* Static class methods */ void DepLibUring::ClassInit() { const auto mayor = io_uring_major_version(); const auto minor = io_uring_minor_version(); MS_DEBUG_TAG(info, "io_uring version: \"%i.%i\"", mayor, minor); if (Settings::configuration.disableLiburing) { MS_DEBUG_TAG(info, "io_uring disabled by user settings"); return; } // This must be called first. if (DepLibUring::CheckRuntimeSupport()) { try { DepLibUring::liburing = new LibUring(); MS_DEBUG_TAG(info, "io_uring enabled"); DepLibUring::enabled = true; } catch (const MediaSoupError& error) { MS_DEBUG_TAG(info, "io_uring initialization failed, io_uring not enabled"); } } else { MS_DEBUG_TAG(info, "io_uring not enabled"); } } void DepLibUring::ClassDestroy() { MS_TRACE(); delete DepLibUring::liburing; } bool DepLibUring::CheckRuntimeSupport() { struct utsname buffer{}; const auto err = uname(std::addressof(buffer)); if (err != 0) { MS_THROW_ERROR("uname() failed: %s", std::strerror(err)); } MS_DEBUG_TAG(info, "kernel version: %s", buffer.version); auto* kernelMayorCstr = buffer.release; auto kernelMayorLong = strtol(kernelMayorCstr, &kernelMayorCstr, 10); // liburing `sento` capabilities are supported for kernel versions greather // than or equal to 6. if (kernelMayorLong < 6) { MS_DEBUG_TAG(info, "kernel doesn't support io_uring"); return false; } return true; } bool DepLibUring::IsEnabled() { return DepLibUring::enabled; } flatbuffers::Offset DepLibUring::FillBuffer(flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); MS_ASSERT(DepLibUring::enabled, "io_uring not enabled"); return DepLibUring::liburing->FillBuffer(builder); } void DepLibUring::StartPollingCQEs() { MS_TRACE(); MS_ASSERT(DepLibUring::enabled, "io_uring not enabled"); DepLibUring::liburing->StartPollingCQEs(); } void DepLibUring::StopPollingCQEs() { MS_TRACE(); MS_ASSERT(DepLibUring::enabled, "io_uring not enabled"); DepLibUring::liburing->StopPollingCQEs(); } uint8_t* DepLibUring::GetSendBuffer() { MS_TRACE(); MS_ASSERT(DepLibUring::enabled, "io_uring not enabled"); return DepLibUring::liburing->GetSendBuffer(); } bool DepLibUring::PrepareSend( int sockfd, const uint8_t* data, size_t len, const struct sockaddr* addr, onSendCallback* cb) { MS_TRACE(); MS_ASSERT(DepLibUring::enabled, "io_uring not enabled"); return DepLibUring::liburing->PrepareSend(sockfd, data, len, addr, cb); } bool DepLibUring::PrepareWrite( int sockfd, const uint8_t* data1, size_t len1, const uint8_t* data2, size_t len2, onSendCallback* cb) { MS_TRACE(); MS_ASSERT(DepLibUring::enabled, "io_uring not enabled"); return DepLibUring::liburing->PrepareWrite(sockfd, data1, len1, data2, len2, cb); } void DepLibUring::Submit() { MS_TRACE(); MS_ASSERT(DepLibUring::enabled, "io_uring not enabled"); DepLibUring::liburing->Submit(); } void DepLibUring::SetActive() { MS_TRACE(); MS_ASSERT(DepLibUring::enabled, "io_uring not enabled"); DepLibUring::liburing->SetActive(); } bool DepLibUring::IsActive() { MS_TRACE(); MS_ASSERT(DepLibUring::enabled, "io_uring not enabled"); return DepLibUring::liburing->IsActive(); } /* Instance methods. */ DepLibUring::LibUring::LibUring() { MS_TRACE(); /** * IORING_SETUP_SINGLE_ISSUER: A hint to the kernel that only a single task * (or thread) will submit requests, which is used for internal optimisations. */ const unsigned int flags = IORING_SETUP_SINGLE_ISSUER; // Initialize io_uring. auto err = io_uring_queue_init(DepLibUring::QueueDepth, std::addressof(this->ring), flags); if (err < 0) { // Get positive errno. const int error = -err; MS_THROW_ERROR("io_uring_queue_init() failed: %s", std::strerror(error)); } // Create an eventfd instance. this->efd = eventfd(0, 0); if (this->efd < 0) { MS_THROW_ERROR("eventfd() failed: %s", std::strerror(-this->efd)); } err = io_uring_register_eventfd(std::addressof(this->ring), this->efd); if (err < 0) { // Get positive errno. const int error = -err; MS_THROW_ERROR("io_uring_register_eventfd() failed: %s", std::strerror(error)); } // Initialize available UserData entries. for (size_t i{ 0 }; i < DepLibUring::QueueDepth; ++i) { this->userDatas[i].store = this->sendBuffers[i]; this->availableUserDataEntries.push(i); } // Initialize iovecs. for (size_t i{ 0 }; i < DepLibUring::QueueDepth; ++i) { this->iovecs[i].iov_base = this->sendBuffers[i]; this->iovecs[i].iov_len = DepLibUring::SendBufferSize; } err = io_uring_register_buffers(std::addressof(this->ring), this->iovecs, DepLibUring::QueueDepth); if (err < 0) { // Get positive errno. const int error = -err; if (error == ENOMEM) { this->zeroCopyEnabled = false; struct rlimit l = {}; if (getrlimit(RLIMIT_MEMLOCK, std::addressof(l)) == -1) { MS_WARN_TAG(info, "getrlimit() failed: %s", std::strerror(errno)); MS_WARN_TAG( info, "io_uring_register_buffers() failed due to low RLIMIT_MEMLOCK, disabling zero copy: %s", std::strerror(error)); } else { MS_WARN_TAG( info, "io_uring_register_buffers() failed due to low RLIMIT_MEMLOCK (soft:%llu, hard:%llu), disabling zero copy: %s", static_cast(l.rlim_cur), static_cast(l.rlim_max), std::strerror(error)); } } else { MS_THROW_ERROR("io_uring_register_buffers() failed: %s", std::strerror(error)); } } } DepLibUring::LibUring::~LibUring() { MS_TRACE(); // Close the event file descriptor. const auto err = close(this->efd); if (err != 0) { // Get positive errno. const int error = -err; try { MS_ABORT("close() failed: %s", std::strerror(error)); } catch (const std::exception& error) // NOLINT(bugprone-empty-catch) { // NOTE: This is to avoid a warning: // warning: ‘throw’ will always call ‘terminate’ [-Wterminate] } } // Close the ring. io_uring_queue_exit(std::addressof(this->ring)); } flatbuffers::Offset DepLibUring::LibUring::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::LibUring::CreateDump( builder, this->sqeProcessCount, this->sqeMissCount, this->userDataMissCount); } void DepLibUring::LibUring::StartPollingCQEs() { MS_TRACE(); // Watch the event file descriptor for completions. this->uvHandle = new uv_poll_t; auto err = uv_poll_init(DepLibUV::GetLoop(), this->uvHandle, this->efd); if (err != 0) { delete this->uvHandle; MS_THROW_ERROR("uv_poll_init() failed: %s", uv_strerror(err)); } this->uvHandle->data = this; err = uv_poll_start(this->uvHandle, UV_READABLE, static_cast(onFdEvent)); if (err != 0) { MS_THROW_ERROR("uv_poll_start() failed: %s", uv_strerror(err)); } } void DepLibUring::LibUring::StopPollingCQEs() { MS_TRACE(); this->uvHandle->data = nullptr; // Stop polling the event file descriptor. const auto err = uv_poll_stop(this->uvHandle); if (err != 0) { MS_ABORT("uv_poll_stop() failed: %s", uv_strerror(err)); } // NOTE: Handles that wrap file descriptors are clossed immediately. uv_close(reinterpret_cast(this->uvHandle), static_cast(onCloseFd)); } uint8_t* DepLibUring::LibUring::GetSendBuffer() { MS_TRACE(); if (this->availableUserDataEntries.empty()) { MS_DEBUG_DEV("no user data entry available"); return nullptr; } auto idx = this->availableUserDataEntries.front(); return this->userDatas[idx].store; } bool DepLibUring::LibUring::PrepareSend( int sockfd, const uint8_t* data, size_t len, const struct sockaddr* addr, onSendCallback* cb) { MS_TRACE(); auto* userData = this->GetUserData(); if (!userData) { MS_DEBUG_DEV("no user data entry available"); this->userDataMissCount++; return false; } auto* sqe = io_uring_get_sqe(std::addressof(this->ring)); if (!sqe) { MS_DEBUG_DEV("no sqe available"); this->sqeMissCount++; return false; } // The send data buffer belongs to us, no need to memcpy. if (this->IsDataInSendBuffers(data)) { MS_ASSERT(data == userData->store, "send buffer does not match userData store"); } else { std::memcpy(userData->store, data, len); } userData->cb = cb; io_uring_sqe_set_data(sqe, userData); const socklen_t addrlen = Utils::IP::GetAddressLen(addr); if (this->zeroCopyEnabled) { auto iovec = this->iovecs[userData->idx]; iovec.iov_len = len; io_uring_prep_send_zc(sqe, sockfd, iovec.iov_base, iovec.iov_len, 0, 0); io_uring_prep_send_set_addr(sqe, addr, addrlen); // Tell io_uring that we are providing the already registered send buffer // for zero copy. sqe->ioprio |= IORING_RECVSEND_FIXED_BUF; sqe->buf_index = userData->idx; } else { io_uring_prep_sendto(sqe, sockfd, userData->store, len, 0, addr, addrlen); } this->sqeProcessCount++; return true; } bool DepLibUring::LibUring::PrepareWrite( int sockfd, const uint8_t* data1, size_t len1, const uint8_t* data2, size_t len2, onSendCallback* cb) { MS_TRACE(); auto* userData = this->GetUserData(); if (!userData) { MS_DEBUG_DEV("no user data entry available"); this->userDataMissCount++; return false; } auto* sqe = io_uring_get_sqe(std::addressof(this->ring)); if (!sqe) { MS_DEBUG_DEV("no sqe available"); this->sqeMissCount++; return false; } // The send data buffer belongs to us, no need to memcpy. // NOTE: data1 contains the TCP framing buffer and data2 the actual payload. if (this->IsDataInSendBuffers(data2)) { MS_ASSERT(data2 == userData->store, "send buffer does not match userData store"); // Always memcpy the frame len as it resides in the stack memory. std::memcpy(userData->frameLen, data1, len1); userData->iov[0].iov_base = userData->frameLen; userData->iov[0].iov_len = len1; userData->iov[1].iov_base = userData->store; userData->iov[1].iov_len = len2; } else { std::memcpy(userData->store, data1, len1); std::memcpy(userData->store + len1, data2, len2); userData->iov[0].iov_base = userData->store; userData->iov[0].iov_len = len1; userData->iov[1].iov_base = userData->store + len1; userData->iov[1].iov_len = len2; } userData->cb = cb; io_uring_sqe_set_data(sqe, userData); io_uring_prep_writev(sqe, sockfd, userData->iov, 2, 0); this->sqeProcessCount++; return true; } void DepLibUring::LibUring::Submit() { MS_TRACE(); // Unset active flag. SetInactive(); const auto err = io_uring_submit(std::addressof(this->ring)); if (err >= 0) { MS_DEBUG_DEV("%i submission queue entries submitted", err); } else { // Get positive errno. const int error = -err; MS_ERROR("io_uring_submit() failed: %s", std::strerror(error)); } } DepLibUring::UserData* DepLibUring::LibUring::GetUserData() { MS_TRACE(); if (this->availableUserDataEntries.empty()) { return nullptr; } auto idx = this->availableUserDataEntries.front(); this->availableUserDataEntries.pop(); auto* userData = std::addressof(this->userDatas[idx]); userData->idx = idx; return userData; } ================================================ FILE: worker/src/DepLibWebRTC.cpp ================================================ #define MS_CLASS "DepLibWebRTC" // #define MS_LOG_DEV_LEVEL 3 #include "DepLibWebRTC.hpp" #include "Logger.hpp" #include "Settings.hpp" #include "system_wrappers/source/field_trial.h" // webrtc::field_trial #include /* Static. */ static std::once_flag GlobalInitOnce; /* Static methods. */ void DepLibWebRTC::ClassInit() { MS_TRACE(); MS_DEBUG_TAG( info, "libwebrtc field trials: \"%s\"", Settings::configuration.libwebrtcFieldTrials.c_str()); std::call_once( GlobalInitOnce, [] { webrtc::field_trial::InitFieldTrialsFromString( Settings::configuration.libwebrtcFieldTrials.c_str()); }); } void DepLibWebRTC::ClassDestroy() { MS_TRACE(); } ================================================ FILE: worker/src/DepOpenSSL.cpp ================================================ #define MS_CLASS "DepOpenSSL" // #define MS_LOG_DEV_LEVEL 3 #include "DepOpenSSL.hpp" #include "Logger.hpp" #include #include #include /* Static. */ static std::once_flag GlobalInitOnce; /* Static methods. */ void DepOpenSSL::ClassInit() { MS_TRACE(); std::call_once( GlobalInitOnce, [] { MS_DEBUG_TAG(info, "openssl version: \"%s\"", OpenSSL_version(OPENSSL_VERSION)); MS_DEBUG_TAG(info, "openssl CPU info: \"%s\"", OpenSSL_version(OPENSSL_CPU_INFO)); // Initialize some crypto stuff. RAND_poll(); }); } ================================================ FILE: worker/src/DepUsrSCTP.cpp ================================================ #define MS_CLASS "DepUsrSCTP" // #define MS_LOG_DEV_LEVEL 3 #include "DepUsrSCTP.hpp" #ifdef MS_LIBURING_SUPPORTED #include "DepLibUring.hpp" #endif #include "DepLibUV.hpp" #include "Logger.hpp" #include #include // std::vsnprintf() #include /* Static. */ static constexpr size_t CheckerInterval{ 10u }; // In ms. static std::mutex GlobalSyncMutex; static size_t GlobalInstances{ 0u }; /* Static methods for usrsctp global callbacks. */ inline static int onSendSctpData(void* addr, void* data, size_t len, uint8_t /*tos*/, uint8_t /*setDf*/) { auto* sctpAssociation = DepUsrSCTP::RetrieveSctpAssociation(reinterpret_cast(addr)); if (!sctpAssociation) { MS_WARN_TAG(sctp, "no SctpAssociation found"); return -1; } sctpAssociation->OnUsrSctpSendSctpData(data, len); // NOTE: Must not free data, usrsctp lib does it. return 0; } // Static method for printing usrsctp debug. inline static void sctpDebug(const char* format, ...) { char buffer[10000]; va_list ap; va_start(ap, format); vsnprintf(buffer, sizeof(buffer), format, ap); // Remove the artificial carriage return set by usrsctp. buffer[std::strlen(buffer) - 1] = '\0'; MS_DEBUG_TAG(sctp, "%s", buffer); va_end(ap); } /* Class variables. */ thread_local DepUsrSCTP::Checker* DepUsrSCTP::checker{ nullptr }; uint64_t DepUsrSCTP::numSctpAssociations{ 0u }; uintptr_t DepUsrSCTP::nextSctpAssociationId{ 0u }; absl::flat_hash_map DepUsrSCTP::mapIdSctpAssociation; /* Static methods. */ void DepUsrSCTP::ClassInit() { MS_TRACE(); MS_DEBUG_TAG(info, "usrsctp"); const std::scoped_lock lock(GlobalSyncMutex); if (GlobalInstances == 0) { usrsctp_init_nothreads(0, onSendSctpData, sctpDebug); // See https://github.com/sctplab/usrsctp/blob/master/Manual.md#usrsctp_sysctl_set_sctp_sendspace. // // TODO: This doesn't have any effect. So let's comment it. // usrsctp_sysctl_set_sctp_sendspace(std::numeric_limits::max()); // usrsctp_sysctl_set_sctp_recvspace(std::numeric_limits::max()); // Disable explicit congestion notifications (ecn). usrsctp_sysctl_set_sctp_ecn_enable(0); #ifdef SCTP_DEBUG usrsctp_sysctl_set_sctp_debug_on(SCTP_DEBUG_ALL); #endif } ++GlobalInstances; } void DepUsrSCTP::ClassDestroy() { MS_TRACE(); const std::scoped_lock lock(GlobalSyncMutex); --GlobalInstances; if (GlobalInstances == 0) { usrsctp_finish(); numSctpAssociations = 0u; nextSctpAssociationId = 0u; DepUsrSCTP::mapIdSctpAssociation.clear(); } } void DepUsrSCTP::CreateChecker(SharedInterface* shared) { MS_TRACE(); MS_ASSERT(DepUsrSCTP::checker == nullptr, "Checker already created"); DepUsrSCTP::checker = new DepUsrSCTP::Checker(shared); } void DepUsrSCTP::CloseChecker() { MS_TRACE(); MS_ASSERT(DepUsrSCTP::checker != nullptr, "Checker not created"); delete DepUsrSCTP::checker; } uintptr_t DepUsrSCTP::GetNextSctpAssociationId() { MS_TRACE(); const std::scoped_lock lock(GlobalSyncMutex); // NOTE: usrsctp_connect() fails with a value of 0. if (DepUsrSCTP::nextSctpAssociationId == 0u) { ++DepUsrSCTP::nextSctpAssociationId; } // In case we've wrapped around and need to find an empty spot from a removed // SctpAssociation. Assumes we'll never be full. while (DepUsrSCTP::mapIdSctpAssociation.find(DepUsrSCTP::nextSctpAssociationId) != DepUsrSCTP::mapIdSctpAssociation.end()) { ++DepUsrSCTP::nextSctpAssociationId; if (DepUsrSCTP::nextSctpAssociationId == 0u) { ++DepUsrSCTP::nextSctpAssociationId; } } return DepUsrSCTP::nextSctpAssociationId++; } void DepUsrSCTP::RegisterSctpAssociation(RTC::SctpAssociation* sctpAssociation) { MS_TRACE(); const std::scoped_lock lock(GlobalSyncMutex); MS_ASSERT(DepUsrSCTP::checker != nullptr, "Checker not created"); auto it = DepUsrSCTP::mapIdSctpAssociation.find(sctpAssociation->id); MS_ASSERT( it == DepUsrSCTP::mapIdSctpAssociation.end(), "the id of the SctpAssociation is already in the map"); DepUsrSCTP::mapIdSctpAssociation[sctpAssociation->id] = sctpAssociation; if (++DepUsrSCTP::numSctpAssociations == 1u) { DepUsrSCTP::checker->Start(); } } void DepUsrSCTP::DeregisterSctpAssociation(RTC::SctpAssociation* sctpAssociation) { MS_TRACE(); const std::scoped_lock lock(GlobalSyncMutex); MS_ASSERT(DepUsrSCTP::checker != nullptr, "Checker not created"); auto found = DepUsrSCTP::mapIdSctpAssociation.erase(sctpAssociation->id); MS_ASSERT(found > 0, "SctpAssociation not found"); MS_ASSERT(DepUsrSCTP::numSctpAssociations > 0u, "numSctpAssociations was not higher than 0"); if (--DepUsrSCTP::numSctpAssociations == 0u) { DepUsrSCTP::checker->Stop(); } } RTC::SctpAssociation* DepUsrSCTP::RetrieveSctpAssociation(uintptr_t id) { MS_TRACE(); const std::scoped_lock lock(GlobalSyncMutex); auto it = DepUsrSCTP::mapIdSctpAssociation.find(id); if (it == DepUsrSCTP::mapIdSctpAssociation.end()) { return nullptr; } return it->second; } /* DepUsrSCTP::Checker instance methods. */ DepUsrSCTP::Checker::Checker(SharedInterface* shared) : timer(shared->CreateTimer(this)) { MS_TRACE(); } DepUsrSCTP::Checker::~Checker() { MS_TRACE(); delete this->timer; } void DepUsrSCTP::Checker::Start() { MS_TRACE(); MS_DEBUG_TAG(sctp, "usrsctp periodic check started"); this->lastCalledAtMs = 0u; this->timer->Start(CheckerInterval, CheckerInterval); } void DepUsrSCTP::Checker::Stop() { MS_TRACE(); MS_DEBUG_TAG(sctp, "usrsctp periodic check stopped"); this->lastCalledAtMs = 0u; this->timer->Stop(); } void DepUsrSCTP::Checker::OnTimer(TimerHandleInterface* /*timer*/) { MS_TRACE(); auto nowMs = DepLibUV::GetTimeMs(); const int elapsedMs = this->lastCalledAtMs ? static_cast(nowMs - this->lastCalledAtMs) : 0; #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Activate liburing usage. // 'usrsctp_handle_timers()' will synchronously call the send/recv // callbacks for the pending data. If there are multiple messages to be // sent over the network then we will send those messages within a single // system call. DepLibUring::SetActive(); } #endif usrsctp_handle_timers(elapsedMs); #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Submit all prepared submission entries. DepLibUring::Submit(); } #endif this->lastCalledAtMs = nowMs; } ================================================ FILE: worker/src/Logger.cpp ================================================ #define MS_CLASS "Logger" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include /* Class variables. */ const uint64_t Logger::Pid{ static_cast(uv_os_getpid()) }; thread_local Channel::ChannelSocket* Logger::channel{ nullptr }; thread_local char Logger::buffer[Logger::BufferSize]; /* Class methods. */ void Logger::ClassInit(Channel::ChannelSocket* channel) { Logger::channel = channel; MS_TRACE(); } ================================================ FILE: worker/src/MediaSoupErrors.cpp ================================================ #define MS_CLASS "MediaSoupError" #include "MediaSoupErrors.hpp" /* Class variables. */ thread_local char MediaSoupError::buffer[MediaSoupError::BufferSize]; ================================================ FILE: worker/src/RTC/ActiveSpeakerObserver.cpp ================================================ #define MS_CLASS "RTC::ActiveSpeakerObserver" #include "RTC/ActiveSpeakerObserver.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/RtpDictionaries.hpp" namespace RTC { /* Static. */ static constexpr uint32_t C1{ 3u }; static constexpr uint32_t C2{ 2u }; static constexpr uint32_t C3{ 0u }; static constexpr uint32_t N1{ 13u }; static constexpr uint32_t N2{ 5u }; static constexpr uint32_t N3{ 10u }; static constexpr uint32_t LongCount{ 1u }; static constexpr uint32_t LevelIdleTimeout{ 40u }; static constexpr uint64_t SpeakerIdleTimeout{ 60 * 60 * 1000 }; static constexpr uint32_t LongThreashold{ 4u }; static constexpr uint32_t MaxLevel{ 127u }; static constexpr uint32_t MinLevel{ 0u }; static constexpr uint32_t MinLevelWindowLen{ 15 * 1000 / 20 }; static constexpr uint32_t MediumThreshold{ 7u }; static constexpr uint32_t SubunitLengthN1{ (MaxLevel - MinLevel + N1 - 1) / N1 }; static constexpr uint32_t ImmediateBuffLen{ LongCount * N3 * N2 }; static constexpr uint32_t MediumsBuffLen{ LongCount * N3 }; static constexpr uint32_t LongsBuffLen{ LongCount }; static constexpr uint32_t LevelsBuffLen{ LongCount * N3 * N2 }; static constexpr double MinActivityScore{ 0.0000000001 }; static inline int64_t binomialCoefficient(int32_t n, int32_t r) { const int32_t m = n - r; r = std::max(r, m); int64_t t{ 1 }; for (int64_t i = n, j = 1; i > r; i--, ++j) { t = t * i / j; } return t; } static inline double computeActivityScore( const uint8_t vL, const uint32_t nR, const double p, const double lambda) { double activityScore = std::log(binomialCoefficient(nR, vL)) + (vL * std::log(p)) + ((nR - vL) * std::log(1 - p)) - std::log(lambda) + (lambda * vL); activityScore = std::max(activityScore, MinActivityScore); return activityScore; } static inline bool computeBigs( const std::vector& littles, std::vector& bigs, uint8_t threashold) { const uint32_t littleLen = littles.size(); const uint32_t bigLen = bigs.size(); const uint32_t littleLenPerBig = littleLen / bigLen; bool changed{ false }; for (uint32_t b = 0u, l = 0u; b < bigLen; ++b) { uint8_t sum{ 0u }; for (const uint32_t lEnd = l + littleLenPerBig; l < lEnd; ++l) { if (littles[l] > threashold) { ++sum; } } if (bigs[b] != sum) { bigs[b] = sum; changed = true; } } return changed; } ActiveSpeakerObserver::ActiveSpeakerObserver( SharedInterface* shared, const std::string& id, RTC::RtpObserver::Listener* listener, const FBS::ActiveSpeakerObserver::ActiveSpeakerObserverOptions* options) : RTC::RtpObserver(shared, id, listener), interval(options->interval()) { MS_TRACE(); if (this->interval < 100) { this->interval = 100; } else if (this->interval > 5000) { this->interval = 5000; } this->periodicTimer = this->shared->CreateTimer(this); this->periodicTimer->Start(interval, interval); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ nullptr); } ActiveSpeakerObserver::~ActiveSpeakerObserver() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); delete this->periodicTimer; // Must clear all entries in this->mapProducerSpeakers since RemoveProducer() // won't be called for each existing Producer if the ActiveSpeakerObserver or // its parent Router are directly closed. for (auto& kv : this->mapProducerSpeakers) { auto* producerSpeaker = kv.second; delete producerSpeaker; } this->mapProducerSpeakers.clear(); } void ActiveSpeakerObserver::AddProducer(RTC::Producer* producer) { MS_TRACE(); if (producer->GetKind() != RTC::Media::Kind::AUDIO) { MS_THROW_TYPE_ERROR("not an audio Producer"); } if (this->mapProducerSpeakers.find(producer->id) != this->mapProducerSpeakers.end()) { MS_THROW_ERROR("Producer already in map"); } this->mapProducerSpeakers[producer->id] = new ProducerSpeaker(this->shared, producer); } void ActiveSpeakerObserver::RemoveProducer(RTC::Producer* producer) { MS_TRACE(); auto it = this->mapProducerSpeakers.find(producer->id); if (it == this->mapProducerSpeakers.end()) { return; } auto* producerSpeaker = it->second; delete producerSpeaker; this->mapProducerSpeakers.erase(producer->id); if (producer->id == this->dominantId) { this->dominantId.erase(); Update(); } } void ActiveSpeakerObserver::ProducerPaused(RTC::Producer* producer) { MS_TRACE(); auto it = this->mapProducerSpeakers.find(producer->id); if (it != this->mapProducerSpeakers.end()) { auto* producerSpeaker = it->second; producerSpeaker->speaker->paused = true; } } void ActiveSpeakerObserver::ProducerResumed(RTC::Producer* producer) { MS_TRACE(); auto it = this->mapProducerSpeakers.find(producer->id); if (it != this->mapProducerSpeakers.end()) { auto* producerSpeaker = it->second; producerSpeaker->speaker->paused = false; } } void ActiveSpeakerObserver::ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) { MS_TRACE(); if (IsPaused()) { return; } uint8_t level{ 0 }; bool voice{ false }; if (!packet->ReadSsrcAudioLevel(level, voice)) { return; } const uint8_t volume = 127 - level; auto it = this->mapProducerSpeakers.find(producer->id); if (it != this->mapProducerSpeakers.end()) { auto* producerSpeaker = it->second; const uint64_t now = this->shared->GetTimeMs(); producerSpeaker->speaker->LevelChanged(volume, now); } } void ActiveSpeakerObserver::Paused() { MS_TRACE(); this->periodicTimer->Stop(); } void ActiveSpeakerObserver::Resumed() { MS_TRACE(); this->periodicTimer->Restart(); } void ActiveSpeakerObserver::OnTimer(TimerHandleInterface* /*timer*/) { MS_TRACE(); Update(); } void ActiveSpeakerObserver::Update() { MS_TRACE(); const uint64_t now = this->shared->GetTimeMs(); if (now - this->lastLevelIdleTime >= LevelIdleTimeout) { if (this->lastLevelIdleTime != 0) { TimeoutIdleLevels(now); } this->lastLevelIdleTime = now; } if (!this->mapProducerSpeakers.empty() && CalculateActiveSpeaker()) { auto notification = FBS::ActiveSpeakerObserver::CreateDominantSpeakerNotificationDirect( this->shared->GetChannelNotifier()->GetBufferBuilder(), this->dominantId.c_str()); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::ACTIVESPEAKEROBSERVER_DOMINANT_SPEAKER, FBS::Notification::Body::ActiveSpeakerObserver_DominantSpeakerNotification, notification); } } bool ActiveSpeakerObserver::CalculateActiveSpeaker() { MS_TRACE(); std::string newDominantId; const int32_t speakerCount = this->mapProducerSpeakers.size(); if (speakerCount == 0) { newDominantId = ""; } else if (speakerCount == 1) { auto it = this->mapProducerSpeakers.begin(); auto* producerSpeaker = it->second; newDominantId = producerSpeaker->producer->id; } else { Speaker* dominantSpeaker = (this->dominantId.empty()) ? nullptr : this->mapProducerSpeakers.at(this->dominantId)->speaker; if (dominantSpeaker == nullptr) { auto it = this->mapProducerSpeakers.begin(); newDominantId = it->first; auto* producerSpeaker = it->second; dominantSpeaker = producerSpeaker->speaker; } else { newDominantId = ""; } dominantSpeaker->EvalActivityScores(); double newDominantC2 = C2; for (auto& kv : this->mapProducerSpeakers) { auto* producerSpeaker = kv.second; auto* speaker = producerSpeaker->speaker; const auto& id = producerSpeaker->producer->id; if (id == this->dominantId || speaker->paused) { continue; } speaker->EvalActivityScores(); for (uint8_t interval = 0u; interval < ActiveSpeakerObserver::RelativeSpeachActivitiesLen; ++interval) { this->relativeSpeachActivities[interval] = std::log( speaker->GetActivityScore(interval) / dominantSpeaker->GetActivityScore(interval)); } const double c1 = this->relativeSpeachActivities[0]; const double c2 = this->relativeSpeachActivities[1]; const double c3 = this->relativeSpeachActivities[2]; if ((c1 > C1) && (c2 > C2) && (c3 > C3) && (c2 > newDominantC2)) { newDominantC2 = c2; newDominantId = id; } } } if (!newDominantId.empty() && newDominantId != this->dominantId) { this->dominantId = newDominantId; return true; } return false; } void ActiveSpeakerObserver::TimeoutIdleLevels(uint64_t now) { MS_TRACE(); for (auto& kv : this->mapProducerSpeakers) { auto* producerSpeaker = kv.second; auto* speaker = producerSpeaker->speaker; const auto& id = producerSpeaker->producer->id; const uint64_t idle = now - speaker->lastLevelChangeTime; if (SpeakerIdleTimeout < idle && (this->dominantId.empty() || id != this->dominantId)) { speaker->paused = true; } else if (LevelIdleTimeout < idle) { speaker->LevelTimedOut(now); } } } ActiveSpeakerObserver::ProducerSpeaker::ProducerSpeaker(SharedInterface* shared, RTC::Producer* producer) : producer(producer), speaker(new Speaker(shared)) { MS_TRACE(); this->speaker->paused = producer->IsPaused(); } ActiveSpeakerObserver::ProducerSpeaker::~ProducerSpeaker() { MS_TRACE(); delete this->speaker; } ActiveSpeakerObserver::Speaker::Speaker(SharedInterface* shared) : immediateActivityScore(MinActivityScore), mediumActivityScore(MinActivityScore), longActivityScore(MinActivityScore), lastLevelChangeTime(shared->GetTimeMs()), minLevel(MinLevel), nextMinLevel(MinLevel), immediates(ImmediateBuffLen, 0), mediums(MediumsBuffLen, 0), longs(LongsBuffLen, 0), levels(LevelsBuffLen, 0) { MS_TRACE(); } void ActiveSpeakerObserver::Speaker::EvalActivityScores() { MS_TRACE(); if (ComputeImmediates()) { EvalImmediateActivityScore(); if (ComputeMediums()) { EvalMediumActivityScore(); if (ComputeLongs()) { EvalLongActivityScore(); } } } } double ActiveSpeakerObserver::Speaker::GetActivityScore(uint8_t interval) const { MS_TRACE(); switch (interval) { case 0u: return this->immediateActivityScore; case 1u: return this->mediumActivityScore; case 2u: return this->longActivityScore; default: MS_ABORT("interval is invalid"); } return 0; } void ActiveSpeakerObserver::Speaker::LevelChanged(uint32_t level, uint64_t now) { if (this->lastLevelChangeTime <= now) { const uint64_t elapsed = now - this->lastLevelChangeTime; this->lastLevelChangeTime = now; int8_t b{ 0 }; if (level < MinLevel) { b = MinLevel; } else if (level > MaxLevel) { b = MaxLevel; } else { b = level; } // The algorithm expect to have an update every 20 milliseconds. If the // Producer is paused, using a different packetization time or using DTX // we need to update more than one sample when receiving an audio packet. const uint32_t intervalsUpdated = std::min(std::max(static_cast(elapsed / 20), 1U), LevelsBuffLen); for (uint32_t i{ 0u }; i < intervalsUpdated; ++i) { this->levels[this->nextLevelIndex] = b; this->nextLevelIndex = (this->nextLevelIndex + 1) % LevelsBuffLen; } UpdateMinLevel(b); } } void ActiveSpeakerObserver::Speaker::LevelTimedOut(uint64_t now) { MS_TRACE(); LevelChanged(MinLevel, now); } bool ActiveSpeakerObserver::Speaker::ComputeImmediates() { MS_TRACE(); const int8_t minLevel = this->minLevel + SubunitLengthN1; bool changed = false; for (uint32_t i = 0; i < ImmediateBuffLen; ++i) { // this->levels is a circular buffer where new samples are written in the // next vector index. this->immediates is a buffer where the most recent // value is always in index 0. const size_t levelIndex = this->nextLevelIndex >= (i + 1) ? this->nextLevelIndex - i - 1 : this->nextLevelIndex + LevelsBuffLen - i - 1; uint8_t level = this->levels[levelIndex]; if (level < minLevel) { level = MinLevel; } const uint8_t immediate = (level / SubunitLengthN1); if (this->immediates[i] != immediate) { this->immediates[i] = immediate; changed = true; } } return changed; } bool ActiveSpeakerObserver::Speaker::ComputeMediums() { MS_TRACE(); return computeBigs(this->immediates, this->mediums, MediumThreshold); } bool ActiveSpeakerObserver::Speaker::ComputeLongs() { MS_TRACE(); return computeBigs(this->mediums, this->longs, LongThreashold); } void ActiveSpeakerObserver::Speaker::EvalImmediateActivityScore() { MS_TRACE(); this->immediateActivityScore = computeActivityScore(this->immediates[0], N1, 0.5, 0.78); } void ActiveSpeakerObserver::Speaker::EvalMediumActivityScore() { MS_TRACE(); this->mediumActivityScore = computeActivityScore(this->mediums[0], N2, 0.5, 24); } void ActiveSpeakerObserver::Speaker::EvalLongActivityScore() { MS_TRACE(); this->longActivityScore = computeActivityScore(this->longs[0], N3, 0.5, 47); } void ActiveSpeakerObserver::Speaker::UpdateMinLevel(int8_t level) { MS_TRACE(); if (level == MinLevel) { return; } if ((this->minLevel == MinLevel) || (this->minLevel > level)) { this->minLevel = level; this->nextMinLevel = MinLevel; this->nextMinLevelWindowLen = 0; } else { if (this->nextMinLevel == MinLevel) { this->nextMinLevel = level; this->nextMinLevelWindowLen = 1; } else { this->nextMinLevel = std::min(this->nextMinLevel, level); this->nextMinLevelWindowLen++; if (this->nextMinLevelWindowLen >= MinLevelWindowLen) { double newMinLevel = std::sqrt(static_cast(this->minLevel * this->nextMinLevel)); if (newMinLevel < MinLevel) { newMinLevel = MinLevel; } else if (newMinLevel > MaxLevel) { newMinLevel = MaxLevel; } this->minLevel = static_cast(newMinLevel); this->nextMinLevel = MinLevel; this->nextMinLevelWindowLen = 0; } } } } } // namespace RTC ================================================ FILE: worker/src/RTC/AudioLevelObserver.cpp ================================================ #define MS_CLASS "RTC::AudioLevelObserver" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/AudioLevelObserver.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/RtpDictionaries.hpp" #include #include // std::lround() namespace RTC { /* Instance methods. */ AudioLevelObserver::AudioLevelObserver( SharedInterface* shared, const std::string& id, RTC::RtpObserver::Listener* listener, const FBS::AudioLevelObserver::AudioLevelObserverOptions* options) : RTC::RtpObserver(shared, id, listener) { MS_TRACE(); this->maxEntries = options->maxEntries(); this->threshold = options->threshold(); this->interval = options->interval(); if (this->threshold > 0) { MS_THROW_TYPE_ERROR("invalid threshold value %" PRIi8, this->threshold); } if (this->interval < 250) { this->interval = 250; } else if (this->interval > 5000) { this->interval = 5000; } this->periodicTimer = this->shared->CreateTimer(this); this->periodicTimer->Start(this->interval, this->interval); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ nullptr); } AudioLevelObserver::~AudioLevelObserver() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); delete this->periodicTimer; } void AudioLevelObserver::AddProducer(RTC::Producer* producer) { MS_TRACE(); if (producer->GetKind() != RTC::Media::Kind::AUDIO) { MS_THROW_TYPE_ERROR("not an audio Producer"); } // Insert into the map. this->mapProducerDBovs[producer]; } void AudioLevelObserver::RemoveProducer(RTC::Producer* producer) { MS_TRACE(); // Remove from the map. this->mapProducerDBovs.erase(producer); } void AudioLevelObserver::ReceiveRtpPacket(RTC::Producer* producer, RTC::RTP::Packet* packet) { MS_TRACE(); if (IsPaused()) { return; } uint8_t volume{ 0 }; bool voice{ false }; if (!packet->ReadSsrcAudioLevel(volume, voice)) { return; } auto& dBovs = this->mapProducerDBovs.at(producer); dBovs.totalSum += volume; dBovs.count++; } void AudioLevelObserver::ProducerPaused(RTC::Producer* producer) { // Remove from the map. this->mapProducerDBovs.erase(producer); } void AudioLevelObserver::ProducerResumed(RTC::Producer* producer) { // Insert into the map. this->mapProducerDBovs[producer]; } void AudioLevelObserver::Paused() { MS_TRACE(); this->periodicTimer->Stop(); ResetMapProducerDBovs(); if (!this->silence) { this->silence = true; this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::AUDIOLEVELOBSERVER_SILENCE); } } void AudioLevelObserver::Resumed() { MS_TRACE(); this->periodicTimer->Restart(); } void AudioLevelObserver::Update() { MS_TRACE(); absl::btree_multimap mapDBovsProducer; for (auto& kv : this->mapProducerDBovs) { auto* producer = kv.first; auto& dBovs = kv.second; if (dBovs.count < 10) { continue; } auto avgDBov = -1 * static_cast(std::lround(dBovs.totalSum / dBovs.count)); if (avgDBov >= this->threshold) { mapDBovsProducer.insert({ avgDBov, producer }); } } // Clear the map. ResetMapProducerDBovs(); if (!mapDBovsProducer.empty()) { this->silence = false; uint16_t idx{ 0 }; auto rit = mapDBovsProducer.crbegin(); std::vector> volumes; for (; idx < this->maxEntries && rit != mapDBovsProducer.crend(); ++idx, ++rit) { volumes.emplace_back( FBS::AudioLevelObserver::CreateVolumeDirect( this->shared->GetChannelNotifier()->GetBufferBuilder(), rit->second->id.c_str(), rit->first)); } auto notification = FBS::AudioLevelObserver::CreateVolumesNotificationDirect( this->shared->GetChannelNotifier()->GetBufferBuilder(), &volumes); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::AUDIOLEVELOBSERVER_VOLUMES, FBS::Notification::Body::AudioLevelObserver_VolumesNotification, notification); } else if (!this->silence) { this->silence = true; this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::AUDIOLEVELOBSERVER_SILENCE); } } void AudioLevelObserver::ResetMapProducerDBovs() { MS_TRACE(); for (auto& kv : this->mapProducerDBovs) { auto& dBovs = kv.second; dBovs.totalSum = 0; dBovs.count = 0; } } inline void AudioLevelObserver::OnTimer(TimerHandleInterface* /*timer*/) { MS_TRACE(); Update(); } } // namespace RTC ================================================ FILE: worker/src/RTC/Consumer.cpp ================================================ #define MS_CLASS "RTC::Consumer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/Consumer.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { /* Instance methods. */ Consumer::Consumer( SharedInterface* shared, const std::string& id, const std::string& producerId, Listener* listener, const FBS::Transport::ConsumeRequest* data, RTC::RtpParameters::Type type) : id(id), producerId(producerId), shared(shared), listener(listener), kind(RTC::Media::Kind(data->kind())), type(type) { MS_TRACE(); // This may throw. this->rtpParameters = RTC::RtpParameters(data->rtpParameters()); if (this->rtpParameters.encodings.empty()) { MS_THROW_TYPE_ERROR("empty rtpParameters.encodings"); } // All encodings must have SSRCs. for (auto& encoding : this->rtpParameters.encodings) { if (encoding.ssrc == 0) { MS_THROW_TYPE_ERROR("invalid encoding in rtpParameters (missing ssrc)"); } else if (encoding.hasRtx && encoding.rtx.ssrc == 0) { MS_THROW_TYPE_ERROR("invalid encoding in rtpParameters (missing rtx.ssrc)"); } } if (data->consumableRtpEncodings()->size() == 0) { MS_THROW_TYPE_ERROR("empty consumableRtpEncodings"); } this->consumableRtpEncodings.reserve(data->consumableRtpEncodings()->size()); for (size_t i{ 0 }; i < data->consumableRtpEncodings()->size(); ++i) { const auto* entry = data->consumableRtpEncodings()->Get(i); // This may throw due the constructor of RTC::RtpEncodingParameters. this->consumableRtpEncodings.emplace_back(entry); // Verify that it has ssrc field. auto& encoding = this->consumableRtpEncodings[i]; if (encoding.ssrc == 0u) { MS_THROW_TYPE_ERROR("wrong encoding in consumableRtpEncodings (missing ssrc)"); } } // Fill RTP header extension ids and their mapped values. // This may throw. for (auto& exten : this->rtpParameters.headerExtensions) { if (exten.id == 0u) { MS_THROW_TYPE_ERROR("RTP extension id cannot be 0"); } if (this->rtpHeaderExtensionIds.mid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MID) { this->rtpHeaderExtensionIds.mid = exten.id; } if (this->rtpHeaderExtensionIds.rid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID) { this->rtpHeaderExtensionIds.rid = exten.id; } if (this->rtpHeaderExtensionIds.rrid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID) { this->rtpHeaderExtensionIds.rrid = exten.id; } if (this->rtpHeaderExtensionIds.absSendTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME) { this->rtpHeaderExtensionIds.absSendTime = exten.id; } if (this->rtpHeaderExtensionIds.transportWideCc01 == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01) { this->rtpHeaderExtensionIds.transportWideCc01 = exten.id; } if (this->rtpHeaderExtensionIds.ssrcAudioLevel == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL) { this->rtpHeaderExtensionIds.ssrcAudioLevel = exten.id; } if ( this->rtpHeaderExtensionIds.dependencyDescriptor == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR) { this->rtpHeaderExtensionIds.dependencyDescriptor = exten.id; } if (this->rtpHeaderExtensionIds.videoOrientation == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION) { this->rtpHeaderExtensionIds.videoOrientation = exten.id; } if (this->rtpHeaderExtensionIds.videoOrientation == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION) { this->rtpHeaderExtensionIds.videoOrientation = exten.id; } if (this->rtpHeaderExtensionIds.absCaptureTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME) { this->rtpHeaderExtensionIds.absCaptureTime = exten.id; } if (this->rtpHeaderExtensionIds.playoutDelay == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY) { this->rtpHeaderExtensionIds.playoutDelay = exten.id; } if (this->rtpHeaderExtensionIds.mediasoupPacketId == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID) { this->rtpHeaderExtensionIds.mediasoupPacketId = exten.id; } } // paused is set to false by default. this->paused = data->paused(); // Fill supported codec payload types. for (auto& codec : this->rtpParameters.codecs) { if (codec.mimeType.IsMediaCodec()) { this->supportedCodecPayloadTypes[codec.payloadType] = true; } } // Fill media SSRCs vector. for (auto& encoding : this->rtpParameters.encodings) { this->mediaSsrcs.push_back(encoding.ssrc); } // Fill RTX SSRCs vector. for (auto& encoding : this->rtpParameters.encodings) { if (encoding.hasRtx) { this->rtxSsrcs.push_back(encoding.rtx.ssrc); } } // Set the RTCP report generation interval. if (this->kind == RTC::Media::Kind::AUDIO) { this->maxRtcpInterval = RTC::RTCP::MaxAudioIntervalMs; } else { this->maxRtcpInterval = RTC::RTCP::MaxVideoIntervalMs; } } Consumer::~Consumer() { MS_TRACE(); } flatbuffers::Offset Consumer::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add rtpParameters. auto rtpParameters = this->rtpParameters.FillBuffer(builder); // Add consumableRtpEncodings. std::vector> consumableRtpEncodings; consumableRtpEncodings.reserve(this->consumableRtpEncodings.size()); for (const auto& encoding : this->consumableRtpEncodings) { consumableRtpEncodings.emplace_back(encoding.FillBuffer(builder)); } // Add supportedCodecPayloadTypes. std::vector supportedCodecPayloadTypes; for (auto i = 0; i < 128; ++i) { if (this->supportedCodecPayloadTypes[i]) { supportedCodecPayloadTypes.push_back(i); } } // Add traceEventTypes. std::vector traceEventTypes; if (this->traceEventTypes.rtp) { traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::RTP); } if (this->traceEventTypes.keyframe) { traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::KEYFRAME); } if (this->traceEventTypes.nack) { traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::NACK); } if (this->traceEventTypes.pli) { traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::PLI); } if (this->traceEventTypes.fir) { traceEventTypes.emplace_back(FBS::Consumer::TraceEventType::FIR); } return FBS::Consumer::CreateBaseConsumerDumpDirect( builder, this->id.c_str(), RTC::RtpParameters::TypeToFbs(this->type), this->producerId.c_str(), this->kind == RTC::Media::Kind::AUDIO ? FBS::RtpParameters::MediaKind::AUDIO : FBS::RtpParameters::MediaKind::VIDEO, rtpParameters, &consumableRtpEncodings, &supportedCodecPayloadTypes, &traceEventTypes, this->paused, this->producerPaused, this->priority); } void Consumer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::CONSUMER_GET_STATS: { auto responseOffset = FillBufferStats(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Consumer_GetStatsResponse, responseOffset); break; } case Channel::ChannelRequest::Method::CONSUMER_PAUSE: { if (this->paused) { request->Accept(); break; } const bool wasActive = IsActive(); this->paused = true; MS_DEBUG_DEV("Consumer paused [consumerId:%s]", this->id.c_str()); if (wasActive) { UserOnPaused(); } request->Accept(); break; } case Channel::ChannelRequest::Method::CONSUMER_RESUME: { if (!this->paused) { request->Accept(); break; } this->paused = false; MS_DEBUG_DEV("Consumer resumed [consumerId:%s]", this->id.c_str()); if (IsActive()) { UserOnResumed(); } request->Accept(); break; } case Channel::ChannelRequest::Method::CONSUMER_SET_PRIORITY: { const auto* body = request->data->body_as(); if (body->priority() < 1u) { MS_THROW_TYPE_ERROR("wrong priority (must be higher than 0)"); } this->priority = body->priority(); auto responseOffset = FBS::Consumer::CreateSetPriorityResponse(request->GetBufferBuilder(), this->priority); request->Accept(FBS::Response::Body::Consumer_SetPriorityResponse, responseOffset); break; } case Channel::ChannelRequest::Method::CONSUMER_ENABLE_TRACE_EVENT: { const auto* body = request->data->body_as(); // Reset traceEventTypes. struct TraceEventTypes newTraceEventTypes; for (const auto& type : *body->events()) { switch (type) { case FBS::Consumer::TraceEventType::KEYFRAME: { newTraceEventTypes.keyframe = true; break; } case FBS::Consumer::TraceEventType::FIR: { newTraceEventTypes.fir = true; break; } case FBS::Consumer::TraceEventType::NACK: { newTraceEventTypes.nack = true; break; } case FBS::Consumer::TraceEventType::PLI: { newTraceEventTypes.pli = true; break; } case FBS::Consumer::TraceEventType::RTP: { newTraceEventTypes.rtp = true; break; } } } this->traceEventTypes = newTraceEventTypes; request->Accept(); break; } default: { MS_THROW_ERROR("unknown method '%s'", request->methodCStr); } } } void Consumer::TransportConnected() { MS_TRACE(); if (this->transportConnected) { return; } this->transportConnected = true; MS_DEBUG_DEV("Transport connected [consumerId:%s]", this->id.c_str()); UserOnTransportConnected(); } void Consumer::TransportDisconnected() { MS_TRACE(); if (!this->transportConnected) { return; } this->transportConnected = false; MS_DEBUG_DEV("Transport disconnected [consumerId:%s]", this->id.c_str()); UserOnTransportDisconnected(); } void Consumer::ProducerPaused() { MS_TRACE(); if (this->producerPaused) { return; } const bool wasActive = IsActive(); this->producerPaused = true; MS_DEBUG_DEV("Producer paused [consumerId:%s]", this->id.c_str()); if (wasActive) { UserOnPaused(); } this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_PRODUCER_PAUSE); } void Consumer::ProducerResumed() { MS_TRACE(); if (!this->producerPaused) { return; } this->producerPaused = false; MS_DEBUG_DEV("Producer resumed [consumerId:%s]", this->id.c_str()); if (IsActive()) { UserOnResumed(); } this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_PRODUCER_RESUME); } void Consumer::ProducerRtpStreamScores(const std::vector* scores) { MS_TRACE(); // This is gonna be a constant pointer. this->producerRtpStreamScores = scores; } // The caller (Router) is supposed to proceed with the deletion of this Consumer // right after calling this method. Otherwise ugly things may happen. void Consumer::ProducerClosed() { MS_TRACE(); this->producerClosed = true; MS_DEBUG_DEV("Producer closed [consumerId:%s]", this->id.c_str()); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_PRODUCER_CLOSE); this->listener->OnConsumerProducerClosed(this); } void Consumer::EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx) const { MS_TRACE(); if (this->traceEventTypes.keyframe && packet->IsKeyFrame()) { auto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto traceInfo = FBS::Consumer::CreateKeyFrameTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Consumer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::KEYFRAME, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_OUT, FBS::Consumer::TraceInfo::KeyFrameTraceInfo, traceInfo.Union()); EmitTraceEvent(notification); } else if (this->traceEventTypes.rtp) { auto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto traceInfo = FBS::Consumer::CreateRtpTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Consumer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::RTP, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_OUT, FBS::Consumer::TraceInfo::RtpTraceInfo, traceInfo.Union()); EmitTraceEvent(notification); } } void Consumer::EmitTraceEventPliType(uint32_t ssrc) const { MS_TRACE(); if (!this->traceEventTypes.pli) { return; } auto traceInfo = FBS::Consumer::CreatePliTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); auto notification = FBS::Consumer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::PLI, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_IN, FBS::Consumer::TraceInfo::PliTraceInfo, traceInfo.Union()); EmitTraceEvent(notification); } void Consumer::EmitTraceEventFirType(uint32_t ssrc) const { MS_TRACE(); if (!this->traceEventTypes.fir) { return; } auto traceInfo = FBS::Consumer::CreateFirTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); auto notification = FBS::Consumer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::FIR, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_IN, FBS::Consumer::TraceInfo::FirTraceInfo, traceInfo.Union()); EmitTraceEvent(notification); } void Consumer::EmitTraceEventNackType() const { MS_TRACE(); if (!this->traceEventTypes.nack) { return; } auto notification = FBS::Consumer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Consumer::TraceEventType::NACK, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_IN); EmitTraceEvent(notification); } void Consumer::EmitTraceEvent(flatbuffers::Offset& notification) const { MS_TRACE(); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_TRACE, FBS::Notification::Body::Consumer_TraceNotification, notification); } } // namespace RTC ================================================ FILE: worker/src/RTC/DataConsumer.cpp ================================================ #define MS_CLASS "RTC::DataConsumer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/DataConsumer.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" namespace RTC { /* Instance methods. */ DataConsumer::DataConsumer( SharedInterface* shared, const std::string& id, const std::string& dataProducerId, RTC::DataConsumer::Listener* listener, const FBS::Transport::ConsumeDataRequest* data, size_t maxMessageSize) : id(id), dataProducerId(dataProducerId), shared(shared), listener(listener), maxMessageSize(maxMessageSize) { MS_TRACE(); switch (data->type()) { case FBS::DataProducer::Type::SCTP: { this->type = DataConsumer::Type::SCTP; break; } case FBS::DataProducer::Type::DIRECT: { this->type = DataConsumer::Type::DIRECT; break; } } if (this->type == DataConsumer::Type::SCTP) { if (!flatbuffers::IsFieldPresent( data, FBS::Transport::ConsumeDataRequest::VT_SCTPSTREAMPARAMETERS)) { MS_THROW_TYPE_ERROR("missing sctpStreamParameters"); } // This may throw. this->sctpStreamParameters = RTC::SctpStreamParameters(data->sctpStreamParameters()); } if (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeDataRequest::VT_LABEL)) { this->label = data->label()->str(); } if (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeDataRequest::VT_PROTOCOL)) { this->protocol = data->protocol()->str(); } // paused is set to false by default. this->paused = data->paused(); if (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeDataRequest::VT_SUBCHANNELS)) { for (const auto subchannel : *data->subchannels()) { this->subchannels.insert(subchannel); } } // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ nullptr); } DataConsumer::~DataConsumer() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); } flatbuffers::Offset DataConsumer::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); flatbuffers::Offset sctpStreamParameters; // Add sctpStreamParameters. if (this->type == DataConsumer::Type::SCTP) { sctpStreamParameters = this->sctpStreamParameters.FillBuffer(builder); } std::vector subchannels; subchannels.reserve(this->subchannels.size()); for (auto subchannel : this->subchannels) { subchannels.emplace_back(subchannel); } return FBS::DataConsumer::CreateDumpResponseDirect( builder, this->id.c_str(), this->dataProducerId.c_str(), this->type == DataConsumer::Type::SCTP ? FBS::DataProducer::Type::SCTP : FBS::DataProducer::Type::DIRECT, sctpStreamParameters, this->label.c_str(), this->protocol.c_str(), this->bufferedAmountLowThreshold, this->paused, this->dataProducerPaused, std::addressof(subchannels)); } flatbuffers::Offset DataConsumer::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::DataConsumer::CreateGetStatsResponseDirect( builder, // timestamp. this->shared->GetTimeMs(), // label. this->label.c_str(), // protocol. this->protocol.c_str(), // messagesSent. this->messagesSent, // bytesSent. this->bytesSent, // bufferedAmount. this->bufferedAmount); } void DataConsumer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::DATACONSUMER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::DataConsumer_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::DATACONSUMER_GET_STATS: { auto responseOffset = FillBufferStats(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::DataConsumer_GetStatsResponse, responseOffset); break; } case Channel::ChannelRequest::Method::DATACONSUMER_PAUSE: { if (this->paused) { request->Accept(); break; } this->paused = true; MS_DEBUG_DEV("DataConsumer paused [dataConsumerId:%s]", this->id.c_str()); request->Accept(); break; } case Channel::ChannelRequest::Method::DATACONSUMER_RESUME: { if (!this->paused) { request->Accept(); break; } this->paused = false; MS_DEBUG_DEV("DataConsumer resumed [dataConsumerId:%s]", this->id.c_str()); request->Accept(); break; } case Channel::ChannelRequest::Method::DATACONSUMER_GET_BUFFERED_AMOUNT: { if (this->GetType() != RTC::DataConsumer::Type::SCTP) { MS_THROW_TYPE_ERROR("invalid DataConsumer type"); } uint32_t bufferedAmount{ 0 }; this->listener->OnDataConsumerNeedBufferedAmount(this, bufferedAmount); auto responseOffset = FBS::DataConsumer::CreateGetBufferedAmountResponse( request->GetBufferBuilder(), bufferedAmount); request->Accept(FBS::Response::Body::DataConsumer_GetBufferedAmountResponse, responseOffset); break; } case Channel::ChannelRequest::Method::DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD: { if (this->GetType() != DataConsumer::Type::SCTP) { MS_THROW_TYPE_ERROR("invalid DataConsumer type"); } const auto* body = request->data->body_as(); this->bufferedAmountLowThreshold = body->threshold(); request->Accept(); // There is less or same buffered data than the given threshold. // Trigger 'bufferedamountlow' now. if (this->bufferedAmount <= this->bufferedAmountLowThreshold) { // Notify the Node DataConsumer. auto bufferedAmountLowOffset = FBS::DataConsumer::CreateBufferedAmountLowNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), this->bufferedAmount); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::DATACONSUMER_BUFFERED_AMOUNT_LOW, FBS::Notification::Body::DataConsumer_BufferedAmountLowNotification, bufferedAmountLowOffset); } // Force the trigger of 'bufferedamountlow' once there is less or same // buffered data than the given threshold. else { this->forceTriggerBufferedAmountLow = true; } break; } case Channel::ChannelRequest::Method::DATACONSUMER_SEND: { if (this->GetType() != RTC::DataConsumer::Type::SCTP) { MS_THROW_TYPE_ERROR("invalid DataConsumer type"); } const auto* body = request->data->body_as(); const uint8_t* data = body->data()->Data(); const size_t len = body->data()->size(); if (len > this->maxMessageSize) { MS_THROW_TYPE_ERROR( "given message exceeds maxMessageSize value [maxMessageSize:%zu, len:%zu]", this->maxMessageSize, len); } const auto* cb = new onQueuedCallback( [&request](bool queued, bool sctpSendBufferFull) { if (queued) { request->Accept(); } else { request->Error(sctpSendBufferFull ? "sctpsendbufferfull" : "message send failed"); } }); static std::vector emptySubchannels; if (Settings::configuration.useBuiltInSctpStack) { const uint16_t streamId = this->type == DataConsumer::Type::SCTP ? this->sctpStreamParameters.streamId : 0; // NOTE: We are creating a copy of the data here, otherwise we cannot // move the Message and pass its ownership to the SCTP stack. RTC::SCTP::Message message(streamId, body->ppid(), std::vector(data, data + len)); SendMessage(std::move(message), emptySubchannels, std::nullopt, cb); } else { SendMessage(data, len, body->ppid(), emptySubchannels, std::nullopt, cb); } break; } case Channel::ChannelRequest::Method::DATACONSUMER_SET_SUBCHANNELS: { const auto* body = request->data->body_as(); this->subchannels.clear(); for (const auto subchannel : *body->subchannels()) { this->subchannels.insert(subchannel); } std::vector subchannels; subchannels.reserve(this->subchannels.size()); for (auto subchannel : this->subchannels) { subchannels.emplace_back(subchannel); } // Create response. auto responseOffset = FBS::DataConsumer::CreateSetSubchannelsResponseDirect( request->GetBufferBuilder(), std::addressof(subchannels)); request->Accept(FBS::Response::Body::DataConsumer_SetSubchannelsResponse, responseOffset); break; } case Channel::ChannelRequest::Method::DATACONSUMER_ADD_SUBCHANNEL: { const auto* body = request->data->body_as(); this->subchannels.insert(body->subchannel()); std::vector subchannels; subchannels.reserve(this->subchannels.size()); for (auto subchannel : this->subchannels) { subchannels.emplace_back(subchannel); } // Create response. auto responseOffset = FBS::DataConsumer::CreateAddSubchannelResponseDirect( request->GetBufferBuilder(), std::addressof(subchannels)); request->Accept(FBS::Response::Body::DataConsumer_AddSubchannelResponse, responseOffset); break; } case Channel::ChannelRequest::Method::DATACONSUMER_REMOVE_SUBCHANNEL: { const auto* body = request->data->body_as(); this->subchannels.erase(body->subchannel()); std::vector subchannels; subchannels.reserve(this->subchannels.size()); for (auto subchannel : this->subchannels) { subchannels.emplace_back(subchannel); } // Create response. auto responseOffset = FBS::DataConsumer::CreateRemoveSubchannelResponseDirect( request->GetBufferBuilder(), std::addressof(subchannels)); request->Accept(FBS::Response::Body::DataConsumer_RemoveSubchannelResponse, responseOffset); break; } default: { MS_THROW_ERROR("unknown method '%s'", request->methodCStr); } } } void DataConsumer::TransportConnected() { MS_TRACE(); this->transportConnected = true; MS_DEBUG_DEV("Transport connected [dataConsumerId:%s]", this->id.c_str()); } void DataConsumer::TransportDisconnected() { MS_TRACE(); this->transportConnected = false; MS_DEBUG_DEV("Transport disconnected [dataConsumerId:%s]", this->id.c_str()); } void DataConsumer::DataProducerPaused() { MS_TRACE(); if (this->dataProducerPaused) { return; } this->dataProducerPaused = true; MS_DEBUG_DEV("DataProducer paused [dataConsumerId:%s]", this->id.c_str()); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::DATACONSUMER_DATAPRODUCER_PAUSE); } void DataConsumer::DataProducerResumed() { MS_TRACE(); if (!this->dataProducerPaused) { return; } this->dataProducerPaused = false; MS_DEBUG_DEV("DataProducer resumed [dataConsumerId:%s]", this->id.c_str()); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::DATACONSUMER_DATAPRODUCER_RESUME); } void DataConsumer::SctpAssociationConnected() { MS_TRACE(); this->sctpAssociationConnected = true; MS_DEBUG_DEV("SctpAssociation connected [dataConsumerId:%s]", this->id.c_str()); } void DataConsumer::SctpAssociationClosed() { MS_TRACE(); this->sctpAssociationConnected = false; MS_DEBUG_DEV("SctpAssociation closed [dataConsumerId:%s]", this->id.c_str()); } void DataConsumer::SetSctpAssociationBufferedAmount(uint32_t bufferedAmount) { MS_TRACE(); auto previousBufferedAmount = this->bufferedAmount; this->bufferedAmount = bufferedAmount; if ( (this->forceTriggerBufferedAmountLow || previousBufferedAmount > this->bufferedAmountLowThreshold) && this->bufferedAmount <= this->bufferedAmountLowThreshold) { this->forceTriggerBufferedAmountLow = false; // Notify the Node DataConsumer. auto bufferedAmountLowOffset = FBS::DataConsumer::CreateBufferedAmountLowNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), this->bufferedAmount); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::DATACONSUMER_BUFFERED_AMOUNT_LOW, FBS::Notification::Body::DataConsumer_BufferedAmountLowNotification, bufferedAmountLowOffset); } } void DataConsumer::SctpAssociationSendBufferFull() { MS_TRACE(); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::DATACONSUMER_SCTP_SENDBUFFER_FULL); } // The caller (Router) is supposed to proceed with the deletion of this DataConsumer // right after calling this method. Otherwise ugly things may happen. void DataConsumer::DataProducerClosed() { MS_TRACE(); this->dataProducerClosed = true; MS_DEBUG_DEV("DataProducer closed [dataConsumerId:%s]", this->id.c_str()); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::DATACONSUMER_DATAPRODUCER_CLOSE); this->listener->OnDataConsumerDataProducerClosed(this); } // TODO: SCTP: Remove when we migrate to the new SCTP stack. bool DataConsumer::SendMessage( const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel, const onQueuedCallback* cb) { MS_TRACE(); if (!IsActive()) { if (cb) { (*cb)(false, false); delete cb; } return false; } // If a required subchannel is given, verify that this data consumer is // subscribed to it. if ( requiredSubchannel.has_value() && this->subchannels.find(requiredSubchannel.value()) == this->subchannels.end()) { if (cb) { (*cb)(false, false); delete cb; } return false; } // If subchannels are given, verify that this data consumer is subscribed // to at least one of them. if (!subchannels.empty()) { bool subchannelMatched{ false }; for (const auto subchannel : subchannels) { if (this->subchannels.find(subchannel) != this->subchannels.end()) { subchannelMatched = true; break; } } if (!subchannelMatched) { if (cb) { (*cb)(false, false); delete cb; } return false; } } if (len > this->maxMessageSize) { MS_WARN_TAG( message, "given message exceeds maxMessageSize value [maxMessageSize:%zu, len:%zu]", len, this->maxMessageSize); if (cb) { (*cb)(false, false); delete cb; } return false; } this->messagesSent++; this->bytesSent += len; this->listener->OnDataConsumerSendMessage(this, msg, len, ppid, cb); return true; } bool DataConsumer::SendMessage( RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel, const onQueuedCallback* cb) { MS_TRACE(); if (!IsActive()) { if (cb) { (*cb)(false, false); delete cb; } return false; } // If a required subchannel is given, verify that this data consumer is // subscribed to it. if ( requiredSubchannel.has_value() && this->subchannels.find(requiredSubchannel.value()) == this->subchannels.end()) { if (cb) { (*cb)(false, false); delete cb; } return false; } // If subchannels are given, verify that this data consumer is subscribed // to at least one of them. if (!subchannels.empty()) { bool subchannelMatched{ false }; for (const auto subchannel : subchannels) { if (this->subchannels.find(subchannel) != this->subchannels.end()) { subchannelMatched = true; break; } } if (!subchannelMatched) { if (cb) { (*cb)(false, false); delete cb; } return false; } } const size_t messageLen = message.GetPayloadLength(); if (messageLen > this->maxMessageSize) { MS_WARN_TAG( message, "given message exceeds maxMessageSize value [maxMessageSize:%zu, len:%zu]", messageLen, this->maxMessageSize); if (cb) { (*cb)(false, false); delete cb; } return false; } this->messagesSent++; this->bytesSent += messageLen; this->listener->OnDataConsumerSendMessage(this, std::move(message), cb); return true; } } // namespace RTC ================================================ FILE: worker/src/RTC/DataProducer.cpp ================================================ #define MS_CLASS "RTC::DataProducer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/DataProducer.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include namespace RTC { /* Instance methods. */ DataProducer::DataProducer( SharedInterface* shared, const std::string& id, size_t maxMessageSize, RTC::DataProducer::Listener* listener, const FBS::Transport::ProduceDataRequest* data) : id(id), shared(shared), maxMessageSize(maxMessageSize), listener(listener) { MS_TRACE(); switch (data->type()) { case FBS::DataProducer::Type::SCTP: { this->type = DataProducer::Type::SCTP; break; } case FBS::DataProducer::Type::DIRECT: { this->type = DataProducer::Type::DIRECT; break; } } if (this->type == DataProducer::Type::SCTP) { if (!flatbuffers::IsFieldPresent( data, FBS::Transport::ProduceDataRequest::VT_SCTPSTREAMPARAMETERS)) { MS_THROW_TYPE_ERROR("missing sctpStreamParameters"); } // This may throw. this->sctpStreamParameters = RTC::SctpStreamParameters(data->sctpStreamParameters()); } if (flatbuffers::IsFieldPresent(data, FBS::Transport::ProduceDataRequest::VT_LABEL)) { this->label = data->label()->str(); } if (flatbuffers::IsFieldPresent(data, FBS::Transport::ProduceDataRequest::VT_PROTOCOL)) { this->protocol = data->protocol()->str(); } this->paused = data->paused(); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ this); } DataProducer::~DataProducer() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); } flatbuffers::Offset DataProducer::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); flatbuffers::Offset sctpStreamParametersOffset; // Add sctpStreamParameters. if (this->type == DataProducer::Type::SCTP) { sctpStreamParametersOffset = this->sctpStreamParameters.FillBuffer(builder); } return FBS::DataProducer::CreateDumpResponseDirect( builder, this->id.c_str(), this->type == DataProducer::Type::SCTP ? FBS::DataProducer::Type::SCTP : FBS::DataProducer::Type::DIRECT, sctpStreamParametersOffset, this->label.c_str(), this->protocol.c_str(), this->paused); } flatbuffers::Offset DataProducer::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::DataProducer::CreateGetStatsResponseDirect( builder, // timestamp. this->shared->GetTimeMs(), // label. this->label.c_str(), // protocol. this->protocol.c_str(), // messagesReceived. this->messagesReceived, // bytesReceived. this->bytesReceived); } void DataProducer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::DATAPRODUCER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::DataProducer_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::DATAPRODUCER_GET_STATS: { auto responseOffset = FillBufferStats(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::DataProducer_GetStatsResponse, responseOffset); break; } case Channel::ChannelRequest::Method::DATAPRODUCER_PAUSE: { if (this->paused) { request->Accept(); break; } this->paused = true; MS_DEBUG_DEV("DataProducer paused [dataProducerId:%s]", this->id.c_str()); this->listener->OnDataProducerPaused(this); request->Accept(); break; } case Channel::ChannelRequest::Method::DATAPRODUCER_RESUME: { if (!this->paused) { request->Accept(); break; } this->paused = false; MS_DEBUG_DEV("DataProducer resumed [dataProducerId:%s]", this->id.c_str()); this->listener->OnDataProducerResumed(this); request->Accept(); break; } default: { MS_THROW_ERROR("unknown method '%s'", request->methodCStr); } } } void DataProducer::HandleNotification(Channel::ChannelNotification* notification) { MS_TRACE(); switch (notification->event) { case Channel::ChannelNotification::Event::DATAPRODUCER_SEND: { const auto* body = notification->data->body_as(); const uint8_t* data = body->data()->Data(); const size_t len = body->data()->size(); if (len > this->maxMessageSize) { MS_THROW_TYPE_ERROR( "given message exceeds maxMessageSize value [maxMessageSize:%zu, len:%zu]", this->maxMessageSize, len); } std::vector subchannels; if (flatbuffers::IsFieldPresent(body, FBS::DataProducer::SendNotification::VT_SUBCHANNELS)) { subchannels.reserve(body->subchannels()->size()); for (const auto subchannel : *body->subchannels()) { subchannels.emplace_back(subchannel); } } std::optional requiredSubchannel{ std::nullopt }; if (body->requiredSubchannel().has_value()) { requiredSubchannel = body->requiredSubchannel(); } if (Settings::configuration.useBuiltInSctpStack) { const uint16_t streamId = this->type == DataProducer::Type::SCTP ? this->sctpStreamParameters.streamId : 0; // NOTE: We are creating a copy of the data here, otherwise we cannot // move the Message and pass its ownership to the SCTP stack. RTC::SCTP::Message message(streamId, body->ppid(), std::vector(data, data + len)); ReceiveMessage(std::move(message), subchannels, requiredSubchannel); } else { ReceiveMessage(data, len, body->ppid(), subchannels, requiredSubchannel); } // Increase receive transmission. this->listener->OnDataProducerReceiveData(this, len); break; } default: { MS_ERROR("unknown event '%s'", notification->eventCStr); } } } // TODO: SCTP: Remove when we migrate to the new SCTP stack. void DataProducer::ReceiveMessage( const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel) { MS_TRACE(); this->messagesReceived++; this->bytesReceived += len; // If paused stop here. if (this->paused) { return; } this->listener->OnDataProducerMessageReceived( this, msg, len, ppid, subchannels, requiredSubchannel); } void DataProducer::ReceiveMessage( RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel) { MS_TRACE(); this->messagesReceived++; this->bytesReceived += message.GetPayloadLength(); // If paused stop here. if (this->paused) { return; } this->listener->OnDataProducerMessageReceived( this, std::move(message), subchannels, requiredSubchannel); } } // namespace RTC ================================================ FILE: worker/src/RTC/DirectTransport.cpp ================================================ #define MS_CLASS "RTC::DirectTransport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/DirectTransport.hpp" #include "Logger.hpp" #include "RTC/Consts.hpp" namespace RTC { /* Instance methods. */ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) DirectTransport::DirectTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::DirectTransport::DirectTransportOptions* options) : RTC::Transport::Transport(shared, id, listener, options->base()) { MS_TRACE(); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ this); } DirectTransport::~DirectTransport() { MS_TRACE(); // Tell the Transport parent class that we are about to destroy // the class instance. SetDestroying(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); } flatbuffers::Offset DirectTransport::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { // Add base transport dump. auto base = Transport::FillBuffer(builder); return FBS::DirectTransport::CreateDumpResponse(builder, base); } flatbuffers::Offset DirectTransport::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); // Base Transport stats. auto base = Transport::FillBufferStats(builder); return FBS::DirectTransport::CreateGetStatsResponse(builder, base); } void DirectTransport::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::TRANSPORT_GET_STATS: { auto responseOffset = FillBufferStats(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::DirectTransport_GetStatsResponse, responseOffset); break; } case Channel::ChannelRequest::Method::TRANSPORT_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::DirectTransport_DumpResponse, dumpOffset); break; } default: { // Pass it to the parent class. RTC::Transport::HandleRequest(request); } } } void DirectTransport::HandleNotification(Channel::ChannelNotification* notification) { MS_TRACE(); switch (notification->event) { case Channel::ChannelNotification::Event::TRANSPORT_SEND_RTCP: { const auto* body = notification->data->body_as(); auto len = body->data()->size(); // Increase receive transmission. RTC::Transport::DataReceived(len); if (len > RTC::Consts::MtuSize + 100) { MS_WARN_TAG(rtp, "given RTCP packet exceeds maximum size [len:%i]", len); return; } auto* packet = RTC::RTCP::Packet::Parse(body->data()->data(), len); if (!packet) { MS_WARN_TAG(rtcp, "received data is not a valid RTCP compound or single packet"); return; } // Pass the packet to the parent transport. RTC::Transport::ReceiveRtcpPacket(packet); break; } default: { // Pass it to the parent class. RTC::Transport::HandleNotification(notification); } } } inline bool DirectTransport::IsConnected() const { return true; } void DirectTransport::SendRtpPacket( RTC::Consumer* consumer, RTC::RTP::Packet* packet, const RTC::Transport::onSendCallback* cb) { MS_TRACE(); if (!consumer) { MS_WARN_TAG(rtp, "cannot send RTP packet not associated to a Consumer"); if (cb) { (*cb)(false); delete cb; } return; } const auto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector( packet->GetBuffer(), packet->GetLength()); auto notification = FBS::Consumer::CreateRtpNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), data); this->shared->GetChannelNotifier()->Emit( consumer->id, FBS::Notification::Event::CONSUMER_RTP, FBS::Notification::Body::Consumer_RtpNotification, notification); if (cb) { (*cb)(true); delete cb; } // Increase send transmission. RTC::Transport::DataSent(packet->GetLength()); } void DirectTransport::SendRtcpPacket(RTC::RTCP::Packet* packet) { MS_TRACE(); // Notify the Node DirectTransport. const auto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector( packet->GetData(), packet->GetSize()); auto notification = FBS::DirectTransport::CreateRtcpNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), data); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::DIRECTTRANSPORT_RTCP, FBS::Notification::Body::DirectTransport_RtcpNotification, notification); // Increase send transmission. RTC::Transport::DataSent(packet->GetSize()); } void DirectTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) { MS_TRACE(); packet->Serialize(RTC::RTCP::SerializationBuffer); const auto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector( packet->GetData(), packet->GetSize()); auto notification = FBS::DirectTransport::CreateRtcpNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), data); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::DIRECTTRANSPORT_RTCP, FBS::Notification::Body::DirectTransport_RtcpNotification, notification); } // TODO: SCTP: Remove once we only use built-in SCTP stack. void DirectTransport::SendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb) { MS_TRACE(); // Notify the Node DirectTransport. auto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector(msg, len); auto notification = FBS::DataConsumer::CreateMessageNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), ppid, data); this->shared->GetChannelNotifier()->Emit( dataConsumer->id, FBS::Notification::Event::DATACONSUMER_MESSAGE, FBS::Notification::Body::DataConsumer_MessageNotification, notification); if (cb) { (*cb)(true, false); delete cb; } // Increase send transmission. RTC::Transport::DataSent(len); } void DirectTransport::SendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) { MS_TRACE(); // Notify the Node DirectTransport. auto data = this->shared->GetChannelNotifier()->GetBufferBuilder().CreateVector( message.GetPayload().data(), message.GetPayloadLength()); auto notification = FBS::DataConsumer::CreateMessageNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), message.GetPayloadProtocolId(), data); this->shared->GetChannelNotifier()->Emit( dataConsumer->id, FBS::Notification::Event::DATACONSUMER_MESSAGE, FBS::Notification::Body::DataConsumer_MessageNotification, notification); if (cb) { (*cb)(true, false); delete cb; } // Increase send transmission. RTC::Transport::DataSent(message.GetPayloadLength()); } bool DirectTransport::SendData(const uint8_t* /*data*/, size_t /*len*/) { MS_TRACE(); // Do nothing. return false; } void DirectTransport::RecvStreamClosed(uint32_t /*ssrc*/) { MS_TRACE(); // Do nothing. } void DirectTransport::SendStreamClosed(uint32_t /*ssrc*/) { MS_TRACE(); // Do nothing. } } // namespace RTC ================================================ FILE: worker/src/RTC/DtlsTransport.cpp ================================================ #define MS_CLASS "RTC::DtlsTransport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/DtlsTransport.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "Utils.hpp" #include #include #include #include // std::snprintf(), std::fopen() #include // std::memcpy(), std::strcmp() // clang-format off // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define LOG_OPENSSL_ERROR(desc) \ do \ { \ if (ERR_peek_error() == 0) \ { \ MS_ERROR("OpenSSL error [desc:'%s']", desc); \ } \ else \ { \ int64_t err; \ while ((err = ERR_get_error()) != 0) \ { \ MS_ERROR("OpenSSL error [desc:'%s', error:'%s']", desc, ERR_error_string(err, nullptr)); \ } \ ERR_clear_error(); \ } \ } while (false) // clang-format on /* Static methods for OpenSSL callbacks. */ inline static int onSslCertificateVerify(int /*preverifyOk*/, X509_STORE_CTX* /*ctx*/) { MS_TRACE(); // Always valid since DTLS certificates are self-signed. return 1; } inline static void onSslInfo(const SSL* ssl, int where, int ret) { static_cast(SSL_get_ex_data(ssl, 0))->OnSslInfo(where, ret); } /** * This callback is called by OpenSSL when it wants to send DTLS data to the * endpoint. Such a data could be a full DTLS message, various DTLS messages, * a DTLS message fragment, various DTLS message fragments or a combination of * these. It's guaranteed (by observation) that |len| argument corresponds to * the entire content of our BIO mem buffer |this->sslBioToNetwork| and it * never exceeds our |DtlsMtu| limit. */ inline static long onSslBioOut( BIO* bio, int operationType, const char* argp, size_t len, int /*argi*/, long /*argl*/, int ret, size_t* /*processed*/) { const long resultOfcallback = (operationType == BIO_CB_RETURN) ? static_cast(ret) : 1; // This callback is called twice for write operations: // - First one with operationType = BIO_CB_WRITE. // - Second one with operationType = BIO_CB_RETURN | BIO_CB_WRITE. // We only care about the former. if ((operationType == BIO_CB_WRITE) && argp && len > 0) { auto* dtlsTransport = reinterpret_cast(BIO_get_callback_arg(bio)); dtlsTransport->SendDtlsData(reinterpret_cast(argp), len); } return resultOfcallback; } inline static unsigned int onSslDtlsTimer(SSL* /*ssl*/, unsigned int timerUs) { if (timerUs == 0u) { return 100000u; } else if (timerUs >= 4000000u) { return 4000000u; } else { return 2 * timerUs; } } namespace RTC { /* Static. */ static constexpr int DtlsMtu{ 1350 }; static constexpr int SslReadBufferSize{ 65536 }; // AES-HMAC: http://tools.ietf.org/html/rfc3711 static constexpr size_t SrtpMasterKeyLength{ 16u }; static constexpr size_t SrtpMasterSaltLength{ 14u }; static constexpr size_t SrtpMasterLength{ SrtpMasterKeyLength + SrtpMasterSaltLength }; // AES-GCM: http://tools.ietf.org/html/rfc7714 static constexpr size_t SrtpAesGcm256MasterKeyLength{ 32u }; static constexpr size_t SrtpAesGcm256MasterSaltLength{ 12u }; static constexpr size_t SrtpAesGcm256MasterLength{ SrtpAesGcm256MasterKeyLength + SrtpAesGcm256MasterSaltLength }; static constexpr size_t SrtpAesGcm128MasterKeyLength{ 16u }; static constexpr size_t SrtpAesGcm128MasterSaltLength{ 12u }; static constexpr size_t SrtpAesGcm128MasterLength{ SrtpAesGcm128MasterKeyLength + SrtpAesGcm128MasterSaltLength }; /* Class variables. */ thread_local X509* DtlsTransport::certificate{ nullptr }; thread_local EVP_PKEY* DtlsTransport::privateKey{ nullptr }; thread_local SSL_CTX* DtlsTransport::sslCtx{ nullptr }; thread_local uint8_t DtlsTransport::sslReadBuffer[SslReadBufferSize]; const absl::flat_hash_map DtlsTransport::String2FingerprintAlgorithm = { { "sha-1", DtlsTransport::FingerprintAlgorithm::SHA1 }, { "sha-224", DtlsTransport::FingerprintAlgorithm::SHA224 }, { "sha-256", DtlsTransport::FingerprintAlgorithm::SHA256 }, { "sha-384", DtlsTransport::FingerprintAlgorithm::SHA384 }, { "sha-512", DtlsTransport::FingerprintAlgorithm::SHA512 } }; const absl::flat_hash_map DtlsTransport::FingerprintAlgorithm2String = { { DtlsTransport::FingerprintAlgorithm::SHA1, "sha-1" }, { DtlsTransport::FingerprintAlgorithm::SHA224, "sha-224" }, { DtlsTransport::FingerprintAlgorithm::SHA256, "sha-256" }, { DtlsTransport::FingerprintAlgorithm::SHA384, "sha-384" }, { DtlsTransport::FingerprintAlgorithm::SHA512, "sha-512" } }; const absl::flat_hash_map DtlsTransport::String2Role = { { "auto", DtlsTransport::Role::AUTO }, { "client", DtlsTransport::Role::CLIENT }, { "server", DtlsTransport::Role::SERVER } }; thread_local std::vector DtlsTransport::localFingerprints; const std::vector DtlsTransport::SrtpCryptoSuites = { { RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM, "SRTP_AEAD_AES_256_GCM" }, { RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM, "SRTP_AEAD_AES_128_GCM" }, { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80, "SRTP_AES128_CM_SHA1_80" }, { RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32, "SRTP_AES128_CM_SHA1_32" } }; /* Class methods. */ void DtlsTransport::ClassInit() { MS_TRACE(); // Generate a X509 certificate and private key (unless PEM files are provided). if ( Settings::configuration.dtlsCertificateFile.empty() || Settings::configuration.dtlsPrivateKeyFile.empty()) { GenerateCertificateAndPrivateKey(); } else { ReadCertificateAndPrivateKeyFromFiles(); } // Create a global SSL_CTX. CreateSslCtx(); // Generate certificate fingerprints. GenerateFingerprints(); } void DtlsTransport::ClassDestroy() { MS_TRACE(); if (DtlsTransport::privateKey) { EVP_PKEY_free(DtlsTransport::privateKey); } if (DtlsTransport::certificate) { X509_free(DtlsTransport::certificate); } if (DtlsTransport::sslCtx) { SSL_CTX_free(DtlsTransport::sslCtx); } } DtlsTransport::Role DtlsTransport::RoleFromFbs(FBS::WebRtcTransport::DtlsRole role) { switch (role) { case FBS::WebRtcTransport::DtlsRole::AUTO: { return DtlsTransport::Role::AUTO; } case FBS::WebRtcTransport::DtlsRole::CLIENT: { return DtlsTransport::Role::CLIENT; } case FBS::WebRtcTransport::DtlsRole::SERVER: { return DtlsTransport::Role::SERVER; } NO_DEFAULT_GCC(); } } FBS::WebRtcTransport::DtlsRole DtlsTransport::RoleToFbs(DtlsTransport::Role role) { switch (role) { case DtlsTransport::Role::AUTO: { return FBS::WebRtcTransport::DtlsRole::AUTO; } case DtlsTransport::Role::CLIENT: { return FBS::WebRtcTransport::DtlsRole::CLIENT; } case DtlsTransport::Role::SERVER: { return FBS::WebRtcTransport::DtlsRole::SERVER; } NO_DEFAULT_GCC(); } } FBS::WebRtcTransport::DtlsState DtlsTransport::StateToFbs(DtlsTransport::DtlsState state) { switch (state) { case DtlsTransport::DtlsState::NEW: { return FBS::WebRtcTransport::DtlsState::NEW; } case DtlsTransport::DtlsState::CONNECTING: { return FBS::WebRtcTransport::DtlsState::CONNECTING; } case DtlsTransport::DtlsState::CONNECTED: { return FBS::WebRtcTransport::DtlsState::CONNECTED; } case DtlsTransport::DtlsState::FAILED: { return FBS::WebRtcTransport::DtlsState::FAILED; } case DtlsTransport::DtlsState::CLOSED: { return FBS::WebRtcTransport::DtlsState::CLOSED; } NO_DEFAULT_GCC(); } } DtlsTransport::FingerprintAlgorithm DtlsTransport::AlgorithmFromFbs( FBS::WebRtcTransport::FingerprintAlgorithm algorithm) { switch (algorithm) { case FBS::WebRtcTransport::FingerprintAlgorithm::SHA1: { return DtlsTransport::FingerprintAlgorithm::SHA1; } case FBS::WebRtcTransport::FingerprintAlgorithm::SHA224: { return DtlsTransport::FingerprintAlgorithm::SHA224; } case FBS::WebRtcTransport::FingerprintAlgorithm::SHA256: { return DtlsTransport::FingerprintAlgorithm::SHA256; } case FBS::WebRtcTransport::FingerprintAlgorithm::SHA384: { return DtlsTransport::FingerprintAlgorithm::SHA384; } case FBS::WebRtcTransport::FingerprintAlgorithm::SHA512: { return DtlsTransport::FingerprintAlgorithm::SHA512; } NO_DEFAULT_GCC(); } } FBS::WebRtcTransport::FingerprintAlgorithm DtlsTransport::AlgorithmToFbs( DtlsTransport::FingerprintAlgorithm algorithm) { switch (algorithm) { case DtlsTransport::FingerprintAlgorithm::SHA1: { return FBS::WebRtcTransport::FingerprintAlgorithm::SHA1; } case DtlsTransport::FingerprintAlgorithm::SHA224: { return FBS::WebRtcTransport::FingerprintAlgorithm::SHA224; } case DtlsTransport::FingerprintAlgorithm::SHA256: { return FBS::WebRtcTransport::FingerprintAlgorithm::SHA256; } case DtlsTransport::FingerprintAlgorithm::SHA384: { return FBS::WebRtcTransport::FingerprintAlgorithm::SHA384; } case DtlsTransport::FingerprintAlgorithm::SHA512: { return FBS::WebRtcTransport::FingerprintAlgorithm::SHA512; } NO_DEFAULT_GCC(); } } void DtlsTransport::GenerateCertificateAndPrivateKey() { MS_TRACE(); int ret{ 0 }; X509_NAME* certName{ nullptr }; const std::string subject = std::string("mediasoup") + std::to_string(Utils::Crypto::GetRandomUInt(100000, 999999)); // Create key with curve. // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast) DtlsTransport::privateKey = EVP_EC_gen(SN_X9_62_prime256v1); if (!DtlsTransport::privateKey) { LOG_OPENSSL_ERROR("EVP_EC_gen() failed"); goto error; } // Create the X509 certificate. DtlsTransport::certificate = X509_new(); if (!DtlsTransport::certificate) { LOG_OPENSSL_ERROR("X509_new() failed"); goto error; } // Set version 3 (note that 0 means version 1). X509_set_version(DtlsTransport::certificate, 2); // Set serial number (avoid default 0). ASN1_INTEGER_set( X509_get_serialNumber(DtlsTransport::certificate), Utils::Crypto::GetRandomUInt(1000000, 9999999)); // Set valid period. X509_gmtime_adj(X509_get_notBefore(DtlsTransport::certificate), -315360000); // -10 years. X509_gmtime_adj(X509_get_notAfter(DtlsTransport::certificate), 315360000); // 10 years. // Set the public key for the certificate using the key. ret = X509_set_pubkey(DtlsTransport::certificate, DtlsTransport::privateKey); if (ret == 0) { LOG_OPENSSL_ERROR("X509_set_pubkey() failed"); goto error; } // Set certificate fields. certName = X509_get_subject_name(DtlsTransport::certificate); if (!certName) { LOG_OPENSSL_ERROR("X509_get_subject_name() failed"); goto error; } X509_NAME_add_entry_by_txt( certName, "O", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0); X509_NAME_add_entry_by_txt( certName, "CN", MBSTRING_ASC, reinterpret_cast(subject.c_str()), -1, -1, 0); // It is self-signed so set the issuer name to be the same as the subject. ret = X509_set_issuer_name(DtlsTransport::certificate, certName); if (ret == 0) { LOG_OPENSSL_ERROR("X509_set_issuer_name() failed"); goto error; } // Sign the certificate with its own private key. ret = X509_sign(DtlsTransport::certificate, DtlsTransport::privateKey, EVP_sha256()); if (ret == 0) { LOG_OPENSSL_ERROR("X509_sign() failed"); goto error; } return; error: if (DtlsTransport::privateKey) { EVP_PKEY_free(DtlsTransport::privateKey); } if (DtlsTransport::certificate) { X509_free(DtlsTransport::certificate); } MS_THROW_ERROR("DTLS certificate and private key generation failed"); } void DtlsTransport::ReadCertificateAndPrivateKeyFromFiles() { MS_TRACE(); FILE* file{ nullptr }; file = fopen(Settings::configuration.dtlsCertificateFile.c_str(), "r"); if (!file) { MS_ERROR("error reading DTLS certificate file: %s", std::strerror(errno)); goto error; } DtlsTransport::certificate = PEM_read_X509(file, nullptr, nullptr, nullptr); if (!DtlsTransport::certificate) { LOG_OPENSSL_ERROR("PEM_read_X509() failed"); goto error; } fclose(file); file = fopen(Settings::configuration.dtlsPrivateKeyFile.c_str(), "r"); if (!file) { MS_ERROR("error reading DTLS private key file: %s", std::strerror(errno)); goto error; } DtlsTransport::privateKey = PEM_read_PrivateKey(file, nullptr, nullptr, nullptr); if (!DtlsTransport::privateKey) { LOG_OPENSSL_ERROR("PEM_read_PrivateKey() failed"); goto error; } fclose(file); return; error: MS_THROW_ERROR("error reading DTLS certificate and private key PEM files"); } void DtlsTransport::CreateSslCtx() { MS_TRACE(); std::string dtlsSrtpCryptoSuites; int ret; /* Set the global DTLS context. */ // Both DTLS 1.0 and 1.2 (requires OpenSSL >= 1.1.0). DtlsTransport::sslCtx = SSL_CTX_new(DTLS_method()); if (!DtlsTransport::sslCtx) { LOG_OPENSSL_ERROR("SSL_CTX_new() failed"); goto error; } ret = SSL_CTX_use_certificate(DtlsTransport::sslCtx, DtlsTransport::certificate); if (ret == 0) { LOG_OPENSSL_ERROR("SSL_CTX_use_certificate() failed"); goto error; } ret = SSL_CTX_use_PrivateKey(DtlsTransport::sslCtx, DtlsTransport::privateKey); if (ret == 0) { LOG_OPENSSL_ERROR("SSL_CTX_use_PrivateKey() failed"); goto error; } ret = SSL_CTX_check_private_key(DtlsTransport::sslCtx); if (ret == 0) { LOG_OPENSSL_ERROR("SSL_CTX_check_private_key() failed"); goto error; } // Set options. SSL_CTX_set_options( DtlsTransport::sslCtx, SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET | SSL_OP_SINGLE_ECDH_USE | SSL_OP_NO_QUERY_MTU); // Don't use sessions cache. SSL_CTX_set_session_cache_mode(DtlsTransport::sslCtx, SSL_SESS_CACHE_OFF); SSL_CTX_set_verify_depth(DtlsTransport::sslCtx, 4); // Require certificate from peer. SSL_CTX_set_verify( DtlsTransport::sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, onSslCertificateVerify); // Set SSL info callback. SSL_CTX_set_info_callback(DtlsTransport::sslCtx, onSslInfo); // Set ciphers. ret = SSL_CTX_set_cipher_list( DtlsTransport::sslCtx, "DEFAULT:!NULL:!aNULL:!SHA256:!SHA384:!aECDH:!AESGCM+AES256:!aPSK"); if (ret == 0) { LOG_OPENSSL_ERROR("SSL_CTX_set_cipher_list() failed"); goto error; } // Enable ECDH ciphers. // // NOTE: As per official docs: // "In OpenSSL 1.1.0, ECDH handling was made automatic. Applications should // not call SSL_CTX_set_ecdh_auto() or similar APIs anymore." // Set the "use_srtp" DTLS extension. for (auto it = DtlsTransport::SrtpCryptoSuites.begin(); it != DtlsTransport::SrtpCryptoSuites.end(); ++it) { if (it != DtlsTransport::SrtpCryptoSuites.begin()) { dtlsSrtpCryptoSuites += ":"; } const SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(*it); dtlsSrtpCryptoSuites += cryptoSuiteEntry->name; } MS_DEBUG_2TAGS( dtls, srtp, "setting SRTP crypto suites for DTLS: %s", dtlsSrtpCryptoSuites.c_str()); // NOTE: This function returns 0 on success. ret = SSL_CTX_set_tlsext_use_srtp(DtlsTransport::sslCtx, dtlsSrtpCryptoSuites.c_str()); if (ret != 0) { MS_ERROR( "SSL_CTX_set_tlsext_use_srtp() failed when entering '%s'", dtlsSrtpCryptoSuites.c_str()); LOG_OPENSSL_ERROR("SSL_CTX_set_tlsext_use_srtp() failed"); goto error; } return; error: if (DtlsTransport::sslCtx) { SSL_CTX_free(DtlsTransport::sslCtx); DtlsTransport::sslCtx = nullptr; } MS_THROW_ERROR("SSL context creation failed"); } void DtlsTransport::GenerateFingerprints() { MS_TRACE(); for (const auto& kv : DtlsTransport::String2FingerprintAlgorithm) { const std::string& algorithmString = kv.first; const FingerprintAlgorithm algorithm = kv.second; uint8_t binaryFingerprint[EVP_MAX_MD_SIZE]; unsigned int size{ 0 }; char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1]; const EVP_MD* hashFunction; int ret; switch (algorithm) { case FingerprintAlgorithm::SHA1: { hashFunction = EVP_sha1(); break; } case FingerprintAlgorithm::SHA224: { hashFunction = EVP_sha224(); break; } case FingerprintAlgorithm::SHA256: { hashFunction = EVP_sha256(); break; } case FingerprintAlgorithm::SHA384: { hashFunction = EVP_sha384(); break; } case FingerprintAlgorithm::SHA512: { hashFunction = EVP_sha512(); break; } default: { MS_THROW_ERROR("unknown algorithm"); } } ret = X509_digest( DtlsTransport::certificate, hashFunction, binaryFingerprint, std::addressof(size)); if (ret == 0 || size == 0) { MS_ERROR("X509_digest() failed"); MS_THROW_ERROR("Fingerprints generation failed"); } // Convert to hexadecimal format in uppercase with colons. for (unsigned int i{ 0 }; i < size; ++i) { std::snprintf(hexFingerprint + (i * 3), 4, "%.2X:", binaryFingerprint[i]); } hexFingerprint[(size * 3) - 1] = '\0'; MS_DEBUG_TAG(dtls, "%-7s fingerprint: %s", algorithmString.c_str(), hexFingerprint); // Store it in the vector. DtlsTransport::Fingerprint fingerprint; fingerprint.algorithm = algorithm; fingerprint.value = hexFingerprint; DtlsTransport::localFingerprints.push_back(fingerprint); } } /* Instance methods. */ DtlsTransport::DtlsTransport(Listener* listener, SharedInterface* shared) : listener(listener), shared(shared), ssl(SSL_new(DtlsTransport::sslCtx)) { MS_TRACE(); if (!this->ssl) { LOG_OPENSSL_ERROR("SSL_new() failed"); goto error; } // Set this as custom data. SSL_set_ex_data(this->ssl, 0, static_cast(this)); this->sslBioFromNetwork = BIO_new(BIO_s_mem()); if (!this->sslBioFromNetwork) { LOG_OPENSSL_ERROR("BIO_new() failed"); SSL_free(this->ssl); goto error; } this->sslBioToNetwork = BIO_new(BIO_s_mem()); if (!this->sslBioToNetwork) { LOG_OPENSSL_ERROR("BIO_new() failed"); BIO_free(this->sslBioFromNetwork); SSL_free(this->ssl); goto error; } // Set the MTU so that we don't send packets that are too large with no // fragmentation. SSL_set_mtu(this->ssl, DtlsMtu); DTLS_set_link_mtu(this->ssl, DtlsMtu); // We want to monitor OpenSSL write operations into our |sslBioToNetwork| // buffer so we can immediately send those DTLS bytes (containing full DTLS // messages, or valid DTLS fragment messages, or combination of them) to // the endpoint, and hence we honor the configured DTLS MTU. BIO_set_callback_ex(this->sslBioToNetwork, onSslBioOut); BIO_set_callback_arg(this->sslBioToNetwork, reinterpret_cast(this)); SSL_set_bio(this->ssl, this->sslBioFromNetwork, this->sslBioToNetwork); // Set callback handler for setting DTLS timer interval. DTLS_set_timer_cb(this->ssl, onSslDtlsTimer); // Set the DTLS timer. this->timer = this->shared->CreateTimer(this); return; error: // NOTE: At this point SSL_set_bio() was not called so we must free BIOs as // well. if (this->sslBioFromNetwork) { BIO_free(this->sslBioFromNetwork); } if (this->sslBioToNetwork) { BIO_free(this->sslBioToNetwork); } if (this->ssl) { SSL_free(this->ssl); } // NOTE: If this is not catched by the caller the program will abort, but // this should never happen. MS_THROW_ERROR("DtlsTransport instance creation failed"); } DtlsTransport::~DtlsTransport() { MS_TRACE(); if (IsRunning()) { // Send close alert to the peer. SSL_shutdown(this->ssl); } if (this->ssl) { SSL_free(this->ssl); this->ssl = nullptr; this->sslBioFromNetwork = nullptr; this->sslBioToNetwork = nullptr; } // Close the DTLS timer. delete this->timer; } void DtlsTransport::Dump(int indentation) const { MS_TRACE(); std::string state{ "new" }; std::string role{ "none " }; switch (this->state) { case DtlsState::CONNECTING: { state = "connecting"; break; } case DtlsState::CONNECTED: { state = "connected"; break; } case DtlsState::FAILED: { state = "failed"; break; } case DtlsState::CLOSED: { state = "closed"; break; } default:; } if (this->localRole.has_value()) { switch (this->localRole.value()) { case Role::AUTO: { role = "auto"; break; } case Role::SERVER: { role = "server"; break; } case Role::CLIENT: { role = "client"; break; } } } MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " state: %s", state.c_str()); MS_DUMP_CLEAN(indentation, " role: %s", role.c_str()); MS_DUMP_CLEAN(indentation, " handshake done: %s", this->handshakeDone ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } void DtlsTransport::Run(Role localRole) { MS_TRACE(); MS_ASSERT( localRole == Role::CLIENT || localRole == Role::SERVER, "local DTLS role must be 'client' or 'server'"); if (this->localRole.has_value() && localRole == this->localRole.value()) { MS_ERROR("same local DTLS role provided, doing nothing"); return; } // If the previous local DTLS role was 'client' or 'server' do reset. if ( this->localRole.has_value() && (this->localRole.value() == Role::CLIENT || this->localRole.value() == Role::SERVER)) { MS_DEBUG_TAG(dtls, "resetting DTLS due to local role change"); Reset(); } // Update local role. this->localRole = localRole; // Set state and notify the listener. this->state = DtlsState::CONNECTING; this->listener->OnDtlsTransportConnecting(this); switch (this->localRole.value()) { case Role::CLIENT: { MS_DEBUG_TAG(dtls, "running [role:client]"); SSL_set_connect_state(this->ssl); SSL_do_handshake(this->ssl); SetTimeout(); break; } case Role::SERVER: { MS_DEBUG_TAG(dtls, "running [role:server]"); SSL_set_accept_state(this->ssl); SSL_do_handshake(this->ssl); break; } default: { MS_ABORT("invalid local DTLS role"); } } } bool DtlsTransport::SetRemoteFingerprint(const Fingerprint& fingerprint) { MS_TRACE(); this->remoteFingerprint = fingerprint; // The remote fingerpring may have been set after DTLS handshake was done, // so we may need to process it now. if (this->handshakeDone && this->state != DtlsState::CONNECTED) { MS_DEBUG_TAG(dtls, "handshake already done, processing it right now"); return ProcessHandshake(); } return true; } void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len) { MS_TRACE(); int written; int read; if (!IsRunning()) { MS_ERROR("cannot process data while not running"); return; } // Write the received DTLS data into the sslBioFromNetwork. written = BIO_write(this->sslBioFromNetwork, static_cast(data), static_cast(len)); if (written != static_cast(len)) { MS_WARN_TAG( dtls, "OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)", static_cast(written), len); } // Must call SSL_read() to process received DTLS data. read = SSL_read(this->ssl, static_cast(DtlsTransport::sslReadBuffer), SslReadBufferSize); // Check SSL status and return if it is bad/closed. if (!CheckStatus(read)) { return; } // Set/update the DTLS timeout. if (!SetTimeout()) { return; } // Application data received. Notify to the listener. if (read > 0) { // It is allowed to receive DTLS data even before validating remote // fingerprint. if (!this->handshakeDone) { MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done"); return; } // Notify the listener. this->listener->OnDtlsTransportApplicationDataReceived( this, static_cast(DtlsTransport::sslReadBuffer), static_cast(read)); } } bool DtlsTransport::SendApplicationData(const uint8_t* data, size_t len) { MS_TRACE(); // We cannot send data to the peer if its remote fingerprint is not // validated. if (this->state != DtlsState::CONNECTED) { MS_WARN_TAG(dtls, "cannot send application data while DTLS is not fully connected"); return false; } if (len == 0) { MS_WARN_TAG(dtls, "ignoring 0 length data"); return false; } const int written = SSL_write(this->ssl, static_cast(data), static_cast(len)); if (written < 0) { LOG_OPENSSL_ERROR("SSL_write() failed"); if (!CheckStatus(written)) { return false; } } else if (written != static_cast(len)) { MS_WARN_TAG( dtls, "OpenSSL SSL_write() wrote less (%d bytes) than given data (%zu bytes)", written, len); return false; } return true; } /** * This method is called within our |onSslBioOut| callback above. As told * there, it's guaranteed that OpenSSL invokes that callback with all the * bytes currently written in our BIO mem buffer |this->sslBioToNetwork| so * we can safely reset/clear that buffer once we have sent the data to the * endpoint. */ void DtlsTransport::SendDtlsData(const uint8_t* data, size_t len) { MS_TRACE(); MS_DEBUG_DEV("%zu bytes of DTLS data ready to be sent", len); // Notify the listener. this->listener->OnDtlsTransportSendData(this, data, len); // Clear the BIO buffer. auto ret = BIO_reset(this->sslBioToNetwork); if (ret != 1) { MS_ERROR("BIO_reset() failed [ret:%d]", ret); } } void DtlsTransport::Reset() { MS_TRACE(); int ret; if (!IsRunning()) { return; } MS_WARN_TAG(dtls, "resetting DTLS transport"); // Stop the DTLS timer. this->timer->Stop(); // NOTE: We need to reset the SSL instance so we need to "shutdown" it, but // we don't want to send a DTLS Close Alert to the peer. However this is // gonna happen since SSL_shutdown() will trigger a DTLS Close Alert and // we'll have our onSslBioOut() callback called to deliver it. SSL_shutdown(this->ssl); this->localRole.reset(); this->state = DtlsState::NEW; this->handshakeDone = false; this->handshakeDoneNow = false; // Reset SSL status. // NOTE: For this to properly work, SSL_shutdown() must be called before. // NOTE: This may fail if not enough DTLS handshake data has been received, // but we don't care so just clear the error queue. ret = SSL_clear(this->ssl); if (ret == 0) { ERR_clear_error(); } } bool DtlsTransport::CheckStatus(int returnCode) { MS_TRACE(); const bool wasHandshakeDone = this->handshakeDone; const int err = SSL_get_error(this->ssl, returnCode); switch (err) { case SSL_ERROR_NONE: { break; } case SSL_ERROR_SSL: { LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SSL"); break; } case SSL_ERROR_WANT_READ: { break; } case SSL_ERROR_WANT_WRITE: { MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_WRITE"); break; } case SSL_ERROR_WANT_X509_LOOKUP: { MS_DEBUG_TAG(dtls, "SSL status: SSL_ERROR_WANT_X509_LOOKUP"); break; } case SSL_ERROR_SYSCALL: { LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SYSCALL"); break; } case SSL_ERROR_ZERO_RETURN: { break; } case SSL_ERROR_WANT_CONNECT: { MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_CONNECT"); break; } case SSL_ERROR_WANT_ACCEPT: { MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_ACCEPT"); break; } default: { MS_WARN_TAG(dtls, "SSL status: unknown error"); } } // Check if the handshake (or re-handshake) has been done right now. if (this->handshakeDoneNow) { this->handshakeDoneNow = false; this->handshakeDone = true; // Stop the timer. this->timer->Stop(); // Process the handshake just once (ignore if DTLS renegotiation). if (!wasHandshakeDone && this->remoteFingerprint.has_value()) { return ProcessHandshake(); } return true; } // Check if the peer sent close alert or a fatal error happened. else if (((SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN) != 0) || err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL) { if (this->state == DtlsState::CONNECTED) { MS_DEBUG_TAG(dtls, "disconnected"); Reset(); // Set state and notify the listener. this->state = DtlsState::CLOSED; this->listener->OnDtlsTransportClosed(this); } else { MS_WARN_TAG(dtls, "connection failed"); Reset(); // Set state and notify the listener. this->state = DtlsState::FAILED; this->listener->OnDtlsTransportFailed(this); } return false; } else { return true; } } // NOLINTNEXTLINE(misc-no-recursion) bool DtlsTransport::SetTimeout() { MS_TRACE(); MS_ASSERT(this->ssl, "this->ssl is not set"); MS_ASSERT( this->state == DtlsState::CONNECTING || this->state == DtlsState::CONNECTED, "invalid DTLS state"); uv_timeval_t dtlsTimeout{ 0, 0 }; uint64_t timeoutMs; // DTLSv1_get_timeout queries the next DTLS handshake timeout. If there is // a timeout in progress, it sets *out to the time remaining and returns // one. Otherwise, it returns zero. DTLSv1_get_timeout(this->ssl, static_cast(std::addressof(dtlsTimeout))); timeoutMs = (dtlsTimeout.tv_sec * static_cast(1000)) + (dtlsTimeout.tv_usec / 1000); if (timeoutMs == 0) { MS_DEBUG_DEV("timeout is 0, calling OnTimer() callback directly"); OnTimer(this->timer); return true; } else if (timeoutMs < 30000) { MS_DEBUG_DEV("DTLS timer set in %" PRIu64 "ms", timeoutMs); this->timer->Start(timeoutMs); return true; } // NOTE: Don't start the timer again if the timeout is greater than 30 // seconds. else { MS_WARN_TAG(dtls, "DTLS timeout too high (%" PRIu64 "ms), resetting DLTS", timeoutMs); Reset(); // Set state and notify the listener. this->state = DtlsState::FAILED; this->listener->OnDtlsTransportFailed(this); return false; } } bool DtlsTransport::ProcessHandshake() { MS_TRACE(); MS_ASSERT(this->handshakeDone, "handshake not done yet"); // Validate the remote fingerprint. if (!CheckRemoteFingerprint()) { Reset(); // Set state and notify the listener. this->state = DtlsState::FAILED; this->listener->OnDtlsTransportFailed(this); return false; } // Get the negotiated SRTP crypto suite. auto srtpCryptoSuite = GetNegotiatedSrtpCryptoSuite(); if (srtpCryptoSuite) { // Extract the SRTP keys (will notify the listener with them). ExtractSrtpKeys(srtpCryptoSuite.value()); return true; } // NOTE: We assume that "use_srtp" DTLS extension is required even if // there is no audio/video. MS_WARN_2TAGS(dtls, srtp, "SRTP crypto suite not negotiated"); Reset(); // Set state and notify the listener. this->state = DtlsState::FAILED; this->listener->OnDtlsTransportFailed(this); return false; } bool DtlsTransport::CheckRemoteFingerprint() { MS_TRACE(); MS_ASSERT(this->remoteFingerprint.has_value(), "remote fingerprint not set"); X509* certificate; uint8_t binaryFingerprint[EVP_MAX_MD_SIZE]; unsigned int size{ 0 }; char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1]; const EVP_MD* hashFunction; int ret; certificate = SSL_get_peer_certificate(this->ssl); if (!certificate) { MS_WARN_TAG(dtls, "no certificate was provided by the peer"); return false; } switch (this->remoteFingerprint->algorithm) { case FingerprintAlgorithm::SHA1: { hashFunction = EVP_sha1(); break; } case FingerprintAlgorithm::SHA224: { hashFunction = EVP_sha224(); break; } case FingerprintAlgorithm::SHA256: { hashFunction = EVP_sha256(); break; } case FingerprintAlgorithm::SHA384: { hashFunction = EVP_sha384(); break; } case FingerprintAlgorithm::SHA512: { hashFunction = EVP_sha512(); break; } NO_DEFAULT_GCC(); } // Compare the remote fingerprint with the value given via signaling. ret = X509_digest(certificate, hashFunction, binaryFingerprint, std::addressof(size)); if (ret == 0 || size == 0) { MS_ERROR("X509_digest() failed"); X509_free(certificate); return false; } // Convert to hexadecimal format in uppercase with colons. for (unsigned int i{ 0 }; i < size; ++i) { std::snprintf(hexFingerprint + (i * 3), 4, "%.2X:", binaryFingerprint[i]); } hexFingerprint[(size * 3) - 1] = '\0'; if (this->remoteFingerprint->value != hexFingerprint) { MS_WARN_TAG( dtls, "fingerprint in the remote certificate (%s) does not match the announced one (%s)", hexFingerprint, this->remoteFingerprint->value.c_str()); X509_free(certificate); return false; } MS_DEBUG_TAG(dtls, "valid remote fingerprint"); // Get the remote certificate in PEM format. BIO* bio = BIO_new(BIO_s_mem()); // Ensure the underlying BUF_MEM structure is also freed. // NOTE: Avoid stupid "warning: value computed is not used [-Wunused-value]" // since BIO_set_close() always returns 1. (void)BIO_set_close(bio, BIO_CLOSE); ret = PEM_write_bio_X509(bio, certificate); if (ret != 1) { LOG_OPENSSL_ERROR("PEM_write_bio_X509() failed"); X509_free(certificate); BIO_free(bio); return false; } BUF_MEM* mem; BIO_get_mem_ptr(bio, std::addressof(mem)); if (!mem || !mem->data || mem->length == 0u) { LOG_OPENSSL_ERROR("BIO_get_mem_ptr() failed"); X509_free(certificate); BIO_free(bio); return false; } this->remoteCert = std::string(mem->data, mem->length); X509_free(certificate); BIO_free(bio); return true; } void DtlsTransport::ExtractSrtpKeys(RTC::SrtpSession::CryptoSuite srtpCryptoSuite) { MS_TRACE(); size_t srtpKeyLength{ 0 }; size_t srtpSaltLength{ 0 }; size_t srtpMasterLength{ 0 }; switch (srtpCryptoSuite) { case RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM: { srtpKeyLength = SrtpAesGcm256MasterKeyLength; srtpSaltLength = SrtpAesGcm256MasterSaltLength; srtpMasterLength = SrtpAesGcm256MasterLength; break; } case RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM: { srtpKeyLength = SrtpAesGcm128MasterKeyLength; srtpSaltLength = SrtpAesGcm128MasterSaltLength; srtpMasterLength = SrtpAesGcm128MasterLength; break; } case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80: case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32: { srtpKeyLength = SrtpMasterKeyLength; srtpSaltLength = SrtpMasterSaltLength; srtpMasterLength = SrtpMasterLength; break; } } auto* srtpMaterial = new uint8_t[srtpMasterLength * 2]; uint8_t* srtpLocalKey{ nullptr }; uint8_t* srtpLocalSalt{ nullptr }; uint8_t* srtpRemoteKey{ nullptr }; uint8_t* srtpRemoteSalt{ nullptr }; auto* srtpLocalMasterKey = new uint8_t[srtpMasterLength]; auto* srtpRemoteMasterKey = new uint8_t[srtpMasterLength]; int ret; ret = SSL_export_keying_material( this->ssl, srtpMaterial, srtpMasterLength * 2, "EXTRACTOR-dtls_srtp", 19, nullptr, 0, 0); MS_ASSERT(ret != 0, "SSL_export_keying_material() failed"); MS_ASSERT(this->localRole.has_value(), "no DTLS role set"); switch (this->localRole.value()) { case Role::SERVER: { srtpRemoteKey = srtpMaterial; srtpLocalKey = srtpRemoteKey + srtpKeyLength; srtpRemoteSalt = srtpLocalKey + srtpKeyLength; srtpLocalSalt = srtpRemoteSalt + srtpSaltLength; break; } case Role::CLIENT: { srtpLocalKey = srtpMaterial; srtpRemoteKey = srtpLocalKey + srtpKeyLength; srtpLocalSalt = srtpRemoteKey + srtpKeyLength; srtpRemoteSalt = srtpLocalSalt + srtpSaltLength; break; } default: { MS_ABORT("no DTLS role set"); } } // Create the SRTP local master key. std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength); std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength); // Create the SRTP remote master key. std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength); std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength); // Set state and notify the listener. this->state = DtlsState::CONNECTED; this->listener->OnDtlsTransportConnected( this, srtpCryptoSuite, srtpLocalMasterKey, srtpMasterLength, srtpRemoteMasterKey, srtpMasterLength, this->remoteCert); delete[] srtpMaterial; delete[] srtpLocalMasterKey; delete[] srtpRemoteMasterKey; } std::optional DtlsTransport::GetNegotiatedSrtpCryptoSuite() { MS_TRACE(); std::optional negotiatedSrtpCryptoSuite; // Ensure that the SRTP crypto suite has been negotiated. // NOTE: This is a OpenSSL type. const SRTP_PROTECTION_PROFILE* sslSrtpCryptoSuite = SSL_get_selected_srtp_profile(this->ssl); if (!sslSrtpCryptoSuite) { return negotiatedSrtpCryptoSuite; } // Get the negotiated SRTP crypto suite. for (const auto& srtpCryptoSuite : DtlsTransport::SrtpCryptoSuites) { const SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(srtpCryptoSuite); if (std::strcmp(sslSrtpCryptoSuite->name, cryptoSuiteEntry->name) == 0) { MS_DEBUG_2TAGS(dtls, srtp, "chosen SRTP crypto suite: %s", cryptoSuiteEntry->name); negotiatedSrtpCryptoSuite = cryptoSuiteEntry->cryptoSuite; } } MS_ASSERT( negotiatedSrtpCryptoSuite.has_value(), "chosen SRTP crypto suite is not an available one"); return negotiatedSrtpCryptoSuite; } void DtlsTransport::OnSslInfo(int where, int ret) { MS_TRACE(); const int w = where & -SSL_ST_MASK; const char* role; if ((w & SSL_ST_CONNECT) != 0) { role = "client"; } else if ((w & SSL_ST_ACCEPT) != 0) { role = "server"; } else { role = "undefined"; } if ((where & SSL_CB_LOOP) != 0) { MS_DEBUG_TAG(dtls, "[role:%s, action:'%s']", role, SSL_state_string_long(this->ssl)); } else if ((where & SSL_CB_ALERT) != 0) { const char* alertType; switch (*SSL_alert_type_string(ret)) { case 'W': { alertType = "warning"; break; } case 'F': { alertType = "fatal"; break; } default: { alertType = "undefined"; } } if ((where & SSL_CB_READ) != 0) { MS_WARN_TAG(dtls, "received DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret)); } else if ((where & SSL_CB_WRITE) != 0) { MS_DEBUG_TAG(dtls, "sending DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret)); } else { MS_DEBUG_TAG(dtls, "DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret)); } } else if ((where & SSL_CB_EXIT) != 0) { if (ret == 0) { MS_DEBUG_TAG(dtls, "[role:%s, failed:'%s']", role, SSL_state_string_long(this->ssl)); } else if (ret < 0) { MS_DEBUG_TAG(dtls, "role: %s, waiting:'%s']", role, SSL_state_string_long(this->ssl)); } } else if ((where & SSL_CB_HANDSHAKE_START) != 0) { MS_DEBUG_TAG(dtls, "DTLS handshake start"); } else if ((where & SSL_CB_HANDSHAKE_DONE) != 0) { MS_DEBUG_TAG(dtls, "DTLS handshake done"); this->handshakeDoneNow = true; } // NOTE: checking SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN here // upon receipt of a close alert does not work (the flag is set after this // callback). } // NOLINTNEXTLINE(misc-no-recursion) void DtlsTransport::OnTimer(TimerHandleInterface* /*timer*/) { MS_TRACE(); // Workaround for https://github.com/openssl/openssl/issues/7998. if (this->handshakeDone) { MS_DEBUG_DEV("handshake is done so return"); return; } // DTLSv1_handle_timeout is called when a DTLS handshake timeout expires. // If no timeout had expired, it returns 0. Otherwise, it retransmits the // previous flight of handshake messages and returns 1. If too many timeouts // had expired without progress or an error occurs, it returns -1. auto ret = DTLSv1_handle_timeout(this->ssl); if (ret == 1) { // Set the DTLS timer again. SetTimeout(); } else if (ret == -1) { MS_WARN_TAG(dtls, "DTLSv1_handle_timeout() failed"); Reset(); // Set state and notify the listener. this->state = DtlsState::FAILED; this->listener->OnDtlsTransportFailed(this); } } } // namespace RTC ================================================ FILE: worker/src/RTC/ICE/IceCandidate.cpp ================================================ #define MS_CLASS "RTC::ICE::IceCandidate" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/ICE/IceCandidate.hpp" #include "Logger.hpp" namespace RTC { namespace ICE { /* Class methods. */ IceCandidate::CandidateType IceCandidate::CandidateTypeFromFbs( FBS::WebRtcTransport::IceCandidateType type) { switch (type) { case FBS::WebRtcTransport::IceCandidateType::HOST: { return IceCandidate::CandidateType::HOST; } NO_DEFAULT_GCC(); } } FBS::WebRtcTransport::IceCandidateType IceCandidate::CandidateTypeToFbs( IceCandidate::CandidateType type) { switch (type) { case IceCandidate::CandidateType::HOST: { return FBS::WebRtcTransport::IceCandidateType::HOST; } NO_DEFAULT_GCC(); } } IceCandidate::TcpCandidateType IceCandidate::TcpCandidateTypeFromFbs( FBS::WebRtcTransport::IceCandidateTcpType type) { switch (type) { case FBS::WebRtcTransport::IceCandidateTcpType::PASSIVE: { return IceCandidate::TcpCandidateType::PASSIVE; } NO_DEFAULT_GCC(); } } FBS::WebRtcTransport::IceCandidateTcpType IceCandidate::TcpCandidateTypeToFbs( IceCandidate::TcpCandidateType type) { switch (type) { case IceCandidate::TcpCandidateType::PASSIVE: { return FBS::WebRtcTransport::IceCandidateTcpType::PASSIVE; } NO_DEFAULT_GCC(); } } /* Instance methods. */ flatbuffers::Offset IceCandidate::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); auto protocol = TransportTuple::ProtocolToFbs(this->protocol); auto type = CandidateTypeToFbs(this->type); flatbuffers::Optional tcpType; if (this->protocol == Protocol::TCP) { tcpType.emplace(TcpCandidateTypeToFbs(this->tcpType)); } return FBS::WebRtcTransport::CreateIceCandidateDirect( builder, // foundation. this->foundation.c_str(), // priority. this->priority, // address. this->address.c_str(), // protocol. protocol, // port. this->port, // type. type, // tcpType. tcpType); } } // namespace ICE } // namespace RTC ================================================ FILE: worker/src/RTC/ICE/IceServer.cpp ================================================ #define MS_CLASS "RTC::ICE::IceServer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/ICE/IceServer.hpp" #include "Logger.hpp" #include namespace RTC { namespace ICE { /* Static. */ static constexpr size_t StunResponseFactoryBufferLength{ 65536 }; static thread_local uint8_t StunResponseFactoryBuffer[StunResponseFactoryBufferLength]; static constexpr size_t MaxTuples{ 8 }; static constexpr uint8_t ConsentCheckMinTimeoutSec{ 10u }; static constexpr uint8_t ConsentCheckMaxTimeoutSec{ 60u }; /* Class variables. */ // clang-format off std::unordered_map IceServer::iceStateToString = { { IceServer::IceState::NEW, "new" }, { IceServer::IceState::CONNECTED, "connected" }, { IceServer::IceState::COMPLETED, "completed" }, { IceServer::IceState::DISCONNECTED, "disconnected" }, }; // clang-format on /* Class methods. */ const std::string& IceServer::IceStateToString(IceState iceState) { MS_TRACE(); return IceServer::iceStateToString.at(iceState); } FBS::WebRtcTransport::IceState IceServer::IceStateToFbs(IceServer::IceState state) { MS_TRACE(); switch (state) { case IceServer::IceState::NEW: { return FBS::WebRtcTransport::IceState::NEW; } case IceServer::IceState::CONNECTED: { return FBS::WebRtcTransport::IceState::CONNECTED; } case IceServer::IceState::COMPLETED: { return FBS::WebRtcTransport::IceState::COMPLETED; } case IceServer::IceState::DISCONNECTED: { return FBS::WebRtcTransport::IceState::DISCONNECTED; } NO_DEFAULT_GCC(); } } /* Instance methods. */ IceServer::IceServer( Listener* listener, SharedInterface* shared, const std::string& usernameFragment, const std::string& password, uint8_t consentTimeoutSec) : listener(listener), shared(shared), usernameFragment(usernameFragment), password(password) { MS_TRACE(); if (consentTimeoutSec == 0u) { // 0 means disabled so it's a valid value. } else if (consentTimeoutSec < ConsentCheckMinTimeoutSec) { MS_WARN_TAG( ice, "consentTimeoutSec cannot be lower than %" PRIu8 " seconds, fixing it", ConsentCheckMinTimeoutSec); consentTimeoutSec = ConsentCheckMinTimeoutSec; } else if (consentTimeoutSec > ConsentCheckMaxTimeoutSec) { MS_WARN_TAG( ice, "consentTimeoutSec cannot be higher than %" PRIu8 " seconds, fixing it", ConsentCheckMaxTimeoutSec); consentTimeoutSec = ConsentCheckMaxTimeoutSec; } this->consentTimeoutMs = consentTimeoutSec * 1000; // Notify the listener. this->listener->OnIceServerLocalUsernameFragmentAdded(this, usernameFragment); } IceServer::~IceServer() { MS_TRACE(); // Here we must notify the listener about the removal of current // usernameFragments (and also the old one if any) and all tuples. this->listener->OnIceServerLocalUsernameFragmentRemoved(this, usernameFragment); if (!this->oldUsernameFragment.empty()) { this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); } // Clear all tuples. this->isRemovingTuples = true; for (const auto& it : this->tuples) { auto* storedTuple = const_cast(std::addressof(it)); // Notify the listener. this->listener->OnIceServerTupleRemoved(this, storedTuple); } this->isRemovingTuples = false; // Clear all tuples. // NOTE: Do it after notifying the listener since the listener may need to // use/read the tuple being removed so we cannot free it yet. this->tuples.clear(); // Unset selected tuple. this->selectedTuple = nullptr; // Delete the ICE consent check timer. delete this->consentCheckTimer; this->consentCheckTimer = nullptr; } void IceServer::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " state: %s", IceServer::IceStateToString(this->state).c_str()); MS_DUMP_CLEAN(indentation, " tuples:"); for (const auto& tuple : this->tuples) { tuple.Dump(indentation + 2); } if (this->selectedTuple) { MS_DUMP_CLEAN(indentation, " selected tuple:"); this->selectedTuple->Dump(indentation + 2); } MS_DUMP_CLEAN(indentation, " consent timeout (ms): %" PRIu16, this->consentTimeoutMs); MS_DUMP_CLEAN(indentation, " remote nomination: %" PRIu32, this->remoteNomination); MS_DUMP_CLEAN(indentation, ""); } void IceServer::ProcessStunPacket(const RTC::ICE::StunPacket* packet, RTC::TransportTuple* tuple) { MS_TRACE(); switch (packet->GetClass()) { case RTC::ICE::StunPacket::Class::REQUEST: { ProcessStunRequest(packet, tuple); break; } case RTC::ICE::StunPacket::Class::INDICATION: { ProcessStunIndication(packet); break; } case RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE: case RTC::ICE::StunPacket::Class::ERROR_RESPONSE: { ProcessStunResponse(packet); break; } default: { MS_WARN_TAG( ice, "unknown STUN class %" PRIu16 ", discarded", static_cast(packet->GetClass())); } } } void IceServer::RestartIce(const std::string& usernameFragment, const std::string& password) { MS_TRACE(); if (!this->oldUsernameFragment.empty()) { this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); } this->oldUsernameFragment = this->usernameFragment; this->usernameFragment = usernameFragment; this->oldPassword = this->password; this->password = password; this->remoteNomination = 0u; // Notify the listener. this->listener->OnIceServerLocalUsernameFragmentAdded(this, usernameFragment); // NOTE: Do not call listener->OnIceServerLocalUsernameFragmentRemoved() // yet with old usernameFragment. Wait until we receive a STUN packet // with the new one. // Restart ICE consent check (if running) to give some time to the // client to establish ICE again. if (IsConsentCheckSupported() && IsConsentCheckRunning()) { RestartConsentCheck(); } } bool IceServer::IsValidTuple(const RTC::TransportTuple* tuple) const { MS_TRACE(); return HasTuple(tuple) != nullptr; } void IceServer::RemoveTuple(RTC::TransportTuple* tuple) { MS_TRACE(); // If IceServer is removing a tuple or all tuples (for instance in the // destructor), the OnIceServerTupleRemoved() callback may end triggering // new calls to RemoveTuple(). We must ignore it to avoid double-free issues. if (this->isRemovingTuples) { return; } RTC::TransportTuple* removedTuple{ nullptr }; // Find the removed tuple. auto it = this->tuples.begin(); for (; it != this->tuples.end(); ++it) { RTC::TransportTuple* storedTuple = std::addressof(*it); if (storedTuple->Compare(tuple)) { removedTuple = storedTuple; break; } } // If not found, ignore. if (!removedTuple) { return; } // Notify the listener. this->isRemovingTuples = true; this->listener->OnIceServerTupleRemoved(this, removedTuple); this->isRemovingTuples = false; // Remove it from the list of tuples. // NOTE: Do it after notifying the listener since the listener may need to // use/read the tuple being removed so we cannot free it yet. this->tuples.erase(it); // If this is the selected tuple, do things. if (removedTuple == this->selectedTuple) { this->selectedTuple = nullptr; // Mark the first tuple as selected tuple (if any) but only if state was // 'connected' or 'completed'. if ( (this->state == IceState::CONNECTED || this->state == IceState::COMPLETED) && this->tuples.begin() != this->tuples.end()) { SetSelectedTuple(std::addressof(*this->tuples.begin())); // Restart ICE consent check to let the client send new consent requests // on the new selected tuple. if (IsConsentCheckSupported()) { RestartConsentCheck(); } } // Or just emit 'disconnected'. else { // Update state. this->state = IceState::DISCONNECTED; // Reset remote nomination. this->remoteNomination = 0u; // Notify the listener. this->listener->OnIceServerDisconnected(this); if (IsConsentCheckSupported() && IsConsentCheckRunning()) { StopConsentCheck(); } } } } void IceServer::ProcessStunRequest(const RTC::ICE::StunPacket* request, RTC::TransportTuple* tuple) { MS_TRACE(); MS_DEBUG_DEV("processing STUN request"); // Must be a Binding method. if (request->GetMethod() != RTC::ICE::StunPacket::Method::BINDING) { MS_WARN_TAG( ice, "STUN request with unknown method %#.3x => 400", static_cast(request->GetMethod())); // Reply 400. auto* response = request->CreateErrorResponse( StunResponseFactoryBuffer, sizeof(StunResponseFactoryBuffer), 400, "unknown method"); response->Protect(); this->listener->OnIceServerSendStunPacket(this, response, tuple); delete response; return; } // Must have FINGERPRINT attribute. if (!request->HasAttribute(StunPacket::AttributeType::FINGERPRINT)) { MS_WARN_TAG(ice, "STUN Binding request without FINGERPRINT attribute => 400"); // Reply 400. auto* response = request->CreateErrorResponse( StunResponseFactoryBuffer, sizeof(StunResponseFactoryBuffer), 400, "missing FINGERPRINT attribute in STUN Binding request"); response->Protect(); this->listener->OnIceServerSendStunPacket(this, response, tuple); delete response; return; } // PRIORITY attribute is required. if (!request->HasAttribute(StunPacket::AttributeType::PRIORITY)) { MS_WARN_TAG(ice, "STUN Binding request without PRIORITY attribute => 400"); // Reply 400. auto* response = request->CreateErrorResponse( StunResponseFactoryBuffer, sizeof(StunResponseFactoryBuffer), 400, "missing PRIORITY attribute in STUN Binding request"); response->Protect(); this->listener->OnIceServerSendStunPacket(this, response, tuple); delete response; return; } // Check authentication. switch (request->CheckAuthentication(this->usernameFragment, this->password)) { case RTC::ICE::StunPacket::AuthenticationResult::OK: { if (!this->oldUsernameFragment.empty() && !this->oldPassword.empty()) { MS_DEBUG_TAG(ice, "new ICE credentials applied"); // Notify the listener. this->listener->OnIceServerLocalUsernameFragmentRemoved(this, this->oldUsernameFragment); this->oldUsernameFragment.clear(); this->oldPassword.clear(); } break; } case RTC::ICE::StunPacket::AuthenticationResult::UNAUTHORIZED: { // We may have changed our usernameFragment and password, so check the // old ones. if ( !this->oldUsernameFragment.empty() && !this->oldPassword.empty() && request->CheckAuthentication(this->oldUsernameFragment, this->oldPassword) == RTC::ICE::StunPacket::AuthenticationResult::OK) { MS_DEBUG_TAG(ice, "using old ICE credentials"); break; } MS_WARN_TAG(ice, "wrong authentication in STUN Binding request => 401"); // Reply 401. auto* response = request->CreateErrorResponse( StunResponseFactoryBuffer, sizeof(StunResponseFactoryBuffer), 401, "wrong authentication in STUN Binding request"); response->Protect(); this->listener->OnIceServerSendStunPacket(this, response, tuple); delete response; return; } case RTC::ICE::StunPacket::AuthenticationResult::BAD_MESSAGE: { MS_WARN_TAG(ice, "cannot check authentication in STUN Binding request => 400"); // Reply 400. auto* response = request->CreateErrorResponse( StunResponseFactoryBuffer, sizeof(StunResponseFactoryBuffer), 400, "cannot check authentication in STUN Binding request"); response->Protect(); this->listener->OnIceServerSendStunPacket(this, response, tuple); delete response; return; } } // The remote peer must be ICE controlling. if (request->GetIceControlled()) { MS_WARN_TAG(ice, "peer indicates ICE-CONTROLLED in STUN Binding request => 487"); // Reply 487 (Role Conflict). auto* response = request->CreateErrorResponse( StunResponseFactoryBuffer, sizeof(StunResponseFactoryBuffer), 487, "invalid ICE-CONTROLLED attribute in STUN Binding request"); response->Protect(); this->listener->OnIceServerSendStunPacket(this, response, tuple); delete response; return; } MS_DEBUG_DEV( "valid STUN Binding request [priority:%" PRIu32 ", useCandidate:%s]", static_cast(request->GetPriority()), request->HasAttribute(RTC::ICE::StunPacket::AttributeType::USE_CANDIDATE) ? "true" : "false"); // Create a success response. auto* response = request->CreateSuccessResponse(StunResponseFactoryBuffer, sizeof(StunResponseFactoryBuffer)); // Add XOR-MAPPED-ADDRESS. response->AddXorMappedAddress(tuple->GetRemoteAddress()); if (this->oldPassword.empty()) { response->Protect(this->password); } else { response->Protect(this->oldPassword); } this->listener->OnIceServerSendStunPacket(this, response, tuple); delete response; // Handle the tuple. HandleTuple( tuple, request->HasAttribute(StunPacket::AttributeType::USE_CANDIDATE), request->HasAttribute(StunPacket::AttributeType::NOMINATION), request->GetNomination()); // If state is 'connected' or 'completed' after handling the tuple, then // start or restart ICE consent check (if supported). if (IsConsentCheckSupported() && (this->state == IceState::CONNECTED || this->state == IceState::COMPLETED)) { if (IsConsentCheckRunning()) { RestartConsentCheck(); } else { StartConsentCheck(); } } } void IceServer::ProcessStunIndication(const RTC::ICE::StunPacket* /*indication*/) { MS_TRACE(); MS_DEBUG_DEV("STUN indication received, ignored"); // Nothig else to do. We just ignore STUN indications. } void IceServer::ProcessStunResponse(const RTC::ICE::StunPacket* response) { MS_TRACE(); if (response->GetClass() == RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE) { MS_DEBUG_DEV("ignoring received STUN successs response", responseType.c_str()); } else { static thread_local std::string_view errorReasonPhrase; response->GetErrorCode(errorReasonPhrase); MS_DEBUG_DEV("ignoring received STUN error response [errorCode:%" PRIu16 ", reasonPhrase:\"%.*s\""], static_cast(errorReasonPhrase.size()), errorReasonPhrase.data()); } // Nothig else to do. We just ignore STUN responses because we do not // generate STUN requests. } void IceServer::MayForceSelectedTuple(const RTC::TransportTuple* tuple) { MS_TRACE(); if (this->state != IceState::CONNECTED && this->state != IceState::COMPLETED) { MS_WARN_TAG(ice, "cannot force selected tuple if not in state 'connected' or 'completed'"); return; } auto* storedTuple = HasTuple(tuple); if (!storedTuple) { MS_WARN_TAG(ice, "cannot force selected tuple if the given tuple was not already a valid one"); return; } SetSelectedTuple(storedTuple); } void IceServer::HandleTuple( RTC::TransportTuple* tuple, bool hasUseCandidate, bool hasNomination, uint32_t nomination) { MS_TRACE(); switch (this->state) { case IceState::NEW: { // There shouldn't be a selected tuple. MS_ASSERT(!this->selectedTuple, "state is 'new' but there is selected tuple"); if (!hasUseCandidate && !hasNomination) { MS_DEBUG_TAG( ice, "transition from state 'new' to 'connected' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32 "]", hasUseCandidate ? "true" : "false", hasNomination ? "true" : "false", nomination); // Store the tuple. auto* storedTuple = AddTuple(tuple); // Update state. this->state = IceState::CONNECTED; // Mark it as selected tuple. SetSelectedTuple(storedTuple); // Notify the listener. this->listener->OnIceServerConnected(this); } else { // Store the tuple. auto* storedTuple = AddTuple(tuple); const auto isNewNomination = hasNomination && nomination > this->remoteNomination; if (isNewNomination || !hasNomination) { MS_DEBUG_TAG( ice, "transition from state 'new' to 'completed' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32 "]", hasUseCandidate ? "true" : "false", hasNomination ? "true" : "false", nomination); // Update state. this->state = IceState::COMPLETED; // Mark it as selected tuple. SetSelectedTuple(storedTuple); // Update nomination. if (isNewNomination) { this->remoteNomination = nomination; } // Notify the listener. this->listener->OnIceServerCompleted(this); } } break; } case IceState::DISCONNECTED: { // There shouldn't be a selected tuple. MS_ASSERT(!this->selectedTuple, "state is 'disconnected' but there is selected tuple"); if (!hasUseCandidate && !hasNomination) { MS_DEBUG_TAG( ice, "transition from state 'disconnected' to 'connected' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32 "]", hasUseCandidate ? "true" : "false", hasNomination ? "true" : "false", nomination); // Store the tuple. auto* storedTuple = AddTuple(tuple); // Update state. this->state = IceState::CONNECTED; // Mark it as selected tuple. SetSelectedTuple(storedTuple); // Notify the listener. this->listener->OnIceServerConnected(this); } else { // Store the tuple. auto* storedTuple = AddTuple(tuple); const auto isNewNomination = hasNomination && nomination > this->remoteNomination; if (isNewNomination || !hasNomination) { MS_DEBUG_TAG( ice, "transition from state 'disconnected' to 'completed' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32 "]", hasUseCandidate ? "true" : "false", hasNomination ? "true" : "false", nomination); // Update state. this->state = IceState::COMPLETED; // Mark it as selected tuple. SetSelectedTuple(storedTuple); // Update nomination. if (isNewNomination) { this->remoteNomination = nomination; } // Notify the listener. this->listener->OnIceServerCompleted(this); } } break; } case IceState::CONNECTED: { // There should be some tuples. MS_ASSERT(!this->tuples.empty(), "state is 'connected' but there are no tuples"); // There should be a selected tuple. MS_ASSERT(this->selectedTuple, "state is 'connected' but there is not selected tuple"); if (!hasUseCandidate && !hasNomination) { // Store the tuple. AddTuple(tuple); } else { MS_DEBUG_TAG( ice, "transition from state 'connected' to 'completed' [hasUseCandidate:%s, hasNomination:%s, nomination:%" PRIu32 "]", hasUseCandidate ? "true" : "false", hasNomination ? "true" : "false", nomination); // Store the tuple. auto* storedTuple = AddTuple(tuple); const auto isNewNomination = hasNomination && nomination > this->remoteNomination; if (isNewNomination || !hasNomination) { // Update state. this->state = IceState::COMPLETED; // Mark it as selected tuple. SetSelectedTuple(storedTuple); // Update nomination. if (isNewNomination) { this->remoteNomination = nomination; } // Notify the listener. this->listener->OnIceServerCompleted(this); } } break; } case IceState::COMPLETED: { // There should be some tuples. MS_ASSERT(!this->tuples.empty(), "state is 'completed' but there are no tuples"); // There should be a selected tuple. MS_ASSERT(this->selectedTuple, "state is 'completed' but there is not selected tuple"); if (!hasUseCandidate && !hasNomination) { // Store the tuple. AddTuple(tuple); } else { // Store the tuple. auto* storedTuple = AddTuple(tuple); const auto isNewNomination = hasNomination && nomination > this->remoteNomination; // When in completed state, update selected tuple if there is ICE // nomination or useCandidate. if (isNewNomination || hasUseCandidate) { // Mark it as selected tuple. SetSelectedTuple(storedTuple); // Update nomination. if (isNewNomination) { this->remoteNomination = nomination; } } } break; } } } RTC::TransportTuple* IceServer::AddTuple(RTC::TransportTuple* tuple) { MS_TRACE(); auto* storedTuple = HasTuple(tuple); if (storedTuple) { MS_DEBUG_DEV("tuple already exists"); return storedTuple; } // Add the new tuple at the beginning of the list. this->tuples.push_front(*tuple); storedTuple = std::addressof(*this->tuples.begin()); // If it is UDP then we must store the remote address (until now it is // just a pointer that will be freed soon). if (storedTuple->GetProtocol() == TransportTuple::Protocol::UDP) { storedTuple->StoreUdpRemoteAddress(); } // Notify the listener. this->listener->OnIceServerTupleAdded(this, storedTuple); // Don't allow more than MaxTuples. if (this->tuples.size() > MaxTuples) { MS_WARN_TAG(ice, "too too many tuples, removing the oldest non selected one"); // Find the oldest tuple which is neither the added one nor the selected // one (if any), and remove it. RTC::TransportTuple* removedTuple{ nullptr }; auto it = this->tuples.rbegin(); for (; it != this->tuples.rend(); ++it) { RTC::TransportTuple* otherStoredTuple = std::addressof(*it); if (otherStoredTuple != storedTuple && otherStoredTuple != this->selectedTuple) { removedTuple = otherStoredTuple; break; } } // This should not happen by design. MS_ASSERT(removedTuple, "couldn't find any tuple to be removed"); // Notify the listener. this->isRemovingTuples = true; this->listener->OnIceServerTupleRemoved(this, removedTuple); this->isRemovingTuples = false; // Remove it from the list of tuples. // NOTE: Do it after notifying the listener since the listener may need to // use/read the tuple being removed so we cannot free it yet. // NOTE: This trick is needed since it is a reverse_iterator and // erase() requires a iterator, const_iterator or bidirectional_iterator. this->tuples.erase(std::next(it).base()); } // Return the address of the inserted tuple. return storedTuple; } RTC::TransportTuple* IceServer::HasTuple(const RTC::TransportTuple* tuple) const { MS_TRACE(); // Check the current selected tuple (if any). if (this->selectedTuple && this->selectedTuple->Compare(tuple)) { return this->selectedTuple; } // Otherwise check other stored tuples. for (const auto& it : this->tuples) { auto* storedTuple = const_cast(std::addressof(it)); if (storedTuple->Compare(tuple)) { return storedTuple; } } return nullptr; } void IceServer::SetSelectedTuple(RTC::TransportTuple* storedTuple) { MS_TRACE(); // If already the selected tuple do nothing. if (storedTuple == this->selectedTuple) { return; } this->selectedTuple = storedTuple; // Notify the listener. this->listener->OnIceServerSelectedTuple(this, this->selectedTuple); } void IceServer::StartConsentCheck() { MS_TRACE(); MS_ASSERT(IsConsentCheckSupported(), "ICE consent check not supported"); MS_ASSERT(!IsConsentCheckRunning(), "ICE consent check already running"); MS_ASSERT(this->selectedTuple, "no selected tuple"); // Create the ICE consent check timer if it doesn't exist. if (!this->consentCheckTimer) { this->consentCheckTimer = this->shared->CreateTimer(this); } this->consentCheckTimer->Start(this->consentTimeoutMs); } void IceServer::RestartConsentCheck() { MS_TRACE(); MS_ASSERT(IsConsentCheckSupported(), "ICE consent check not supported"); MS_ASSERT(IsConsentCheckRunning(), "ICE consent check not running"); MS_ASSERT(this->selectedTuple, "no selected tuple"); this->consentCheckTimer->Restart(); } void IceServer::StopConsentCheck() { MS_TRACE(); MS_ASSERT(IsConsentCheckSupported(), "ICE consent check not supported"); MS_ASSERT(IsConsentCheckRunning(), "ICE consent check not running"); this->consentCheckTimer->Stop(); } inline void IceServer::OnTimer(TimerHandleInterface* timer) { MS_TRACE(); if (timer == this->consentCheckTimer) { MS_ASSERT(IsConsentCheckSupported(), "ICE consent check not supported"); // State must be 'connected' or 'completed'. MS_ASSERT( this->state == IceState::COMPLETED || this->state == IceState::CONNECTED, "ICE consent check timer fired but state is neither 'completed' nor 'connected'"); // There should be a selected tuple. MS_ASSERT( this->selectedTuple, "ICE consent check timer fired but there is not selected tuple"); MS_WARN_TAG(ice, "ICE consent expired due to timeout, moving to 'disconnected' state"); // Update state. this->state = IceState::DISCONNECTED; // Reset remote nomination. this->remoteNomination = 0u; // Clear all tuples. this->isRemovingTuples = true; for (const auto& it : this->tuples) { auto* storedTuple = const_cast(std::addressof(it)); // Notify the listener. this->listener->OnIceServerTupleRemoved(this, storedTuple); } this->isRemovingTuples = false; // Clear all tuples. // NOTE: Do it after notifying the listener since the listener may need to // use/read the tuple being removed so we cannot free it yet. this->tuples.clear(); // Unset selected tuple. this->selectedTuple = nullptr; // Notify the listener. this->listener->OnIceServerDisconnected(this); } } } // namespace ICE } // namespace RTC ================================================ FILE: worker/src/RTC/ICE/StunPacket.cpp ================================================ #define MS_CLASS "RTC::ICE::StunPacket" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/ICE/StunPacket.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::snprintf() #include // std::memcmp(), std::memcpy(), std::memset() #include namespace RTC { namespace ICE { /* Static. */ static constexpr size_t AttributeFactoryBufferLength{ 65536 }; static thread_local uint8_t AttributeFactoryBuffer[AttributeFactoryBufferLength]; /* Class variables. */ const uint8_t StunPacket::MagicCookie[] = { 0x21, 0x12, 0xA4, 0x42 }; /* Class methods. */ bool StunPacket::IsStun(const uint8_t* buffer, size_t bufferLength) { return ( // STUN headers are 20 bytes. (bufferLength >= StunPacket::FixedHeaderLength) && // @see RFC 7983. (buffer[0] < 3) && // Magic Cookie must match. (buffer[4] == StunPacket::MagicCookie[0]) && (buffer[5] == StunPacket::MagicCookie[1]) && (buffer[6] == StunPacket::MagicCookie[2]) && (buffer[7] == StunPacket::MagicCookie[3])); } StunPacket* StunPacket::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (!StunPacket::IsStun(buffer, bufferLength)) { MS_WARN_TAG(ice, "not a STUN Packet"); return nullptr; } auto* packet = new StunPacket(const_cast(buffer), bufferLength); // `bufferLength` must be the exact length of the STUN Packet, so let's // assign it immediately. packet->SetLength(bufferLength); // Get STUN Message Type field. const uint16_t typeField = Utils::Byte::Get2Bytes(buffer, 0); // Get STUN class. const auto klass = static_cast(((buffer[0] & 0x01) << 1) | ((buffer[1] & 0x10) >> 4)); // Get STUN method. const auto method = static_cast( (typeField & 0x000f) | ((typeField & 0x00e0) >> 1) | ((typeField & 0x3E00) >> 2)); packet->klass = klass; packet->method = method; if (!packet->Validate(/*storeAttributes*/ true)) { delete packet; return nullptr; } return packet; } StunPacket* StunPacket::Factory( uint8_t* buffer, size_t bufferLength, StunPacket::Class klass, StunPacket::Method method, const uint8_t* transactionId) { MS_TRACE(); if (bufferLength < StunPacket::FixedHeaderLength) { MS_THROW_TYPE_ERROR("no space for fixed header"); } auto* packet = new StunPacket(buffer, bufferLength); std::memset(packet->GetFixedHeaderPointer(), 0x00, packet->GetLength()); packet->klass = klass; packet->method = method; // Merge class and method fields into type. auto typeField = (static_cast(method) & 0x0f80) << 2; typeField |= (static_cast(method) & 0x0070) << 1; typeField |= (static_cast(method) & 0x000f); typeField |= (static_cast(klass) & 0x02) << 7; typeField |= (static_cast(klass) & 0x01) << 4; // Set type field. Utils::Byte::Set2Bytes(packet->GetFixedHeaderPointer(), 0, typeField); // NOTE: No need to write message length since it's already 0. // Set magic Cookie. std::memcpy(packet->GetFixedHeaderPointer() + 4, StunPacket::MagicCookie, 4); if (transactionId) { std::memcpy(packet->GetTransactionIdPointer(), transactionId, StunPacket::TransactionIdLength); } else { Utils::Crypto::WriteRandomBytes( packet->GetTransactionIdPointer(), StunPacket::TransactionIdLength); } // No need to invoke SetLength() since constructor invoked it with // minimum STUN Packet length. return packet; } StunPacket* StunPacket::Factory( uint8_t* buffer, size_t bufferLength, StunPacket::Class klass, StunPacket::Method method) { MS_TRACE(); return Factory(buffer, bufferLength, klass, method, nullptr); } /* Instance methods. */ StunPacket::StunPacket(uint8_t* buffer, size_t bufferLength) : Serializable(buffer, bufferLength) { MS_TRACE(); SetLength(StunPacket::FixedHeaderLength); } StunPacket::~StunPacket() { MS_TRACE(); } void StunPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " length: %zu (buffer length: %zu)", GetLength(), GetBufferLength()); std::string klass; switch (this->klass) { case Class::REQUEST: { klass = "request"; break; } case Class::INDICATION: { klass = "indication"; break; } case Class::SUCCESS_RESPONSE: { klass = "success response"; break; } case Class::ERROR_RESPONSE: { klass = "error response"; break; } case Class::UNSET: { klass = "(unset)"; break; } } if (this->method == Method::BINDING) { MS_DUMP_CLEAN(indentation, " method and class: Binding %s", klass.c_str()); } else { // This prints the unknown method number. Example: TURN Allocate => 0x003. MS_DUMP_CLEAN( indentation, " method & class: %s with unknown method %#.3x", klass.c_str(), static_cast(this->method)); } char transactionId[(12 * 2) + 1]; // 12 bytes × 2 hex chars + null terminator. for (uint8_t i{ 0 }; i < 12; ++i) { std::snprintf(transactionId + (i * 2), 3, "%02X", GetTransactionId()[i]); } MS_DUMP_CLEAN(indentation, " transaction id: 0x%s", transactionId); MS_DUMP_CLEAN(indentation, " attributes length: %zu", GetAttributesLength()); MS_DUMP_CLEAN(indentation, " "); if (HasAttribute(StunPacket::AttributeType::USERNAME)) { const auto username = GetUsername(); MS_DUMP_CLEAN( indentation + 1, " username: \"%.*s\"", static_cast(username.size()), username.data()); } if (HasAttribute(StunPacket::AttributeType::PRIORITY)) { MS_DUMP_CLEAN(indentation + 1, " priority: %" PRIu32, GetPriority()); } if (HasAttribute(StunPacket::AttributeType::ICE_CONTROLLING)) { MS_DUMP_CLEAN(indentation + 1, " ice controlling: %" PRIu64, GetIceControlling()); } if (HasAttribute(StunPacket::AttributeType::ICE_CONTROLLED)) { MS_DUMP_CLEAN(indentation + 1, " ice controlled: %" PRIu64, GetIceControlled()); } if (HasAttribute(StunPacket::AttributeType::USE_CANDIDATE)) { MS_DUMP_CLEAN(indentation + 1, " use candidate: yes"); } if (HasAttribute(StunPacket::AttributeType::NOMINATION)) { MS_DUMP_CLEAN(indentation + 1, " nomination: %" PRIu32, GetNomination()); } if (HasAttribute(StunPacket::AttributeType::SOFTWARE)) { const auto software = GetSoftware(); MS_DUMP_CLEAN( indentation + 1, " software: \"%.*s\"", static_cast(software.size()), software.data()); } if (HasAttribute(StunPacket::AttributeType::XOR_MAPPED_ADDRESS)) { struct sockaddr_storage xorMappedAddressStorage{}; if (GetXorMappedAddress(std::addressof(xorMappedAddressStorage))) { int family; uint16_t port; std::string ip; Utils::IP::GetAddressInfo( reinterpret_cast(std::addressof(xorMappedAddressStorage)), family, ip, port); if (family == AF_INET) { MS_DUMP_CLEAN(indentation + 1, " xor mapped address: IPv4 %s:%" PRIu16, ip.c_str(), port); } else if (family == AF_INET6) { MS_DUMP_CLEAN( indentation + 1, " xor mapped address: IPv6 [%s]:%" PRIu16, ip.c_str(), port); } } } if (HasAttribute(StunPacket::AttributeType::ERROR_CODE)) { std::string_view reasonPhrase{}; const auto errorCode = GetErrorCode(reasonPhrase); MS_DUMP_CLEAN( indentation + 1, " error code: %" PRIu16 " (reason phrase: \"%.*s\")", errorCode, static_cast(reasonPhrase.size()), reasonPhrase.data()); } if (HasAttribute(StunPacket::AttributeType::MESSAGE_INTEGRITY)) { char messageIntegrity[41]; for (size_t i{ 0 }; i < StunPacket::MessageIntegrityAttributeLength; ++i) { std::snprintf(messageIntegrity + (i * 2), 3, "%.2x", GetMessageIntegrity()[i]); } MS_DUMP_CLEAN(indentation + 1, " message integrity: %s", messageIntegrity); } if (HasAttribute(StunPacket::AttributeType::FINGERPRINT)) { MS_DUMP_CLEAN(indentation + 1, " fingerprint: %" PRIu32, GetFingerprint()); } MS_DUMP_CLEAN(indentation, " "); MS_DUMP_CLEAN(indentation, ""); } StunPacket* StunPacket::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedPacket = new StunPacket(buffer, bufferLength); Serializable::CloneInto(clonedPacket); // Clone private members. clonedPacket->klass = this->klass; clonedPacket->method = this->method; clonedPacket->attributes = this->attributes; return clonedPacket; } void StunPacket::AddUsername(const std::string_view username) { MS_TRACE(); AssertNotProtected(); if (username.length() > StunPacket::UsernameAttributeMaxLength) { MS_THROW_TYPE_ERROR( "Attribute USERNAME must be at most %zu bytes", StunPacket::UsernameAttributeMaxLength); } StoreNewAttribute(StunPacket::AttributeType::USERNAME, username.data(), username.length()); } void StunPacket::AddPriority(uint32_t priority) { MS_TRACE(); AssertNotProtected(); Utils::Byte::Set4Bytes(AttributeFactoryBuffer, 0, priority); StoreNewAttribute(StunPacket::AttributeType::PRIORITY, AttributeFactoryBuffer, sizeof(priority)); } void StunPacket::AddIceControlling(uint64_t iceControlling) { MS_TRACE(); AssertNotProtected(); Utils::Byte::Set8Bytes(AttributeFactoryBuffer, 0, iceControlling); StoreNewAttribute( StunPacket::AttributeType::ICE_CONTROLLING, AttributeFactoryBuffer, sizeof(iceControlling)); } void StunPacket::AddIceControlled(uint64_t iceControlled) { MS_TRACE(); AssertNotProtected(); Utils::Byte::Set8Bytes(AttributeFactoryBuffer, 0, iceControlled); StoreNewAttribute( StunPacket::AttributeType::ICE_CONTROLLED, AttributeFactoryBuffer, sizeof(iceControlled)); } void StunPacket::AddUseCandidate() { MS_TRACE(); AssertNotProtected(); StoreNewAttribute(StunPacket::AttributeType::USE_CANDIDATE, nullptr, 0); } void StunPacket::AddNomination(uint32_t nomination) { MS_TRACE(); AssertNotProtected(); Utils::Byte::Set4Bytes(AttributeFactoryBuffer, 0, nomination); StoreNewAttribute( StunPacket::AttributeType::NOMINATION, AttributeFactoryBuffer, sizeof(nomination)); } void StunPacket::AddSoftware(const std::string_view software) { MS_TRACE(); AssertNotProtected(); if (software.length() > StunPacket::SoftwareAttributeMaxLength) { MS_THROW_TYPE_ERROR( "Attribute SOFTWARE must be at most %zu bytes", StunPacket::SoftwareAttributeMaxLength); } StoreNewAttribute(StunPacket::AttributeType::SOFTWARE, software.data(), software.length()); } bool StunPacket::GetXorMappedAddress(struct sockaddr_storage* xorMappedAddressStorage) const { MS_TRACE(); std::memset(xorMappedAddressStorage, 0x00, sizeof(struct sockaddr_storage)); const auto* attribute = GetAttribute(StunPacket::AttributeType::XOR_MAPPED_ADDRESS); if (!attribute) { return false; } const auto* attributeValue = GetAttributeValue(attribute); const uint8_t family = attributeValue[1]; uint16_t port; std::memcpy(std::addressof(port), attributeValue + 2, 2); // XOR with the first 2 bytes of the Magic Cookie. port = ntohs(port) ^ (StunPacket::MagicCookie[0] << 8 | StunPacket::MagicCookie[1]); // IPv4. if (family == 0x01) { if (attribute->len != StunPacket::XorMappedAddressIPv4Length) { MS_WARN_TAG( ice, "cannot get XOR_MAPPED_ADDRESS Attribute value, length of the Attribute is not %zu", StunPacket::XorMappedAddressIPv4Length); return false; } auto* addr4 = reinterpret_cast(xorMappedAddressStorage); addr4->sin_family = AF_INET; addr4->sin_port = htons(port); uint32_t addr; std::memcpy(std::addressof(addr), attributeValue + 4, 4); // XOR with each byte of the Magic Cookie. auto* addrBytes = reinterpret_cast(&addr); for (size_t i{ 0 }; i < sizeof(StunPacket::MagicCookie); ++i) { addrBytes[i] ^= StunPacket::MagicCookie[i]; } addr4->sin_addr.s_addr = addr; return true; } // IPv6. else if (family == 0x02) { if (attribute->len != StunPacket::XorMappedAddressIPv6Length) { MS_WARN_TAG( ice, "cannot get XOR_MAPPED_ADDRESS Attribute value, length of the Attribute is not %zu", StunPacket::XorMappedAddressIPv6Length); return false; } auto* addr6 = reinterpret_cast(xorMappedAddressStorage); addr6->sin6_family = AF_INET6; addr6->sin6_port = htons(port); const auto* transactionId = GetTransactionId(); // XOR with first 4 bytes with each byte of the Magic Cookie. for (size_t i{ 0 }; i < sizeof(StunPacket::MagicCookie); ++i) { addr6->sin6_addr.s6_addr[i] = attributeValue[sizeof(StunPacket::MagicCookie) + i] ^ StunPacket::MagicCookie[i]; } // XOR remaining 12 bytes with each byte of the Transaction id. for (size_t i{ 0 }; i < StunPacket::TransactionIdLength; ++i) { addr6->sin6_addr.s6_addr[sizeof(StunPacket::MagicCookie) + i] = attributeValue[(sizeof(StunPacket::MagicCookie) + sizeof(StunPacket::MagicCookie)) + i] ^ transactionId[i]; } return true; } // Unknown family. else { MS_WARN_TAG(ice, "cannot get XOR_MAPPED_ADDRESS Attribute value, unknown family"); return false; } } void StunPacket::AddXorMappedAddress(const struct sockaddr* xorMappedAddress) { MS_TRACE(); AssertNotProtected(); switch (xorMappedAddress->sa_family) { case AF_INET: { // Set first byte to 0. AttributeFactoryBuffer[0] = 0; // Set inet family. AttributeFactoryBuffer[1] = 0x01; // Set port and XOR it. std::memcpy( AttributeFactoryBuffer + 2, &(reinterpret_cast(xorMappedAddress))->sin_port, 2); AttributeFactoryBuffer[2] ^= StunPacket::MagicCookie[0]; AttributeFactoryBuffer[3] ^= StunPacket::MagicCookie[1]; // Set address and XOR it. std::memcpy( AttributeFactoryBuffer + 4, &(reinterpret_cast(xorMappedAddress))->sin_addr.s_addr, 4); AttributeFactoryBuffer[4] ^= StunPacket::MagicCookie[0]; AttributeFactoryBuffer[5] ^= StunPacket::MagicCookie[1]; AttributeFactoryBuffer[6] ^= StunPacket::MagicCookie[2]; AttributeFactoryBuffer[7] ^= StunPacket::MagicCookie[3]; StoreNewAttribute( StunPacket::AttributeType::XOR_MAPPED_ADDRESS, AttributeFactoryBuffer, StunPacket::XorMappedAddressIPv4Length); break; } case AF_INET6: { // Set first byte to 0. AttributeFactoryBuffer[0] = 0; // Set inet family. AttributeFactoryBuffer[1] = 0x02; // Set port and XOR it. std::memcpy( AttributeFactoryBuffer + 2, &(reinterpret_cast(xorMappedAddress))->sin6_port, 2); AttributeFactoryBuffer[2] ^= StunPacket::MagicCookie[0]; AttributeFactoryBuffer[3] ^= StunPacket::MagicCookie[1]; // Set address and XOR it. std::memcpy( AttributeFactoryBuffer + 4, &(reinterpret_cast(xorMappedAddress))->sin6_addr.s6_addr, 16); const auto* transactionId = GetTransactionId(); AttributeFactoryBuffer[4] ^= StunPacket::MagicCookie[0]; AttributeFactoryBuffer[5] ^= StunPacket::MagicCookie[1]; AttributeFactoryBuffer[6] ^= StunPacket::MagicCookie[2]; AttributeFactoryBuffer[7] ^= StunPacket::MagicCookie[3]; AttributeFactoryBuffer[8] ^= transactionId[0]; AttributeFactoryBuffer[9] ^= transactionId[1]; AttributeFactoryBuffer[10] ^= transactionId[2]; AttributeFactoryBuffer[11] ^= transactionId[3]; AttributeFactoryBuffer[12] ^= transactionId[4]; AttributeFactoryBuffer[13] ^= transactionId[5]; AttributeFactoryBuffer[14] ^= transactionId[6]; AttributeFactoryBuffer[15] ^= transactionId[7]; AttributeFactoryBuffer[16] ^= transactionId[8]; AttributeFactoryBuffer[17] ^= transactionId[9]; AttributeFactoryBuffer[18] ^= transactionId[10]; AttributeFactoryBuffer[19] ^= transactionId[11]; StoreNewAttribute( StunPacket::AttributeType::XOR_MAPPED_ADDRESS, AttributeFactoryBuffer, StunPacket::XorMappedAddressIPv6Length); break; } default: { MS_THROW_TYPE_ERROR("unknown IP family"); } } } void StunPacket::AddErrorCode(uint16_t errorCode, const std::string_view reasonPhrase) { MS_TRACE(); AssertNotProtected(); const auto codeClass = static_cast(errorCode / 100); const auto codeNumber = static_cast(errorCode) - (codeClass * 100); Utils::Byte::Set2Bytes(AttributeFactoryBuffer, 0, 0); Utils::Byte::Set1Byte(AttributeFactoryBuffer, 2, codeClass); Utils::Byte::Set1Byte(AttributeFactoryBuffer, 3, codeNumber); std::memcpy(AttributeFactoryBuffer + 4, reasonPhrase.data(), reasonPhrase.length()); StoreNewAttribute( StunPacket::AttributeType::ERROR_CODE, AttributeFactoryBuffer, 4 + reasonPhrase.length()); } StunPacket::AuthenticationResult StunPacket::CheckAuthentication( const std::string_view usernameFragment1, const std::string_view& password) const { MS_TRACE(); const auto* messageIntegrity = GetMessageIntegrity(); const auto hasFingerprint = HasAttribute(StunPacket::AttributeType::FINGERPRINT); switch (this->klass) { case StunPacket::Class::REQUEST: case StunPacket::Class::INDICATION: { // usernameFragment1 must not be empty. if (usernameFragment1.empty()) { MS_WARN_TAG( ice, "cannot authenticate request or indication, empty usernameFragment1 given"); return StunPacket::AuthenticationResult::BAD_MESSAGE; } // USERNAME Attribute must be present. if (!HasAttribute(StunPacket::AttributeType::USERNAME)) { MS_WARN_TAG(ice, "cannot authenticate request or indication, missing USERNAME Attribute"); return StunPacket::AuthenticationResult::BAD_MESSAGE; } // MESSAGE-INTEGRITY Attribute must be present. if (!messageIntegrity) { MS_WARN_TAG( ice, "cannot authenticate request or indication, missing MESSAGE-INTEGRITY Attribute"); return StunPacket::AuthenticationResult::BAD_MESSAGE; } // Check that the USERNAME Attribute begins with the first username // fragment plus ":". const auto username = GetUsername(); if ( username.length() <= usernameFragment1.length() || username.at(usernameFragment1.length()) != ':' || username.compare(0, usernameFragment1.length(), usernameFragment1.data()) != 0) { return StunPacket::AuthenticationResult::UNAUTHORIZED; } break; } case StunPacket::Class::SUCCESS_RESPONSE: case StunPacket::Class::ERROR_RESPONSE: { // MESSAGE-INTEGRITY Attribute must be present. if (!messageIntegrity) { MS_WARN_TAG( ice, "cannot authenticate success response or error response, missing MESSAGE-INTEGRITY Attribute"); return StunPacket::AuthenticationResult::BAD_MESSAGE; } break; } default: { MS_WARN_TAG( ice, "cannot authenticate STUN Packet, unknown STUN class %" PRIu16, static_cast(this->klass)); return StunPacket::AuthenticationResult::BAD_MESSAGE; } } auto* fixedHeader = GetFixedHeaderPointer(); // If there is FINGERPRINT it must be discarded for MESSAGE-INTEGRITY // calculation, so the message length field must be modified (and later // restored). if (hasFingerprint) { // Set the message length field by removing the length of the // FINGERPRINT Attribute (4 + 4). // NOTE: We cannot use SetMessageLength() because CheckAuthentication() // is marked as a `const` method. Utils::Byte::Set2Bytes(fixedHeader, 2, static_cast(GetAttributesLength() - 4 - 4)); } // Calculate the HMAC-SHA1 of the message according to MESSAGE-INTEGRITY // rules, this is, by checking the bytes from 0 to the beginning of the // MESSAGE-INTEGRITY Attribute. const uint8_t* computedMessageIntegrity = Utils::Crypto::GetHmacSha1( password.data(), password.length(), fixedHeader, (messageIntegrity - 4) - fixedHeader); StunPacket::AuthenticationResult result; // Compare the computed HMAC-SHA1 with the MESSAGE-INTEGRITY in the STUN // Packet. if (std::memcmp(messageIntegrity, computedMessageIntegrity, StunPacket::FixedHeaderLength) == 0) { result = StunPacket::AuthenticationResult::OK; } else { result = StunPacket::AuthenticationResult::UNAUTHORIZED; } // Restore the message length field. // NOTE: We cannot use SetMessageLength() because CheckAuthentication() // is marked as a `const` method. if (hasFingerprint) { Utils::Byte::Set2Bytes(fixedHeader, 2, static_cast(GetAttributesLength())); } return result; } StunPacket::AuthenticationResult StunPacket::CheckAuthentication(std::string_view password) const { MS_TRACE(); return CheckAuthentication({}, password); } void StunPacket::Protect(const std::string_view password) { MS_TRACE(); AssertNotProtected(); const auto currentLength = GetLength(); const size_t addedLength = 4 + StunPacket::MessageIntegrityAttributeLength + 4 + 4; // We need to add Attribute(s) so we must increase the length of the // STUN Packet. // NOTE: This may throw. SetLength(GetLength() + addedLength); // Once we know it doesn't throw (so there is space in the buffer), let's // revert it because code below will do it when needed. SetLength(currentLength); // Add MESSAGE-INTEGRITY Attribute (only if password was given). if (!password.empty()) { // When must include the length of MESSAGE-INTEGRITY Attribute in // message length field of the STUN Packet. SetMessageLength(GetMessageLength() + 4 + StunPacket::MessageIntegrityAttributeLength); // Calculate the HMAC-SHA1 of the STUN Packet according to // MESSAGE-INTEGRITY rules. const uint8_t* computedMessageIntegrity = Utils::Crypto::GetHmacSha1(password.data(), password.length(), GetBuffer(), currentLength); StoreNewAttribute( StunPacket::AttributeType::MESSAGE_INTEGRITY, computedMessageIntegrity, StunPacket::MessageIntegrityAttributeLength); } // Add FINGERPRINT Attribute. // When must include the length of FINGERPRINT Attribute in // message length field of the STUN Packet. SetMessageLength(GetMessageLength() + 4 + 4); // Compute the CRC32 of the STUN Packet up to (but excluding) the // FINGERPRINT Attribute and XOR it with 0x5354554e. const uint32_t computedFingerprint = Utils::Crypto::GetCRC32(GetBuffer(), GetLength()) ^ 0x5354554e; Utils::Byte::Set4Bytes(AttributeFactoryBuffer, 0, computedFingerprint); StoreNewAttribute(StunPacket::AttributeType::FINGERPRINT, AttributeFactoryBuffer, 4); } void StunPacket::Protect() { MS_TRACE(); Protect({}); } StunPacket* StunPacket::CreateSuccessResponse(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); if (this->klass != StunPacket::Class::REQUEST) { MS_THROW_ERROR("cannot create a success response, original STUN Packet is not a request"); } auto* successResponse = Factory( buffer, bufferLength, StunPacket::Class::SUCCESS_RESPONSE, this->method, GetTransactionId()); return successResponse; } StunPacket* StunPacket::CreateErrorResponse( uint8_t* buffer, size_t bufferLength, uint16_t errorCode, const std::string_view& reasonPhrase) const { MS_TRACE(); if (this->klass != StunPacket::Class::REQUEST) { MS_THROW_ERROR("cannot create an error response, original STUN Packet is not a request"); } auto* errorResponse = Factory( buffer, bufferLength, StunPacket::Class::ERROR_RESPONSE, this->method, GetTransactionId()); errorResponse->AddErrorCode(errorCode, reasonPhrase); return errorResponse; } bool StunPacket::Validate(bool storeAttributes) { MS_TRACE(); const auto* fixedHeader = GetFixedHeaderPointer(); // Get message length field. const auto msgLength = GetMessageLength(); // Message length field must be total length minus header's 20 bytes, and // must be multiple of 4 Bytes. // NOTE: Message length is effectively the total length of the Attributes // (with all paddings). if (static_cast(msgLength) != GetAttributesLength() || !Utils::Byte::IsPaddedTo4Bytes(msgLength)) { MS_WARN_TAG( ice, "invalid STUN Packet, message length field (%" PRIu16 ") does not match given buffer length or it's not multiple of 4 bytes", msgLength); return false; } if (!ParseAttributes(storeAttributes)) { MS_WARN_TAG(rtp, "invalid STUN Packet, invalid Attributes"); return false; } // If it has FINGERPRINT Attribute then verify it. const auto* fingerprintAttr = GetAttribute(StunPacket::AttributeType::FINGERPRINT); if (fingerprintAttr) { // Compute the CRC32 of the received STUN Packet up to (but excluding) // the FINGERPRINT Attribute and XOR it with 0x5354554e. const auto computedFingerprint = Utils::Crypto::GetCRC32( fixedHeader, StunPacket::FixedHeaderLength + fingerprintAttr->offset) ^ 0x5354554e; // Compare with the FINGERPRINT value in the STUN Packet. if (GetFingerprint() != computedFingerprint) { MS_WARN_TAG( ice, "invalid STUN Packet, computed fingerprint value does not match the value in the FINGERPRINT Attribute"); return false; } } return true; } bool StunPacket::ParseAttributes(bool storeAttributes) { MS_TRACE(); const uint8_t* attributesStart = GetAttributesPointer(); const uint8_t* attributesEnd = attributesStart + GetAttributesLength(); auto* ptr = const_cast(attributesStart); // Ensure there are at least 4 remaining bytes (Attribute with 0 length). while (ptr + 4 <= attributesEnd) { // NOTE: We cannot cast `ptr` to `StunPacket::Attribute*` here because // `StunPacket::Attribute` requires 8-byte alignment (due to its `size_t` // member) but `ptr` points into a network buffer with no guaranteed // alignment, making the cast undefined behavior. // Read Attribute type and length. const auto attrType = static_cast(Utils::Byte::Get2Bytes(ptr, 0)); const uint16_t attrLen = Utils::Byte::Get2Bytes(ptr, 2); // Offset of the Attribute from the start of the attributes. const auto attrOffset = static_cast((ptr - attributesStart)); // Ensure the Attribute length is not greater than the remaining length. if (ptr + 4 + attrLen > attributesEnd) { MS_WARN_TAG( ice, "invalid STUN Packet, not enough space for the announced value of the Attribute with type %" PRIu16, static_cast(attrType)); return false; } // FINGERPRINT must be the last Attribute. if (storeAttributes && HasAttribute(StunPacket::AttributeType::FINGERPRINT)) { MS_WARN_TAG(ice, "invalid STUN Packet, Attribute after FINGERPRINT is not allowed"); return false; } // After a MESSAGE-INTEGRITY Attribute only FINGERPRINT is allowed. if ( storeAttributes && HasAttribute(StunPacket::AttributeType::MESSAGE_INTEGRITY) && attrType != StunPacket::AttributeType::FINGERPRINT) { MS_WARN_TAG( ice, "invalid STUN Packet, Attribute after MESSAGE-INTEGRITY other than FINGERPRINT is not allowed"); return false; } switch (attrType) { case StunPacket::AttributeType::USERNAME: { if (attrLen > StunPacket::UsernameAttributeMaxLength) { MS_WARN_TAG( ice, "invalid STUN Packet, Attribute USERNAME must be at most %zu bytes", StunPacket::UsernameAttributeMaxLength); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::PRIORITY: { if (attrLen != 4) { MS_WARN_TAG(ice, "invalid STUN Packet, Attribute PRIORITY must be 4 bytes length"); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::ICE_CONTROLLING: { if (attrLen != 8) { MS_WARN_TAG( ice, "invalid STUN Packet, Attribute ICE-CONTROLLING must be 8 bytes length"); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::ICE_CONTROLLED: { if (attrLen != 8) { MS_WARN_TAG(ice, "invalid STUN Packet, Attribute ICE-CONTROLLED must be 8 bytes length"); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::USE_CANDIDATE: { if (attrLen != 0) { MS_WARN_TAG(ice, "invalid STUN Packet, Attribute USE-CANDIDATE must be 0 bytes length"); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::NOMINATION: { if (attrLen != 4) { MS_WARN_TAG(ice, "invalid STUN Packet, Attribute NOMINATION must be 4 bytes length"); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::SOFTWARE: { if (attrLen > StunPacket::SoftwareAttributeMaxLength) { MS_WARN_TAG( ice, "invalid STUN Packet, Attribute SOFTWARE must be at most %zu bytes length", StunPacket::SoftwareAttributeMaxLength); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::XOR_MAPPED_ADDRESS: { if (attrLen != StunPacket::XorMappedAddressIPv4Length && attrLen != StunPacket::XorMappedAddressIPv6Length) { MS_WARN_TAG( ice, "invalid STUN Packet, Attribute XOR_MAPPED_ADDRESS-CODE must be %zu or %zu bytes length", StunPacket::XorMappedAddressIPv4Length, StunPacket::XorMappedAddressIPv6Length); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::ERROR_CODE: { if (attrLen < 4) { MS_WARN_TAG(ice, "invalid STUN Packet, Attribute ERROR-CODE must be >= 4 bytes length"); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::MESSAGE_INTEGRITY: { if (attrLen != StunPacket::MessageIntegrityAttributeLength) { MS_WARN_TAG( ice, "invalid STUN Packet, Attribute MESSAGE-INTEGRITY must be %zu bytes length", StunPacket::MessageIntegrityAttributeLength); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } case StunPacket::AttributeType::FINGERPRINT: { if (attrLen != 4) { MS_WARN_TAG(ice, "invalid STUN Packet, Attribute FINGERPRINT must be 4 bytes length"); return false; } if (storeAttributes && !StoreParsedAttribute(attrType, attrLen, attrOffset)) { return false; } break; } default: { MS_DEBUG_DEV("unknown Attribute with type %" PRIu16, attrType); } } // Move to next Attribute. ptr += Utils::Byte::PadTo4Bytes(static_cast(4 + attrLen)); } // Ensure we read the Attributes length entirely. if (ptr != attributesStart + GetAttributesLength()) { MS_WARN_TAG( ice, "invalid STUN Packet, computed length of Attributes (%zu) does not match announced length (%zu)", static_cast(ptr - attributesStart), GetAttributesLength()); return false; } return true; } bool StunPacket::StoreParsedAttribute(AttributeType type, uint16_t len, size_t offset) { MS_TRACE(); if (!this->attributes.try_emplace(type, type, len, offset).second) { MS_WARN_TAG( ice, "cannot store parsed Attribute with type %" PRIu16 ", there is an Attribute with same type already in the map", static_cast(type)); return false; } return true; } void StunPacket::StoreNewAttribute(AttributeType type, const void* data, uint16_t len) { MS_TRACE(); MS_ASSERT( (data && len) || (!data && !len), "data and len must either both have a value or both be empty/zero"); if (this->attributes.find(type) != this->attributes.end()) { MS_THROW_ERROR( "cannot store new Attribute with type %" PRIu16 ", there is an Attribute with same type already in the map", static_cast(type)); } // Add the Attribute at the end of the STUN Packet. const auto attrTotalPaddedLength = Utils::Byte::PadTo4Bytes(static_cast(4 + len)); // Get the pointer in which the new Attribute must be written. // NOTE: Do this before updating lengths. auto* attrPtr = GetAttributesPointer() + GetAttributesLength(); // First update STUN Packet length (it may throw). SetLength(GetLength() + attrTotalPaddedLength); // Also update the message length field. SetMessageLength(GetAttributesLength()); Utils::Byte::Set2Bytes(attrPtr, 0, static_cast(type)); Utils::Byte::Set2Bytes(attrPtr, 2, len); if (data) { std::memcpy(attrPtr + 4, data, len); // Fill padding bytes with zeroes. std::memset(attrPtr + 4 + len, 0x00, attrTotalPaddedLength - len); } const auto [it, inserted] = this->attributes.try_emplace(type, type, len, 0); auto& attribute = it->second; // Update stored Attribute's offset. attribute.offset = attrPtr - GetAttributesPointer(); MS_ASSERT(inserted, "Attribute not inserted in the map (this shouldn't happen)"); } void StunPacket::AssertNotProtected() const { MS_TRACE(); if (IsProtected()) { MS_THROW_ERROR("STUN Packet is protected"); } } } // namespace ICE } // namespace RTC ================================================ FILE: worker/src/RTC/KeyFrameRequestManager.cpp ================================================ #define MS_CLASS "KeyFrameRequestManager" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/KeyFrameRequestManager.hpp" #include "Logger.hpp" static constexpr uint32_t KeyFrameRetransmissionWaitTime{ 1000u }; /* PendingKeyFrameInfo methods. */ RTC::PendingKeyFrameInfo::PendingKeyFrameInfo( PendingKeyFrameInfo::Listener* listener, SharedInterface* shared, uint32_t ssrc) : listener(listener), ssrc(ssrc), timer(shared->CreateTimer(this)) { MS_TRACE(); this->timer->Start(KeyFrameRetransmissionWaitTime); } RTC::PendingKeyFrameInfo::~PendingKeyFrameInfo() { MS_TRACE(); this->timer->Stop(); delete this->timer; } void RTC::PendingKeyFrameInfo::OnTimer(TimerHandleInterface* timer) { MS_TRACE(); if (timer == this->timer) { this->listener->OnKeyFrameRequestTimeout(this); } } /* KeyFrameRequestDelayer methods. */ RTC::KeyFrameRequestDelayer::KeyFrameRequestDelayer( KeyFrameRequestDelayer::Listener* listener, SharedInterface* shared, uint32_t ssrc, uint32_t delay) : listener(listener), ssrc(ssrc), timer(shared->CreateTimer(this)) { MS_TRACE(); this->timer->Start(delay); } RTC::KeyFrameRequestDelayer::~KeyFrameRequestDelayer() { MS_TRACE(); this->timer->Stop(); delete this->timer; } void RTC::KeyFrameRequestDelayer::OnTimer(TimerHandleInterface* timer) { MS_TRACE(); if (timer == this->timer) { this->listener->OnKeyFrameDelayTimeout(this); } } /* KeyFrameRequestManager methods. */ RTC::KeyFrameRequestManager::KeyFrameRequestManager( KeyFrameRequestManager::Listener* listener, SharedInterface* shared, uint32_t keyFrameRequestDelay) : listener(listener), shared(shared), keyFrameRequestDelay(keyFrameRequestDelay) { MS_TRACE(); } RTC::KeyFrameRequestManager::~KeyFrameRequestManager() { MS_TRACE(); for (auto& kv : this->mapSsrcPendingKeyFrameInfo) { auto* pendingKeyFrameInfo = kv.second; delete pendingKeyFrameInfo; } this->mapSsrcPendingKeyFrameInfo.clear(); for (auto& kv : this->mapSsrcKeyFrameRequestDelayer) { auto* keyFrameRequestDelayer = kv.second; delete keyFrameRequestDelayer; } this->mapSsrcKeyFrameRequestDelayer.clear(); } void RTC::KeyFrameRequestManager::KeyFrameNeeded(uint32_t ssrc) { MS_TRACE(); if (this->keyFrameRequestDelay > 0u) { auto it = this->mapSsrcKeyFrameRequestDelayer.find(ssrc); // There is a delayer for the given ssrc, so enable it and return. if (it != this->mapSsrcKeyFrameRequestDelayer.end()) { MS_DEBUG_DEV("there is a delayer for the given ssrc, enabling it and returning"); auto* keyFrameRequestDelayer = it->second; keyFrameRequestDelayer->SetKeyFrameRequested(true); return; } // Otherwise create a delayer (not yet enabled) and continue. else { MS_DEBUG_DEV("creating a delayer for the given ssrc"); this->mapSsrcKeyFrameRequestDelayer[ssrc] = new KeyFrameRequestDelayer(this, this->shared, ssrc, this->keyFrameRequestDelay); } } auto it = this->mapSsrcPendingKeyFrameInfo.find(ssrc); // There is a pending key frame for the given ssrc. if (it != this->mapSsrcPendingKeyFrameInfo.end()) { auto* pendingKeyFrameInfo = it->second; // Re-request the key frame if not received on time. pendingKeyFrameInfo->SetRetryOnTimeout(true); return; } this->mapSsrcPendingKeyFrameInfo[ssrc] = new PendingKeyFrameInfo(this, this->shared, ssrc); this->listener->OnKeyFrameNeeded(this, ssrc); } void RTC::KeyFrameRequestManager::ForceKeyFrameNeeded(uint32_t ssrc) { MS_TRACE(); if (this->keyFrameRequestDelay > 0u) { // Create/replace a delayer for this ssrc. auto it = this->mapSsrcKeyFrameRequestDelayer.find(ssrc); // There is a delayer for the given ssrc, so enable it and return. if (it != this->mapSsrcKeyFrameRequestDelayer.end()) { auto* keyFrameRequestDelayer = it->second; delete keyFrameRequestDelayer; } this->mapSsrcKeyFrameRequestDelayer[ssrc] = new KeyFrameRequestDelayer(this, this->shared, ssrc, this->keyFrameRequestDelay); } auto it = this->mapSsrcPendingKeyFrameInfo.find(ssrc); // There is a pending key frame for the given ssrc. if (it != this->mapSsrcPendingKeyFrameInfo.end()) { auto* pendingKeyFrameInfo = it->second; pendingKeyFrameInfo->SetRetryOnTimeout(true); pendingKeyFrameInfo->Restart(); } else { this->mapSsrcPendingKeyFrameInfo[ssrc] = new PendingKeyFrameInfo(this, this->shared, ssrc); } this->listener->OnKeyFrameNeeded(this, ssrc); } void RTC::KeyFrameRequestManager::KeyFrameReceived(uint32_t ssrc) { MS_TRACE(); auto it = this->mapSsrcPendingKeyFrameInfo.find(ssrc); // There is no pending key frame for the given ssrc. if (it == this->mapSsrcPendingKeyFrameInfo.end()) { return; } auto* pendingKeyFrameInfo = it->second; delete pendingKeyFrameInfo; this->mapSsrcPendingKeyFrameInfo.erase(it); } void RTC::KeyFrameRequestManager::OnKeyFrameRequestTimeout(PendingKeyFrameInfo* pendingKeyFrameInfo) { MS_TRACE(); auto it = this->mapSsrcPendingKeyFrameInfo.find(pendingKeyFrameInfo->GetSsrc()); MS_ASSERT( it != this->mapSsrcPendingKeyFrameInfo.end(), "PendingKeyFrameInfo not present in the map"); if (!pendingKeyFrameInfo->GetRetryOnTimeout()) { delete pendingKeyFrameInfo; this->mapSsrcPendingKeyFrameInfo.erase(it); return; } // Best effort in case the PLI/FIR was lost. Do not retry on timeout. pendingKeyFrameInfo->SetRetryOnTimeout(false); pendingKeyFrameInfo->Restart(); MS_DEBUG_DEV("requesting key frame on timeout"); this->listener->OnKeyFrameNeeded(this, pendingKeyFrameInfo->GetSsrc()); } void RTC::KeyFrameRequestManager::OnKeyFrameDelayTimeout(KeyFrameRequestDelayer* keyFrameRequestDelayer) { MS_TRACE(); auto it = this->mapSsrcKeyFrameRequestDelayer.find(keyFrameRequestDelayer->GetSsrc()); MS_ASSERT( it != this->mapSsrcKeyFrameRequestDelayer.end(), "KeyFrameRequestDelayer not present in the map"); auto ssrc = keyFrameRequestDelayer->GetSsrc(); auto keyFrameRequested = keyFrameRequestDelayer->GetKeyFrameRequested(); delete keyFrameRequestDelayer; this->mapSsrcKeyFrameRequestDelayer.erase(it); // Ask for a new key frame as normal if needed. if (keyFrameRequested) { MS_DEBUG_DEV("requesting key frame after delay timeout"); KeyFrameNeeded(ssrc); } } ================================================ FILE: worker/src/RTC/NackGenerator.cpp ================================================ #define MS_CLASS "RTC::NackGenerator" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/NackGenerator.hpp" #include "Logger.hpp" namespace RTC { /* Static. */ static constexpr size_t MaxPacketAge{ 10000u }; static constexpr size_t MaxNackPackets{ 1000u }; static constexpr uint32_t DefaultRtt{ 100u }; static constexpr uint8_t MaxNackRetries{ 10u }; static constexpr uint64_t TimerInterval{ 40u }; /* Instance methods. */ NackGenerator::NackGenerator(Listener* listener, SharedInterface* shared, unsigned int sendNackDelayMs) : listener(listener), shared(shared), sendNackDelayMs(sendNackDelayMs), timer(shared->CreateTimer(this)), rtt(DefaultRtt) { MS_TRACE(); } NackGenerator::~NackGenerator() { MS_TRACE(); // Close the timer. delete this->timer; } /** * Returns true if this is a found nacked packet. False otherwise. * * NOTE: It also returns true if packet comes via RTX and contains a sequence * number higher than the highest seen. */ bool NackGenerator::ReceivePacket(const RTC::RTP::Packet* packet, bool isRecovered) { MS_TRACE(); const uint16_t seq = packet->GetSequenceNumber(); const bool isKeyFrame = packet->IsKeyFrame(); if (!this->started) { this->started = true; this->lastSeq = seq; if (isKeyFrame) { this->keyFrameList.insert(seq); } return false; } // Obviously never nacked, so ignore. if (seq == this->lastSeq) { return false; } // May be an out of order packet, or already handled retransmitted packet, // or a retransmitted packet. if (SeqManager::IsSeqLowerThan(seq, this->lastSeq)) { // It was a nacked packet. if (this->nackList.erase(seq) != 0u) { MS_DEBUG_DEV( "NACKed packet received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", recovered:%s]", packet->GetSsrc(), packet->GetSequenceNumber(), isRecovered ? "true" : "false"); // NOTE: Accept the packet since it was in the `nackList`, regardless // the NACK requesting this packet was not sent yet (this is, if // nackInfo.retries == 0) because we would request it later anyway. return true; } // Out of order packet or already handled NACKed packet. if (!isRecovered) { MS_WARN_DEV( "ignoring older packet not present in the NACK list [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); } return false; } // If we are here it means that we may have lost some packets so seq is // newer than the latest seq seen. if (isKeyFrame) { this->keyFrameList.insert(seq); } // Remove old keyframes. { auto it = this->keyFrameList.lower_bound(seq - MaxPacketAge); if (it != this->keyFrameList.begin()) { this->keyFrameList.erase(this->keyFrameList.begin(), it); } } if (isRecovered) { const auto inserted = this->recoveredList.insert(seq).second; // Packet already recovered, ignore it. if (!inserted) { return false; } // Remove old ones so we don't accumulate recovered packets. auto it = this->recoveredList.lower_bound(seq - MaxPacketAge); if (it != this->recoveredList.begin()) { this->recoveredList.erase(this->recoveredList.begin(), it); } // NOTE: It may happen that this packet received via RTX contains a real // RTP packet that (with highest seq not seen yet) whose transmission // failed so we didn't receive it. So do not return false here but let // the packet go through. } AddPacketsToNackList(this->lastSeq + 1, seq); this->lastSeq = seq; // Check if there are any nacks that are waiting for this seq number. const std::vector nackBatch = GetNackBatch(NackFilter::SEQ); if (!nackBatch.empty()) { this->listener->OnNackGeneratorNackRequired(nackBatch); } // This is important. Otherwise the running timer (filter:TIME) would be // interrupted and NACKs would never been sent more than once for each seq. if (!this->timer->IsActive()) { MayRunTimer(); } // libwebrtc may use RTX for probation and such packets may contain // RTX-encoded real RTP packets that were sent before but didn't arrive yet // to us or they were lost. Let's deal with them as normal packets. return isRecovered ? true : false; } void NackGenerator::AddPacketsToNackList(uint16_t seqStart, uint16_t seqEnd) { MS_TRACE(); // Remove old packets. auto it = this->nackList.lower_bound(seqEnd - MaxPacketAge); this->nackList.erase(this->nackList.begin(), it); // If the nack list is too large, remove packets from the nack list until // the latest first packet of a keyframe. If the list is still too large, // clear it and request a keyframe. const uint16_t numNewNacks = seqEnd - seqStart; if (static_cast(this->nackList.size()) + numNewNacks > MaxNackPackets) { while (RemoveNackItemsUntilKeyFrame() && static_cast(this->nackList.size()) + numNewNacks > MaxNackPackets) { } if (static_cast(this->nackList.size()) + numNewNacks > MaxNackPackets) { MS_WARN_TAG( rtx, "NACK list full, clearing it and requesting a key frame [seqEnd:%" PRIu16 "]", seqEnd); this->nackList.clear(); this->listener->OnNackGeneratorKeyFrameRequired(); return; } } for (uint16_t seq = seqStart; seq != seqEnd; ++seq) { MS_ASSERT(this->nackList.find(seq) == this->nackList.end(), "packet already in the NACK list"); // Do not send NACK for packets that are already recovered by RTX. if (this->recoveredList.find(seq) != this->recoveredList.end()) { continue; } this->nackList.emplace( seq, NackInfo{ this->shared->GetTimeMs(), seq, seq, }); } } bool NackGenerator::RemoveNackItemsUntilKeyFrame() { MS_TRACE(); while (!this->keyFrameList.empty()) { auto it = this->nackList.lower_bound(*this->keyFrameList.begin()); if (it != this->nackList.begin()) { // We have found a keyframe that actually is newer than at least one // packet in the nack list. this->nackList.erase(this->nackList.begin(), it); return true; } // If this keyframe is so old it does not remove any packets from the list, // remove it from the list of keyframes and try the next keyframe. this->keyFrameList.erase(this->keyFrameList.begin()); } return false; } std::vector NackGenerator::GetNackBatch(NackFilter filter) { MS_TRACE(); const uint64_t nowMs = this->shared->GetTimeMs(); std::vector nackBatch; auto it = this->nackList.begin(); while (it != this->nackList.end()) { NackInfo& nackInfo = it->second; const uint16_t seq = nackInfo.seq; if (this->sendNackDelayMs > 0 && nowMs - nackInfo.createdAtMs < this->sendNackDelayMs) { ++it; continue; } if ( filter == NackFilter::SEQ && nackInfo.sentAtMs == 0 && (nackInfo.sendAtSeq == this->lastSeq || SeqManager::IsSeqHigherThan(this->lastSeq, nackInfo.sendAtSeq))) { nackBatch.emplace_back(seq); nackInfo.retries++; nackInfo.sentAtMs = nowMs; if (nackInfo.retries >= MaxNackRetries) { MS_WARN_TAG( rtx, "sequence number removed from the NACK list due to max retries [filter:seq, seq:%" PRIu16 "]", seq); it = this->nackList.erase(it); } else { ++it; } continue; } if ( filter == NackFilter::TIME && (nackInfo.sentAtMs == 0 || nowMs - nackInfo.sentAtMs >= (this->rtt > 0u ? this->rtt : DefaultRtt))) { nackBatch.emplace_back(seq); nackInfo.retries++; nackInfo.sentAtMs = nowMs; if (nackInfo.retries >= MaxNackRetries) { MS_WARN_TAG( rtx, "sequence number removed from the NACK list due to max retries [filter:time, seq:%" PRIu16 "]", seq); it = this->nackList.erase(it); } else { ++it; } continue; } ++it; } #if MS_LOG_DEV_LEVEL == 3 if (!nackBatch.empty()) { std::ostringstream seqsStream; std::copy( nackBatch.begin(), nackBatch.end() - 1, std::ostream_iterator(seqsStream, ",")); seqsStream << nackBatch.back(); if (filter == NackFilter::SEQ) { MS_DEBUG_DEV("[filter:SEQ, asking seqs:%s]", seqsStream.str().c_str()); } else { MS_DEBUG_DEV("[filter:TIME, asking seqs:%s]", seqsStream.str().c_str()); } } #endif return nackBatch; } void NackGenerator::Reset() { MS_TRACE(); this->nackList.clear(); this->keyFrameList.clear(); this->recoveredList.clear(); this->started = false; this->lastSeq = 0u; } inline void NackGenerator::MayRunTimer() const { if (this->nackList.empty()) { this->timer->Stop(); } else { this->timer->Start(TimerInterval); } } inline void NackGenerator::OnTimer(TimerHandleInterface* /*timer*/) { MS_TRACE(); const std::vector nackBatch = GetNackBatch(NackFilter::TIME); if (!nackBatch.empty()) { this->listener->OnNackGeneratorNackRequired(nackBatch); } MayRunTimer(); } } // namespace RTC ================================================ FILE: worker/src/RTC/PipeConsumer.cpp ================================================ #define MS_CLASS "RTC::PipeConsumer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/PipeConsumer.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "RTC/RTP/Codecs/Tools.hpp" #ifdef MS_RTC_LOGGER_RTP #include "RTC/RtcLogger.hpp" #endif #include // std::numeric_limits namespace RTC { /* Static. */ static constexpr size_t TargetLayerRetransmissionBufferSize{ 15u }; /* Class methods */ void PipeConsumer::StorePacketInTargetLayerRetransmissionBuffer( std::map::SeqLowerThan>& targetLayerRetransmissionBuffer, RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) { MS_TRACE(); MS_DEBUG_DEV( "storing packet in target layer retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); // Store original packet into the buffer. Only clone once and only if // necessary. if (!sharedPacket.HasPacket()) { sharedPacket.Assign(packet); } // Assert that, if sharedPacket was already filled, both packet and // sharedPacket are the very same RTP packet. else { sharedPacket.AssertSamePacket(packet); } targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket; if (targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize) { targetLayerRetransmissionBuffer.erase(targetLayerRetransmissionBuffer.begin()); } } /* Instance methods. */ PipeConsumer::PipeConsumer( SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, const FBS::Transport::ConsumeRequest* data) : RTC::Consumer::Consumer(shared, id, producerId, listener, data, RTC::RtpParameters::Type::PIPE) { MS_TRACE(); // Ensure there are as many encodings as consumable encodings. if (this->rtpParameters.encodings.size() != this->consumableRtpEncodings.size()) { MS_THROW_TYPE_ERROR("number of rtpParameters.encodings and consumableRtpEncodings do not match"); } auto& encoding = this->rtpParameters.encodings[0]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); this->keyFrameSupported = RTC::RTP::Codecs::Tools::CanBeKeyFrame(mediaCodec->mimeType); // Create RtpStreamSend instances. CreateRtpStreams(); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ nullptr); } PipeConsumer::~PipeConsumer() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); for (auto* rtpStream : this->rtpStreams) { delete rtpStream; } this->rtpStreams.clear(); this->mapMappedSsrcSsrc.clear(); this->mapSsrcRtpStream.clear(); this->mapRtpStreamSyncRequired.clear(); this->mapRtpStreamRtpSeqManager.clear(); this->mapRtpStreamTargetLayerRetransmissionBuffer.clear(); } flatbuffers::Offset PipeConsumer::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Call the parent method. auto base = RTC::Consumer::FillBuffer(builder); // Add rtpStreams. std::vector> rtpStreams; rtpStreams.reserve(this->rtpStreams.size()); for (const auto* rtpStream : this->rtpStreams) { rtpStreams.emplace_back(rtpStream->FillBuffer(builder)); } auto dump = FBS::Consumer::CreateConsumerDumpDirect(builder, base, &rtpStreams); return FBS::Consumer::CreateDumpResponse(builder, dump); } flatbuffers::Offset PipeConsumer::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); std::vector> rtpStreams; rtpStreams.reserve(this->rtpStreams.size()); // Add stats of our send streams. for (auto* rtpStream : this->rtpStreams) { rtpStreams.emplace_back(rtpStream->FillBufferStats(builder)); } return FBS::Consumer::CreateGetStatsResponseDirect(builder, &rtpStreams); } flatbuffers::Offset PipeConsumer::FillBufferScore( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); MS_ASSERT(this->producerRtpStreamScores, "producerRtpStreamScores not set"); // NOTE: Hardcoded values in PipeTransport. return FBS::Consumer::CreateConsumerScoreDirect(builder, 10, 10, this->producerRtpStreamScores); } void PipeConsumer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::CONSUMER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Consumer_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::CONSUMER_REQUEST_KEY_FRAME: { if (IsActive()) { RequestKeyFrame(); } request->Accept(); break; } case Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS: { // Accept with empty preferred layers object. auto responseOffset = FBS::Consumer::CreateSetPreferredLayersResponse(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset); break; } default: { // Pass it to the parent class. RTC::Consumer::HandleRequest(request); } } } void PipeConsumer::ProducerRtpStream(RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint32_t /*mappedSsrc*/) { MS_TRACE(); // Do nothing. } void PipeConsumer::ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint32_t /*mappedSsrc*/) { MS_TRACE(); // Do nothing. } void PipeConsumer::ProducerRtpStreamScore( RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) { MS_TRACE(); // Do nothing. } void PipeConsumer::ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/) { MS_TRACE(); // Do nothing. } uint8_t PipeConsumer::GetBitratePriority() const { MS_TRACE(); // PipeConsumer does not play the BWE game. return 0u; } uint32_t PipeConsumer::IncreaseLayer(uint32_t /*bitrate*/, bool /*considerLoss*/) { MS_TRACE(); // PipeConsumer does not play the BWE game. return 0u; } void PipeConsumer::ApplyLayers() { MS_TRACE(); // PipeConsumer does not play the BWE game. } uint32_t PipeConsumer::GetDesiredBitrate() const { MS_TRACE(); // PipeConsumer does not play the BWE game. return 0u; } // NOLINTNEXTLINE(misc-no-recursion) void PipeConsumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) { MS_TRACE(); #ifdef MS_RTC_LOGGER_RTP packet->logger.consumerId = this->id; #endif auto ssrc = this->mapMappedSsrcSsrc.at(packet->GetSsrc()); auto* rtpStream = this->mapSsrcRtpStream.at(ssrc); auto& syncRequired = this->mapRtpStreamSyncRequired.at(rtpStream); auto& rtpSeqManager = this->mapRtpStreamRtpSeqManager.at(rtpStream); auto& targetLayerRetransmissionBuffer = this->mapRtpStreamTargetLayerRetransmissionBuffer.at(rtpStream); if (!IsActive()) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE); #endif rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } // If we need to sync, support key frames and this is not a key frame, // ignore the packet. if (syncRequired && this->keyFrameSupported && !packet->IsKeyFrame()) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); #endif // NOTE: No need to drop the packet in the RTP sequence manager since // here we are blocking all packets but the key frame that would trigger // sync below. // Store the packet for the scenario in which this packet is part of the // key frame and it arrived before the first packet of the key frame. StorePacketInTargetLayerRetransmissionBuffer( targetLayerRetransmissionBuffer, packet, sharedPacket); return; } auto payloadType = packet->GetPayloadType(); // NOTE: This may happen if this Consumer supports just some codecs of // those in the corresponding Producer. if (!this->supportedCodecPayloadTypes[payloadType]) { MS_WARN_DEV("payload type not supported [payloadType:%" PRIu8 "]", payloadType); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE); #endif rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } // Packets with only padding are not forwarded. if (packet->GetPayloadLength() == 0) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD); #endif rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } // Whether this is the first packet after re-sync. const bool isSyncPacket = syncRequired; // Whether packets stored in the target layer retransmission buffer must be // sent once this packet is sent. bool sendPacketsInTargetLayerRetransmissionBuffer{ false }; // Sync sequence number and timestamp if required. if (isSyncPacket) { if (packet->IsKeyFrame()) { MS_DEBUG_TAG( rtp, "sync key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); sendPacketsInTargetLayerRetransmissionBuffer = true; } rtpSeqManager.Sync(packet->GetSequenceNumber() - 1); syncRequired = false; } // Update RTP seq number and timestamp. uint16_t seq; rtpSeqManager.Input(packet->GetSequenceNumber(), seq); // Save original packet fields. auto origSsrc = packet->GetSsrc(); auto origSeq = packet->GetSequenceNumber(); // Rewrite packet. packet->SetSsrc(ssrc); packet->SetSequenceNumber(seq); #ifdef MS_RTC_LOGGER_RTP packet->logger.sendRtpTimestamp = packet->GetTimestamp(); packet->logger.sendSeqNumber = seq; #endif if (isSyncPacket) { MS_DEBUG_TAG( rtp, "sending sync packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] from original [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp(), origSsrc, origSeq); } const RTC::RTP::RtpStreamSend::ReceivePacketResult result = rtpStream->ReceivePacket(packet, sharedPacket); if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { // Send the packet. this->listener->OnConsumerSendRtpPacket(this, packet); // May emit 'trace' event. EmitTraceEventRtpAndKeyFrameTypes(packet); } else { MS_WARN_TAG( rtp, "failed to send packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] from original [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp(), origSsrc, origSeq); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED); #endif } // Restore packet fields. packet->SetSsrc(origSsrc); packet->SetSequenceNumber(origSeq); // If sharedPacket doesn't have a packet inside and it has been stored we // need to clone the packet into it. if (!sharedPacket.HasPacket() && result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED) { sharedPacket.Assign(packet); } // If sent packet was the first packet of a key frame, let's send buffered // packets belonging to the same key frame that arrived earlier due to // packet misorder. if (sendPacketsInTargetLayerRetransmissionBuffer) { // NOTE: Only send buffered packets if the first packet containing the // key frame was sent. if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { for (auto& kv : targetLayerRetransmissionBuffer) { auto& bufferedSharedPacket = kv.second; auto* bufferedPacket = bufferedSharedPacket.GetPacket(); if (bufferedPacket->GetSequenceNumber() > origSeq) { MS_DEBUG_DEV( "sending packet buffered in the target layer retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] after sending first packet of the key frame [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", bufferedPacket->GetSsrc(), bufferedPacket->GetSequenceNumber(), bufferedPacket->GetTimestamp(), packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); SendRtpPacket(bufferedPacket, bufferedSharedPacket); // Be sure that the target layer retransmission buffer has not been // emptied as a result of sending this packet. If so, exit the loop. if (targetLayerRetransmissionBuffer.empty()) { MS_DEBUG_DEV( "target layer retransmission buffer emptied while iterating it, exiting the loop"); break; } } } } targetLayerRetransmissionBuffer.clear(); } } bool PipeConsumer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) { MS_TRACE(); // Special condition for PipeConsumer since this method will be called in a // loop for each stream in this PipeConsumer. if ( nowMs != this->lastRtcpSentTime && static_cast((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval) { return true; } std::vector senderReports; std::vector sdesChunks; std::vector delaySinceLastRrSsrcInfos; for (auto* rtpStream : this->rtpStreams) { auto* report = rtpStream->GetRtcpSenderReport(nowMs); if (!report) { continue; } senderReports.push_back(report); // Build SDES chunk for this sender. auto* sdesChunk = rtpStream->GetRtcpSdesChunk(); sdesChunks.push_back(sdesChunk); auto* delaySinceLastRrSsrcInfo = rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs); if (delaySinceLastRrSsrcInfo) { delaySinceLastRrSsrcInfos.push_back(delaySinceLastRrSsrcInfo); } } // RTCP Compound packet buffer cannot hold the data. if (!packet->Add(senderReports, sdesChunks, delaySinceLastRrSsrcInfos)) { return false; } this->lastRtcpSentTime = nowMs; return true; } void PipeConsumer::NeedWorstRemoteFractionLost(uint32_t /*mappedSsrc*/, uint8_t& worstRemoteFractionLost) { MS_TRACE(); if (!IsActive()) { return; } for (auto* rtpStream : this->rtpStreams) { auto fractionLost = rtpStream->GetFractionLost(); // If our fraction lost is worse than the given one, update it. worstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost); } } void PipeConsumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) { MS_TRACE(); if (!IsActive()) { return; } // May emit 'trace' event. EmitTraceEventNackType(); auto ssrc = nackPacket->GetMediaSsrc(); auto* rtpStream = this->mapSsrcRtpStream.at(ssrc); rtpStream->ReceiveNack(nackPacket); } void PipeConsumer::ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) { MS_TRACE(); switch (messageType) { case RTC::RTCP::FeedbackPs::MessageType::PLI: { EmitTraceEventPliType(ssrc); break; } case RTC::RTCP::FeedbackPs::MessageType::FIR: { EmitTraceEventFirType(ssrc); break; } default:; } auto* rtpStream = this->mapSsrcRtpStream.at(ssrc); rtpStream->ReceiveKeyFrameRequest(messageType); if (IsActive()) { RequestKeyFrame(); } } void PipeConsumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) { MS_TRACE(); auto* rtpStream = this->mapSsrcRtpStream.at(report->GetSsrc()); rtpStream->ReceiveRtcpReceiverReport(report); } void PipeConsumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) { MS_TRACE(); for (auto* rtpStream : this->rtpStreams) { rtpStream->ReceiveRtcpXrReceiverReferenceTime(report); } } uint32_t PipeConsumer::GetTransmissionRate(uint64_t nowMs) { MS_TRACE(); if (!IsActive()) { return 0u; } uint32_t rate{ 0u }; for (auto* rtpStream : this->rtpStreams) { rate += rtpStream->GetBitrate(nowMs); } return rate; } float PipeConsumer::GetRtt() const { MS_TRACE(); float rtt{ 0 }; for (auto* rtpStream : this->rtpStreams) { rtt = std::max(rtpStream->GetRtt(), rtt); } return rtt; } void PipeConsumer::UserOnTransportConnected() { MS_TRACE(); for (auto& kv : this->mapRtpStreamSyncRequired) { kv.second = true; } if (IsActive()) { for (auto* rtpStream : this->rtpStreams) { rtpStream->Resume(); } RequestKeyFrame(); } } void PipeConsumer::UserOnTransportDisconnected() { MS_TRACE(); for (auto* rtpStream : this->rtpStreams) { rtpStream->Pause(); } for (auto& kv : this->mapRtpStreamTargetLayerRetransmissionBuffer) { auto& targetLayerRetransmissionBuffer = kv.second; targetLayerRetransmissionBuffer.clear(); } } void PipeConsumer::UserOnPaused() { MS_TRACE(); for (auto* rtpStream : this->rtpStreams) { rtpStream->Pause(); } for (auto& kv : this->mapRtpStreamTargetLayerRetransmissionBuffer) { auto& targetLayerRetransmissionBuffer = kv.second; targetLayerRetransmissionBuffer.clear(); } } void PipeConsumer::UserOnResumed() { MS_TRACE(); for (auto& kv : this->mapRtpStreamSyncRequired) { kv.second = true; } if (IsActive()) { for (auto* rtpStream : this->rtpStreams) { rtpStream->Resume(); } RequestKeyFrame(); } } void PipeConsumer::CreateRtpStreams() { MS_TRACE(); // NOTE: Here we know that SSRCs in Consumer's rtpParameters must be the same // as in the given consumableRtpEncodings. for (size_t idx{ 0u }; idx < this->rtpParameters.encodings.size(); ++idx) { auto& encoding = this->rtpParameters.encodings[idx]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); auto& consumableEncoding = this->consumableRtpEncodings[idx]; MS_DEBUG_TAG( rtp, "[ssrc:%" PRIu32 ", payloadType:%" PRIu8 "]", encoding.ssrc, mediaCodec->payloadType); // Set stream params. RTC::RTP::RtpStream::Params params; params.encodingIdx = idx; params.ssrc = encoding.ssrc; params.payloadType = mediaCodec->payloadType; params.mimeType = mediaCodec->mimeType; params.clockRate = mediaCodec->clockRate; params.cname = this->rtpParameters.rtcp.cname; params.spatialLayers = encoding.spatialLayers; params.temporalLayers = encoding.temporalLayers; // Check in band FEC in codec parameters. if (mediaCodec->parameters.HasInteger("useinbandfec") && mediaCodec->parameters.GetInteger("useinbandfec") == 1) { MS_DEBUG_TAG(rtp, "in band FEC enabled"); params.useInBandFec = true; } // Check DTX in codec parameters. if (mediaCodec->parameters.HasInteger("usedtx") && mediaCodec->parameters.GetInteger("usedtx") == 1) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } // Check DTX in the encoding. if (encoding.dtx) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } for (const auto& fb : mediaCodec->rtcpFeedback) { if (!params.useNack && fb.type == "nack" && fb.parameter.empty()) { MS_DEBUG_2TAGS(rtp, rtcp, "NACK supported"); params.useNack = true; } else if (!params.usePli && fb.type == "nack" && fb.parameter == "pli") { MS_DEBUG_2TAGS(rtp, rtcp, "PLI supported"); params.usePli = true; } else if (!params.useFir && fb.type == "ccm" && fb.parameter == "fir") { MS_DEBUG_2TAGS(rtp, rtcp, "FIR supported"); params.useFir = true; } } auto* rtpStream = new RTC::RTP::RtpStreamSend(this, this->shared, params, this->rtpParameters.mid); // If the Consumer is paused, tell the RtpStreamSend. if (IsPaused() || IsProducerPaused()) { rtpStream->Pause(); } const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); if (rtxCodec && encoding.hasRtx) { rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc); } this->rtpStreams.push_back(rtpStream); this->mapMappedSsrcSsrc[consumableEncoding.ssrc] = encoding.ssrc; this->mapSsrcRtpStream[encoding.ssrc] = rtpStream; this->mapRtpStreamSyncRequired[rtpStream] = false; // Let's choose an initial output seq number between 1000 and 32768 to avoid // libsrtp bug: // https://github.com/versatica/mediasoup/issues/1437 const uint16_t initialOutputSeq = Utils::Crypto::GetRandomUInt(1000u, std::numeric_limits::max() / 2); this->mapRtpStreamRtpSeqManager[rtpStream] = RTC::SeqManager(initialOutputSeq); this->mapRtpStreamTargetLayerRetransmissionBuffer[rtpStream]; } } void PipeConsumer::RequestKeyFrame() { MS_TRACE(); if (this->kind != RTC::Media::Kind::VIDEO) { return; } for (auto& consumableRtpEncoding : this->consumableRtpEncodings) { auto mappedSsrc = consumableRtpEncoding.ssrc; this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); } } void PipeConsumer::OnRtpStreamScore( RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) { MS_TRACE(); // Do nothing. } void PipeConsumer::OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* rtpStream, RTC::RTP::Packet* packet) { MS_TRACE(); this->listener->OnConsumerRetransmitRtpPacket(this, packet); // May emit 'trace' event. EmitTraceEventRtpAndKeyFrameTypes(packet, rtpStream->HasRtx()); } } // namespace RTC ================================================ FILE: worker/src/RTC/PipeTransport.cpp ================================================ #define MS_CLASS "RTC::PipeTransport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/PipeTransport.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include // std::memcpy() namespace RTC { /* Static. */ // NOTE: PipeTransport just allows AEAD_AES_256_GCM SRTP crypto suite. RTC::SrtpSession::CryptoSuite PipeTransport::srtpCryptoSuite{ RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM }; // MAster length of AEAD_AES_256_GCM. size_t PipeTransport::srtpMasterLength{ 44 }; /* Instance methods. */ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) PipeTransport::PipeTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::PipeTransport::PipeTransportOptions* options) : RTC::Transport::Transport(shared, id, listener, options->base()) { MS_TRACE(); if (options->listenInfo()->protocol() != FBS::Transport::Protocol::UDP) { MS_THROW_TYPE_ERROR("unsupported listen protocol"); } this->listenInfo.ip.assign(options->listenInfo()->ip()->str()); // This may throw. Utils::IP::NormalizeIp(this->listenInfo.ip); if (flatbuffers::IsFieldPresent(options->listenInfo(), FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS)) { this->listenInfo.announcedAddress.assign(options->listenInfo()->announcedAddress()->str()); } if (flatbuffers::IsFieldPresent(options->listenInfo(), FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS)) { this->listenInfo.announcedAddress.assign(options->listenInfo()->announcedAddress()->str()); } this->listenInfo.port = options->listenInfo()->port(); this->listenInfo.portRange.min = options->listenInfo()->portRange()->min(); this->listenInfo.portRange.max = options->listenInfo()->portRange()->max(); this->listenInfo.sendBufferSize = options->listenInfo()->sendBufferSize(); this->listenInfo.recvBufferSize = options->listenInfo()->recvBufferSize(); this->listenInfo.flags.ipv6Only = options->listenInfo()->flags()->ipv6Only(); this->listenInfo.flags.udpReusePort = options->listenInfo()->flags()->udpReusePort(); this->rtx = options->enableRtx(); if (options->enableSrtp()) { this->srtpKey = Utils::Crypto::GetRandomString(PipeTransport::srtpMasterLength); this->srtpKeyBase64 = Utils::String::Base64Encode(this->srtpKey); } try { if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0) { uint64_t portRangeHash{ 0u }; this->udpSocket = new RTC::UdpSocket( this, this->listenInfo.ip, this->listenInfo.portRange.min, this->listenInfo.portRange.max, this->listenInfo.flags, portRangeHash); } else if (this->listenInfo.port != 0) { this->udpSocket = new RTC::UdpSocket( this, this->listenInfo.ip, this->listenInfo.port, this->listenInfo.flags); } // NOTE: This is temporal to allow deprecated usage of worker port range. // In the future this should throw since |port| or |portRange| will be // required. else { uint64_t portRangeHash{ 0u }; this->udpSocket = new RTC::UdpSocket( this, this->listenInfo.ip, Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, this->listenInfo.flags, portRangeHash); } if (this->listenInfo.sendBufferSize != 0) { // NOTE: This may throw. this->udpSocket->SetSendBufferSize(this->listenInfo.sendBufferSize); } if (this->listenInfo.recvBufferSize != 0) { // NOTE: This may throw. this->udpSocket->SetRecvBufferSize(this->listenInfo.recvBufferSize); } MS_DEBUG_TAG( info, "UDP socket buffer sizes [send:%" PRIu32 ", recv:%" PRIu32 "]", udpSocket->GetSendBufferSize(), udpSocket->GetRecvBufferSize()); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ this); } catch (const MediaSoupError& error) { // Must delete everything since the destructor won't be called. delete this->udpSocket; this->udpSocket = nullptr; throw; } } PipeTransport::~PipeTransport() { MS_TRACE(); // Tell the Transport parent class that we are about to destroy // the class instance. SetDestroying(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); delete this->udpSocket; this->udpSocket = nullptr; delete this->tuple; this->tuple = nullptr; delete this->srtpSendSession; this->srtpSendSession = nullptr; delete this->srtpRecvSession; this->srtpRecvSession = nullptr; } flatbuffers::Offset PipeTransport::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add tuple. flatbuffers::Offset tuple; if (this->tuple) { tuple = this->tuple->FillBuffer(builder); } else { std::string localIp; if (this->listenInfo.announcedAddress.empty()) { localIp = this->udpSocket->GetLocalIp(); } else { localIp = this->listenInfo.announcedAddress; } tuple = FBS::Transport::CreateTupleDirect( builder, localIp.c_str(), this->udpSocket->GetLocalPort(), nullptr, 0, FBS::Transport::Protocol::UDP); } // Add srtpParameters. flatbuffers::Offset srtpParameters; if (HasSrtp()) { srtpParameters = FBS::SrtpParameters::CreateSrtpParametersDirect( builder, SrtpSession::CryptoSuiteToFbs(PipeTransport::srtpCryptoSuite), this->srtpKeyBase64.c_str()); } // Add base transport dump. auto base = Transport::FillBuffer(builder); return FBS::PipeTransport::CreateDumpResponse(builder, base, tuple, this->rtx, srtpParameters); } flatbuffers::Offset PipeTransport::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); // Add tuple. flatbuffers::Offset tuple; if (this->tuple) { tuple = this->tuple->FillBuffer(builder); } else { std::string localIp; if (this->listenInfo.announcedAddress.empty()) { localIp = this->udpSocket->GetLocalIp(); } else { localIp = this->listenInfo.announcedAddress; } tuple = FBS::Transport::CreateTupleDirect( builder, // localIp. localIp.c_str(), // localPort, this->udpSocket->GetLocalPort(), // remoteIp. nullptr, // remotePort. 0, // protocol. FBS::Transport::Protocol::UDP); } // Base Transport stats. auto base = Transport::FillBufferStats(builder); return FBS::PipeTransport::CreateGetStatsResponse(builder, base, tuple); } void PipeTransport::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::TRANSPORT_GET_STATS: { auto responseOffset = FillBufferStats(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::PipeTransport_GetStatsResponse, responseOffset); break; } case Channel::ChannelRequest::Method::TRANSPORT_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::PipeTransport_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::PIPETRANSPORT_CONNECT: { // Ensure this method is not called twice. if (this->tuple) { MS_THROW_ERROR("connect() already called"); } try { std::string ip; uint16_t port{ 0u }; std::string srtpKeyBase64; const auto* body = request->data->body_as(); auto srtpParametersPresent = flatbuffers::IsFieldPresent(body, FBS::PipeTransport::ConnectRequest::VT_SRTPPARAMETERS); if (!HasSrtp() && srtpParametersPresent) { MS_THROW_TYPE_ERROR("invalid srtpParameters (SRTP not enabled)"); } else if (HasSrtp()) { if (!srtpParametersPresent) { MS_THROW_TYPE_ERROR("missing srtpParameters (SRTP enabled)"); } const auto* srtpParameters = body->srtpParameters(); // NOTE: We just use AEAD_AES_256_GCM as SRTP crypto suite in // PipeTransport. if (srtpParameters->cryptoSuite() != FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_256_GCM) { MS_THROW_TYPE_ERROR("invalid/unsupported srtpParameters.cryptoSuite"); } srtpKeyBase64 = srtpParameters->keyBase64()->str(); size_t outLen; // This may throw. auto* srtpKey = Utils::String::Base64Decode(srtpKeyBase64, outLen); if (outLen != PipeTransport::srtpMasterLength) { MS_THROW_TYPE_ERROR("invalid decoded SRTP key length"); } auto* srtpLocalKey = new uint8_t[PipeTransport::srtpMasterLength]; auto* srtpRemoteKey = new uint8_t[PipeTransport::srtpMasterLength]; std::memcpy(srtpLocalKey, this->srtpKey.c_str(), PipeTransport::srtpMasterLength); std::memcpy(srtpRemoteKey, srtpKey, PipeTransport::srtpMasterLength); try { this->srtpSendSession = new RTC::SrtpSession( RTC::SrtpSession::Type::OUTBOUND, PipeTransport::srtpCryptoSuite, srtpLocalKey, PipeTransport::srtpMasterLength); } catch (const MediaSoupError& error) { delete[] srtpLocalKey; delete[] srtpRemoteKey; MS_THROW_ERROR("error creating SRTP sending session: %s", error.what()); } try { this->srtpRecvSession = new RTC::SrtpSession( RTC::SrtpSession::Type::INBOUND, PipeTransport::srtpCryptoSuite, srtpRemoteKey, PipeTransport::srtpMasterLength); } catch (const MediaSoupError& error) { delete[] srtpLocalKey; delete[] srtpRemoteKey; MS_THROW_ERROR("error creating SRTP receiving session: %s", error.what()); } delete[] srtpLocalKey; delete[] srtpRemoteKey; } if (!flatbuffers::IsFieldPresent(body, FBS::PipeTransport::ConnectRequest::VT_IP)) { MS_THROW_TYPE_ERROR("missing ip"); } ip = body->ip()->str(); // This may throw. Utils::IP::NormalizeIp(ip); if (!body->port().has_value()) { MS_THROW_TYPE_ERROR("missing port"); } // NOLINTNEXTLINE(bugprone-unchecked-optional-access) port = body->port().value(); int err; switch (Utils::IP::GetFamily(ip)) { case AF_INET: { err = uv_ip4_addr( ip.c_str(), static_cast(port), reinterpret_cast(&this->remoteAddrStorage)); if (err != 0) { MS_THROW_ERROR("uv_ip4_addr() failed: %s", uv_strerror(err)); } break; } case AF_INET6: { err = uv_ip6_addr( ip.c_str(), static_cast(port), reinterpret_cast(&this->remoteAddrStorage)); if (err != 0) { MS_THROW_ERROR("uv_ip6_addr() failed: %s", uv_strerror(err)); } break; } default: { MS_THROW_ERROR("invalid IP '%s'", ip.c_str()); } } // Create the tuple. this->tuple = new RTC::TransportTuple( this->udpSocket, reinterpret_cast(&this->remoteAddrStorage)); if (!this->listenInfo.announcedAddress.empty()) { this->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress); } } catch (const MediaSoupError& error) { delete this->tuple; this->tuple = nullptr; delete this->srtpSendSession; this->srtpSendSession = nullptr; delete this->srtpRecvSession; this->srtpRecvSession = nullptr; throw; } auto tupleOffset = this->tuple->FillBuffer(request->GetBufferBuilder()); auto responseOffset = FBS::PipeTransport::CreateConnectResponse(request->GetBufferBuilder(), tupleOffset); request->Accept(FBS::Response::Body::PipeTransport_ConnectResponse, responseOffset); // Assume we are connected (there is no much more we can do to know it) // and tell the parent class. RTC::Transport::Connected(); break; } default: { // Pass it to the parent class. RTC::Transport::HandleRequest(request); } } } void PipeTransport::HandleNotification(Channel::ChannelNotification* notification) { MS_TRACE(); // Pass it to the parent class. RTC::Transport::HandleNotification(notification); } inline bool PipeTransport::IsConnected() const { return this->tuple ? true : false; } inline bool PipeTransport::HasSrtp() const { return !this->srtpKey.empty(); } void PipeTransport::SendRtpPacket( RTC::Consumer* /*consumer*/, RTC::RTP::Packet* packet, RTC::Transport::onSendCallback* cb) { MS_TRACE(); if (!IsConnected()) { if (cb) { (*cb)(false); delete cb; } return; } const uint8_t* data = packet->GetBuffer(); auto len = packet->GetLength(); if (HasSrtp() && !this->srtpSendSession->EncryptRtp(&data, &len)) { if (cb) { (*cb)(false); delete cb; } return; } this->tuple->Send(data, len, cb); // Increase send transmission. RTC::Transport::DataSent(len); } void PipeTransport::SendRtcpPacket(RTC::RTCP::Packet* packet) { MS_TRACE(); if (!IsConnected()) { return; } const uint8_t* data = packet->GetData(); auto len = packet->GetSize(); if (HasSrtp() && !this->srtpSendSession->EncryptRtcp(&data, &len)) { return; } this->tuple->Send(data, len); // Increase send transmission. RTC::Transport::DataSent(len); } void PipeTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) { MS_TRACE(); if (!IsConnected()) { return; } packet->Serialize(RTC::RTCP::SerializationBuffer); const uint8_t* data = packet->GetData(); auto len = packet->GetSize(); if (HasSrtp() && !this->srtpSendSession->EncryptRtcp(&data, &len)) { return; } this->tuple->Send(data, len); // Increase send transmission. RTC::Transport::DataSent(len); } // TODO: SCTP: Remove once we only use built-in SCTP stack. void PipeTransport::SendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb) { MS_TRACE(); SendSctpMessage(dataConsumer, msg, len, ppid, cb); } void PipeTransport::SendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) { MS_TRACE(); SendSctpMessage(dataConsumer, std::move(message), cb); } bool PipeTransport::SendData(const uint8_t* data, size_t len) { MS_TRACE(); if (!IsConnected()) { return false; } this->tuple->Send(data, len); // Increase send transmission. RTC::Transport::DataSent(len); return true; } void PipeTransport::RecvStreamClosed(uint32_t ssrc) { MS_TRACE(); if (this->srtpRecvSession) { this->srtpRecvSession->RemoveStream(ssrc); } } void PipeTransport::SendStreamClosed(uint32_t ssrc) { MS_TRACE(); if (this->srtpSendSession) { this->srtpSendSession->RemoveStream(ssrc); } } inline void PipeTransport::OnPacketReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); // Increase receive transmission. RTC::Transport::DataReceived(len); // Check if it's RTCP. if (RTC::RTCP::Packet::IsRtcp(data, len)) { OnRtcpDataReceived(tuple, data, len); } // Check if it's RTP. else if (RTC::RTP::Packet::IsRtp(data, len)) { OnRtpDataReceived(tuple, data, len, bufferLen); } // Check if it's SCTP. else if (Settings::configuration.useBuiltInSctpStack && RTC::SCTP::Packet::IsSctp(data, len)) { OnSctpDataReceived(tuple, data, len); } // TODO: SCTP: Remove once we only use built-in SCTP stack. else if (!Settings::configuration.useBuiltInSctpStack && RTC::SctpAssociation::IsSctp(data, len)) { OnSctpDataReceived(tuple, data, len); } else { MS_WARN_DEV("ignoring received packet of unknown type"); } } inline void PipeTransport::OnRtpDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); if (!IsConnected()) { return; } // Decrypt the SRTP packet. if (HasSrtp() && !this->srtpRecvSession->DecryptSrtp(const_cast(data), &len)) { const auto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen); if (!packet) { MS_WARN_TAG(srtp, "DecryptSrtp() failed due to an invalid RTP packet"); } else { MS_WARN_TAG( srtp, "DecryptSrtp() failed [ssrc:%" PRIu32 ", payloadType:%" PRIu8 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetPayloadType(), packet->GetSequenceNumber()); delete packet; } return; } auto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen); if (!packet) { MS_WARN_TAG(rtp, "received data is not a valid RTP packet"); return; } // Verify that the packet's tuple matches our tuple. if (!this->tuple->Compare(tuple)) { MS_DEBUG_TAG(rtp, "ignoring RTP packet from unknown IP:port"); // Remove this SSRC. RecvStreamClosed(packet->GetSsrc()); delete packet; return; } // Pass the packet to the parent transport. RTC::Transport::ReceiveRtpPacket(packet); } inline void PipeTransport::OnRtcpDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len) { MS_TRACE(); if (!IsConnected()) { return; } // Decrypt the SRTCP packet. if (HasSrtp() && !this->srtpRecvSession->DecryptSrtcp(const_cast(data), &len)) { return; } // Verify that the packet's tuple matches our tuple. if (!this->tuple->Compare(tuple)) { MS_DEBUG_TAG(rtcp, "ignoring RTCP packet from unknown IP:port"); return; } auto* packet = RTC::RTCP::Packet::Parse(data, len); if (!packet) { MS_WARN_TAG(rtcp, "received data is not a valid RTCP compound or single packet"); return; } // Pass the packet to the parent transport. RTC::Transport::ReceiveRtcpPacket(packet); } inline void PipeTransport::OnSctpDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len) { MS_TRACE(); if (!IsConnected()) { return; } // Verify that the packet's tuple matches our tuple. if (!this->tuple->Compare(tuple)) { MS_DEBUG_TAG(sctp, "ignoring SCTP packet from unknown IP:port"); return; } // Pass it to the parent transport. RTC::Transport::ReceiveSctpData(data, len); } inline void PipeTransport::OnUdpSocketPacketReceived( RTC::UdpSocket* socket, const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* remoteAddr) { MS_TRACE(); RTC::TransportTuple tuple(socket, remoteAddr); OnPacketReceived(&tuple, data, len, bufferLen); } } // namespace RTC ================================================ FILE: worker/src/RTC/PlainTransport.cpp ================================================ #define MS_CLASS "RTC::PlainTransport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/PlainTransport.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include // std::memcpy() namespace RTC { /* Static. */ // AES-HMAC: http://tools.ietf.org/html/rfc3711 static constexpr size_t SrtpMasterKeyLength{ 16 }; static constexpr size_t SrtpMasterSaltLength{ 14 }; static constexpr size_t SrtpMasterLength{ SrtpMasterKeyLength + SrtpMasterSaltLength }; // AES-GCM: http://tools.ietf.org/html/rfc7714 static constexpr size_t SrtpAesGcm256MasterKeyLength{ 32 }; static constexpr size_t SrtpAesGcm256MasterSaltLength{ 12 }; static constexpr size_t SrtpAesGcm256MasterLength{ SrtpAesGcm256MasterKeyLength + SrtpAesGcm256MasterSaltLength }; static constexpr size_t SrtpAesGcm128MasterKeyLength{ 16 }; static constexpr size_t SrtpAesGcm128MasterSaltLength{ 12 }; static constexpr size_t SrtpAesGcm128MasterLength{ SrtpAesGcm128MasterKeyLength + SrtpAesGcm128MasterSaltLength }; /* Class variables. */ /* Instance methods. */ PlainTransport::PlainTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::PlainTransport::PlainTransportOptions* options) : RTC::Transport::Transport(shared, id, listener, options->base()) { MS_TRACE(); if (options->listenInfo()->protocol() != FBS::Transport::Protocol::UDP) { MS_THROW_TYPE_ERROR("unsupported listen protocol"); } this->listenInfo.ip.assign(options->listenInfo()->ip()->str()); // This may throw. Utils::IP::NormalizeIp(this->listenInfo.ip); if (flatbuffers::IsFieldPresent(options->listenInfo(), FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS)) { this->listenInfo.announcedAddress.assign(options->listenInfo()->announcedAddress()->str()); } this->listenInfo.port = options->listenInfo()->port(); this->listenInfo.portRange.min = options->listenInfo()->portRange()->min(); this->listenInfo.portRange.max = options->listenInfo()->portRange()->max(); this->listenInfo.sendBufferSize = options->listenInfo()->sendBufferSize(); this->listenInfo.recvBufferSize = options->listenInfo()->recvBufferSize(); this->listenInfo.flags.ipv6Only = options->listenInfo()->flags()->ipv6Only(); this->listenInfo.flags.udpReusePort = options->listenInfo()->flags()->udpReusePort(); this->rtcpMux = options->rtcpMux(); this->comedia = options->comedia(); if (!this->rtcpMux) { if (flatbuffers::IsFieldPresent(options, FBS::PlainTransport::PlainTransportOptions::VT_RTCPLISTENINFO)) { if (options->rtcpListenInfo()->protocol() != FBS::Transport::Protocol::UDP) { MS_THROW_TYPE_ERROR("unsupported RTCP listen protocol"); } this->rtcpListenInfo.ip.assign(options->rtcpListenInfo()->ip()->str()); // This may throw. Utils::IP::NormalizeIp(this->rtcpListenInfo.ip); if (flatbuffers::IsFieldPresent(options->rtcpListenInfo(), FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS)) { this->rtcpListenInfo.announcedAddress.assign( options->rtcpListenInfo()->announcedAddress()->str()); } this->rtcpListenInfo.port = options->rtcpListenInfo()->port(); this->rtcpListenInfo.portRange.min = options->rtcpListenInfo()->portRange()->min(); this->rtcpListenInfo.portRange.max = options->rtcpListenInfo()->portRange()->max(); this->rtcpListenInfo.sendBufferSize = options->rtcpListenInfo()->sendBufferSize(); this->rtcpListenInfo.recvBufferSize = options->rtcpListenInfo()->recvBufferSize(); this->rtcpListenInfo.flags.ipv6Only = options->rtcpListenInfo()->flags()->ipv6Only(); this->rtcpListenInfo.flags.udpReusePort = options->rtcpListenInfo()->flags()->udpReusePort(); } // If rtcpListenInfo is not given, just clone listenInfo. else { this->rtcpListenInfo = this->listenInfo; } } if (options->enableSrtp()) { auto srtpCryptoSuite = options->srtpCryptoSuite(); if (!srtpCryptoSuite.has_value()) { MS_THROW_TYPE_ERROR("missing srtpCryptoSuite"); } // NOTE: The SRTP crypto suite may change later on connect(). this->srtpCryptoSuite = SrtpSession::CryptoSuiteFromFbs(srtpCryptoSuite.value()); switch (this->srtpCryptoSuite) { case RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM: { this->srtpMasterLength = SrtpAesGcm256MasterLength; break; } case RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM: { this->srtpMasterLength = SrtpAesGcm128MasterLength; break; } case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80: case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32: { this->srtpMasterLength = SrtpMasterLength; break; } default: { MS_ABORT("unknown SRTP crypto suite"); } } this->srtpKey = Utils::Crypto::GetRandomString(this->srtpMasterLength); this->srtpKeyBase64 = Utils::String::Base64Encode(this->srtpKey); } try { if (this->listenInfo.portRange.min != 0 && this->listenInfo.portRange.max != 0) { uint64_t portRangeHash{ 0u }; this->udpSocket = new RTC::UdpSocket( this, this->listenInfo.ip, this->listenInfo.portRange.min, this->listenInfo.portRange.max, this->listenInfo.flags, portRangeHash); } else if (this->listenInfo.port != 0) { this->udpSocket = new RTC::UdpSocket( this, this->listenInfo.ip, this->listenInfo.port, this->listenInfo.flags); } // NOTE: This is temporal to allow deprecated usage of worker port range. // In the future this should throw since |port| or |portRange| will be // required. else { uint64_t portRangeHash{ 0u }; this->udpSocket = new RTC::UdpSocket( this, this->listenInfo.ip, Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, this->listenInfo.flags, portRangeHash); } if (this->listenInfo.sendBufferSize != 0) { // NOTE: This may throw. this->udpSocket->SetSendBufferSize(this->listenInfo.sendBufferSize); } if (this->listenInfo.recvBufferSize != 0) { // NOTE: This may throw. this->udpSocket->SetRecvBufferSize(this->listenInfo.recvBufferSize); } if (!this->rtcpMux) { if (this->rtcpListenInfo.portRange.min != 0 && this->rtcpListenInfo.portRange.max != 0) { uint64_t portRangeHash{ 0u }; this->rtcpUdpSocket = new RTC::UdpSocket( this, this->rtcpListenInfo.ip, this->rtcpListenInfo.portRange.min, this->rtcpListenInfo.portRange.max, this->rtcpListenInfo.flags, portRangeHash); } else if (this->rtcpListenInfo.port != 0) { this->rtcpUdpSocket = new RTC::UdpSocket( this, this->rtcpListenInfo.ip, this->rtcpListenInfo.port, this->rtcpListenInfo.flags); } // NOTE: This is temporal to allow deprecated usage of worker port range. // In the future this should throw since |port| or |portRange| will be // required. else { uint64_t portRangeHash{ 0u }; this->rtcpUdpSocket = new RTC::UdpSocket( this, this->rtcpListenInfo.ip, Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, this->rtcpListenInfo.flags, portRangeHash); } if (this->rtcpListenInfo.sendBufferSize != 0) { // NOTE: This may throw. this->rtcpUdpSocket->SetSendBufferSize(this->rtcpListenInfo.sendBufferSize); } if (this->rtcpListenInfo.recvBufferSize != 0) { // NOTE: This may throw. this->rtcpUdpSocket->SetRecvBufferSize(this->rtcpListenInfo.recvBufferSize); } } // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ this); } catch (const MediaSoupError& error) { delete this->udpSocket; this->udpSocket = nullptr; delete this->rtcpUdpSocket; this->rtcpUdpSocket = nullptr; throw; } } PlainTransport::~PlainTransport() { MS_TRACE(); // Tell the Transport parent class that we are about to destroy // the class instance. SetDestroying(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); delete this->udpSocket; this->udpSocket = nullptr; delete this->rtcpUdpSocket; this->rtcpUdpSocket = nullptr; delete this->tuple; this->tuple = nullptr; delete this->rtcpTuple; this->rtcpTuple = nullptr; delete this->srtpSendSession; this->srtpSendSession = nullptr; delete this->srtpRecvSession; this->srtpRecvSession = nullptr; } flatbuffers::Offset PlainTransport::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add tuple. flatbuffers::Offset tuple; if (this->tuple) { tuple = this->tuple->FillBuffer(builder); } else { std::string localIp; if (this->listenInfo.announcedAddress.empty()) { localIp = this->udpSocket->GetLocalIp(); } else { localIp = this->listenInfo.announcedAddress; } tuple = FBS::Transport::CreateTupleDirect( builder, localIp.c_str(), this->udpSocket->GetLocalPort(), nullptr, 0, FBS::Transport::Protocol::UDP); } // Add rtcpTuple. flatbuffers::Offset rtcpTuple; if (!this->rtcpMux) { if (this->rtcpTuple) { rtcpTuple = this->rtcpTuple->FillBuffer(builder); } else { std::string localIp; if (this->rtcpListenInfo.announcedAddress.empty()) { localIp = this->rtcpUdpSocket->GetLocalIp(); } else { localIp = this->rtcpListenInfo.announcedAddress; } rtcpTuple = FBS::Transport::CreateTupleDirect( builder, localIp.c_str(), this->rtcpUdpSocket->GetLocalPort(), nullptr, 0, FBS::Transport::Protocol::UDP); } } // Add srtpParameters. flatbuffers::Offset srtpParameters; if (HasSrtp()) { srtpParameters = FBS::SrtpParameters::CreateSrtpParametersDirect( builder, SrtpSession::CryptoSuiteToFbs(this->srtpCryptoSuite), this->srtpKeyBase64.c_str()); } // Add base transport dump. auto base = Transport::FillBuffer(builder); return FBS::PlainTransport::CreateDumpResponse( builder, base, this->rtcpMux, this->comedia, tuple, rtcpTuple, srtpParameters); } flatbuffers::Offset PlainTransport::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); // Add tuple. flatbuffers::Offset tuple; if (this->tuple) { tuple = this->tuple->FillBuffer(builder); } else { std::string localIp; if (this->listenInfo.announcedAddress.empty()) { localIp = this->udpSocket->GetLocalIp(); } else { localIp = this->listenInfo.announcedAddress; } tuple = FBS::Transport::CreateTupleDirect( builder, // localIp. localIp.c_str(), // localPort, this->udpSocket->GetLocalPort(), // remoteIp. nullptr, // remotePort. 0, // protocol. FBS::Transport::Protocol::UDP); } // Add rtcpTuple. flatbuffers::Offset rtcpTuple; if (!this->rtcpMux && this->rtcpTuple) { rtcpTuple = this->rtcpTuple->FillBuffer(builder); } // Base Transport stats. auto base = Transport::FillBufferStats(builder); return FBS::PlainTransport::CreateGetStatsResponse( builder, base, this->rtcpMux, this->comedia, tuple, rtcpTuple); } void PlainTransport::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::TRANSPORT_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::PlainTransport_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::TRANSPORT_GET_STATS: { auto responseOffset = FillBufferStats(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::PlainTransport_GetStatsResponse, responseOffset); break; } case Channel::ChannelRequest::Method::PLAINTRANSPORT_CONNECT: { // Ensure this method is not called twice. if (this->connectCalled) { MS_THROW_ERROR("connect() already called"); } try { std::string ip; uint16_t port{ 0u }; uint16_t rtcpPort{ 0u }; std::string srtpKeyBase64; const auto* body = request->data->body_as(); auto srtpParametersPresent = flatbuffers::IsFieldPresent( body, FBS::PlainTransport::ConnectRequest::VT_SRTPPARAMETERS); if (!HasSrtp() && srtpParametersPresent) { MS_THROW_TYPE_ERROR("invalid srtpParameters (SRTP not enabled)"); } else if (HasSrtp()) { if (!srtpParametersPresent) { MS_THROW_TYPE_ERROR("missing srtpParameters (SRTP enabled)"); } const auto* const srtpParameters = body->srtpParameters(); // Update out SRTP crypto suite with the one used by the remote. auto previousSrtpCryptoSuite = this->srtpCryptoSuite; this->srtpCryptoSuite = SrtpSession::CryptoSuiteFromFbs(srtpParameters->cryptoSuite()); switch (this->srtpCryptoSuite) { case RTC::SrtpSession::CryptoSuite::AEAD_AES_256_GCM: { this->srtpMasterLength = SrtpAesGcm256MasterLength; break; } case RTC::SrtpSession::CryptoSuite::AEAD_AES_128_GCM: { this->srtpMasterLength = SrtpAesGcm128MasterLength; break; } case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80: case RTC::SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32: { this->srtpMasterLength = SrtpMasterLength; break; } default: { MS_ABORT("unknown SRTP crypto suite"); } } // If the SRTP crypto suite changed we must regenerate our SRTP key. if (this->srtpCryptoSuite != previousSrtpCryptoSuite) { this->srtpKey = Utils::Crypto::GetRandomString(this->srtpMasterLength); this->srtpKeyBase64 = Utils::String::Base64Encode(this->srtpKey); } srtpKeyBase64 = srtpParameters->keyBase64()->str(); size_t outLen; // This may throw. auto* srtpKey = Utils::String::Base64Decode(srtpKeyBase64, outLen); if (outLen != this->srtpMasterLength) { MS_THROW_TYPE_ERROR("invalid decoded SRTP key length"); } auto* srtpLocalKey = new uint8_t[this->srtpMasterLength]; auto* srtpRemoteKey = new uint8_t[this->srtpMasterLength]; std::memcpy(srtpLocalKey, this->srtpKey.c_str(), this->srtpMasterLength); std::memcpy(srtpRemoteKey, srtpKey, this->srtpMasterLength); try { this->srtpSendSession = new RTC::SrtpSession( RTC::SrtpSession::Type::OUTBOUND, this->srtpCryptoSuite, srtpLocalKey, this->srtpMasterLength); } catch (const MediaSoupError& error) { delete[] srtpLocalKey; delete[] srtpRemoteKey; MS_THROW_ERROR("error creating SRTP sending session: %s", error.what()); } try { this->srtpRecvSession = new RTC::SrtpSession( RTC::SrtpSession::Type::INBOUND, this->srtpCryptoSuite, srtpRemoteKey, this->srtpMasterLength); } catch (const MediaSoupError& error) { delete[] srtpLocalKey; delete[] srtpRemoteKey; MS_THROW_ERROR("error creating SRTP receiving session: %s", error.what()); } delete[] srtpLocalKey; delete[] srtpRemoteKey; } if (!this->comedia) { if (!flatbuffers::IsFieldPresent(body, FBS::PlainTransport::ConnectRequest::VT_IP)) { MS_THROW_TYPE_ERROR("missing ip"); } ip = body->ip()->str(); // This may throw. Utils::IP::NormalizeIp(ip); if (!body->port().has_value()) { MS_THROW_TYPE_ERROR("missing port"); } // NOLINTNEXTLINE(bugprone-unchecked-optional-access) port = body->port().value(); if (body->rtcpPort().has_value()) { if (this->rtcpMux) { MS_THROW_TYPE_ERROR("cannot set rtcpPort with rtcpMux enabled"); } // NOLINTNEXTLINE(bugprone-unchecked-optional-access) rtcpPort = body->rtcpPort().value(); } else { if (!this->rtcpMux) { MS_THROW_TYPE_ERROR("missing rtcpPort (required with rtcpMux disabled)"); } } int err; switch (Utils::IP::GetFamily(ip)) { case AF_INET: { err = uv_ip4_addr( ip.c_str(), static_cast(port), reinterpret_cast(&this->remoteAddrStorage)); if (err != 0) { MS_THROW_ERROR("uv_ip4_addr() failed: %s", uv_strerror(err)); } break; } case AF_INET6: { err = uv_ip6_addr( ip.c_str(), static_cast(port), reinterpret_cast(&this->remoteAddrStorage)); if (err != 0) { MS_THROW_ERROR("uv_ip6_addr() failed: %s", uv_strerror(err)); } break; } default: { MS_THROW_ERROR("invalid IP '%s'", ip.c_str()); } } // Create the tuple. this->tuple = new RTC::TransportTuple( this->udpSocket, reinterpret_cast(&this->remoteAddrStorage)); if (!this->listenInfo.announcedAddress.empty()) { this->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress); } if (!this->rtcpMux) { switch (Utils::IP::GetFamily(ip)) { case AF_INET: { err = uv_ip4_addr( ip.c_str(), static_cast(rtcpPort), reinterpret_cast(&this->rtcpRemoteAddrStorage)); if (err != 0) { MS_THROW_ERROR("uv_ip4_addr() failed: %s", uv_strerror(err)); } break; } case AF_INET6: { err = uv_ip6_addr( ip.c_str(), static_cast(rtcpPort), reinterpret_cast(&this->rtcpRemoteAddrStorage)); if (err != 0) { MS_THROW_ERROR("uv_ip6_addr() failed: %s", uv_strerror(err)); } break; } default: { MS_THROW_ERROR("invalid IP '%s'", ip.c_str()); } } // Create the tuple. this->rtcpTuple = new RTC::TransportTuple( this->rtcpUdpSocket, reinterpret_cast(&this->rtcpRemoteAddrStorage)); if (!this->rtcpListenInfo.announcedAddress.empty()) { this->rtcpTuple->SetLocalAnnouncedAddress(this->rtcpListenInfo.announcedAddress); } } } } catch (const MediaSoupError& error) { delete this->tuple; this->tuple = nullptr; delete this->rtcpTuple; this->rtcpTuple = nullptr; delete this->srtpSendSession; this->srtpSendSession = nullptr; delete this->srtpRecvSession; this->srtpRecvSession = nullptr; throw; } this->connectCalled = true; // Tell the caller about the selected local DTLS role. flatbuffers::Offset tupleOffset; flatbuffers::Offset rtcpTupleOffset; flatbuffers::Offset srtpParametersOffset; if (this->tuple) { tupleOffset = this->tuple->FillBuffer(request->GetBufferBuilder()); } if (!this->rtcpMux && this->rtcpTuple) { rtcpTupleOffset = this->rtcpTuple->FillBuffer(request->GetBufferBuilder()); } if (HasSrtp()) { srtpParametersOffset = FBS::SrtpParameters::CreateSrtpParametersDirect( request->GetBufferBuilder(), SrtpSession::CryptoSuiteToFbs(this->srtpCryptoSuite), this->srtpKeyBase64.c_str()); } auto responseOffset = FBS::PlainTransport::CreateConnectResponse( request->GetBufferBuilder(), tupleOffset, rtcpTupleOffset, srtpParametersOffset); request->Accept(FBS::Response::Body::PlainTransport_ConnectResponse, responseOffset); // Assume we are connected (there is no much more we can do to know it) // and tell the parent class. RTC::Transport::Connected(); break; } default: { // Pass it to the parent class. RTC::Transport::HandleRequest(request); } } } void PlainTransport::HandleNotification(Channel::ChannelNotification* notification) { MS_TRACE(); // Pass it to the parent class. RTC::Transport::HandleNotification(notification); } inline bool PlainTransport::IsConnected() const { return this->tuple ? true : false; } inline bool PlainTransport::HasSrtp() const { return !this->srtpKey.empty(); } inline bool PlainTransport::IsSrtpReady() const { return HasSrtp() && this->srtpSendSession && this->srtpRecvSession; } void PlainTransport::SendRtpPacket( RTC::Consumer* /*consumer*/, RTC::RTP::Packet* packet, const RTC::Transport::onSendCallback* cb) { MS_TRACE(); if (!IsConnected()) { if (cb) { (*cb)(false); delete cb; } return; } const uint8_t* data = packet->GetBuffer(); auto len = packet->GetLength(); if (HasSrtp() && !this->srtpSendSession->EncryptRtp(&data, &len)) { if (cb) { (*cb)(false); delete cb; } return; } this->tuple->Send(data, len, cb); // Increase send transmission. RTC::Transport::DataSent(len); } void PlainTransport::SendRtcpPacket(RTC::RTCP::Packet* packet) { MS_TRACE(); if (!IsConnected()) { return; } const uint8_t* data = packet->GetData(); auto len = packet->GetSize(); if (HasSrtp() && !this->srtpSendSession->EncryptRtcp(&data, &len)) { return; } if (this->rtcpMux) { this->tuple->Send(data, len); } else if (this->rtcpTuple) { this->rtcpTuple->Send(data, len); } // Increase send transmission. RTC::Transport::DataSent(len); } void PlainTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) { MS_TRACE(); if (!IsConnected()) { return; } packet->Serialize(RTC::RTCP::SerializationBuffer); const uint8_t* data = packet->GetData(); auto len = packet->GetSize(); if (HasSrtp() && !this->srtpSendSession->EncryptRtcp(&data, &len)) { return; } if (this->rtcpMux) { this->tuple->Send(data, len); } else if (this->rtcpTuple) { this->rtcpTuple->Send(data, len); } // Increase send transmission. RTC::Transport::DataSent(len); } // TODO: SCTP: Remove once we only use built-in SCTP stack. void PlainTransport::SendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb) { MS_TRACE(); SendSctpMessage(dataConsumer, msg, len, ppid, cb); } void PlainTransport::SendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) { MS_TRACE(); SendSctpMessage(dataConsumer, std::move(message), cb); } bool PlainTransport::SendData(const uint8_t* data, size_t len) { MS_TRACE(); if (!IsConnected()) { MS_WARN_TAG(sctp, "not connected, cannot send SCTP data"); return false; } this->tuple->Send(data, len); // Increase send transmission. RTC::Transport::DataSent(len); return true; } void PlainTransport::RecvStreamClosed(uint32_t ssrc) { MS_TRACE(); if (this->srtpRecvSession) { this->srtpRecvSession->RemoveStream(ssrc); } } void PlainTransport::SendStreamClosed(uint32_t ssrc) { MS_TRACE(); if (this->srtpSendSession) { this->srtpSendSession->RemoveStream(ssrc); } } inline void PlainTransport::OnPacketReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); // Increase receive transmission. RTC::Transport::DataReceived(len); // Check if it's RTCP. if (RTC::RTCP::Packet::IsRtcp(data, len)) { OnRtcpDataReceived(tuple, data, len); } // Check if it's RTP. else if (RTC::RTP::Packet::IsRtp(data, len)) { OnRtpDataReceived(tuple, data, len, bufferLen); } // Check if it's SCTP. else if (Settings::configuration.useBuiltInSctpStack && RTC::SCTP::Packet::IsSctp(data, len)) { OnSctpDataReceived(tuple, data, len); } // TODO: SCTP: Remove once we only use built-in SCTP stack. else if (!Settings::configuration.useBuiltInSctpStack && RTC::SctpAssociation::IsSctp(data, len)) { OnSctpDataReceived(tuple, data, len); } else { MS_WARN_DEV("ignoring received packet of unknown type"); } } inline void PlainTransport::OnRtpDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); if (HasSrtp() && !IsSrtpReady()) { return; } // Decrypt the SRTP packet. if (HasSrtp() && !this->srtpRecvSession->DecryptSrtp(const_cast(data), &len)) { const auto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen); if (!packet) { MS_WARN_TAG(srtp, "DecryptSrtp() failed due to an invalid RTP packet"); } else { MS_WARN_TAG( srtp, "DecryptSrtp() failed [ssrc:%" PRIu32 ", payloadType:%" PRIu8 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetPayloadType(), packet->GetSequenceNumber()); delete packet; } return; } auto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen); if (!packet) { MS_WARN_TAG(rtp, "received data is not a valid RTP packet"); return; } // If we don't have a RTP tuple yet, check whether comedia mode is set. if (!this->tuple) { if (!this->comedia) { MS_DEBUG_TAG(rtp, "ignoring RTP packet while not connected"); // Remove this SSRC. RecvStreamClosed(packet->GetSsrc()); delete packet; return; } MS_DEBUG_TAG(rtp, "setting RTP tuple (comedia mode enabled)"); auto wasConnected = IsConnected(); this->tuple = new RTC::TransportTuple(tuple); if (!this->listenInfo.announcedAddress.empty()) { this->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress); } // If not yet connected do it now. if (!wasConnected) { // Notify the Node PlainTransport. EmitTuple(); RTC::Transport::Connected(); } } // Otherwise, if RTP tuple is set, verify that it matches the origin // of the packet. else if (!this->tuple->Compare(tuple)) { MS_DEBUG_TAG(rtp, "ignoring RTP packet from unknown IP:port"); // Remove this SSRC. RecvStreamClosed(packet->GetSsrc()); delete packet; return; } // Pass the packet to the parent transport. RTC::Transport::ReceiveRtpPacket(packet); } inline void PlainTransport::OnRtcpDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len) { MS_TRACE(); if (HasSrtp() && !IsSrtpReady()) { return; } // Decrypt the SRTCP packet. if (HasSrtp() && !this->srtpRecvSession->DecryptSrtcp(const_cast(data), &len)) { return; } // If we don't have a RTP tuple yet, check whether RTCP-mux and comedia // mode are set. if (this->rtcpMux && !this->tuple) { if (!this->comedia) { MS_DEBUG_TAG(rtcp, "ignoring RTCP packet while not connected"); return; } MS_DEBUG_TAG(rtp, "setting RTP tuple (comedia mode enabled)"); auto wasConnected = IsConnected(); this->tuple = new RTC::TransportTuple(tuple); if (!this->listenInfo.announcedAddress.empty()) { this->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress); } // If not yet connected do it now. if (!wasConnected) { // Notify the Node PlainTransport. EmitTuple(); RTC::Transport::Connected(); } } // Otherwise, if RTCP-mux is unset and RTCP tuple is unset, set it if we // are in comedia mode. else if (!this->rtcpMux && !this->rtcpTuple) { if (!this->comedia) { MS_DEBUG_TAG(rtcp, "ignoring RTCP packet while not connected"); return; } MS_DEBUG_TAG(rtcp, "setting RTCP tuple (comedia mode enabled)"); this->rtcpTuple = new RTC::TransportTuple(tuple); if (!this->rtcpListenInfo.announcedAddress.empty()) { this->rtcpTuple->SetLocalAnnouncedAddress(this->rtcpListenInfo.announcedAddress); } // Notify the Node PlainTransport. EmitRtcpTuple(); } // If RTCP-mux verify that the packet's tuple matches our RTP tuple. else if (this->rtcpMux && !this->tuple->Compare(tuple)) { MS_DEBUG_TAG(rtcp, "ignoring RTCP packet from unknown IP:port"); return; } // If no RTCP-mux verify that the packet's tuple matches our RTCP tuple. else if (!this->rtcpMux && !this->rtcpTuple->Compare(tuple)) { MS_DEBUG_TAG(rtcp, "ignoring RTCP packet from unknown IP:port"); return; } auto* packet = RTC::RTCP::Packet::Parse(data, len); if (!packet) { MS_WARN_TAG(rtcp, "received data is not a valid RTCP compound or single packet"); return; } // Pass the packet to the parent transport. RTC::Transport::ReceiveRtcpPacket(packet); } inline void PlainTransport::OnSctpDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len) { MS_TRACE(); // If we don't have a RTP tuple yet, check whether comedia mode is set. if (!this->tuple) { if (!this->comedia) { MS_DEBUG_TAG(sctp, "ignoring SCTP packet while not connected"); return; } MS_DEBUG_TAG(sctp, "setting RTP/SCTP tuple (comedia mode enabled)"); auto wasConnected = IsConnected(); this->tuple = new RTC::TransportTuple(tuple); if (!this->listenInfo.announcedAddress.empty()) { this->tuple->SetLocalAnnouncedAddress(this->listenInfo.announcedAddress); } // If not yet connected do it now. if (!wasConnected) { // Notify the Node PlainTransport. EmitTuple(); RTC::Transport::Connected(); } } // Otherwise, if RTP tuple is set, verify that it matches the origin // of the packet. if (!this->tuple->Compare(tuple)) { MS_DEBUG_TAG(sctp, "ignoring SCTP packet from unknown IP:port"); return; } // Pass it to the parent transport. RTC::Transport::ReceiveSctpData(data, len); } inline void PlainTransport::EmitTuple() const { auto tuple = this->tuple->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto notification = FBS::PlainTransport::CreateTupleNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), tuple); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::PLAINTRANSPORT_TUPLE, FBS::Notification::Body::PlainTransport_TupleNotification, notification); } inline void PlainTransport::EmitRtcpTuple() const { auto rtcpTuple = this->rtcpTuple->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto notification = FBS::PlainTransport::CreateRtcpTupleNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), rtcpTuple); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::PLAINTRANSPORT_RTCP_TUPLE, FBS::Notification::Body::PlainTransport_RtcpTupleNotification, notification); } inline void PlainTransport::OnUdpSocketPacketReceived( RTC::UdpSocket* socket, const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* remoteAddr) { MS_TRACE(); RTC::TransportTuple tuple(socket, remoteAddr); OnPacketReceived(&tuple, data, len, bufferLen); } } // namespace RTC ================================================ FILE: worker/src/RTC/PortManager.cpp ================================================ #define MS_CLASS "PortManager" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/PortManager.hpp" #include "DepLibUV.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include // std:make_tuple() /* Static methods for UV callbacks. */ // NOTE: We have different onCloseXxx() callbacks to avoid an ASAN warning by // ensuring that we call `delete xxx` with same type as `new xxx` before. static inline void onCloseUdp(uv_handle_t* handle) { delete reinterpret_cast(handle); } static inline void onCloseTcp(uv_handle_t* handle) { delete reinterpret_cast(handle); } inline static void onFakeConnection(uv_stream_t* /*handle*/, int /*status*/) { // Do nothing. } namespace RTC { /* Class variables. */ thread_local absl::flat_hash_map PortManager::mapPortRanges; /* Class methods. */ uv_handle_t* PortManager::Bind( Protocol protocol, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags) { MS_TRACE(); // First normalize the IP. This may throw if invalid IP. Utils::IP::NormalizeIp(ip); int err; const int family = Utils::IP::GetFamily(ip); struct sockaddr_storage bindAddr{}; uv_handle_t* uvHandle{ nullptr }; std::string protocolStr; const uint8_t bitFlags = ConvertSocketFlags(flags, protocol, family); switch (protocol) { case Protocol::UDP: { protocolStr.assign("udp"); break; } case Protocol::TCP: { protocolStr.assign("tcp"); break; } } switch (family) { case AF_INET: { err = uv_ip4_addr(ip.c_str(), 0, reinterpret_cast(&bindAddr)); if (err != 0) { MS_THROW_ERROR("uv_ip4_addr() failed: %s", uv_strerror(err)); } break; } case AF_INET6: { err = uv_ip6_addr(ip.c_str(), 0, reinterpret_cast(&bindAddr)); if (err != 0) { MS_THROW_ERROR("uv_ip6_addr() failed: %s", uv_strerror(err)); } break; } // This cannot happen. default: { MS_THROW_ERROR("unknown IP family"); } } // Set the port into the sockaddr struct. switch (family) { case AF_INET: { (reinterpret_cast(&bindAddr))->sin_port = htons(port); break; } case AF_INET6: { (reinterpret_cast(&bindAddr))->sin6_port = htons(port); break; } // This cannot happen. default: { MS_THROW_ERROR("unknown IP family"); } } // Try to bind on it. switch (protocol) { case Protocol::UDP: { uvHandle = reinterpret_cast(new uv_udp_t()); err = uv_udp_init_ex( DepLibUV::GetLoop(), reinterpret_cast(uvHandle), UV_UDP_RECVMMSG); break; } case Protocol::TCP: { uvHandle = reinterpret_cast(new uv_tcp_t()); err = uv_tcp_init(DepLibUV::GetLoop(), reinterpret_cast(uvHandle)); break; } } if (err != 0) { switch (protocol) { case Protocol::UDP: { delete reinterpret_cast(uvHandle); MS_THROW_ERROR("uv_udp_init_ex() failed: %s", uv_strerror(err)); break; } case Protocol::TCP: { delete reinterpret_cast(uvHandle); MS_THROW_ERROR("uv_tcp_init() failed: %s", uv_strerror(err)); break; } } } switch (protocol) { case Protocol::UDP: { err = uv_udp_bind( reinterpret_cast(uvHandle), reinterpret_cast(&bindAddr), bitFlags); if (err != 0) { // If it failed, close the handle and check the reason. uv_close(uvHandle, static_cast(onCloseUdp)); MS_THROW_ERROR( "uv_udp_bind() failed [protocol:%s, ip:'%s', port:%" PRIu16 "]: %s", protocolStr.c_str(), ip.c_str(), port, uv_strerror(err)); } break; } case Protocol::TCP: { err = uv_tcp_bind( reinterpret_cast(uvHandle), reinterpret_cast(&bindAddr), bitFlags); if (err != 0) { // If it failed, close the handle and check the reason. uv_close(uvHandle, static_cast(onCloseTcp)); MS_THROW_ERROR( "uv_tcp_bind() failed [protocol:%s, ip:'%s', port:%" PRIu16 "]: %s", protocolStr.c_str(), ip.c_str(), port, uv_strerror(err)); } // uv_tcp_bind() may succeed even if later uv_listen() fails, so // double check it. err = uv_listen( reinterpret_cast(uvHandle), 256, static_cast(onFakeConnection)); if (err != 0) { // If it failed, close the handle and check the reason. uv_close(uvHandle, static_cast(onCloseTcp)); MS_THROW_ERROR( "uv_listen() failed [protocol:%s, ip:'%s', port:%" PRIu16 "]: %s", protocolStr.c_str(), ip.c_str(), port, uv_strerror(err)); } break; } } MS_DEBUG_DEV( "bind succeeded [protocol:%s, ip:'%s', port:%" PRIu16 "]", protocolStr.c_str(), ip.c_str(), port); return uvHandle; } uv_handle_t* PortManager::Bind( Protocol protocol, std::string& ip, uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, uint64_t& hash) { MS_TRACE(); if (maxPort < minPort) { MS_THROW_TYPE_ERROR("maxPort cannot be less than minPort"); } // First normalize the IP. This may throw if invalid IP. Utils::IP::NormalizeIp(ip); int err; const int family = Utils::IP::GetFamily(ip); struct sockaddr_storage bindAddr{}; std::string protocolStr; switch (protocol) { case Protocol::UDP: { protocolStr.assign("udp"); break; } case Protocol::TCP: { protocolStr.assign("tcp"); break; } } switch (family) { case AF_INET: { err = uv_ip4_addr(ip.c_str(), 0, reinterpret_cast(&bindAddr)); if (err != 0) { MS_THROW_ERROR("uv_ip4_addr() failed: %s", uv_strerror(err)); } break; } case AF_INET6: { err = uv_ip6_addr(ip.c_str(), 0, reinterpret_cast(&bindAddr)); if (err != 0) { MS_THROW_ERROR("uv_ip6_addr() failed: %s", uv_strerror(err)); } break; } // This cannot happen. default: { MS_THROW_ERROR("unknown IP family"); } } hash = GeneratePortRangeHash(protocol, std::addressof(bindAddr), minPort, maxPort); auto& portRange = PortManager::GetOrCreatePortRange(hash, minPort, maxPort); const size_t numPorts = portRange.ports.size(); const size_t numAttempts = numPorts; size_t attempt{ 0u }; size_t portIdx; uint16_t port; uv_handle_t* uvHandle{ nullptr }; const uint8_t bitFlags = ConvertSocketFlags(flags, protocol, family); // Choose a random port index to start from. portIdx = Utils::Crypto::GetRandomUInt( static_cast(0), static_cast(numPorts - 1)); // Iterate all ports until getting one available. Fail if none found and also // if bind() fails N times in theoretically available ports. while (true) { // Increase attempt number. ++attempt; // If we have tried all the ports in the range throw. if (attempt > numAttempts) { MS_THROW_ERROR( "no more available ports [protocol:%s, ip:'%s', numAttempt:%zu]", protocolStr.c_str(), ip.c_str(), numAttempts); } // Increase current port index. portIdx = (portIdx + 1) % numPorts; // So the corresponding port is the vector position plus the RTC minimum port. port = static_cast(portIdx + minPort); MS_DEBUG_DEV( "testing port [protocol:%s, ip:'%s', port:%" PRIu16 ", attempt:%zu/%zu]", protocolStr.c_str(), ip.c_str(), port, attempt, numAttempts); // Check whether this port is not available. if (portRange.ports[portIdx]) { MS_DEBUG_DEV( "port in use, trying again [protocol:%s, ip:'%s', port:%" PRIu16 ", attempt:%zu/%zu]", protocolStr.c_str(), ip.c_str(), port, attempt, numAttempts); continue; } // Here we already have a theoretically available port. Now let's check // whether no other process is binding into it. // Set the chosen port into the sockaddr struct. switch (family) { case AF_INET: { (reinterpret_cast(&bindAddr))->sin_port = htons(port); break; } case AF_INET6: { (reinterpret_cast(&bindAddr))->sin6_port = htons(port); break; } // This cannot happen. default: { MS_THROW_ERROR("unknown IP family"); } } // Try to bind on it. switch (protocol) { case Protocol::UDP: { uvHandle = reinterpret_cast(new uv_udp_t()); err = uv_udp_init_ex( DepLibUV::GetLoop(), reinterpret_cast(uvHandle), UV_UDP_RECVMMSG); break; } case Protocol::TCP: { uvHandle = reinterpret_cast(new uv_tcp_t()); err = uv_tcp_init(DepLibUV::GetLoop(), reinterpret_cast(uvHandle)); break; } } if (err != 0) { switch (protocol) { case Protocol::UDP: { delete reinterpret_cast(uvHandle); MS_THROW_ERROR("uv_udp_init_ex() failed: %s", uv_strerror(err)); break; } case Protocol::TCP: { delete reinterpret_cast(uvHandle); MS_THROW_ERROR("uv_tcp_init() failed: %s", uv_strerror(err)); break; } } } switch (protocol) { case Protocol::UDP: { err = uv_udp_bind( reinterpret_cast(uvHandle), reinterpret_cast(&bindAddr), bitFlags); if (err != 0) { MS_WARN_DEV( "uv_udp_bind() failed [protocol:%s, ip:'%s', port:%" PRIu16 ", attempt:%zu/%zu]: %s", protocolStr.c_str(), ip.c_str(), port, attempt, numAttempts, uv_strerror(err)); } break; } case Protocol::TCP: { err = uv_tcp_bind( reinterpret_cast(uvHandle), reinterpret_cast(&bindAddr), bitFlags); if (err != 0) { MS_WARN_DEV( "uv_tcp_bind() failed [protocol:%s, ip:'%s', port:%" PRIu16 ", attempt:%zu/%zu]: %s", protocolStr.c_str(), ip.c_str(), port, attempt, numAttempts, uv_strerror(err)); } // uv_tcp_bind() may succeed even if later uv_listen() fails, so // double check it. if (err == 0) { err = uv_listen( reinterpret_cast(uvHandle), 256, static_cast(onFakeConnection)); MS_WARN_DEV( "uv_listen() failed [protocol:%s, ip:'%s', port:%" PRIu16 ", attempt:%zu/%zu]: %s", protocolStr.c_str(), ip.c_str(), port, attempt, numAttempts, uv_strerror(err)); } break; } } // If it succeeded, exit the loop here. if (err == 0) { break; } // If it failed, close the handle and check the reason. switch (protocol) { case Protocol::UDP: { uv_close(uvHandle, static_cast(onCloseUdp)); break; }; case Protocol::TCP: { uv_close(uvHandle, static_cast(onCloseTcp)); break; } } switch (err) { // If bind() fails due to "too many open files" just throw. case UV_EMFILE: { MS_THROW_ERROR( "port bind failed due to too many open files [protocol:%s, ip:'%s', port:%" PRIu16 ", attempt:%zu/%zu]", protocolStr.c_str(), ip.c_str(), port, attempt, numAttempts); break; } // If cannot bind in the given IP, throw. case UV_EADDRNOTAVAIL: { MS_THROW_ERROR( "port bind failed due to address not available [protocol:%s, ip:'%s', port:%" PRIu16 ", attempt:%zu/%zu]", protocolStr.c_str(), ip.c_str(), port, attempt, numAttempts); break; } default: { // Otherwise continue in the loop to try again with next port. } } } // If here, we got an available port. Mark it as unavailable. portRange.ports[portIdx] = true; // Increase number of used ports in the range. portRange.numUsedPorts++; MS_DEBUG_DEV( "bind succeeded [protocol:%s, ip:'%s', port:%" PRIu16 ", attempt:%zu/%zu]", protocolStr.c_str(), ip.c_str(), port, attempt, numAttempts); return uvHandle; } void PortManager::Unbind(uint64_t hash, uint16_t port) { MS_TRACE(); auto it = PortManager::mapPortRanges.find(hash); // This should not happen. if (it == PortManager::mapPortRanges.end()) { MS_ERROR("hash %" PRIu64 " doesn't exist in the map", hash); return; } auto& portRange = it->second; const auto portIdx = static_cast(port - portRange.minPort); // This should not happen. MS_ASSERT(portRange.ports.at(portIdx) == true, "port %" PRIu16 " is not used", port); MS_ASSERT(portRange.numUsedPorts > 0u, "number of used ports is 0"); // Mark the port as available. portRange.ports[portIdx] = false; // Decrease number of used ports in the range. portRange.numUsedPorts--; // Remove vector if there are no used ports. if (portRange.numUsedPorts == 0u) { PortManager::mapPortRanges.erase(it); } } void PortManager::Dump(int indentation) const { MS_DUMP_CLEAN(indentation, ""); for (auto& kv : PortManager::mapPortRanges) { auto hash = kv.first; auto portRange = kv.second; MS_DUMP_CLEAN(indentation + 1, ""); MS_DUMP_CLEAN(indentation + 1, " hash: %" PRIu64, hash); MS_DUMP_CLEAN(indentation + 1, " minPort: %" PRIu16, portRange.minPort); MS_DUMP_CLEAN(indentation + 1, " maxPort: %zu", portRange.minPort + portRange.ports.size() - 1); MS_DUMP_CLEAN(indentation + 1, " numUsedPorts: %" PRIu16, portRange.numUsedPorts); MS_DUMP_CLEAN(indentation + 1, ""); } MS_DUMP_CLEAN(indentation, ""); } /* * Hash for IPv4. * * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | MIN PORT | MAX PORT | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | IP | IP >> 2 |F|P| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * Hash for IPv6. * * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | MIN PORT | MAX PORT | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |IP[0] ^ IP[1] ^ IP[2] ^ IP[3] | same >> 2 |F|P| * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ uint64_t PortManager::GeneratePortRangeHash( Protocol protocol, sockaddr_storage* bindAddr, uint16_t minPort, uint16_t maxPort) { MS_TRACE(); uint64_t hash{ 0u }; switch (bindAddr->ss_family) { case AF_INET: { auto* bindAddrIn = reinterpret_cast(bindAddr); // We want it in network order. const uint64_t address = bindAddrIn->sin_addr.s_addr; hash |= static_cast(minPort) << 48; hash |= static_cast(maxPort) << 32; hash |= (address >> 2) << 2; hash |= 0x0000; // AF_INET. break; } case AF_INET6: { auto* bindAddrIn6 = reinterpret_cast(bindAddr); auto* a = reinterpret_cast(std::addressof(bindAddrIn6->sin6_addr)); const auto address = a[0] ^ a[1] ^ a[2] ^ a[3]; hash |= static_cast(minPort) << 48; hash |= static_cast(maxPort) << 32; hash |= static_cast(address) << 16; hash |= (static_cast(address) >> 2) << 2; hash |= 0x0002; // AF_INET6. break; } // This cannot happen. default: { MS_THROW_ERROR("unknown IP family"); } } // Override least significant bit with protocol information: // - If UDP, start with 0. // - If TCP, start with 1. if (protocol == Protocol::UDP) { hash |= 0x0000; } else { hash |= 0x0001; } return hash; } PortManager::PortRange& PortManager::GetOrCreatePortRange( uint64_t hash, uint16_t minPort, uint16_t maxPort) { MS_TRACE(); auto it = PortManager::mapPortRanges.find(hash); // If the hash is already handled, return its port range. if (it != PortManager::mapPortRanges.end()) { auto& portRange = it->second; return portRange; } const uint16_t numPorts = maxPort - minPort + 1; // Emplace a new vector filled with numPorts false values, meaning that // all ports are available. auto pair = PortManager::mapPortRanges.emplace( std::piecewise_construct, std::make_tuple(hash), std::make_tuple(numPorts, minPort)); // pair.first is an iterator to the inserted value. auto& portRange = pair.first->second; return portRange; } uint8_t PortManager::ConvertSocketFlags(RTC::Transport::SocketFlags& flags, Protocol protocol, int family) { MS_TRACE(); uint8_t bitFlags{ 0b00000000 }; // Ignore ipv6Only in IPv4, otherwise libuv will throw. if (flags.ipv6Only && family == AF_INET6) { switch (protocol) { case Protocol::UDP: { bitFlags |= UV_UDP_IPV6ONLY; break; } case Protocol::TCP: { bitFlags |= UV_TCP_IPV6ONLY; break; } } } // Ignore udpReusePort in TCP, otherwise libuv will throw. if (flags.udpReusePort && protocol == Protocol::UDP) { bitFlags |= UV_UDP_REUSEADDR; } return bitFlags; } } // namespace RTC ================================================ FILE: worker/src/RTC/Producer.cpp ================================================ #define MS_CLASS "RTC::Producer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/Producer.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "RTC/Consts.hpp" #include "RTC/RTCP/Feedback.hpp" #include "RTC/RTCP/XrReceiverReferenceTime.hpp" #include "RTC/RTP/Codecs/Tools.hpp" #ifdef MS_RTC_LOGGER_RTP #include "RTC/RtcLogger.hpp" #endif #include #include // std::memcpy() namespace RTC { /* Static */ static constexpr size_t ProducerSendBufferSize{ 65536 }; static thread_local uint8_t ProducerSendBuffer[ProducerSendBufferSize]; static constexpr unsigned int SendNackDelay{ 10u }; // In ms. /* Instance methods. */ Producer::Producer( SharedInterface* shared, const std::string& id, RTC::Producer::Listener* listener, const FBS::Transport::ProduceRequest* data) : id(id), shared(shared), listener(listener), kind(RTC::Media::Kind(data->kind())) { MS_TRACE(); // This may throw. this->rtpParameters = RTC::RtpParameters(data->rtpParameters()); // Evaluate type. auto type = RTC::RtpParameters::GetType(this->rtpParameters); if (!type.has_value()) { MS_THROW_TYPE_ERROR("invalid RTP parameters"); } this->type = type.value(); // Reserve a slot in rtpStreamByEncodingIdx and rtpStreamsScores vectors // for each RTP stream. this->rtpStreamByEncodingIdx.resize(this->rtpParameters.encodings.size(), nullptr); this->rtpStreamScores.resize(this->rtpParameters.encodings.size(), 0u); auto& encoding = this->rtpParameters.encodings[0]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); if (!RTC::RTP::Codecs::Tools::IsValidTypeForCodec(this->type, mediaCodec->mimeType)) { MS_THROW_TYPE_ERROR( "%s codec not supported for %s", mediaCodec->mimeType.ToString().c_str(), RTC::RtpParameters::GetTypeString(this->type).c_str()); } for (const auto& codec : *data->rtpMapping()->codecs()) { this->rtpMapping.codecs[codec->payloadType()] = codec->mappedPayloadType(); } const auto* encodings = data->rtpMapping()->encodings(); this->rtpMapping.encodings.reserve(encodings->size()); for (const auto& encoding : *encodings) { this->rtpMapping.encodings.emplace_back(); auto& encodingMapping = this->rtpMapping.encodings.back(); if (auto ssrc = encoding->ssrc(); ssrc.has_value()) { encodingMapping.ssrc = ssrc.value(); } // rid is optional. // However ssrc or rid must be present (if more than 1 encoding). if ( encodings->size() > 1 && !encoding->ssrc().has_value() && !flatbuffers::IsFieldPresent(encoding, FBS::RtpParameters::EncodingMapping::VT_RID)) { MS_THROW_TYPE_ERROR("wrong entry in rtpMapping.encodings (missing ssrc or rid)"); } // If there is no mid and a single encoding, ssrc or rid must be present. if ( this->rtpParameters.mid.empty() && encodings->size() == 1 && !encoding->ssrc().has_value() && !flatbuffers::IsFieldPresent(encoding, FBS::RtpParameters::EncodingMapping::VT_RID)) { MS_THROW_TYPE_ERROR( "wrong entry in rtpMapping.encodings (missing ssrc or rid, or rtpParameters.mid)"); } // mappedSsrc is mandatory. if (!encoding->mappedSsrc()) { MS_THROW_TYPE_ERROR("wrong entry in rtpMapping.encodings (missing mappedSsrc)"); } encodingMapping.mappedSsrc = encoding->mappedSsrc(); } this->paused = data->paused(); this->enableMediasoupPacketIdHeaderExtension = data->enableMediasoupPacketIdHeaderExtension(); // The number of encodings in rtpParameters must match the number of encodings // in rtpMapping. if (this->rtpParameters.encodings.size() != this->rtpMapping.encodings.size()) { MS_THROW_TYPE_ERROR("rtpParameters.encodings size does not match rtpMapping.encodings size"); } // Fill RTP header extension ids. // This may throw. for (auto& exten : this->rtpParameters.headerExtensions) { if (exten.id == 0u) { MS_THROW_TYPE_ERROR("RTP extension id cannot be 0"); } if (this->rtpHeaderExtensionIds.mid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MID) { this->rtpHeaderExtensionIds.mid = exten.id; } if (this->rtpHeaderExtensionIds.rid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID) { this->rtpHeaderExtensionIds.rid = exten.id; } if (this->rtpHeaderExtensionIds.rrid == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID) { this->rtpHeaderExtensionIds.rrid = exten.id; } if (this->rtpHeaderExtensionIds.absSendTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME) { this->rtpHeaderExtensionIds.absSendTime = exten.id; } if (this->rtpHeaderExtensionIds.transportWideCc01 == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01) { this->rtpHeaderExtensionIds.transportWideCc01 = exten.id; } if (this->rtpHeaderExtensionIds.ssrcAudioLevel == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL) { this->rtpHeaderExtensionIds.ssrcAudioLevel = exten.id; } if ( this->rtpHeaderExtensionIds.dependencyDescriptor == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR) { this->rtpHeaderExtensionIds.dependencyDescriptor = exten.id; } if (this->rtpHeaderExtensionIds.videoOrientation == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION) { this->rtpHeaderExtensionIds.videoOrientation = exten.id; } if (this->rtpHeaderExtensionIds.timeOffset == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET) { this->rtpHeaderExtensionIds.timeOffset = exten.id; } if (this->rtpHeaderExtensionIds.absCaptureTime == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME) { this->rtpHeaderExtensionIds.absCaptureTime = exten.id; } if (this->rtpHeaderExtensionIds.playoutDelay == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY) { this->rtpHeaderExtensionIds.playoutDelay = exten.id; } if (this->rtpHeaderExtensionIds.mediasoupPacketId == 0u && exten.type == RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID) { this->rtpHeaderExtensionIds.mediasoupPacketId = exten.id; } } // Set the RTCP report generation interval. if (this->kind == RTC::Media::Kind::AUDIO) { this->maxRtcpInterval = RTC::RTCP::MaxAudioIntervalMs; } else { this->maxRtcpInterval = RTC::RTCP::MaxVideoIntervalMs; } // Create a KeyFrameRequestManager. if (this->kind == RTC::Media::Kind::VIDEO) { auto keyFrameRequestDelay = data->keyFrameRequestDelay(); this->keyFrameRequestManager = new RTC::KeyFrameRequestManager(this, this->shared, keyFrameRequestDelay); } // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ this); } Producer::~Producer() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); // Delete all streams. for (auto& kv : this->mapSsrcRtpStream) { auto* rtpStream = kv.second; delete rtpStream; } this->mapSsrcRtpStream.clear(); this->rtpStreamByEncodingIdx.clear(); this->rtpStreamScores.clear(); this->mapRtxSsrcRtpStream.clear(); this->mapRtpStreamMappedSsrc.clear(); this->mapMappedSsrcSsrc.clear(); // Delete the KeyFrameRequestManager. delete this->keyFrameRequestManager; } flatbuffers::Offset Producer::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add rtpParameters. auto rtpParameters = this->rtpParameters.FillBuffer(builder); // Add rtpMapping.codecs. std::vector> codecs; for (const auto& kv : this->rtpMapping.codecs) { codecs.emplace_back(FBS::RtpParameters::CreateCodecMapping(builder, kv.first, kv.second)); } // Add rtpMapping.encodings. std::vector> encodings; encodings.reserve(this->rtpMapping.encodings.size()); for (const auto& encodingMapping : this->rtpMapping.encodings) { encodings.emplace_back( FBS::RtpParameters::CreateEncodingMappingDirect( builder, encodingMapping.rid.c_str(), encodingMapping.ssrc != 0u ? flatbuffers::Optional(encodingMapping.ssrc) : flatbuffers::nullopt, nullptr, /* capability mode. NOTE: Present in NODE*/ encodingMapping.mappedSsrc)); } // Build rtpMapping. auto rtpMapping = FBS::RtpParameters::CreateRtpMappingDirect(builder, &codecs, &encodings); // Add rtpStreams. std::vector> rtpStreams; for (const auto* rtpStream : this->rtpStreamByEncodingIdx) { if (!rtpStream) { continue; } rtpStreams.emplace_back(rtpStream->FillBuffer(builder)); } // Add traceEventTypes. std::vector traceEventTypes; if (this->traceEventTypes.rtp) { traceEventTypes.emplace_back(FBS::Producer::TraceEventType::RTP); } if (this->traceEventTypes.keyframe) { traceEventTypes.emplace_back(FBS::Producer::TraceEventType::KEYFRAME); } if (this->traceEventTypes.nack) { traceEventTypes.emplace_back(FBS::Producer::TraceEventType::NACK); } if (this->traceEventTypes.pli) { traceEventTypes.emplace_back(FBS::Producer::TraceEventType::PLI); } if (this->traceEventTypes.fir) { traceEventTypes.emplace_back(FBS::Producer::TraceEventType::FIR); } return FBS::Producer::CreateDumpResponseDirect( builder, this->id.c_str(), this->kind == RTC::Media::Kind::AUDIO ? FBS::RtpParameters::MediaKind::AUDIO : FBS::RtpParameters::MediaKind::VIDEO, RTC::RtpParameters::TypeToFbs(this->type), rtpParameters, rtpMapping, &rtpStreams, &traceEventTypes, this->paused); } flatbuffers::Offset Producer::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); std::vector> rtpStreams; for (auto* rtpStream : this->rtpStreamByEncodingIdx) { if (!rtpStream) { continue; } rtpStreams.emplace_back(rtpStream->FillBufferStats(builder)); } return FBS::Producer::CreateGetStatsResponseDirect(builder, &rtpStreams); } void Producer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::PRODUCER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Producer_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::PRODUCER_GET_STATS: { auto responseOffset = FillBufferStats(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Producer_GetStatsResponse, responseOffset); break; } case Channel::ChannelRequest::Method::PRODUCER_PAUSE: { if (this->paused) { request->Accept(); break; } // Pause all streams. for (auto& kv : this->mapSsrcRtpStream) { auto* rtpStream = kv.second; rtpStream->Pause(); } this->paused = true; MS_DEBUG_DEV("Producer paused [producerId:%s]", this->id.c_str()); this->listener->OnProducerPaused(this); request->Accept(); break; } case Channel::ChannelRequest::Method::PRODUCER_RESUME: { if (!this->paused) { request->Accept(); break; } // Resume all streams. for (auto& kv : this->mapSsrcRtpStream) { auto* rtpStream = kv.second; rtpStream->Resume(); } this->paused = false; MS_DEBUG_DEV("Producer resumed [producerId:%s]", this->id.c_str()); this->listener->OnProducerResumed(this); if (this->keyFrameRequestManager) { MS_DEBUG_2TAGS(rtcp, rtx, "requesting forced key frame(s) after resumed"); // Request a key frame for all streams. for (auto& kv : this->mapSsrcRtpStream) { auto ssrc = kv.first; this->keyFrameRequestManager->ForceKeyFrameNeeded(ssrc); } } request->Accept(); break; } case Channel::ChannelRequest::Method::PRODUCER_ENABLE_TRACE_EVENT: { const auto* body = request->data->body_as(); // Reset traceEventTypes. struct TraceEventTypes newTraceEventTypes; for (const auto& type : *body->events()) { switch (type) { case FBS::Producer::TraceEventType::KEYFRAME: { newTraceEventTypes.keyframe = true; break; } case FBS::Producer::TraceEventType::FIR: { newTraceEventTypes.fir = true; break; } case FBS::Producer::TraceEventType::NACK: { newTraceEventTypes.nack = true; break; } case FBS::Producer::TraceEventType::PLI: { newTraceEventTypes.pli = true; break; } case FBS::Producer::TraceEventType::RTP: { newTraceEventTypes.rtp = true; break; } case FBS::Producer::TraceEventType::SR: { newTraceEventTypes.sr = true; break; } } } this->traceEventTypes = newTraceEventTypes; request->Accept(); break; } default: { MS_THROW_ERROR("unknown method '%s'", request->methodCStr); } } } void Producer::HandleNotification(Channel::ChannelNotification* notification) { MS_TRACE(); switch (notification->event) { case Channel::ChannelNotification::Event::PRODUCER_SEND: { const auto* body = notification->data->body_as(); auto len = body->data()->size(); // Increase receive transmission. this->listener->OnProducerReceiveData(this, len); if (len > ProducerSendBufferSize) { MS_WARN_TAG(rtp, "given RTP packet exceeds maximum size [len:%i]", len); break; } // Copy the received packet into this buffer so it can be expanded later. std::memcpy(ProducerSendBuffer, body->data()->data(), static_cast(len)); auto* packet = RTC::RTP::Packet::Parse(ProducerSendBuffer, len, ProducerSendBufferSize); if (!packet) { MS_WARN_TAG(rtp, "received data is not a valid RTP packet"); break; } // Pass the packet to the parent transport. this->listener->OnProducerReceiveRtpPacket(this, packet); break; } default: { MS_ERROR("unknown event '%s'", notification->eventCStr); } } } Producer::ReceiveRtpPacketResult Producer::ReceiveRtpPacket(RTC::RTP::Packet* packet) { MS_TRACE(); #ifdef MS_RTC_LOGGER_RTP packet->logger.producerId = this->id; #endif // Reset current packet. this->currentRtpPacket = nullptr; // Count number of RTP streams. auto numRtpStreamsBefore = this->mapSsrcRtpStream.size(); auto* rtpStream = GetRtpStream(packet); if (!rtpStream) { MS_WARN_TAG(rtp, "no stream found for received packet [ssrc:%" PRIu32 "]", packet->GetSsrc()); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::RECV_RTP_STREAM_NOT_FOUND); #endif return ReceiveRtpPacketResult::DISCARDED; } ReceiveRtpPacketResult result; bool isRtx{ false }; // Media packet. if (packet->GetSsrc() == rtpStream->GetSsrc()) { result = ReceiveRtpPacketResult::MEDIA; // Process the packet. if (!rtpStream->ReceivePacket(packet)) { // May have to announce a new RTP stream to the listener. if (this->mapSsrcRtpStream.size() > numRtpStreamsBefore) { NotifyNewRtpStream(rtpStream); } #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::RECV_RTP_STREAM_DISCARDED); #endif return result; } } // RTX packet. else if (packet->GetSsrc() == rtpStream->GetRtxSsrc()) { result = ReceiveRtpPacketResult::RETRANSMISSION; isRtx = true; // Process the packet. if (!rtpStream->ReceiveRtxPacket(packet)) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded( RTC::RtcLogger::RtpPacket::DiscardReason::RECV_RTP_RTX_STREAM_DISCARDED); #endif return result; } } // Should not happen. else { MS_ABORT("found stream does not match received packet"); } if (packet->IsKeyFrame()) { MS_DEBUG_TAG( rtp, "key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); // Tell the keyFrameRequestManager. if (this->keyFrameRequestManager) { this->keyFrameRequestManager->KeyFrameReceived(packet->GetSsrc()); } } // May have to announce a new RTP stream to the listener. if (this->mapSsrcRtpStream.size() > numRtpStreamsBefore) { // Request a key frame for this stream since we may have lost the first packets // (do not do it if this is a key frame). if (this->keyFrameRequestManager && !this->paused && !packet->IsKeyFrame()) { this->keyFrameRequestManager->ForceKeyFrameNeeded(packet->GetSsrc()); } // Update current packet. this->currentRtpPacket = packet; NotifyNewRtpStream(rtpStream); // Reset current packet. this->currentRtpPacket = nullptr; } // If paused stop here. if (this->paused) { return result; } // May emit 'trace' event. EmitTraceEventRtpAndKeyFrameTypes(packet, isRtx); // Mangle the packet before providing the listener with it. if (!MangleRtpPacket(packet, rtpStream)) { return ReceiveRtpPacketResult::DISCARDED; } // Post-process the packet. PostProcessRtpPacket(packet); this->listener->OnProducerRtpPacketReceived(this, packet); return result; } void Producer::ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report) { MS_TRACE(); auto it = this->mapSsrcRtpStream.find(report->GetSsrc()); if (it != this->mapSsrcRtpStream.end()) { auto* rtpStream = it->second; const bool first = rtpStream->GetSenderReportNtpMs() == 0; rtpStream->ReceiveRtcpSenderReport(report); this->listener->OnProducerRtcpSenderReport(this, rtpStream, first); EmitTraceEventSrType(report); return; } // If not found, check with RTX. auto it2 = this->mapRtxSsrcRtpStream.find(report->GetSsrc()); if (it2 != this->mapRtxSsrcRtpStream.end()) { auto* rtpStream = it2->second; rtpStream->ReceiveRtxRtcpSenderReport(report); return; } MS_DEBUG_TAG(rtcp, "RtpStream not found [ssrc:%" PRIu32 "]", report->GetSsrc()); } void Producer::ReceiveRtcpXrDelaySinceLastRr(RTC::RTCP::DelaySinceLastRr::SsrcInfo* ssrcInfo) { MS_TRACE(); auto it = this->mapSsrcRtpStream.find(ssrcInfo->GetSsrc()); if (it == this->mapSsrcRtpStream.end()) { MS_WARN_TAG(rtcp, "RtpStream not found [ssrc:%" PRIu32 "]", ssrcInfo->GetSsrc()); return; } auto* rtpStream = it->second; rtpStream->ReceiveRtcpXrDelaySinceLastRr(ssrcInfo); } bool Producer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) { MS_TRACE(); if (static_cast((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval) { return true; } std::vector receiverReports; RTCP::ReceiverReferenceTime* receiverReferenceTimeReport{ nullptr }; for (auto& kv : this->mapSsrcRtpStream) { auto* rtpStream = kv.second; auto* report = rtpStream->GetRtcpReceiverReport(); receiverReports.push_back(report); auto* rtxReport = rtpStream->GetRtxRtcpReceiverReport(); if (rtxReport) { receiverReports.push_back(rtxReport); } } // Add a receiver reference time report if no present in the packet. if (!packet->HasReceiverReferenceTime()) { auto ntp = Utils::Time::TimeMs2Ntp(nowMs); receiverReferenceTimeReport = new RTC::RTCP::ReceiverReferenceTime(); receiverReferenceTimeReport->SetNtpSec(ntp.seconds); receiverReferenceTimeReport->SetNtpFrac(ntp.fractions); } // RTCP Compound packet buffer cannot hold the data. if (!packet->Add(receiverReports, receiverReferenceTimeReport)) { return false; } this->lastRtcpSentTime = nowMs; return true; } void Producer::RequestKeyFrame(uint32_t mappedSsrc) { MS_TRACE(); if (!this->keyFrameRequestManager || this->paused) { return; } auto it = this->mapMappedSsrcSsrc.find(mappedSsrc); if (it == this->mapMappedSsrcSsrc.end()) { MS_WARN_2TAGS(rtcp, rtx, "given mappedSsrc not found, ignoring"); return; } const uint32_t ssrc = it->second; // If the current RTP packet is a key frame for the given mapped SSRC do // nothing since we are gonna provide Consumers with the requested key frame // right now. // // NOTE: We know that this may only happen before calling MangleRtpPacket() // so the SSRC of the packet is still the original one and not the mapped one. if ( this->currentRtpPacket && this->currentRtpPacket->GetSsrc() == ssrc && this->currentRtpPacket->IsKeyFrame()) { return; } this->keyFrameRequestManager->KeyFrameNeeded(ssrc); } RTC::RTP::RtpStreamRecv* Producer::GetRtpStream(const RTC::RTP::Packet* packet) { MS_TRACE(); const uint32_t ssrc = packet->GetSsrc(); const uint8_t payloadType = packet->GetPayloadType(); // If stream found in media ssrcs map, return it. { auto it = this->mapSsrcRtpStream.find(ssrc); if (it != this->mapSsrcRtpStream.end()) { auto* rtpStream = it->second; return rtpStream; } } // If stream found in RTX ssrcs map, return it. { auto it = this->mapRtxSsrcRtpStream.find(ssrc); if (it != this->mapRtxSsrcRtpStream.end()) { auto* rtpStream = it->second; return rtpStream; } } // Otherwise check our encodings and, if appropriate, create a new stream. // First, look for an encoding with matching media or RTX ssrc value. for (size_t i{ 0 }; i < this->rtpParameters.encodings.size(); ++i) { auto& encoding = this->rtpParameters.encodings[i]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); const bool isMediaPacket = (mediaCodec->payloadType == payloadType); const bool isRtxPacket = (rtxCodec && rtxCodec->payloadType == payloadType); if (isMediaPacket && encoding.ssrc == ssrc) { auto* rtpStream = CreateRtpStream(packet, *mediaCodec, i); return rtpStream; } else if (isRtxPacket && encoding.hasRtx && encoding.rtx.ssrc == ssrc) { auto it = this->mapSsrcRtpStream.find(encoding.ssrc); // Ignore if no stream has been created yet for the corresponding encoding. if (it == this->mapSsrcRtpStream.end()) { MS_DEBUG_2TAGS(rtp, rtx, "ignoring RTX packet for not yet created RtpStream (ssrc lookup)"); return nullptr; } auto* rtpStream = it->second; // Ensure no RTX ssrc was previously detected. if (rtpStream->HasRtx()) { MS_DEBUG_2TAGS(rtp, rtx, "ignoring RTX packet with new ssrc (ssrc lookup)"); return nullptr; } // Update the stream RTX data. rtpStream->SetRtx(payloadType, ssrc); // Insert the new RTX ssrc into the map. this->mapRtxSsrcRtpStream[ssrc] = rtpStream; return rtpStream; } } // If not found, look for an encoding matching the packet RID value. std::string rid; if (packet->ReadRid(rid)) { for (size_t i{ 0 }; i < this->rtpParameters.encodings.size(); ++i) { auto& encoding = this->rtpParameters.encodings[i]; if (encoding.rid != rid) { continue; } const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); const bool isMediaPacket = (mediaCodec->payloadType == payloadType); const bool isRtxPacket = (rtxCodec && rtxCodec->payloadType == payloadType); if (isMediaPacket) { // Ensure no other stream already exists with same RID. for (auto& kv : this->mapSsrcRtpStream) { auto* rtpStream = kv.second; if (rtpStream->GetRid() == rid) { MS_WARN_TAG( rtp, "ignoring packet with unknown ssrc but already handled RID (RID lookup)"); return nullptr; } } auto* rtpStream = CreateRtpStream(packet, *mediaCodec, i); return rtpStream; } else if (isRtxPacket) { // Ensure a stream already exists with same RID. for (auto& kv : this->mapSsrcRtpStream) { auto* rtpStream = kv.second; if (rtpStream->GetRid() == rid) { // Ensure no RTX ssrc was previously detected. if (rtpStream->HasRtx()) { MS_DEBUG_2TAGS(rtp, rtx, "ignoring RTX packet with new SSRC (RID lookup)"); return nullptr; } // Update the stream RTX data. rtpStream->SetRtx(payloadType, ssrc); // Insert the new RTX ssrc into the map. this->mapRtxSsrcRtpStream[ssrc] = rtpStream; return rtpStream; } } MS_DEBUG_2TAGS(rtp, rtx, "ignoring RTX packet for not yet created RtpStream (RID lookup)"); return nullptr; } } MS_WARN_TAG(rtp, "ignoring packet with unknown RID (RID lookup)"); return nullptr; } // If not found, and there is a single encoding without ssrc and RID, this // may be the media or RTX stream. if ( this->rtpParameters.encodings.size() == 1 && !this->rtpParameters.encodings[0].ssrc && this->rtpParameters.encodings[0].rid.empty()) { auto& encoding = this->rtpParameters.encodings[0]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); const bool isMediaPacket = (mediaCodec->payloadType == payloadType); const bool isRtxPacket = (rtxCodec && rtxCodec->payloadType == payloadType); if (isMediaPacket) { // Ensure there is no other RTP stream already. if (!this->mapSsrcRtpStream.empty()) { MS_WARN_TAG( rtp, "ignoring packet with unknown ssrc not matching the already existing stream (single RtpStream lookup)"); return nullptr; } auto* rtpStream = CreateRtpStream(packet, *mediaCodec, 0); return rtpStream; } else if (isRtxPacket) { // There must be already a media RTP stream. auto it = this->mapSsrcRtpStream.begin(); if (it == this->mapSsrcRtpStream.end()) { MS_DEBUG_2TAGS( rtp, rtx, "ignoring RTX packet for not yet created RtpStream (single stream lookup)"); return nullptr; } auto* rtpStream = it->second; // Ensure no RTX SSRC was previously detected. if (rtpStream->HasRtx()) { MS_DEBUG_2TAGS(rtp, rtx, "ignoring RTX packet with new SSRC (single stream lookup)"); return nullptr; } // Update the stream RTX data. rtpStream->SetRtx(payloadType, ssrc); // Insert the new RTX SSRC into the map. this->mapRtxSsrcRtpStream[ssrc] = rtpStream; return rtpStream; } } return nullptr; } RTC::RTP::RtpStreamRecv* Producer::CreateRtpStream( const RTC::RTP::Packet* packet, const RTC::RtpCodecParameters& mediaCodec, size_t encodingIdx) { MS_TRACE(); const uint32_t ssrc = packet->GetSsrc(); MS_ASSERT( this->mapSsrcRtpStream.find(ssrc) == this->mapSsrcRtpStream.end(), "RtpStream with given SSRC already exists"); MS_ASSERT( !this->rtpStreamByEncodingIdx[encodingIdx], "RtpStream for given encoding index already exists"); auto& encoding = this->rtpParameters.encodings[encodingIdx]; auto& encodingMapping = this->rtpMapping.encodings[encodingIdx]; MS_DEBUG_TAG( rtp, "[encodingIdx:%zu, ssrc:%" PRIu32 ", rid:%s, payloadType:%" PRIu8 "]", encodingIdx, ssrc, encoding.rid.c_str(), mediaCodec.payloadType); // Set stream params. RTC::RTP::RtpStream::Params params; params.encodingIdx = encodingIdx; params.ssrc = ssrc; params.payloadType = mediaCodec.payloadType; params.mimeType = mediaCodec.mimeType; params.clockRate = mediaCodec.clockRate; params.rid = encoding.rid; params.cname = this->rtpParameters.rtcp.cname; params.spatialLayers = encoding.spatialLayers; params.temporalLayers = encoding.temporalLayers; // Check in band FEC in codec parameters. if (mediaCodec.parameters.HasInteger("useinbandfec") && mediaCodec.parameters.GetInteger("useinbandfec") == 1) { MS_DEBUG_TAG(rtcp, "in band FEC enabled"); params.useInBandFec = true; } // Check DTX in codec parameters. if (mediaCodec.parameters.HasInteger("usedtx") && mediaCodec.parameters.GetInteger("usedtx") == 1) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } // Check DTX in the encoding. if (encoding.dtx) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } for (const auto& fb : mediaCodec.rtcpFeedback) { if (!params.useNack && fb.type == "nack" && fb.parameter.empty()) { MS_DEBUG_2TAGS(rtp, rtcp, "NACK supported"); params.useNack = true; } else if (!params.usePli && fb.type == "nack" && fb.parameter == "pli") { MS_DEBUG_2TAGS(rtp, rtcp, "PLI supported"); params.usePli = true; } else if (!params.useFir && fb.type == "ccm" && fb.parameter == "fir") { MS_DEBUG_2TAGS(rtp, rtcp, "FIR supported"); params.useFir = true; } } // Only perform RTP inactivity check on simulcast and only if there are // more than 1 stream. auto useRtpInactivityCheck = this->type == RtpParameters::Type::SIMULCAST && this->rtpMapping.encodings.size() > 1; // Create a RtpStreamRecv for receiving a media stream. auto* rtpStream = new RTC::RTP::RtpStreamRecv(this, this->shared, params, SendNackDelay, useRtpInactivityCheck); // Insert into the maps. this->mapSsrcRtpStream[ssrc] = rtpStream; this->rtpStreamByEncodingIdx[encodingIdx] = rtpStream; this->rtpStreamScores[encodingIdx] = rtpStream->GetScore(); // Set the mapped SSRC. this->mapRtpStreamMappedSsrc[rtpStream] = encodingMapping.mappedSsrc; this->mapMappedSsrcSsrc[encodingMapping.mappedSsrc] = ssrc; // If the Producer is paused tell it to the new RtpStreamRecv. if (this->paused) { rtpStream->Pause(); } // Emit the first score event right now. EmitScore(); return rtpStream; } void Producer::NotifyNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream) { MS_TRACE(); auto mappedSsrc = this->mapRtpStreamMappedSsrc.at(rtpStream); // Notify the listener. this->listener->OnProducerNewRtpStream(this, rtpStream, mappedSsrc); } inline bool Producer::MangleRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::RtpStreamRecv* rtpStream) const { MS_TRACE(); // Mangle the payload type. { const uint8_t payloadType = packet->GetPayloadType(); auto it = this->rtpMapping.codecs.find(payloadType); if (it == this->rtpMapping.codecs.end()) { MS_WARN_TAG(rtp, "unknown payload type [payloadType:%" PRIu8 "]", payloadType); return false; } const uint8_t mappedPayloadType = it->second; packet->SetPayloadType(mappedPayloadType); } // Mangle the SSRC. { const uint32_t mappedSsrc = this->mapRtpStreamMappedSsrc.at(rtpStream); packet->SetSsrc(mappedSsrc); } // Mangle RTP header extensions. { static thread_local uint8_t buffer[4096]; static thread_local std::vector extensions; // This happens just once. if (extensions.capacity() != 24) { extensions.reserve(24); } extensions.clear(); uint8_t* extenValue; uint8_t extenLen; uint8_t* bufferPtr{ buffer }; // Add urn:ietf:params:rtp-hdrext:sdes:mid. { extenLen = RTC::Consts::MidRtpExtensionMaxLength; extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::MID, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::MID), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } // Proxy http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time. extenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.absCaptureTime, extenLen); if (extenValue) { std::memcpy(bufferPtr, extenValue, extenLen); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } // Proxy http://www.webrtc.org/experiments/rtp-hdrext/playout-delay extenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.playoutDelay, extenLen); if (extenValue) { std::memcpy(bufferPtr, extenValue, extenLen); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } if (this->kind == RTC::Media::Kind::AUDIO) { // Proxy urn:ietf:params:rtp-hdrext:ssrc-audio-level. extenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.ssrcAudioLevel, extenLen); if (extenValue) { std::memcpy(bufferPtr, extenValue, extenLen); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } } else if (this->kind == RTC::Media::Kind::VIDEO) { // Add http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time. // NOTE: This is for REMB. { extenLen = 3u; // NOTE: Add value 0. The sending Transport will update it. const uint32_t absSendTime{ 0u }; Utils::Byte::Set3Bytes(bufferPtr, 0, absSendTime); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } // Add http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01. // NOTE: We don't include it in outbound audio packets for now. { extenLen = 2u; // NOTE: Add value 0. The sending Transport will update it. const uint16_t wideSeqNumber{ 0u }; Utils::Byte::Set2Bytes(bufferPtr, 0, wideSeqNumber); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } // Proxy https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension. extenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.dependencyDescriptor, extenLen); if (extenValue) { std::memcpy(bufferPtr, extenValue, extenLen); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } // Proxy urn:3gpp:video-orientation. extenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.videoOrientation, extenLen); if (extenValue) { std::memcpy(bufferPtr, extenValue, extenLen); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } // Proxy urn:ietf:params:rtp-hdrext:toffset. extenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.timeOffset, extenLen); if (extenValue) { std::memcpy(bufferPtr, extenValue, extenLen); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } } // Add urn:mediasoup:params:rtp-hdrext:packet-id. // // Here if may happen that the packet ALREADY contains the header (if it comes // from another mediasoup Router in which it was added). If so, honor it. // Otherwise, if the flag `enableMediasoupPacketIdHeaderExtension` is set, // add it. { extenValue = packet->GetExtensionValue(this->rtpHeaderExtensionIds.mediasoupPacketId, extenLen); if (extenValue) { std::memcpy(bufferPtr, extenValue, extenLen); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID), /*len*/ extenLen, /*value*/ bufferPtr); // Not needed since this is the latest added extension. // bufferPtr += extenLen; } else if (this->enableMediasoupPacketIdHeaderExtension) { extenLen = 4; Utils::Byte::Set4Bytes(bufferPtr, 0, RTC::RTP::Packet::GetNextMediasoupPacketId()); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID), /*len*/ extenLen, /*value*/ bufferPtr); // Not needed since this is the latest added extension. // bufferPtr += extenLen; } } packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions); } return true; } inline void Producer::PostProcessRtpPacket(RTC::RTP::Packet* packet) { MS_TRACE(); if (this->kind == RTC::Media::Kind::VIDEO) { bool camera{ false }; bool flip{ false }; uint16_t rotation{ 0 }; if (packet->ReadVideoOrientation(camera, flip, rotation)) { // If video orientation was not yet detected or any value has changed, // emit event. if ( !this->videoOrientationDetected || camera != this->videoOrientation.camera || flip != this->videoOrientation.flip || rotation != this->videoOrientation.rotation) { this->videoOrientationDetected = true; this->videoOrientation.camera = camera; this->videoOrientation.flip = flip; this->videoOrientation.rotation = rotation; auto notification = FBS::Producer::CreateVideoOrientationChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), this->videoOrientation.camera, this->videoOrientation.flip, this->videoOrientation.rotation); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::PRODUCER_VIDEO_ORIENTATION_CHANGE, FBS::Notification::Body::Producer_VideoOrientationChangeNotification, notification); } } } } inline void Producer::EmitScore() const { MS_TRACE(); std::vector> scores; for (const auto* rtpStream : this->rtpStreamByEncodingIdx) { if (!rtpStream) { continue; } scores.emplace_back( FBS::Producer::CreateScoreDirect( this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpStream->GetEncodingIdx(), rtpStream->GetSsrc(), !rtpStream->GetRid().empty() ? rtpStream->GetRid().c_str() : nullptr, rtpStream->GetScore())); } auto notification = FBS::Producer::CreateScoreNotificationDirect( this->shared->GetChannelNotifier()->GetBufferBuilder(), &scores); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::PRODUCER_SCORE, FBS::Notification::Body::Producer_ScoreNotification, notification); } inline void Producer::EmitTraceEventRtpAndKeyFrameTypes(const RTC::RTP::Packet* packet, bool isRtx) const { MS_TRACE(); if (this->traceEventTypes.keyframe && packet->IsKeyFrame()) { auto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto traceInfo = FBS::Producer::CreateKeyFrameTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Producer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Producer::TraceEventType::KEYFRAME, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_IN, FBS::Producer::TraceInfo::KeyFrameTraceInfo, traceInfo.Union()); EmitTraceEvent(notification); } else if (this->traceEventTypes.rtp) { auto rtpPacketDump = packet->FillBuffer(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto traceInfo = FBS::Producer::CreateRtpTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), rtpPacketDump, isRtx); auto notification = FBS::Producer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Producer::TraceEventType::RTP, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_IN, FBS::Producer::TraceInfo::RtpTraceInfo, traceInfo.Union()); EmitTraceEvent(notification); } } inline void Producer::EmitTraceEventPliType(uint32_t ssrc) const { MS_TRACE(); if (!this->traceEventTypes.pli) { return; } auto traceInfo = FBS::Producer::CreatePliTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); auto notification = FBS::Producer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Producer::TraceEventType::PLI, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_OUT, FBS::Producer::TraceInfo::PliTraceInfo, traceInfo.Union()); EmitTraceEvent(notification); } inline void Producer::EmitTraceEventFirType(uint32_t ssrc) const { MS_TRACE(); if (!this->traceEventTypes.fir) { return; } auto traceInfo = FBS::Producer::CreateFirTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), ssrc); auto notification = FBS::Producer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Producer::TraceEventType::FIR, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_OUT, FBS::Producer::TraceInfo::FirTraceInfo, traceInfo.Union()); EmitTraceEvent(notification); } inline void Producer::EmitTraceEventNackType() const { MS_TRACE(); if (!this->traceEventTypes.nack) { return; } auto notification = FBS::Producer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Producer::TraceEventType::NACK, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_OUT); EmitTraceEvent(notification); } inline void Producer::EmitTraceEventSrType(RTC::RTCP::SenderReport* report) const { MS_TRACE(); if (!this->traceEventTypes.sr) { return; } auto traceInfo = FBS::Producer::CreateSrTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), report->GetSsrc(), report->GetNtpSec(), report->GetNtpFrac(), report->GetRtpTs(), report->GetPacketCount(), report->GetOctetCount()); auto notification = FBS::Producer::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Producer::TraceEventType::SR, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_IN, FBS::Producer::TraceInfo::SrTraceInfo, traceInfo.Union()); EmitTraceEvent(notification); } inline void Producer::EmitTraceEvent( flatbuffers::Offset& notification) const { MS_TRACE(); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::PRODUCER_TRACE, FBS::Notification::Body::Producer_TraceNotification, notification); } inline void Producer::OnRtpStreamScore( RTC::RTP::RtpStream* rtpStream, uint8_t score, uint8_t previousScore) { MS_TRACE(); // Update the vector of scores. this->rtpStreamScores[rtpStream->GetEncodingIdx()] = score; // Notify the listener. this->listener->OnProducerRtpStreamScore( this, static_cast(rtpStream), score, previousScore); // Emit the score event. EmitScore(); } inline void Producer::OnRtpStreamSendRtcpPacket( RTC::RTP::RtpStreamRecv* /*rtpStream*/, RTC::RTCP::Packet* packet) { switch (packet->GetType()) { case RTC::RTCP::Type::PSFB: { auto* feedback = static_cast(packet); switch (feedback->GetMessageType()) { case RTC::RTCP::FeedbackPs::MessageType::PLI: { // May emit 'trace' event. EmitTraceEventPliType(feedback->GetMediaSsrc()); break; } case RTC::RTCP::FeedbackPs::MessageType::FIR: { // May emit 'trace' event. EmitTraceEventFirType(feedback->GetMediaSsrc()); break; } default:; } } case RTC::RTCP::Type::RTPFB: { auto* feedback = static_cast(packet); switch (feedback->GetMessageType()) { case RTC::RTCP::FeedbackRtp::MessageType::NACK: { // May emit 'trace' event. EmitTraceEventNackType(); break; } default:; } } default:; } // Notify the listener. this->listener->OnProducerSendRtcpPacket(this, packet); } inline void Producer::OnRtpStreamNeedWorstRemoteFractionLost( RTC::RTP::RtpStreamRecv* rtpStream, uint8_t& worstRemoteFractionLost) { auto mappedSsrc = this->mapRtpStreamMappedSsrc.at(rtpStream); // Notify the listener. this->listener->OnProducerNeedWorstRemoteFractionLost(this, mappedSsrc, worstRemoteFractionLost); } inline void Producer::OnKeyFrameNeeded( RTC::KeyFrameRequestManager* /*keyFrameRequestManager*/, uint32_t ssrc) { MS_TRACE(); auto it = this->mapSsrcRtpStream.find(ssrc); if (it == this->mapSsrcRtpStream.end()) { MS_WARN_2TAGS(rtcp, rtx, "no associated RtpStream found [ssrc:%" PRIu32 "]", ssrc); return; } auto* rtpStream = it->second; rtpStream->RequestKeyFrame(); } } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/Bye.cpp ================================================ #define MS_CLASS "RTC::RTCP::Bye" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/Bye.hpp" #include "Logger.hpp" #include "Utils.hpp" #include namespace RTC { namespace RTCP { /* Class methods. */ ByePacket* ByePacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Get the header. auto* header = const_cast(reinterpret_cast(data)); std::unique_ptr packet(new ByePacket(header)); size_t offset = Packet::CommonHeaderSize; uint8_t count = header->count; while (((count--) != 0u) && (len > offset)) { if (len - offset < 4u) { MS_WARN_TAG(rtcp, "not enough space for SSRC in RTCP Bye message"); return nullptr; } packet->AddSsrc(Utils::Byte::Get4Bytes(data, offset)); offset += 4u; } if (len > offset) { auto length = size_t{ Utils::Byte::Get1Byte(data, offset) }; offset += 1u; if (length <= len - offset) { packet->SetReason(std::string(reinterpret_cast(data) + offset, length)); } } return packet.release(); } /* Instance methods. */ size_t ByePacket::Serialize(uint8_t* buffer) { MS_TRACE(); size_t offset = Packet::Serialize(buffer); // SSRCs. for (auto ssrc : this->ssrcs) { Utils::Byte::Set4Bytes(buffer, offset, ssrc); offset += 4u; } if (!this->reason.empty()) { // Length field. Utils::Byte::Set1Byte(buffer, offset, this->reason.length()); offset += 1u; // Reason field. std::memcpy(buffer + offset, this->reason.c_str(), this->reason.length()); offset += this->reason.length(); } // 32 bits padding. const size_t padding = (-offset) & 3; for (size_t i{ 0 }; i < padding; ++i) { buffer[offset + i] = 0; } return offset + padding; } void ByePacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); for (auto ssrc : this->ssrcs) { MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, ssrc); } if (!this->reason.empty()) { MS_DUMP_CLEAN(indentation, " reason: %s", this->reason.c_str()); } MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/CompoundPacket.cpp ================================================ #define MS_CLASS "RTC::RTCP::CompoundPacket" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/CompoundPacket.hpp" #include "Logger.hpp" #include "RTC/Consts.hpp" namespace RTC { namespace RTCP { /* Instance methods. */ size_t CompoundPacket::GetSize() { size_t size{ 0 }; if (this->senderReportPacket.GetCount() > 0u) { size += this->senderReportPacket.GetSize(); } if (this->receiverReportPacket.GetCount() > 0u) { size += this->receiverReportPacket.GetSize(); } if (this->sdesPacket.GetCount() > 0u) { size += this->sdesPacket.GetSize(); } if (this->xrPacket.Begin() != this->xrPacket.End()) { size += this->xrPacket.GetSize(); } return size; } void CompoundPacket::Serialize(uint8_t* data) { MS_TRACE(); this->header = data; // Fill it. size_t offset{ 0 }; MS_ASSERT( this->senderReportPacket.GetCount() > 0u || this->receiverReportPacket.GetCount() > 0u, "no Sender or Receiver report present"); if (this->senderReportPacket.GetCount() > 0u) { offset += this->senderReportPacket.Serialize(this->header); } if (this->receiverReportPacket.GetCount() > 0u) { offset += this->receiverReportPacket.Serialize(this->header + offset); } if (this->sdesPacket.GetCount() > 0u) { offset += this->sdesPacket.Serialize(this->header + offset); } if (this->xrPacket.Begin() != this->xrPacket.End()) { this->xrPacket.Serialize(this->header + offset); } } bool CompoundPacket::Add( SenderReport* senderReport, SdesChunk* sdesChunk, DelaySinceLastRr::SsrcInfo* delaySinceLastRrSsrcInfo) { // Add the items into the packet. if (senderReport) { this->senderReportPacket.AddReport(senderReport); } if (sdesChunk) { this->sdesPacket.AddChunk(sdesChunk); } if (delaySinceLastRrSsrcInfo) { // Add a DLRR block into the XR packet if no present. if (!this->delaySinceLastRr) { this->delaySinceLastRr = new RTC::RTCP::DelaySinceLastRr(); this->xrPacket.AddReport(this->delaySinceLastRr); } this->delaySinceLastRr->AddSsrcInfo(delaySinceLastRrSsrcInfo); } // New items can hold in the packet, report it. if (GetSize() <= RTC::Consts::RtcpPacketMaxSize) { return true; } // New items can not hold in the packet, remove them, // delete and report it. if (senderReport) { this->senderReportPacket.RemoveReport(senderReport); delete senderReport; } if (sdesChunk) { this->sdesPacket.RemoveChunk(sdesChunk); delete sdesChunk; } if (delaySinceLastRrSsrcInfo) { // NOTE: This method deletes the removed instances in place. this->delaySinceLastRr->RemoveLastSsrcInfos(1); } return false; } bool CompoundPacket::Add( std::vector& senderReports, std::vector& sdesChunks, std::vector& delaySinceLastRrSsrcInfos) { // Add the items into the packet. for (auto* report : senderReports) { this->senderReportPacket.AddReport(report); } for (auto* chunk : sdesChunks) { this->sdesPacket.AddChunk(chunk); } // Add a DLRR block into the XR packet if no present. if (!delaySinceLastRrSsrcInfos.empty() && !this->delaySinceLastRr) { this->delaySinceLastRr = new RTC::RTCP::DelaySinceLastRr(); this->xrPacket.AddReport(this->delaySinceLastRr); } for (auto* ssrcInfo : delaySinceLastRrSsrcInfos) { this->delaySinceLastRr->AddSsrcInfo(ssrcInfo); } // New items can hold in the packet, report it. if (GetSize() <= RTC::Consts::RtcpPacketMaxSize) { return true; } // New items can not hold in the packet, remove them, // delete and report it. for (auto* report : senderReports) { this->senderReportPacket.RemoveReport(report); delete report; } for (auto* chunk : sdesChunks) { this->sdesPacket.RemoveChunk(chunk); delete chunk; } if (!delaySinceLastRrSsrcInfos.empty()) { // NOTE: This method deletes the instances in place. this->delaySinceLastRr->RemoveLastSsrcInfos(delaySinceLastRrSsrcInfos.size()); } return false; } bool CompoundPacket::Add( std::vector& receiverReports, ReceiverReferenceTime* receiverReferenceTimeReport) { // Add the items into the packet. for (auto* report : receiverReports) { this->receiverReportPacket.AddReport(report); } if (receiverReferenceTimeReport) { this->xrPacket.AddReport(receiverReferenceTimeReport); } // New items can hold in the packet, report it. if (GetSize() <= RTC::Consts::RtcpPacketMaxSize) { return true; } // New items can not hold in the packet, remove them, // delete and report it. for (auto* report : receiverReports) { this->receiverReportPacket.RemoveReport(report); delete report; } if (receiverReferenceTimeReport) { this->xrPacket.RemoveReport(receiverReferenceTimeReport); delete receiverReferenceTimeReport; } return false; } void CompoundPacket::Dump(int indentation) { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); if (HasSenderReport()) { this->senderReportPacket.Dump(indentation + 1); if (this->receiverReportPacket.GetCount() != 0u) { this->receiverReportPacket.Dump(indentation + 1); } } else { this->receiverReportPacket.Dump(indentation + 1); } if (this->sdesPacket.GetCount() != 0u) { this->sdesPacket.Dump(indentation + 1); } if (this->xrPacket.Begin() != this->xrPacket.End()) { this->xrPacket.Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } void CompoundPacket::AddSenderReport(SenderReport* report) { MS_TRACE(); this->senderReportPacket.AddReport(report); } void CompoundPacket::AddReceiverReport(ReceiverReport* report) { MS_TRACE(); this->receiverReportPacket.AddReport(report); } void CompoundPacket::AddSdesChunk(SdesChunk* chunk) { MS_TRACE(); this->sdesPacket.AddChunk(chunk); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/Feedback.cpp ================================================ #define MS_CLASS "RTC::RTCP::Feedback" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/Feedback.hpp" #include "Logger.hpp" #include "RTC/RTCP/FeedbackPsAfb.hpp" #include "RTC/RTCP/FeedbackPsFir.hpp" #include "RTC/RTCP/FeedbackPsLei.hpp" #include "RTC/RTCP/FeedbackPsPli.hpp" #include "RTC/RTCP/FeedbackPsRpsi.hpp" #include "RTC/RTCP/FeedbackPsSli.hpp" #include "RTC/RTCP/FeedbackPsTst.hpp" #include "RTC/RTCP/FeedbackPsVbcm.hpp" #include "RTC/RTCP/FeedbackRtpEcn.hpp" #include "RTC/RTCP/FeedbackRtpNack.hpp" #include "RTC/RTCP/FeedbackRtpSrReq.hpp" #include "RTC/RTCP/FeedbackRtpTllei.hpp" #include "RTC/RTCP/FeedbackRtpTmmb.hpp" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include namespace RTC { namespace RTCP { /* Class methods. */ template const std::string& FeedbackPacket::MessageTypeToString(typename T::MessageType type) { static const std::string Unknown("UNKNOWN"); auto it = FeedbackPacket::MessageType2String.find(type); if (it == FeedbackPacket::MessageType2String.end()) { return Unknown; } return it->second; } /* Instance methods. */ template FeedbackPacket::FeedbackPacket(CommonHeader* commonHeader) : Packet(commonHeader), messageType(typename T::MessageType(commonHeader->count)) { this->header = reinterpret_cast( reinterpret_cast(commonHeader) + Packet::CommonHeaderSize); } template FeedbackPacket::FeedbackPacket( typename T::MessageType messageType, uint32_t senderSsrc, uint32_t mediaSsrc) : Packet(rtcpType), messageType(messageType) { this->raw = new uint8_t[HeaderSize]; this->header = reinterpret_cast(this->raw); this->header->senderSsrc = htonl(senderSsrc); this->header->mediaSsrc = htonl(mediaSsrc); } template FeedbackPacket::~FeedbackPacket() { delete[] this->raw; } /* Instance methods. */ template size_t FeedbackPacket::Serialize(uint8_t* buffer) { MS_TRACE(); const size_t offset = Packet::Serialize(buffer); // Copy the header. std::memcpy(buffer + offset, this->header, HeaderSize); return offset + HeaderSize; } template void FeedbackPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, " sender ssrc: %" PRIu32, GetSenderSsrc()); MS_DUMP_CLEAN(indentation, " media ssrc: %" PRIu32, GetMediaSsrc()); MS_DUMP_CLEAN(indentation, " size: %zu", this->GetSize()); } /* Specialization for Ps class. */ template<> RTCP::Type FeedbackPacket::rtcpType = RTCP::Type::PSFB; // clang-format off template<> const absl::flat_hash_map FeedbackPacket::MessageType2String = { { FeedbackPs::MessageType::PLI, "PLI" }, { FeedbackPs::MessageType::SLI, "SLI" }, { FeedbackPs::MessageType::RPSI, "RPSI" }, { FeedbackPs::MessageType::FIR, "FIR" }, { FeedbackPs::MessageType::TSTR, "TSTR" }, { FeedbackPs::MessageType::TSTN, "TSTN" }, { FeedbackPs::MessageType::VBCM, "VBCM" }, { FeedbackPs::MessageType::PSLEI, "PSLEI" }, { FeedbackPs::MessageType::ROI, "ROI" }, { FeedbackPs::MessageType::AFB, "AFB" }, { FeedbackPs::MessageType::EXT, "EXT" } }; // clang-format on template<> FeedbackPacket* FeedbackPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize) { MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded"); return nullptr; } auto* commonHeader = const_cast(reinterpret_cast(data)); FeedbackPsPacket* packet{ nullptr }; // NOLINT(misc-const-correctness) switch (FeedbackPs::MessageType(commonHeader->count)) { case FeedbackPs::MessageType::PLI: packet = FeedbackPsPliPacket::Parse(data, len); break; case FeedbackPs::MessageType::SLI: packet = FeedbackPsSliPacket::Parse(data, len); break; case FeedbackPs::MessageType::RPSI: packet = FeedbackPsRpsiPacket::Parse(data, len); break; case FeedbackPs::MessageType::FIR: packet = FeedbackPsFirPacket::Parse(data, len); break; case FeedbackPs::MessageType::TSTR: packet = FeedbackPsTstrPacket::Parse(data, len); break; case FeedbackPs::MessageType::TSTN: packet = FeedbackPsTstnPacket::Parse(data, len); break; case FeedbackPs::MessageType::VBCM: packet = FeedbackPsVbcmPacket::Parse(data, len); break; case FeedbackPs::MessageType::PSLEI: packet = FeedbackPsLeiPacket::Parse(data, len); break; case FeedbackPs::MessageType::ROI: break; case FeedbackPs::MessageType::AFB: packet = FeedbackPsAfbPacket::Parse(data, len); break; case FeedbackPs::MessageType::EXT: break; default: MS_WARN_TAG( rtcp, "unknown RTCP PS Feedback message type [packetType:%" PRIu8 "]", commonHeader->count); } return packet; } /* Specialization for Rtcp class. */ template<> Type FeedbackPacket::rtcpType = RTCP::Type::RTPFB; // clang-format off template<> const absl::flat_hash_map FeedbackPacket::MessageType2String = { { FeedbackRtp::MessageType::NACK, "NACK" }, { FeedbackRtp::MessageType::TMMBR, "TMMBR" }, { FeedbackRtp::MessageType::TMMBN, "TMMBN" }, { FeedbackRtp::MessageType::SR_REQ, "SR_REQ" }, { FeedbackRtp::MessageType::RAMS, "RAMS" }, { FeedbackRtp::MessageType::TLLEI, "TLLEI" }, { FeedbackRtp::MessageType::ECN, "ECN" }, { FeedbackRtp::MessageType::PS, "PS" }, { FeedbackRtp::MessageType::EXT, "EXT" }, { FeedbackRtp::MessageType::TCC, "TCC" } }; // clang-format on /* Class methods. */ template<> FeedbackPacket* FeedbackPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize) { MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded"); return nullptr; } auto* commonHeader = reinterpret_cast(const_cast(data)); FeedbackRtpPacket* packet{ nullptr }; switch (FeedbackRtp::MessageType(commonHeader->count)) { case FeedbackRtp::MessageType::NACK: packet = FeedbackRtpNackPacket::Parse(data, len); break; case FeedbackRtp::MessageType::TMMBR: packet = FeedbackRtpTmmbrPacket::Parse(data, len); break; case FeedbackRtp::MessageType::TMMBN: packet = FeedbackRtpTmmbnPacket::Parse(data, len); break; case FeedbackRtp::MessageType::SR_REQ: packet = FeedbackRtpSrReqPacket::Parse(data, len); break; case FeedbackRtp::MessageType::RAMS: break; case FeedbackRtp::MessageType::TLLEI: packet = FeedbackRtpTlleiPacket::Parse(data, len); break; case FeedbackRtp::MessageType::ECN: packet = FeedbackRtpEcnPacket::Parse(data, len); break; case FeedbackRtp::MessageType::PS: break; case FeedbackRtp::MessageType::EXT: break; case FeedbackRtp::MessageType::TCC: packet = FeedbackRtpTransportPacket::Parse(data, len); break; default: MS_WARN_TAG( rtcp, "unknown RTCP RTP Feedback message type [packetType:%" PRIu8 "]", commonHeader->count); } return packet; } // Explicit instantiation to have all FeedbackPacket definitions in this file. template class FeedbackPacket; template class FeedbackPacket; } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPs.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPs" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPs.hpp" #include "Logger.hpp" #include "RTC/RTCP/FeedbackItem.hpp" #include "RTC/RTCP/FeedbackPsFir.hpp" #include "RTC/RTCP/FeedbackPsLei.hpp" #include "RTC/RTCP/FeedbackPsRpsi.hpp" #include "RTC/RTCP/FeedbackPsSli.hpp" #include "RTC/RTCP/FeedbackPsTst.hpp" #include "RTC/RTCP/FeedbackPsVbcm.hpp" namespace RTC { namespace RTCP { /* Class methods. */ template FeedbackPsItemsPacket* FeedbackPsItemsPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize) { MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded"); return nullptr; } // NOLINTNEXTLINE(llvm-qualified-auto) auto* commonHeader = const_cast(reinterpret_cast(data)); std::unique_ptr> packet( new FeedbackPsItemsPacket(commonHeader)); size_t offset = Packet::CommonHeaderSize + FeedbackPacket::HeaderSize; while (len > offset) { auto* item = FeedbackItem::Parse(data + offset, len - offset); if (item) { if (!item->IsCorrect()) { delete item; break; } packet->AddItem(item); offset += item->GetSize(); } else { break; } } return packet.release(); } /* Instance methods. */ template size_t FeedbackPsItemsPacket::Serialize(uint8_t* buffer) { MS_TRACE(); size_t offset = FeedbackPacket::Serialize(buffer); for (auto* item : this->items) { offset += item->Serialize(buffer + offset); } return offset; } template void FeedbackPsItemsPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN( indentation, "<%s>", FeedbackPsPacket::MessageTypeToString(Item::MessageType).c_str()); FeedbackPsPacket::Dump(indentation + 1); for (auto* item : this->items) { item->Dump(indentation + 1); } MS_DUMP_CLEAN( indentation, "", FeedbackPsPacket::MessageTypeToString(Item::MessageType).c_str()); } // explicit instantiation to have all FeedbackRtpPacket definitions in this file. template class FeedbackPsItemsPacket; template class FeedbackPsItemsPacket; template class FeedbackPsItemsPacket; template class FeedbackPsItemsPacket; template class FeedbackPsItemsPacket; template class FeedbackPsItemsPacket; template class FeedbackPsItemsPacket; } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPsAfb.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPsAfb" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPsAfb.hpp" #include "Logger.hpp" #include "Utils.hpp" #include "RTC/RTCP/FeedbackPsRemb.hpp" #include namespace RTC { namespace RTCP { /* Class methods. */ FeedbackPsAfbPacket* FeedbackPsAfbPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize) { MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded"); return nullptr; } // NOLINTNEXTLINE(llvm-qualified-auto) auto* commonHeader = const_cast(reinterpret_cast(data)); std::unique_ptr packet; constexpr size_t Offset = Packet::CommonHeaderSize + FeedbackPacket::HeaderSize; if ( len >= Packet::CommonHeaderSize + FeedbackPacket::HeaderSize + 4 && Utils::Byte::Get4Bytes(data, Offset) == FeedbackPsRembPacket::UniqueIdentifier) { packet.reset(FeedbackPsRembPacket::Parse(data, len)); } else { packet.reset(new FeedbackPsAfbPacket(commonHeader)); } return packet.release(); } size_t FeedbackPsAfbPacket::Serialize(uint8_t* buffer) { MS_TRACE(); const size_t offset = FeedbackPsPacket::Serialize(buffer); // Copy the content. std::memcpy(buffer + offset, this->data, this->size); return offset + this->size; } void FeedbackPsAfbPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); FeedbackPsPacket::Dump(indentation + 1); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPsFir.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPsFir" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPsFir.hpp" #include "Logger.hpp" #include // std::memset() namespace RTC { namespace RTCP { /* Class methods. */ /* Instance methods. */ FeedbackPsFirItem::FeedbackPsFirItem(uint32_t ssrc, uint8_t sequenceNumber) { MS_TRACE(); this->raw = new uint8_t[HeaderSize]; this->header = reinterpret_cast(this->raw); // Set reserved bits to zero. std::memset(this->header, 0, HeaderSize); this->header->ssrc = htonl(ssrc); this->header->sequenceNumber = sequenceNumber; } size_t FeedbackPsFirItem::Serialize(uint8_t* buffer) { MS_TRACE(); std::memcpy(buffer, this->header, HeaderSize); return HeaderSize; } void FeedbackPsFirItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, this->GetSsrc()); MS_DUMP_CLEAN(indentation, " sequence number: %" PRIu8, this->GetSequenceNumber()); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPsLei.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPsPsLei" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPsLei.hpp" #include "Logger.hpp" #include namespace RTC { namespace RTCP { /* Instance methods. */ FeedbackPsLeiItem::FeedbackPsLeiItem(uint32_t ssrc) { MS_TRACE(); this->raw = new uint8_t[HeaderSize]; this->header = reinterpret_cast(this->raw); this->header->ssrc = htonl(ssrc); } size_t FeedbackPsLeiItem::Serialize(uint8_t* buffer) { MS_TRACE(); // Add minimum header. std::memcpy(buffer, this->header, HeaderSize); return HeaderSize; } void FeedbackPsLeiItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, this->GetSsrc()); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPsPli.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPsPli" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPsPli.hpp" #include "Logger.hpp" namespace RTC { namespace RTCP { /* Class methods. */ FeedbackPsPliPacket* FeedbackPsPliPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize) { MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded"); return nullptr; } // NOLINTNEXTLINE(llvm-qualified-auto) auto* commonHeader = const_cast(reinterpret_cast(data)); std::unique_ptr packet(new FeedbackPsPliPacket(commonHeader)); return packet.release(); } void FeedbackPsPliPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); FeedbackPsPacket::Dump(indentation + 1); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPsRemb.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPsRemb" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPsRemb.hpp" #include "Logger.hpp" #include "Utils.hpp" #include namespace RTC { namespace RTCP { /* Class methods. */ FeedbackPsRembPacket* FeedbackPsRembPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Check that there is space for the REMB unique identifier and basic fields. // NOTE: Feedback.cpp already checked that there is space for CommonHeader and // Feedback Header. if (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize + 8u) { MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded"); return nullptr; } // NOLINTNEXTLINE(llvm-qualified-auto) auto* commonHeader = const_cast(reinterpret_cast(data)); std::unique_ptr packet(new FeedbackPsRembPacket(commonHeader, len)); if (!packet->IsCorrect()) { return nullptr; } return packet.release(); } FeedbackPsRembPacket::FeedbackPsRembPacket(CommonHeader* commonHeader, size_t availableLen) : FeedbackPsAfbPacket(commonHeader, FeedbackPsAfbPacket::Application::REMB) { const size_t len = static_cast(ntohs(commonHeader->length) + 1) * 4; if (len > availableLen) { MS_WARN_TAG(rtcp, "packet announced length exceeds the available buffer length, discarded"); this->isCorrect = false; return; } // Make data point to the 4 bytes that must containt the "REMB" identifier. auto* data = reinterpret_cast(commonHeader) + Packet::CommonHeaderSize + FeedbackPacket::HeaderSize; const size_t numSsrcs = data[4]; // Ensure there is space for the the announced number of SSRC feedbacks. if (len != Packet::CommonHeaderSize + FeedbackPacket::HeaderSize + 8u + (numSsrcs * 4u)) { MS_WARN_TAG( rtcp, "invalid payload size (%zu bytes) for the given number of ssrcs (%zu)", len, numSsrcs); this->isCorrect = false; return; } // Verify the "REMB" unique identifier. if (Utils::Byte::Get4Bytes(data, 0) != FeedbackPsRembPacket::UniqueIdentifier) { MS_WARN_TAG(rtcp, "invalid unique indentifier in REMB packet"); this->isCorrect = false; return; } const uint8_t exponent = data[5] >> 2; const uint64_t mantissa = (static_cast(data[5] & 0x03) << 16) | Utils::Byte::Get2Bytes(data, 6); this->bitrate = (mantissa << exponent); if ((this->bitrate >> exponent) != mantissa) { MS_WARN_TAG(rtcp, "invalid REMB bitrate value: %" PRIu64 " *2^%u", mantissa, exponent); this->isCorrect = false; return; } // Make index point to the first SSRC feedback item. size_t index{ 8 }; this->ssrcs.reserve(numSsrcs); for (size_t n{ 0 }; n < numSsrcs; ++n) { this->ssrcs.push_back(Utils::Byte::Get4Bytes(data, index)); index += 4u; } } size_t FeedbackPsRembPacket::Serialize(uint8_t* buffer) { MS_TRACE(); // NOLINTNEXTLINE(bugprone-parent-virtual-call) size_t offset = FeedbackPsPacket::Serialize(buffer); uint64_t mantissa = this->bitrate; uint8_t exponent{ 0u }; while (mantissa > 0x3FFFF /* max mantissa (18 bits) */) { mantissa >>= 1; ++exponent; } Utils::Byte::Set4Bytes(buffer, offset, FeedbackPsRembPacket::UniqueIdentifier); offset += FeedbackPsRembPacket::UniqueIdentifierSize; buffer[offset] = this->ssrcs.size(); offset += 1; buffer[offset] = (exponent << 2) | (mantissa >> 16); offset += 1; Utils::Byte::Set2Bytes(buffer, offset, mantissa & 0xFFFF); offset += 2; for (auto ssrc : this->ssrcs) { Utils::Byte::Set4Bytes(buffer, offset, ssrc); offset += 4u; } return offset; } void FeedbackPsRembPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); // NOLINTNEXTLINE(bugprone-parent-virtual-call) FeedbackPsPacket::Dump(); MS_DUMP_CLEAN(indentation, " bitrate (bps): %" PRIu64, this->bitrate); for (auto ssrc : this->ssrcs) { MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, ssrc); } MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPsRpsi.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPsRpsi" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPsRpsi.hpp" #include "Logger.hpp" #include namespace RTC { namespace RTCP { /* Instance methods. */ FeedbackPsRpsiItem::FeedbackPsRpsiItem(Header* header) { MS_TRACE(); this->header = header; // Calculate bitString length. if (this->header->paddingBits % 8 != 0) { MS_WARN_TAG(rtcp, "invalid Rpsi packet with fractional padding bytes value"); isCorrect = false; } const size_t paddingBytes = this->header->paddingBits / 8; if (paddingBytes > FeedbackPsRpsiItem::MaxBitStringSize) { MS_WARN_TAG(rtcp, "invalid Rpsi packet with too many padding bytes"); isCorrect = false; } this->length = FeedbackPsRpsiItem::MaxBitStringSize - paddingBytes; } FeedbackPsRpsiItem::FeedbackPsRpsiItem(uint8_t payloadType, uint8_t* bitString, size_t length) { MS_TRACE(); MS_ASSERT(payloadType <= 0x7f, "rpsi payload type exceeds the maximum value"); MS_ASSERT( length <= FeedbackPsRpsiItem::MaxBitStringSize, "rpsi bit string length exceeds the maximum value"); this->header = reinterpret_cast(this->raw); // TODO: We should use Utils::Byte::PadTo4Bytes(). // 32 bits padding. const uint8_t padding = (-length) & 3; this->header->paddingBits = padding * 8; this->header->zero = 0; std::memcpy(this->header->bitString, bitString, length); this->raw = new uint8_t[FeedbackPsRpsiItem::HeaderSize + padding]; // Fill padding. for (uint8_t i{ 0 }; i < padding; ++i) { this->raw[FeedbackPsRpsiItem::HeaderSize + i - 1] = 0; } } size_t FeedbackPsRpsiItem::Serialize(uint8_t* buffer) { MS_TRACE(); std::memcpy(buffer, this->header, FeedbackPsRpsiItem::HeaderSize); return FeedbackPsRpsiItem::HeaderSize; } void FeedbackPsRpsiItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " padding bits %" PRIu8, this->header->paddingBits); MS_DUMP_CLEAN(indentation, " payload type: %" PRIu8, this->GetPayloadType()); MS_DUMP_CLEAN(indentation, " length: %zu", this->GetLength()); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPsSli.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPsSli" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPsSli.hpp" #include "Logger.hpp" namespace RTC { namespace RTCP { /* Instance methods. */ FeedbackPsSliItem::FeedbackPsSliItem(Header* header) { MS_TRACE(); this->header = header; auto compact = ntohl(header->compact); this->first = compact >> 19; /* first 13 bits */ this->number = (compact >> 6) & 0x1fff; /* next 13 bits */ this->pictureId = compact & 0x3f; /* last 6 bits */ } size_t FeedbackPsSliItem::Serialize(uint8_t* buffer) { const uint32_t compact = (this->first << 19) | (this->number << 6) | this->pictureId; auto* header = reinterpret_cast(buffer); header->compact = htonl(compact); std::memcpy(buffer, header, HeaderSize); return HeaderSize; } void FeedbackPsSliItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " first: %" PRIu16, this->first); MS_DUMP_CLEAN(indentation, " number: %" PRIu16, this->number); MS_DUMP_CLEAN(indentation, " picture id: %" PRIu8, this->pictureId); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPsTst.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPsTst" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPsTst.hpp" #include "Logger.hpp" #include // std::memcpy namespace RTC { namespace RTCP { template FeedbackPsTstItem::FeedbackPsTstItem(uint32_t ssrc, uint8_t sequenceNumber, uint8_t index) { MS_TRACE(); this->raw = new uint8_t[HeaderSize]; this->header = reinterpret_cast(this->raw); // Set reserved bits to zero. std::memset(this->header, 0, HeaderSize); this->header->ssrc = htonl(ssrc); this->header->sequenceNumber = sequenceNumber; this->header->index = index; } template size_t FeedbackPsTstItem::Serialize(uint8_t* buffer) { MS_TRACE(); std::memcpy(buffer, this->header, HeaderSize); return HeaderSize; } template void FeedbackPsTstItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, this->GetSsrc()); MS_DUMP_CLEAN(indentation, " sequence number: %" PRIu32, this->GetSequenceNumber()); MS_DUMP_CLEAN(indentation, " index: %" PRIu32, this->GetIndex()); MS_DUMP_CLEAN(indentation, ""); } /* Specialization for Tstr class. */ template<> const FeedbackPs::MessageType FeedbackPsTstItem::MessageType = FeedbackPs::MessageType::TSTR; /* Specialization for Tstn class. */ template<> const FeedbackPs::MessageType FeedbackPsTstItem::MessageType = FeedbackPs::MessageType::TSTN; // Explicit instantiation to have all definitions in this file. template class FeedbackPsTstItem; template class FeedbackPsTstItem; } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackPsVbcm.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackPsVbcm" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackPsVbcm.hpp" #include "Logger.hpp" #include // std::memcpy namespace RTC { namespace RTCP { /* Instance methods. */ FeedbackPsVbcmItem::FeedbackPsVbcmItem( uint32_t ssrc, uint8_t sequenceNumber, uint8_t payloadType, uint16_t length, uint8_t* value) { this->raw = new uint8_t[8 + length]; this->header = reinterpret_cast(this->raw); this->header->ssrc = htonl(ssrc); this->header->sequenceNumber = sequenceNumber; this->header->zero = 0; this->header->payloadType = payloadType; this->header->length = htons(length); std::memcpy(this->header->value, value, sizeof(length)); } size_t FeedbackPsVbcmItem::Serialize(uint8_t* buffer) { MS_TRACE(); // Add minimum header. std::memcpy(buffer, this->header, 8); // Copy the content. std::memcpy(buffer + 8, this->header->value, GetLength()); const size_t offset = 8 + GetLength(); // 32 bits padding. const size_t padding = (-offset) & 3; for (size_t i{ 0 }; i < padding; ++i) { buffer[offset + i] = 0; } return offset + padding; } void FeedbackPsVbcmItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, this->GetSsrc()); MS_DUMP_CLEAN(indentation, " sequence number: %" PRIu8, this->GetSequenceNumber()); MS_DUMP_CLEAN(indentation, " payload type: %" PRIu8, this->GetPayloadType()); MS_DUMP_CLEAN(indentation, " length: %" PRIu16, this->GetLength()); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackRtp.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackRtp" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackRtp.hpp" #include "Logger.hpp" #include "RTC/RTCP/FeedbackRtpEcn.hpp" #include "RTC/RTCP/FeedbackRtpNack.hpp" #include "RTC/RTCP/FeedbackRtpTllei.hpp" #include "RTC/RTCP/FeedbackRtpTmmb.hpp" namespace RTC { namespace RTCP { /* Class methods. */ template FeedbackRtpItemsPacket* FeedbackRtpItemsPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize) { MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded"); return nullptr; } // NOLINTNEXTLINE(llvm-qualified-auto) auto* commonHeader = const_cast(reinterpret_cast(data)); std::unique_ptr> packet( new FeedbackRtpItemsPacket(commonHeader)); size_t offset = Packet::CommonHeaderSize + FeedbackPacket::HeaderSize; while (len > offset) { auto* item = FeedbackItem::Parse(data + offset, len - offset); if (item) { packet->AddItem(item); offset += item->GetSize(); } else { break; } } return packet.release(); } /* Instance methods. */ template size_t FeedbackRtpItemsPacket::Serialize(uint8_t* buffer) { MS_TRACE(); size_t offset = FeedbackPacket::Serialize(buffer); for (auto* item : this->items) { offset += item->Serialize(buffer + offset); } return offset; } template void FeedbackRtpItemsPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN( indentation, "<%s>", FeedbackRtpPacket::MessageTypeToString(Item::MessageType).c_str()); FeedbackRtpPacket::Dump(indentation + 1); for (auto* item : this->items) { item->Dump(indentation + 1); } MS_DUMP_CLEAN( indentation, "", FeedbackRtpPacket::MessageTypeToString(Item::MessageType).c_str()); } // Explicit instantiation to have all FeedbackRtpPacket definitions in this file. template class FeedbackRtpItemsPacket; template class FeedbackRtpItemsPacket; template class FeedbackRtpItemsPacket; template class FeedbackRtpItemsPacket; template class FeedbackRtpItemsPacket; } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackRtpEcn.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackRtpEcn" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackRtpEcn.hpp" #include "Logger.hpp" #include // std::memcpy namespace RTC { namespace RTCP { size_t FeedbackRtpEcnItem::Serialize(uint8_t* buffer) { MS_TRACE(); // Add minimum header. std::memcpy(buffer, this->header, HeaderSize); return HeaderSize; } void FeedbackRtpEcnItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " sequence number: %" PRIu32, this->GetSequenceNumber()); MS_DUMP_CLEAN(indentation, " ect0 counter: %" PRIu32, this->GetEct0Counter()); MS_DUMP_CLEAN(indentation, " ect1 counter: %" PRIu32, this->GetEct1Counter()); MS_DUMP_CLEAN(indentation, " ecn ce counter: %" PRIu16, this->GetEcnCeCounter()); MS_DUMP_CLEAN(indentation, " not ect counter: %" PRIu16, this->GetNotEctCounter()); MS_DUMP_CLEAN(indentation, " lost packets: %" PRIu16, this->GetLostPackets()); MS_DUMP_CLEAN(indentation, " duplicated packets: %" PRIu16, this->GetDuplicatedPackets()); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackRtpNack.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackRtpNack" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackRtpNack.hpp" #include "Logger.hpp" #include // std::bitset() #include // std::memcpy #include namespace RTC { namespace RTCP { /* Instance methods. */ FeedbackRtpNackItem::FeedbackRtpNackItem(uint16_t packetId, uint16_t lostPacketBitmask) { this->raw = new uint8_t[HeaderSize]; this->header = reinterpret_cast(this->raw); this->header->packetId = htons(packetId); this->header->lostPacketBitmask = htons(lostPacketBitmask); } size_t FeedbackRtpNackItem::Serialize(uint8_t* buffer) { MS_TRACE(); // Add minimum header. std::memcpy(buffer, this->header, HeaderSize); return HeaderSize; } void FeedbackRtpNackItem::Dump(int indentation) const { MS_TRACE(); const std::bitset<16> nackBitset(GetLostPacketBitmask()); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " pid: %" PRIu16, this->GetPacketId()); MS_DUMP_CLEAN(indentation, " bpl: %s", nackBitset.to_string().c_str()); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackRtpSrReq.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackRtpSrReq" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackRtpSrReq.hpp" #include "Logger.hpp" namespace RTC { namespace RTCP { /* Class methods. */ FeedbackRtpSrReqPacket* FeedbackRtpSrReqPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize) { MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded"); return nullptr; } auto* commonHeader = reinterpret_cast(const_cast(data)); return new FeedbackRtpSrReqPacket(commonHeader); } void FeedbackRtpSrReqPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); FeedbackRtpPacket::Dump(indentation + 1); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackRtpTllei.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackRtpTllei" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackRtpTllei.hpp" #include "Logger.hpp" #include // std::memcpy namespace RTC { namespace RTCP { /* Instance methods. */ FeedbackRtpTlleiItem::FeedbackRtpTlleiItem(uint16_t packetId, uint16_t lostPacketBitmask) { this->raw = new uint8_t[HeaderSize]; this->header = reinterpret_cast(this->raw); this->header->packetId = htons(packetId); this->header->lostPacketBitmask = htons(lostPacketBitmask); } size_t FeedbackRtpTlleiItem::Serialize(uint8_t* buffer) { MS_TRACE(); // Add minimum header. std::memcpy(buffer, this->header, HeaderSize); return HeaderSize; } void FeedbackRtpTlleiItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " pid: %" PRIu16, this->GetPacketId()); MS_DUMP_CLEAN(indentation, " bpl: %" PRIu16, this->GetLostPacketBitmask()); MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackRtpTmmb.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackRtpTmmb" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackRtpTmmb.hpp" #include "Logger.hpp" #include "Utils.hpp" namespace RTC { namespace RTCP { /* Instance methods. */ template FeedbackRtpTmmbItem::FeedbackRtpTmmbItem(const Header* header) : FeedbackRtpTmmbItem(reinterpret_cast(header)) { } template FeedbackRtpTmmbItem::FeedbackRtpTmmbItem(const uint8_t* data) { this->ssrc = Utils::Byte::Get4Bytes(data, 0); // Read the 4 bytes block. const uint32_t compact = Utils::Byte::Get4Bytes(data, 4); // Read each component. const uint8_t exponent = compact >> 26; // 6 bits. const uint64_t mantissa = (compact >> 9) & 0x1ffff; // 17 bits. this->overhead = compact & 0x1ff; // 9 bits. // Get the bitrate out of exponent and mantissa. this->bitrate = (mantissa << exponent); if ((this->bitrate >> exponent) != mantissa) { MS_WARN_TAG(rtcp, "invalid TMMB bitrate value : %" PRIu64 " x 2^%" PRIu8, mantissa, exponent); this->isCorrect = false; } } template size_t FeedbackRtpTmmbItem::Serialize(uint8_t* buffer) { static constexpr uint32_t MaxMantissa{ 0x1ffff }; // 17 bits. uint64_t mantissa = this->bitrate; uint32_t exponent{ 0 }; while (mantissa > MaxMantissa) { mantissa >>= 1; ++exponent; } Utils::Byte::Set4Bytes(buffer, 0, this->ssrc); const uint32_t compact = (exponent << 26) | (mantissa << 9) | this->overhead; Utils::Byte::Set4Bytes(buffer, 4, compact); return HeaderSize; } template void FeedbackRtpTmmbItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, this->GetSsrc()); MS_DUMP_CLEAN(indentation, " bitrate: %" PRIu64, this->GetBitrate()); MS_DUMP_CLEAN(indentation, " overhead: %" PRIu16, this->GetOverhead()); MS_DUMP_CLEAN(indentation, ""); } /* Specialization for Tmmbr class. */ template<> const FeedbackRtp::MessageType FeedbackRtpTmmbItem::MessageType = FeedbackRtp::MessageType::TMMBR; /* Specialization for Tmmbn class. */ template<> const FeedbackRtp::MessageType FeedbackRtpTmmbItem::MessageType = FeedbackRtp::MessageType::TMMBN; // Explicit instantiation to have all FeedbackRtpTmmbItem definitions in this file. template class FeedbackRtpTmmbItem; template class FeedbackRtpTmmbItem; } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/FeedbackRtpTransport.cpp ================================================ #define MS_CLASS "RTC::RTCP::FeedbackRtpTransport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include "Logger.hpp" #include "Utils.hpp" #include "RTC/SeqManager.hpp" #include // std::ostringstream namespace RTC { namespace RTCP { /* Static members. */ size_t FeedbackRtpTransportPacket::fixedHeaderSize{ 8u }; uint16_t FeedbackRtpTransportPacket::maxMissingPackets{ (1 << 13) - 1 }; uint16_t FeedbackRtpTransportPacket::maxPacketStatusCount{ (1 << 16) - 1 }; int16_t FeedbackRtpTransportPacket::maxPacketDelta{ 0x7FFF }; // clang-format off const absl::flat_hash_map FeedbackRtpTransportPacket::Status2String = { { FeedbackRtpTransportPacket::Status::NotReceived, "NR" }, { FeedbackRtpTransportPacket::Status::SmallDelta, "SD" }, { FeedbackRtpTransportPacket::Status::LargeDelta, "LD" } }; // clang-format on /* Class methods. */ FeedbackRtpTransportPacket* FeedbackRtpTransportPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < Packet::CommonHeaderSize + FeedbackPacket::HeaderSize + FeedbackRtpTransportPacket::fixedHeaderSize) { MS_WARN_TAG(rtcp, "not enough space for Feedback packet, discarded"); return nullptr; } // NOLINTNEXTLINE(llvm-qualified-auto) auto* commonHeader = const_cast(reinterpret_cast(data)); std::unique_ptr packet( new FeedbackRtpTransportPacket(commonHeader, len)); if (!packet->IsCorrect()) { return nullptr; } return packet.release(); } /* Instance methods. */ FeedbackRtpTransportPacket::FeedbackRtpTransportPacket(CommonHeader* commonHeader, size_t availableLen) : FeedbackRtpPacket(commonHeader) { MS_TRACE(); const size_t len = static_cast(ntohs(commonHeader->length) + 1) * 4; if (len > availableLen) { MS_WARN_TAG(rtcp, "packet announced length exceeds the available buffer length, discarded"); this->isCorrect = false; return; } // Make data point to the packet specific info. auto* data = reinterpret_cast(commonHeader) + Packet::CommonHeaderSize + FeedbackPacket::HeaderSize; this->baseSequenceNumber = Utils::Byte::Get2Bytes(data, 0); this->packetStatusCount = Utils::Byte::Get2Bytes(data, 2); this->referenceTime = Utils::Byte::Get3BytesSigned(data, 4); this->feedbackPacketCount = Utils::Byte::Get1Byte(data, 7); this->size = len; // Make contentData point to the beginning of the chunks. uint8_t* contentData = data + FeedbackRtpTransportPacket::fixedHeaderSize; // Make contentLen be the available length for chunks. const size_t contentLen = len - Packet::CommonHeaderSize - FeedbackPacket::HeaderSize - FeedbackRtpTransportPacket::fixedHeaderSize; size_t offset{ 0u }; uint16_t count{ 0u }; uint16_t receivedPacketStatusCount{ 0u }; while (count < this->packetStatusCount && contentLen > offset) { if (contentLen - offset < 2u) { MS_WARN_TAG(rtcp, "not enough space for chunk"); this->isCorrect = false; return; } auto* chunk = Chunk::Parse(contentData + offset, contentLen - offset, this->packetStatusCount - count); if (!chunk) { MS_WARN_TAG(rtcp, "invalid chunk"); this->isCorrect = false; return; } this->chunks.push_back(chunk); this->deltasAndChunksSize += 2u; offset += 2u; count += chunk->GetCount(); receivedPacketStatusCount += chunk->GetReceivedStatusCount(); } if (count != this->packetStatusCount) { MS_WARN_TAG(rtcp, "provided packet status count does not match with content"); this->isCorrect = false; return; } auto chunksIt = this->chunks.begin(); while (chunksIt != this->chunks.end() && contentLen > offset) { size_t deltasOffset{ 0u }; auto* chunk = *chunksIt; if (!chunk->AddDeltas(contentData + offset, contentLen - offset, this->deltas, deltasOffset)) { MS_WARN_TAG(rtcp, "not enough space for deltas"); this->isCorrect = false; return; } offset += deltasOffset; this->deltasAndChunksSize += deltasOffset; ++chunksIt; } if (this->deltas.size() != receivedPacketStatusCount) { MS_WARN_TAG(rtcp, "received deltas does not match received status count"); this->isCorrect = false; return; } } FeedbackRtpTransportPacket::~FeedbackRtpTransportPacket() { MS_TRACE(); for (auto* chunk : this->chunks) { delete chunk; } this->chunks.clear(); } void FeedbackRtpTransportPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " base sequence: %" PRIu16, this->baseSequenceNumber); MS_DUMP_CLEAN(indentation, " packet status count: %" PRIu16, this->packetStatusCount); MS_DUMP_CLEAN(indentation, " reference time: %" PRIi32, this->referenceTime); MS_DUMP_CLEAN(indentation, " feedback packet count: %" PRIu8, this->feedbackPacketCount); MS_DUMP_CLEAN(indentation, " size: %zu", GetSize()); for (auto* chunk : this->chunks) { chunk->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation + 1, ""); for (auto delta : this->deltas) { MS_DUMP_CLEAN(indentation + 1, " %" PRIi16 " ms", static_cast(delta / 4)); } MS_DUMP_CLEAN(indentation + 1, ""); auto packetResults = GetPacketResults(); MS_DUMP_CLEAN(indentation + 1, ""); for (auto& packetResult : packetResults) { if (packetResult.received) { MS_DUMP_CLEAN( indentation + 1, " seq:%" PRIu16 ", received:yes, receivedAtMs:%" PRIi64, packetResult.sequenceNumber, packetResult.receivedAtMs); } else { MS_DUMP_CLEAN( indentation + 1, " seq:%" PRIu16 ", received:no", packetResult.sequenceNumber); } } MS_DUMP_CLEAN(indentation + 1, ""); MS_DUMP_CLEAN(indentation, ""); } size_t FeedbackRtpTransportPacket::Serialize(uint8_t* buffer) { MS_TRACE(); // Add chunks for status packets that may not be represented yet. AddPendingChunks(); size_t offset = FeedbackPacket::Serialize(buffer); // Base sequence number. Utils::Byte::Set2Bytes(buffer, offset, this->baseSequenceNumber); offset += 2; // Packet status count. Utils::Byte::Set2Bytes(buffer, offset, this->packetStatusCount); offset += 2; // Reference time. Utils::Byte::Set3BytesSigned(buffer, offset, this->referenceTime); offset += 3; // Feedback packet count. Utils::Byte::Set1Byte(buffer, offset, this->feedbackPacketCount); offset += 1; // Serialize chunks. for (auto* chunk : this->chunks) { offset += chunk->Serialize(buffer + offset); } // Serialize deltas. for (auto delta : this->deltas) { if (delta >= 0 && delta <= 255) { Utils::Byte::Set1Byte(buffer, offset, delta); offset += 1u; } else { Utils::Byte::Set2Bytes(buffer, offset, delta); offset += 2u; } } // 32 bits padding. const size_t padding = (-offset) & 3; for (size_t i{ 0u }; i < padding; ++i) { buffer[offset + i] = 0u; } offset += padding; return offset; } void FeedbackRtpTransportPacket::SetBase(uint16_t sequenceNumber, uint64_t timestamp) { MS_TRACE(); MS_ASSERT(!this->baseSet, "base already set"); this->baseSet = true; this->baseSequenceNumber = sequenceNumber; this->referenceTime = static_cast((timestamp & 0x1FFFFFC0) / 64); this->latestSequenceNumber = sequenceNumber - 1; this->latestTimestamp = (timestamp >> 6) * 64; // IMPORTANT: Loose precision. } FeedbackRtpTransportPacket::AddPacketResult FeedbackRtpTransportPacket::AddPacket( uint16_t sequenceNumber, uint64_t timestamp, size_t maxRtcpPacketLen) { MS_TRACE(); MS_ASSERT(this->baseSet, "base not set"); MS_ASSERT(!IsFull(), "packet is full"); // If the wide sequence number of the new packet is lower than the latest // seen, ignore it. // NOTE: Not very spec compliant but libwebrtc does it. // Also ignore if the sequence number matches the latest seen. if (!RTC::SeqManager::IsSeqHigherThan(sequenceNumber, this->latestSequenceNumber)) { return AddPacketResult::SUCCESS; } // Check if there are too many missing packets. { // NOTE: We CANNOT use auto here, we must use uint16_t. Otherwise this is a bug. // https://github.com/versatica/mediasoup/issues/1385#issuecomment-2084982087 const uint16_t missingPackets = sequenceNumber - (this->latestSequenceNumber + 1); if (missingPackets > FeedbackRtpTransportPacket::maxMissingPackets) { MS_WARN_DEV("RTP missing packet number exceeded"); return AddPacketResult::FATAL; } } // Deltas are represented as multiples of 250 us. // NOTE: Read it as int 64 to detect long elapsed times. const int64_t delta64 = (timestamp - this->latestTimestamp) * 4; if ( delta64 > FeedbackRtpTransportPacket::maxPacketDelta || delta64 < -1 * static_cast(FeedbackRtpTransportPacket::maxPacketDelta)) { MS_WARN_DEV( "RTP packet delta exceeded [latestTimestamp:%" PRIu64 ", timestamp:%" PRIu64 "]", this->latestTimestamp, timestamp); return AddPacketResult::FATAL; } // Delta in 16 bits signed. auto delta = static_cast(delta64); // Check whether another chunks and corresponding delta infos could be // added. { // Fixed packet size. size_t size = FeedbackRtpPacket::GetSize(); size += FeedbackRtpTransportPacket::fixedHeaderSize; size += this->deltasAndChunksSize; // Maximum size needed for another chunk and its delta infos. size += 2u; size += 2u; // 32 bits padding. size += (-size) & 3; if (size > maxRtcpPacketLen) { MS_WARN_DEV("maximum packet size exceeded"); return AddPacketResult::MAX_SIZE_EXCEEDED; } } // Fill a chunk. FillChunk(this->latestSequenceNumber, sequenceNumber, delta); // Update latest seen sequence number and timestamp. this->latestSequenceNumber = sequenceNumber; this->latestTimestamp = timestamp; return AddPacketResult::SUCCESS; } void FeedbackRtpTransportPacket::Finish() { MS_TRACE(); AddPendingChunks(); } std::vector FeedbackRtpTransportPacket::GetPacketResults() const { MS_TRACE(); std::vector packetResults; uint16_t currentSequenceNumber = this->baseSequenceNumber - 1; for (auto* chunk : this->chunks) { chunk->FillResults(packetResults, currentSequenceNumber); } size_t deltaIdx{ 0u }; // NOLINTNEXTLINE(bugprone-misplaced-widening-cast) auto currentReceivedAtMs = static_cast(this->referenceTime * 64); for (size_t idx{ 0u }; idx < packetResults.size(); ++idx) { auto& packetResult = packetResults[idx]; if (!packetResult.received) { continue; } currentReceivedAtMs += this->deltas.at(deltaIdx) / 4; packetResult.delta = this->deltas.at(deltaIdx); packetResult.receivedAtMs = currentReceivedAtMs; deltaIdx++; } return packetResults; } uint8_t FeedbackRtpTransportPacket::GetPacketFractionLost() const { MS_TRACE(); const uint16_t expected = this->packetStatusCount; uint16_t lost{ 0u }; if (expected == 0u) { return 0u; } for (auto* chunk : this->chunks) { lost += chunk->GetCount() - chunk->GetReceivedStatusCount(); } // NOTE: If lost equals expected, the math below would produce 256, which // becomes 0 in uint8_t. if (lost == expected) { return 255u; } return (lost << 8) / expected; } void FeedbackRtpTransportPacket::FillChunk( uint16_t previousSequenceNumber, uint16_t sequenceNumber, int16_t delta) { MS_TRACE(); auto missingPackets = static_cast(sequenceNumber - (previousSequenceNumber + 1)); if (missingPackets > 0) { // Create a long run chunk before processing this packet, if needed. if (this->context.statuses.size() >= 7 && this->context.allSameStatus) { CreateRunLengthChunk(this->context.currentStatus, this->context.statuses.size()); this->context.statuses.clear(); this->context.currentStatus = Status::None; } this->context.currentStatus = Status::NotReceived; size_t representedPackets{ 0u }; // Fill statuses vector. for (uint16_t i{ 0u }; i < missingPackets && this->context.statuses.size() < 7; ++i) { this->context.statuses.emplace_back(Status::NotReceived); representedPackets++; } // Create a two bit vector if needed. if (this->context.statuses.size() == 7) { // Fill a vector chunk. CreateTwoBitVectorChunk(this->context.statuses); this->context.statuses.clear(); this->context.currentStatus = Status::None; } missingPackets -= representedPackets; // Not all missing packets have been represented. if (missingPackets != 0) { // Fill a run length chunk with the remaining missing packets. CreateRunLengthChunk(Status::NotReceived, missingPackets); this->context.statuses.clear(); this->context.currentStatus = Status::None; } } Status status; if (delta >= 0 && delta <= 255) { status = Status::SmallDelta; } else { status = Status::LargeDelta; } // Create a long run chunk before processing this packet, if needed. if ( this->context.statuses.size() >= 7 && this->context.allSameStatus && status != this->context.currentStatus) { CreateRunLengthChunk(this->context.currentStatus, this->context.statuses.size()); this->context.statuses.clear(); } this->context.statuses.emplace_back(status); this->deltas.push_back(delta); this->deltasAndChunksSize += (status == Status::SmallDelta) ? 1u : 2u; // Update context info. if ( this->context.currentStatus == Status::None || (this->context.allSameStatus && this->context.currentStatus == status)) { this->context.allSameStatus = true; } else { this->context.allSameStatus = false; } this->context.currentStatus = status; // Not enough packet infos for creating a chunk. if (this->context.statuses.size() < 7) { return; } // 7 packet infos with heterogeneous status, create the chunk. else if (this->context.statuses.size() == 7 && !this->context.allSameStatus) { // Reset current status. this->context.currentStatus = Status::None; // Fill a vector chunk and return. CreateTwoBitVectorChunk(this->context.statuses); this->context.statuses.clear(); } } void FeedbackRtpTransportPacket::CreateRunLengthChunk(Status status, uint16_t count) { auto* chunk = new RunLengthChunk(status, count); this->chunks.push_back(chunk); this->packetStatusCount += count; this->deltasAndChunksSize += 2u; } void FeedbackRtpTransportPacket::CreateOneBitVectorChunk(std::vector& statuses) { auto* chunk = new OneBitVectorChunk(statuses); this->chunks.push_back(chunk); this->packetStatusCount += static_cast(statuses.size()); this->deltasAndChunksSize += 2u; } void FeedbackRtpTransportPacket::CreateTwoBitVectorChunk(std::vector& statuses) { auto* chunk = new TwoBitVectorChunk(statuses); this->chunks.push_back(chunk); this->packetStatusCount += static_cast(statuses.size()); this->deltasAndChunksSize += 2u; } void FeedbackRtpTransportPacket::AddPendingChunks() { // No pending status packets. if (this->context.statuses.empty()) { return; } if (this->context.allSameStatus) { CreateRunLengthChunk(this->context.currentStatus, this->context.statuses.size()); } else { MS_ASSERT(this->context.statuses.size() < 7, "already 7 status packets present"); CreateTwoBitVectorChunk(this->context.statuses); } this->context.statuses.clear(); } FeedbackRtpTransportPacket::Chunk* FeedbackRtpTransportPacket::Chunk::Parse( const uint8_t* data, size_t len, uint16_t count) { MS_TRACE(); if (len < 2u) { MS_WARN_TAG(rtcp, "not enough space for FeedbackRtpTransportPacket chunk, discarded"); return nullptr; } auto bytes = Utils::Byte::Get2Bytes(data, 0); const uint8_t chunkType = (bytes >> 15) & 0x01; // Run length chunk. if (chunkType == 0) { auto* chunk = new RunLengthChunk(bytes); // Verify that the status is a valid one. switch (chunk->GetStatus()) { case Status::NotReceived: case Status::SmallDelta: case Status::LargeDelta: { return chunk; } default: { MS_WARN_DEV("invalid status for a run length chunk"); delete chunk; return nullptr; } } } // Vector chunk. else { const uint8_t symbolSize = data[0] & 0x40; if (symbolSize == 0) { return new OneBitVectorChunk(bytes, count); } else { return new TwoBitVectorChunk(bytes, count); } } return nullptr; } FeedbackRtpTransportPacket::RunLengthChunk::RunLengthChunk(uint16_t buffer) { MS_TRACE(); this->status = static_cast((buffer >> 13) & 0x03); this->count = buffer & 0x1FFF; } bool FeedbackRtpTransportPacket::RunLengthChunk::AddDeltas( const uint8_t* data, size_t len, std::vector& deltas, size_t& offset) { MS_TRACE(); // No delta to be added. if (this->status == Status::NotReceived) { return true; } else if (this->status == Status::SmallDelta) { if (len < this->count * 1u) { MS_WARN_TAG(rtcp, "not enough space for small deltas"); return false; } for (size_t i{ 0 }; i < this->count; ++i) { auto delta = static_cast(Utils::Byte::Get1Byte(data, offset)); deltas.push_back(delta); offset += 1u; } } else if (this->status == Status::LargeDelta) { if (len < this->count * 2u) { MS_WARN_TAG(rtcp, "not enough space for large deltas"); return false; } for (size_t i{ 0 }; i < this->count; ++i) { auto delta = static_cast(Utils::Byte::Get2Bytes(data, offset)); deltas.push_back(delta); offset += 2u; } } return true; } void FeedbackRtpTransportPacket::RunLengthChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN( indentation, " status: %s", FeedbackRtpTransportPacket::Status2String.at(this->status).c_str()); MS_DUMP_CLEAN(indentation, " count: %" PRIu16, this->count); MS_DUMP_CLEAN(indentation, ""); } uint16_t FeedbackRtpTransportPacket::RunLengthChunk::GetReceivedStatusCount() const { MS_TRACE(); if (this->status == Status::SmallDelta || this->status == Status::LargeDelta) { return this->count; } else { return 0u; } } void FeedbackRtpTransportPacket::RunLengthChunk::FillResults( std::vector& packetResults, uint16_t& currentSequenceNumber) const { MS_TRACE(); const bool received = (this->status == Status::SmallDelta || this->status == Status::LargeDelta); for (uint16_t count{ 1u }; count <= this->count; ++count) { packetResults.emplace_back(++currentSequenceNumber, received); } } size_t FeedbackRtpTransportPacket::RunLengthChunk::Serialize(uint8_t* buffer) { MS_TRACE(); uint16_t bytes{ 0x0000 }; bytes |= this->status << 13; bytes |= this->count & 0x1FFF; Utils::Byte::Set2Bytes(buffer, 0, bytes); return 2u; } FeedbackRtpTransportPacket::OneBitVectorChunk::OneBitVectorChunk(uint16_t buffer, uint16_t count) { MS_TRACE(); MS_ASSERT(buffer & 0x8000, "invalid one bit vector chunk"); for (uint8_t i{ 0u }; i < 14 && count > 0; ++i, --count) { auto status = static_cast((buffer >> (14 - 1 - i)) & 0x01); this->statuses.emplace_back(status); } } bool FeedbackRtpTransportPacket::OneBitVectorChunk::AddDeltas( const uint8_t* data, size_t len, std::vector& deltas, size_t& offset) { MS_TRACE(); for (auto status : this->statuses) { if (status == Status::NotReceived) { continue; } else if (status == Status::SmallDelta) { if (len < 1u) { MS_WARN_TAG(rtcp, "not enough space for small delta"); return false; } auto delta = static_cast(Utils::Byte::Get1Byte(data, offset)); deltas.push_back(delta); offset += 1u; len -= 1u; continue; } else { MS_WARN_TAG(rtcp, "invalid status for one bit vector chunk"); return false; } } return true; } void FeedbackRtpTransportPacket::OneBitVectorChunk::Dump(int indentation) const { MS_TRACE(); std::ostringstream out; // Dump status slots. for (auto status : this->statuses) { out << "|" << FeedbackRtpTransportPacket::Status2String.at(status); } // Dump empty slots. for (size_t i{ this->statuses.size() }; i < 14; ++i) { out << "|--"; } out << "|"; MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " %s", out.str().c_str()); MS_DUMP_CLEAN(indentation, ""); } uint16_t FeedbackRtpTransportPacket::OneBitVectorChunk::GetReceivedStatusCount() const { MS_TRACE(); uint16_t count{ 0u }; for (auto status : this->statuses) { if (status == Status::SmallDelta || status == Status::LargeDelta) { count++; } } return count; } void FeedbackRtpTransportPacket::OneBitVectorChunk::FillResults( std::vector& packetResults, uint16_t& currentSequenceNumber) const { MS_TRACE(); for (auto status : this->statuses) { const bool received = (status == Status::SmallDelta || status == Status::LargeDelta); packetResults.emplace_back(++currentSequenceNumber, received); } } size_t FeedbackRtpTransportPacket::OneBitVectorChunk::Serialize(uint8_t* buffer) { MS_TRACE(); MS_ASSERT(this->statuses.size() <= 14, "packet info size must be 14 or less"); uint16_t bytes{ 0x8000 }; uint8_t i{ 13u }; for (auto status : this->statuses) { bytes |= status << i; i -= 1; } Utils::Byte::Set2Bytes(buffer, 0, bytes); return 2u; } FeedbackRtpTransportPacket::TwoBitVectorChunk::TwoBitVectorChunk(uint16_t buffer, uint16_t count) { MS_TRACE(); MS_ASSERT(buffer & 0xC000, "invalid two bit vector chunk"); for (uint8_t i{ 0u }; i < 7 && count > 0; ++i, --count) { auto status = static_cast((buffer >> 2 * (7 - 1 - i)) & 0x03); this->statuses.emplace_back(status); } } bool FeedbackRtpTransportPacket::TwoBitVectorChunk::AddDeltas( const uint8_t* data, size_t len, std::vector& deltas, size_t& offset) { MS_TRACE(); for (auto status : this->statuses) { if (status == Status::NotReceived) { continue; } else if (status == Status::SmallDelta) { if (len < 1u) { MS_WARN_TAG(rtcp, "not enough space for small delta"); return false; } auto delta = static_cast(Utils::Byte::Get1Byte(data, offset)); deltas.push_back(delta); offset += 1u; len -= 1u; continue; } else if (status == Status::LargeDelta) { if (len < 2u) { MS_WARN_TAG(rtcp, "not enough space for large delta"); return false; } auto delta = static_cast(Utils::Byte::Get2Bytes(data, offset)); deltas.push_back(delta); offset += 2u; len -= 2u; continue; } } return true; } void FeedbackRtpTransportPacket::TwoBitVectorChunk::Dump(int indentation) const { MS_TRACE(); std::ostringstream out; // Dump status slots. for (auto status : this->statuses) { out << "|" << FeedbackRtpTransportPacket::Status2String.at(status); } // Dump empty slots. for (size_t i{ this->statuses.size() }; i < 7; ++i) { out << "|--"; } out << "|"; MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " %s", out.str().c_str()); MS_DUMP_CLEAN(indentation, ""); } uint16_t FeedbackRtpTransportPacket::TwoBitVectorChunk::GetReceivedStatusCount() const { MS_TRACE(); uint16_t count{ 0u }; for (auto status : this->statuses) { if (status == Status::SmallDelta || status == Status::LargeDelta) { count++; } } return count; } void FeedbackRtpTransportPacket::TwoBitVectorChunk::FillResults( std::vector& packetResults, uint16_t& currentSequenceNumber) const { MS_TRACE(); for (auto status : this->statuses) { const bool received = (status == Status::SmallDelta || status == Status::LargeDelta); packetResults.emplace_back(++currentSequenceNumber, received); } } size_t FeedbackRtpTransportPacket::TwoBitVectorChunk::Serialize(uint8_t* buffer) { MS_TRACE(); MS_ASSERT(this->statuses.size() <= 7, "packet info size must be 7 or less"); uint16_t bytes{ 0x8000 }; uint8_t i{ 12u }; bytes |= 0x01 << 14; for (auto status : this->statuses) { bytes |= status << i; i -= 2; } Utils::Byte::Set2Bytes(buffer, 0, bytes); return 2u; } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/Packet.cpp ================================================ #define MS_CLASS "RTC::RTCP::Packet" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/Packet.hpp" #include "Logger.hpp" #include "RTC/RTCP/Bye.hpp" #include "RTC/RTCP/Feedback.hpp" #include "RTC/RTCP/ReceiverReport.hpp" #include "RTC/RTCP/Sdes.hpp" #include "RTC/RTCP/SenderReport.hpp" #include "RTC/RTCP/XR.hpp" namespace RTC { namespace RTCP { /* Namespace variables. */ alignas(4) uint8_t SerializationBuffer[SerializationBufferSize]; /* Class variables. */ // clang-format off const absl::flat_hash_map Packet::Type2String = { { Type::SR, "SR" }, { Type::RR, "RR" }, { Type::SDES, "SDES" }, { Type::BYE, "BYE" }, { Type::APP, "APP" }, { Type::RTPFB, "RTPFB" }, { Type::PSFB, "PSFB" }, { Type::XR, "XR" } }; // clang-format on /* Class methods. */ Packet* Packet::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // First, Currently parsing and Last RTCP packets in the compound packet. Packet* first{ nullptr }; // NOLINT(misc-const-correctness) Packet* current{ nullptr }; Packet* last{ nullptr }; while (len > 0u) { if (!Packet::IsRtcp(data, len)) { MS_WARN_TAG(rtcp, "data is not a RTCP packet"); return first; } auto* header = const_cast(reinterpret_cast(data)); const size_t packetLen = static_cast(ntohs(header->length) + 1) * 4; if (len < packetLen) { MS_WARN_TAG( rtcp, "packet length exceeds remaining data [len:%zu, " "packet len:%zu]", len, packetLen); return first; } switch (Type(header->packetType)) { case Type::SR: { current = SenderReportPacket::Parse(data, packetLen); if (!current) { break; } if (header->count > 0) { Packet* rr = ReceiverReportPacket::Parse(data, packetLen, current->GetSize()); if (!rr) { break; } current->SetNext(rr); } break; } case Type::RR: { current = ReceiverReportPacket::Parse(data, packetLen); break; } case Type::SDES: { current = SdesPacket::Parse(data, packetLen); break; } case Type::BYE: { current = ByePacket::Parse(data, packetLen); break; } case Type::APP: { current = nullptr; break; } case Type::RTPFB: { current = FeedbackRtpPacket::Parse(data, packetLen); break; } case Type::PSFB: { current = FeedbackPsPacket::Parse(data, packetLen); break; } case Type::XR: { current = ExtendedReportPacket::Parse(data, packetLen); break; } default: { MS_WARN_TAG(rtcp, "unknown RTCP packet type [packetType:%" PRIu8 "]", header->packetType); current = nullptr; } } if (!current) { std::string packetType = TypeToString(Type(header->packetType)); if (Type(header->packetType) == Type::PSFB) { packetType += " " + FeedbackPsPacket::MessageTypeToString(FeedbackPs::MessageType(header->count)); } else if (Type(header->packetType) == Type::RTPFB) { packetType += " " + FeedbackRtpPacket::MessageTypeToString(FeedbackRtp::MessageType(header->count)); } MS_WARN_TAG(rtcp, "error parsing %s Packet", packetType.c_str()); return first; } data += packetLen; len -= packetLen; if (!first) { first = current; } else { last->SetNext(current); } last = current->GetNext() != nullptr ? current->GetNext() : current; } return first; } const std::string& Packet::TypeToString(Type type) { MS_TRACE(); static const std::string Unknown("UNKNOWN"); auto it = Packet::Type2String.find(type); if (it == Packet::Type2String.end()) { return Unknown; } return it->second; } /* Instance methods. */ size_t Packet::Serialize(uint8_t* buffer) { MS_TRACE(); this->header = reinterpret_cast(buffer); const size_t length = (GetSize() / 4) - 1; // Fill the common header. this->header->version = 2; this->header->padding = 0; this->header->count = static_cast(GetCount()); this->header->packetType = static_cast(GetType()); this->header->length = htons(length); return CommonHeaderSize; } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/ReceiverReport.cpp ================================================ #define MS_CLASS "RTC::RTCP::ReceiverReport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/ReceiverReport.hpp" #include "Logger.hpp" #include "Utils.hpp" #include // std::memcpy namespace RTC { namespace RTCP { /* Class methods. */ ReceiverReport* ReceiverReport::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Get the header. auto* header = const_cast(reinterpret_cast(data)); // Packet size must be >= header size. if (len < HeaderSize) { MS_WARN_TAG(rtcp, "not enough space for receiver report, packet discarded"); return nullptr; } return new ReceiverReport(header); } /* Instance methods. */ void ReceiverReport::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, GetSsrc()); MS_DUMP_CLEAN(indentation, " fraction lost: %" PRIu8, GetFractionLost()); MS_DUMP_CLEAN(indentation, " total lost: %" PRIi32, GetTotalLost()); MS_DUMP_CLEAN(indentation, " last seq: %" PRIu32, GetLastSeq()); MS_DUMP_CLEAN(indentation, " jitter: %" PRIu32, GetJitter()); MS_DUMP_CLEAN(indentation, " lsr: %" PRIu32, GetLastSenderReport()); MS_DUMP_CLEAN(indentation, " dlsr: %" PRIu32, GetDelaySinceLastSenderReport()); MS_DUMP_CLEAN(indentation, ""); } size_t ReceiverReport::Serialize(uint8_t* buffer) { MS_TRACE(); // Copy the header. std::memcpy(buffer, this->header, HeaderSize); return HeaderSize; } /* Static Class members */ size_t ReceiverReportPacket::maxReportsPerPacket = 31; /* Class methods. */ /** * ReceiverReportPacket::Parse() * @param data - Points to the begining of the incoming RTCP packet. * @param len - Total length of the packet. * @param offset - points to the first Receiver Report in the incoming packet. */ ReceiverReportPacket* ReceiverReportPacket::Parse(const uint8_t* data, size_t len, size_t offset) { MS_TRACE(); // Get the header. auto* header = const_cast(reinterpret_cast(data)); // Ensure there is space for the common header and the SSRC of packet sender. if (len < Packet::CommonHeaderSize + 4u /* ssrc */) { MS_WARN_TAG(rtcp, "not enough space for receiver report packet, packet discarded"); return nullptr; } std::unique_ptr packet(new ReceiverReportPacket(header)); const uint32_t ssrc = Utils::Byte::Get4Bytes(reinterpret_cast(header), Packet::CommonHeaderSize); packet->SetSsrc(ssrc); if (offset == 0) { offset = Packet::CommonHeaderSize + 4u /* ssrc */; } uint8_t count = header->count; while ((count-- != 0u) && (len > offset)) { ReceiverReport* report = ReceiverReport::Parse(data + offset, len - offset); if (report != nullptr) { packet->AddReport(report); offset += report->GetSize(); } else { return packet.release(); } } return packet.release(); } /* Instance methods. */ size_t ReceiverReportPacket::Serialize(uint8_t* buffer) { MS_TRACE(); size_t offset = 0; uint8_t* header = { nullptr }; for (size_t i = 0; i < this->GetCount(); i++) { // Create a new RR packet header for each 31 reports. if (i % maxReportsPerPacket == 0) { // Reference current common header. header = buffer + offset; offset += Packet::Serialize(buffer + offset); // Copy the SSRC. Utils::Byte::Set4Bytes(header, Packet::CommonHeaderSize, this->ssrc); offset += 4u; } // Serialize next report. offset += this->reports[i]->Serialize(buffer + offset); // Adjust the header count field. reinterpret_cast(header)->count = static_cast((i % maxReportsPerPacket) + 1); // Adjust the header length field. size_t length = (Packet::CommonHeaderSize + 4u /* this->ssrc */); length += ReceiverReport::HeaderSize * ((i % maxReportsPerPacket) + 1); reinterpret_cast(header)->length = htons((length / 4) - 1); } return offset; } void ReceiverReportPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, this->ssrc); for (auto* report : this->reports) { report->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/Sdes.cpp ================================================ #define MS_CLASS "RTC::RTCP::Sdes" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/Sdes.hpp" #include "Logger.hpp" #include "Utils.hpp" #include // std::memcpy namespace RTC { namespace RTCP { /* Item Class variables. */ // clang-format off const absl::flat_hash_map SdesItem::Type2String = { { SdesItem::Type::END, "END" }, { SdesItem::Type::CNAME, "CNAME" }, { SdesItem::Type::NAME, "NAME" }, { SdesItem::Type::EMAIL, "EMAIL" }, { SdesItem::Type::PHONE, "PHONE" }, { SdesItem::Type::LOC, "LOC" }, { SdesItem::Type::TOOL, "TOOL" }, { SdesItem::Type::NOTE, "NOTE" }, { SdesItem::Type::PRIV, "PRIV" } }; // clang-format on /* Class methods. */ SdesItem* SdesItem::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Get the header. auto* header = const_cast(reinterpret_cast(data)); // If item type is 0, there is no need for length field (unless padding // is needed). if (len > 0 && header->type == SdesItem::Type::END) { return nullptr; } // data size must be >= header + length value. if (len < HeaderSize || len < HeaderSize + header->length) { MS_WARN_TAG(rtcp, "not enough space for SDES item, discarded"); return nullptr; } return new SdesItem(header); } const std::string& SdesItem::TypeToString(SdesItem::Type type) { static const std::string Unknown("UNKNOWN"); auto it = SdesItem::Type2String.find(type); if (it == SdesItem::Type2String.end()) { return Unknown; } return it->second; } /* Instance methods. */ SdesItem::SdesItem(SdesItem::Type type, size_t len, const char* value) { MS_TRACE(); // Allocate memory. this->raw.reset(new uint8_t[2 + len]); // Update the header pointer. this->header = reinterpret_cast(this->raw.get()); this->header->type = type; this->header->length = len; // Copy the value into raw. std::memcpy(this->header->value, value, this->header->length); } void SdesItem::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " type: %s", SdesItem::TypeToString(this->GetType()).c_str()); MS_DUMP_CLEAN(indentation, " length: %" PRIu8, this->header->length); MS_DUMP_CLEAN(indentation, " value: %.*s", this->header->length, this->header->value); MS_DUMP_CLEAN(indentation, ""); } size_t SdesItem::Serialize(uint8_t* buffer) { MS_TRACE(); // Add minimum header. std::memcpy(buffer, this->header, 2); // Copy the content. std::memcpy(buffer + 2, this->header->value, this->header->length); return 2 + this->header->length; } /* Class methods. */ SdesChunk* SdesChunk::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // data size must be > SSRC field. if (len < 4u /* ssrc */) { MS_WARN_TAG(rtcp, "not enough space for SDES chunk, discarded"); return nullptr; } const uint32_t ssrc = Utils::Byte::Get4Bytes(data, 0); std::unique_ptr chunk(new SdesChunk(ssrc)); size_t offset{ 4u }; /* ssrc */ size_t chunkLength{ 4u }; while (len > offset) { auto* item = SdesItem::Parse(data + offset, len - offset); if (!item) { break; } chunk->AddItem(item); chunkLength += item->GetSize(); offset += item->GetSize(); } // Once all items have been parsed, there must be 1, 2, 3 or 4 null octets // (this is mandatory). The first one (AKA item type 0) means "end of // items", and the others maybe needed for padding the chunk to 4 bytes. // First ensure that there is at least one null octet. if (offset == len || (offset < len && Utils::Byte::Get1Byte(data, offset) != 0u)) { MS_WARN_TAG(rtcp, "invalid SDES chunk (missing mandatory null octet), discarded"); return nullptr; } // So we have the mandatory null octet. ++chunkLength; ++offset; // Then check that there are 0, 1, 2 or 3 (no more) null octets that pad // the chunk to 4 bytes. auto neededAdditionalNullOctets = Utils::Byte::PadTo4Bytes(static_cast(chunkLength)) - static_cast(chunkLength); uint16_t foundAdditionalNullOctets{ 0u }; for (uint16_t i{ 0u }; len > offset && i < neededAdditionalNullOctets; ++i) { if (Utils::Byte::Get1Byte(data, offset) != 0u) { MS_WARN_TAG( rtcp, "invalid SDES chunk (missing additional null octets [needed:%" PRIu16 ", found:%" PRIu16 "]), discarded", neededAdditionalNullOctets, foundAdditionalNullOctets); return nullptr; } ++foundAdditionalNullOctets; ++chunkLength; ++offset; } if (foundAdditionalNullOctets != neededAdditionalNullOctets) { MS_WARN_TAG( rtcp, "invalid SDES chunk (missing additional null octets [needed:%" PRIu16 ", found:%" PRIu16 "]), discarded", neededAdditionalNullOctets, foundAdditionalNullOctets); return nullptr; } return chunk.release(); } /* Instance methods. */ size_t SdesChunk::Serialize(uint8_t* buffer) { MS_TRACE(); // Copy the SSRC. Utils::Byte::Set4Bytes(buffer, 0, this->ssrc); size_t offset{ 4u }; // ssrc. for (auto* item : this->items) { offset += item->Serialize(buffer + offset); } // Add the mandatory null octet. buffer[offset] = 0; ++offset; // 32 bits padding. const size_t padding = (-offset) & 3; for (size_t i{ 0u }; i < padding; ++i) { buffer[offset + i] = 0; } return offset + padding; } void SdesChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, this->ssrc); for (auto* item : this->items) { item->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } /* Static Class members */ size_t SdesPacket::maxChunksPerPacket = 31; /* Class methods. */ SdesPacket* SdesPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Get the header. auto* header = const_cast(reinterpret_cast(data)); std::unique_ptr packet(new SdesPacket(header)); size_t offset = Packet::CommonHeaderSize; uint8_t count = header->count; while (count-- && (len > offset)) { auto* chunk = SdesChunk::Parse(data + offset, len - offset); if (chunk != nullptr) { packet->AddChunk(chunk); offset += chunk->GetSize(); } else { break; } } if (packet->GetCount() != header->count) { MS_WARN_TAG(rtcp, "RTCP count value doesn't match found number of chunks, discarded"); return nullptr; } return packet.release(); } /* Instance methods. */ size_t SdesPacket::Serialize(uint8_t* buffer) { MS_TRACE(); size_t offset = 0; size_t length = 0; uint8_t* header = { nullptr }; for (size_t i{ 0u }; i < this->GetCount(); ++i) { // Create a new SDES packet header for each 31 chunks. if (i % SdesPacket::maxChunksPerPacket == 0) { // Reference current common header. header = buffer + offset; offset += Packet::Serialize(buffer + offset); length = Packet::CommonHeaderSize; } // Serialize the next chunk. auto chunkSize = chunks[i]->Serialize(buffer + offset); offset += chunkSize; length += chunkSize; // Adjust the header count field. reinterpret_cast(header)->count = static_cast((i % SdesPacket::maxChunksPerPacket) + 1); // Adjust the header length field. reinterpret_cast(header)->length = htons((length / 4) - 1); } return offset; } void SdesPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); for (auto* chunk : this->chunks) { chunk->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/SenderReport.cpp ================================================ #define MS_CLASS "RTC::RTCP::SenderReport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/SenderReport.hpp" #include "Logger.hpp" #include // std::memcpy namespace RTC { namespace RTCP { /* Class methods. */ SenderReport* SenderReport::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Get the header. auto* header = const_cast(reinterpret_cast(data)); // Packet size must be >= header size. if (len < HeaderSize) { MS_WARN_TAG(rtcp, "not enough space for sender report, packet discarded"); return nullptr; } return new SenderReport(header); } /* Instance methods. */ void SenderReport::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, GetSsrc()); MS_DUMP_CLEAN(indentation, " ntp sec: %" PRIu32, GetNtpSec()); MS_DUMP_CLEAN(indentation, " ntp frac: %" PRIu32, GetNtpFrac()); MS_DUMP_CLEAN(indentation, " rtp ts: %" PRIu32, GetRtpTs()); MS_DUMP_CLEAN(indentation, " packet count: %" PRIu32, GetPacketCount()); MS_DUMP_CLEAN(indentation, " octet count: %" PRIu32, GetOctetCount()); MS_DUMP_CLEAN(indentation, ""); } size_t SenderReport::Serialize(uint8_t* buffer) { MS_TRACE(); // Copy the header. std::memcpy(buffer, this->header, HeaderSize); return HeaderSize; } /* Class methods. */ SenderReportPacket* SenderReportPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Get the header. auto* header = const_cast(reinterpret_cast(data)); std::unique_ptr packet(new SenderReportPacket(header)); const size_t offset = Packet::CommonHeaderSize; SenderReport* report = SenderReport::Parse(data + offset, len - offset); if (report) { packet->AddReport(report); } return packet.release(); } /* Instance methods. */ size_t SenderReportPacket::Serialize(uint8_t* buffer) { MS_TRACE(); size_t offset{ 0 }; uint8_t* header = { nullptr }; // Serialize packets (common header + 1 report) each. for (auto* report : this->reports) { // Reference current common header. header = buffer + offset; offset += Packet::Serialize(buffer + offset); offset += report->Serialize(buffer + offset); // Adjust the header count field. reinterpret_cast(header)->count = 0; // Adjust the header length field. size_t length = Packet::CommonHeaderSize; length += SenderReport::HeaderSize; reinterpret_cast(header)->length = htons((length / 4) - 1); } return offset; } void SenderReportPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); for (auto* report : this->reports) { report->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/XR.cpp ================================================ #define MS_CLASS "RTC::RTCP::XR" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "Utils.hpp" #include "RTC/RTCP/XrDelaySinceLastRr.hpp" #include "RTC/RTCP/XrReceiverReferenceTime.hpp" namespace RTC { namespace RTCP { /* Class methods. */ ExtendedReportBlock* ExtendedReportBlock::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Get the header. auto* header = const_cast(reinterpret_cast(data)); // Ensure there is space for the common header and the SSRC of packet sender. if (len < Packet::CommonHeaderSize) { MS_WARN_TAG(rtcp, "not enough space for a extended report block, report discarded"); return nullptr; } switch (ExtendedReportBlock::Type(header->blockType)) { case RTC::RTCP::ExtendedReportBlock::Type::RRT: { return ReceiverReferenceTime::Parse(data, len); } case RTC::RTCP::ExtendedReportBlock::Type::DLRR: { return DelaySinceLastRr::Parse(data, len); } default: { MS_DEBUG_TAG(rtcp, "unknown RTCP XR block type [blockType:%" PRIu8 "]", header->blockType); break; } } return nullptr; } /* Instance methods. */ /* Class methods. */ ExtendedReportPacket* ExtendedReportPacket::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Get the header. auto* header = const_cast(reinterpret_cast(data)); // Ensure there is space for the common header and the SSRC of packet sender. if (len < Packet::CommonHeaderSize + 4u) { MS_WARN_TAG(rtcp, "not enough space for a extended report packet, packet discarded"); return nullptr; } std::unique_ptr packet(new ExtendedReportPacket(header)); const uint32_t ssrc = Utils::Byte::Get4Bytes(reinterpret_cast(header), Packet::CommonHeaderSize); packet->SetSsrc(ssrc); auto offset = Packet::CommonHeaderSize + 4u /* ssrc */; while (len > offset) { ExtendedReportBlock* report = ExtendedReportBlock::Parse(data + offset, len - offset); if (report) { packet->AddReport(report); offset += report->GetSize(); } else { return packet.release(); } } return packet.release(); } /* Instance methods. */ size_t ExtendedReportPacket::Serialize(uint8_t* buffer) { MS_TRACE(); size_t offset = Packet::Serialize(buffer); // Copy the SSRC. Utils::Byte::Set4Bytes(buffer, Packet::CommonHeaderSize, this->ssrc); offset += 4u /*ssrc*/; // Serialize reports. for (auto* report : this->reports) { offset += report->Serialize(buffer + offset); } return offset; } void ExtendedReportPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, this->ssrc); for (auto* report : this->reports) { report->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/XrDelaySinceLastRr.cpp ================================================ #define MS_CLASS "RTC::RTCP::XrDelaySinceLastRr" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/XrDelaySinceLastRr.hpp" #include "Logger.hpp" #include // std::memcpy namespace RTC { namespace RTCP { /* Class methods. */ DelaySinceLastRr::SsrcInfo* DelaySinceLastRr::SsrcInfo::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Ensure there is space for the common header and the SSRC of packet sender. if (len < BodySize) { MS_WARN_TAG(rtcp, "not enough space for a extended DSLR sub-block, sub-block discarded"); return nullptr; } // Get the header. auto* body = const_cast(reinterpret_cast(data)); auto* ssrcInfo = new DelaySinceLastRr::SsrcInfo(body); return ssrcInfo; } /* Instance methods. */ void DelaySinceLastRr::SsrcInfo::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, GetSsrc()); MS_DUMP_CLEAN(indentation, " lrr: %" PRIu32, GetLastReceiverReport()); MS_DUMP_CLEAN(indentation, " dlrr: %" PRIu32, GetDelaySinceLastReceiverReport()); MS_DUMP_CLEAN(indentation, ""); } size_t DelaySinceLastRr::SsrcInfo::Serialize(uint8_t* buffer) { MS_TRACE(); // Copy the body. std::memcpy(buffer, this->body, DelaySinceLastRr::SsrcInfo::BodySize); return DelaySinceLastRr::SsrcInfo::BodySize; } /* Class methods. */ DelaySinceLastRr* DelaySinceLastRr::Parse(const uint8_t* data, size_t len) { MS_TRACE(); auto* header = const_cast( reinterpret_cast(data)); std::unique_ptr report(new DelaySinceLastRr(header)); size_t offset{ ExtendedReportBlock::CommonHeaderSize }; uint16_t reportLength = ntohs(header->length) * 4; while (len > offset && reportLength >= DelaySinceLastRr::SsrcInfo::BodySize) { DelaySinceLastRr::SsrcInfo* ssrcInfo = DelaySinceLastRr::SsrcInfo::Parse(data + offset, len - offset); if (ssrcInfo) { report->AddSsrcInfo(ssrcInfo); offset += ssrcInfo->GetSize(); reportLength -= ssrcInfo->GetSize(); } else { return report.release(); } } return report.release(); } /* Instance methods. */ size_t DelaySinceLastRr::Serialize(uint8_t* buffer) { MS_TRACE(); const size_t length = static_cast((SsrcInfo::BodySize * this->ssrcInfos.size() / 4)); // Fill the common header. this->header->blockType = static_cast(this->type); this->header->reserved = 0; this->header->length = htons(length); std::memcpy(buffer, this->header, ExtendedReportBlock::CommonHeaderSize); size_t offset{ ExtendedReportBlock::CommonHeaderSize }; // Serialize sub-blocks. for (auto* ssrcInfo : this->ssrcInfos) { offset += ssrcInfo->Serialize(buffer + offset); } return offset; } void DelaySinceLastRr::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " block type: %" PRIu8, (uint8_t)this->type); MS_DUMP_CLEAN(indentation, " reserved: 0"); MS_DUMP_CLEAN( indentation, " length: %" PRIu16, static_cast((SsrcInfo::BodySize * this->ssrcInfos.size() / 4))); for (auto* ssrcInfo : this->ssrcInfos) { ssrcInfo->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTCP/XrReceiverReferenceTime.cpp ================================================ #define MS_CLASS "RTC::RTCP::XrReceiverReferenceTime" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTCP/XrReceiverReferenceTime.hpp" #include "Logger.hpp" #include // std::memcpy namespace RTC { namespace RTCP { /* Class methods. */ ReceiverReferenceTime* ReceiverReferenceTime::Parse(const uint8_t* data, size_t len) { MS_TRACE(); // Ensure there is space for the common header and the body. if (len < ExtendedReportBlock::CommonHeaderSize + ReceiverReferenceTime::BodySize) { MS_WARN_TAG(rtcp, "not enough space for a extended RRT block, block discarded"); return nullptr; } // Get the header. auto* header = const_cast( reinterpret_cast(data)); return new ReceiverReferenceTime(header); } void ReceiverReferenceTime::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " block type: %" PRIu8, static_cast(this->type)); MS_DUMP_CLEAN(indentation, " reserved: 0"); MS_DUMP_CLEAN(indentation, " length: 2"); MS_DUMP_CLEAN(indentation, " ntp sec: %" PRIu32, GetNtpSec()); MS_DUMP_CLEAN(indentation, " ntp frac: %" PRIu32, GetNtpFrac()); MS_DUMP_CLEAN(indentation, ""); } size_t ReceiverReferenceTime::Serialize(uint8_t* buffer) { MS_TRACE(); // Fill the common header. this->header->blockType = static_cast(this->type); this->header->reserved = 0; this->header->length = htons(2); std::memcpy(buffer, this->header, ExtendedReportBlock::CommonHeaderSize); size_t offset{ ExtendedReportBlock::CommonHeaderSize }; // Copy the body. std::memcpy(buffer + offset, this->body, ReceiverReferenceTime::BodySize); offset += ReceiverReferenceTime::BodySize; return offset; } } // namespace RTCP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/Codecs/AV1.cpp ================================================ #define MS_CLASS "RTC::RTP::Codecs::AV1" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/Codecs/AV1.hpp" #include "Logger.hpp" namespace RTC { namespace RTP { namespace Codecs { /* Class methods. */ AV1::PayloadDescriptor* AV1::Parse( std::unique_ptr& dependencyDescriptor) { MS_TRACE(); if (!dependencyDescriptor) { return nullptr; } auto* payloadDescriptor = new PayloadDescriptor(dependencyDescriptor); return payloadDescriptor; } void AV1::ProcessRtpPacket( RTP::Packet* packet, std::unique_ptr& templateDependencyStructure) { MS_TRACE(); std::unique_ptr dependencyDescriptor; // Read dependency descriptor. packet->ReadDependencyDescriptor(dependencyDescriptor, templateDependencyStructure); PayloadDescriptor* payloadDescriptor = AV1::Parse(dependencyDescriptor); if (!payloadDescriptor) { return; } auto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor); packet->SetPayloadDescriptorHandler(payloadDescriptorHandler); } /* Instance methods. */ AV1::PayloadDescriptor::PayloadDescriptor( std::unique_ptr& dependencyDescriptor) { MS_TRACE(); // Read fields. this->startOfFrame = dependencyDescriptor->startOfFrame; this->endOfFrame = dependencyDescriptor->endOfFrame; this->frameNumber = dependencyDescriptor->frameNumber; this->spatialLayer = dependencyDescriptor->spatialLayer; this->temporalLayer = dependencyDescriptor->temporalLayer; this->isKeyFrame = dependencyDescriptor->isKeyFrame; this->dependencyDescriptor = std::move(dependencyDescriptor); } void AV1::PayloadDescriptor::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); this->dependencyDescriptor->Dump(indentation + 1); MS_DUMP_CLEAN(indentation, ""); } void AV1::PayloadDescriptor::Encode() { MS_TRACE(); if (!this->encoder.has_value()) { MS_WARN_DEV("there is no encoder present"); return; } UpdateActiveDecodeTargets( this->encoder->encodingData.maxSpatialLayer, this->encoder->encodingData.maxTemporalLayer); } void AV1::PayloadDescriptor::Restore() const { MS_TRACE(); // Nothing to do as next time this packet is sent will rewrite // the active decode targets mask. } // NOLINTNEXTLINE(readability-make-member-function-const) void AV1::PayloadDescriptor::UpdateActiveDecodeTargets(uint16_t spatialLayer, uint16_t temporalLayer) { MS_TRACE(); this->dependencyDescriptor->UpdateActiveDecodeTargets(spatialLayer, temporalLayer); } void AV1::PayloadDescriptor::Encoder::Encode(PayloadDescriptor* payloadDescriptor) const { payloadDescriptor->UpdateActiveDecodeTargets( this->encodingData.maxSpatialLayer, this->encodingData.maxTemporalLayer); } AV1::PayloadDescriptorHandler::PayloadDescriptorHandler(AV1::PayloadDescriptor* payloadDescriptor) { MS_TRACE(); this->payloadDescriptor.reset(payloadDescriptor); } bool AV1::PayloadDescriptorHandler::Process( Codecs::EncodingContext* encodingContext, RTP::Packet* /*packet*/, bool& marker) { MS_TRACE(); auto* context = static_cast(encodingContext); MS_ASSERT(context->GetTargetSpatialLayer() >= 0, "target spatial layer cannot be -1"); MS_ASSERT(context->GetTargetTemporalLayer() >= 0, "target temporal layer cannot be -1"); auto packetSpatialLayer = GetSpatialLayer(); auto packetTemporalLayer = GetTemporalLayer(); auto tmpSpatialLayer = context->GetCurrentSpatialLayer(); auto tmpTemporalLayer = context->GetCurrentTemporalLayer(); // If packet spatial or temporal layer is higher than maximum announced // one, drop the packet. if (packetSpatialLayer >= context->GetSpatialLayers() || packetTemporalLayer >= context->GetTemporalLayers()) { MS_WARN_TAG( rtp, "too high packet layers %" PRIu8 ":%" PRIu8, packetSpatialLayer, packetTemporalLayer); return false; } // Upgrade current spatial layer if needed. if (context->GetTargetSpatialLayer() > context->GetCurrentSpatialLayer()) { if (this->payloadDescriptor->isKeyFrame) { MS_DEBUG_DEV( "upgrading tmpSpatialLayer from %" PRIu16 " to %" PRIu16 " (packet:%" PRIu8 ":%" PRIu8 ")", context->GetCurrentSpatialLayer(), context->GetTargetSpatialLayer(), packetSpatialLayer, packetTemporalLayer); tmpSpatialLayer = context->GetTargetSpatialLayer(); tmpTemporalLayer = 0; // Just in case. } } // Downgrade current spatial layer if needed. else if (context->GetTargetSpatialLayer() < context->GetCurrentSpatialLayer()) { if (packetSpatialLayer == context->GetTargetSpatialLayer() && this->payloadDescriptor->endOfFrame) { MS_DEBUG_DEV( "downgrading tmpSpatialLayer from %" PRIu16 " to %" PRIu16 " (packet:%" PRIu8 ":%" PRIu8 ") without keyframe (full SVC)", context->GetCurrentSpatialLayer(), context->GetTargetSpatialLayer(), packetSpatialLayer, packetTemporalLayer); tmpSpatialLayer = context->GetTargetSpatialLayer(); tmpTemporalLayer = 0; // Just in case. } } // Filter spatial layers higher than current one. if (packetSpatialLayer > tmpSpatialLayer) { return false; } // Upgrade current temporal layer if needed. if (context->GetTargetTemporalLayer() > context->GetCurrentTemporalLayer()) { if (packetTemporalLayer > context->GetCurrentTemporalLayer()) { MS_DEBUG_DEV( "upgrading tmpTemporalLayer from %" PRIu16 " to %" PRIu8 " (packet:%" PRIu8 ":%" PRIu8 ")", context->GetCurrentTemporalLayer(), packetTemporalLayer, packetSpatialLayer, packetTemporalLayer); tmpTemporalLayer = packetTemporalLayer; } } // Downgrade current temporal layer if needed. else if (context->GetTargetTemporalLayer() < context->GetCurrentTemporalLayer()) { if (packetTemporalLayer == context->GetTargetTemporalLayer() && this->payloadDescriptor->endOfFrame) { MS_DEBUG_DEV( "downgrading tmpTemporalLayer from %" PRIu16 " to %" PRIu16 " (packet:%" PRIu8 ":%" PRIu8 ")", context->GetCurrentTemporalLayer(), context->GetTargetTemporalLayer(), packetSpatialLayer, packetTemporalLayer); tmpTemporalLayer = packetTemporalLayer; } } // Filter temporal layers higher than current one. if (packetTemporalLayer > tmpTemporalLayer) { return false; } // Set marker bit if needed. if (packetSpatialLayer == tmpSpatialLayer && this->payloadDescriptor->endOfFrame) { marker = true; } // Update current spatial layer if needed. if (tmpSpatialLayer != context->GetCurrentSpatialLayer()) { context->SetCurrentSpatialLayer(tmpSpatialLayer); } // Update current temporal layer if needed. if (tmpTemporalLayer != context->GetCurrentTemporalLayer()) { context->SetCurrentTemporalLayer(tmpTemporalLayer); } // TODO: Enable once we rewrite the Dependency Descriptor header extension. // // Store the encoding data for retransmissions. // this->payloadDescriptor->CreateEncoder({ // static_cast(context->GetCurrentSpatialLayer()), // static_cast(context->GetCurrentTemporalLayer()) // }); // this->payloadDescriptor->Encode(); return true; } void AV1::PayloadDescriptorHandler::Encode( RTP::Packet* /*packet*/, Codecs::PayloadDescriptor::Encoder* encoder) { MS_TRACE(); auto* av1Encoder = static_cast(encoder); av1Encoder->Encode(this->payloadDescriptor.get()); } void AV1::PayloadDescriptorHandler::Restore(RTP::Packet* /*packet*/) { MS_TRACE(); this->payloadDescriptor->Restore(); } } // namespace Codecs } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/Codecs/DependencyDescriptor.cpp ================================================ #define MS_CLASS "RTC::Codecs::DependencyDescriptor" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/Codecs/DependencyDescriptor.hpp" #include "Logger.hpp" #include #include namespace RTC { namespace RTP { namespace Codecs { /* Static members. */ // clang-format off std::unordered_map DependencyDescriptor::dtiToString = { { DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT, "-" }, { DependencyDescriptor::DecodeTargetIndication::DISCARDABLE, "D" }, { DependencyDescriptor::DecodeTargetIndication::SWITCH, "S" }, { DependencyDescriptor::DecodeTargetIndication::REQUIRED, "R" }, }; // clang-format on /* Class methods. */ DependencyDescriptor* DependencyDescriptor::Parse( const uint8_t* data, size_t len, DependencyDescriptor::Listener* listener, std::unique_ptr& templateDependencyStructure) { MS_TRACE(); if (len < 3) { MS_WARN_DEV("ignoring payload with length < 3"); return nullptr; } if (len > Consts::TwoBytesRtpExtensionMaxLength) { MS_WARN_DEV("ignoring payload with length > %" PRIu8, Consts::TwoBytesRtpExtensionMaxLength); return nullptr; } if (templateDependencyStructure == nullptr) { templateDependencyStructure = std::make_unique(); } std::unique_ptr dependencyDescriptor( new DependencyDescriptor(data, len, listener, templateDependencyStructure.get())); if (!dependencyDescriptor->ReadMandatoryDescriptorFields()) { MS_WARN_DEV("failed to read mandatory fields"); return nullptr; } if (len > 3) { if (!dependencyDescriptor->ReadExtendedDescriptorFields()) { MS_WARN_DEV("failed to read extended fields"); return nullptr; } } if (!dependencyDescriptor->ReadFrameDependencyDefinition()) { MS_WARN_DEV("failed to read frame dependency definition"); return nullptr; } return dependencyDescriptor.release(); } /* Instance methods. */ DependencyDescriptor::DependencyDescriptor( const uint8_t* data, size_t len, DependencyDescriptor::Listener* listener, TemplateDependencyStructure* templateDependencyStructure) : templateDependencyStructure(templateDependencyStructure), listener(listener), bitStream(const_cast(data), len) { MS_TRACE(); } uint8_t DependencyDescriptor::GetSpatialLayer() const { return this->templateDependencyStructure->templateLayers[this->templateId].spatialLayer; } uint8_t DependencyDescriptor::GetTemporalLayer() const { return this->templateDependencyStructure->templateLayers[this->templateId].temporalLayer; } void DependencyDescriptor::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " startOfFrame: %s", this->startOfFrame ? "true" : "false"); MS_DUMP_CLEAN(indentation, " endOfFrame: %s", this->endOfFrame ? "true" : "false"); MS_DUMP_CLEAN(indentation, " frameDependencyTemplateId: %u", this->frameDependencyTemplateId); MS_DUMP_CLEAN(indentation, " frameNumber: %u", this->frameNumber); MS_DUMP_CLEAN(indentation, " templateId: %u", this->templateId); MS_DUMP_CLEAN(indentation, " spatialLayer: %u", this->spatialLayer); MS_DUMP_CLEAN(indentation, " temporalLayer: %u", this->temporalLayer); if (this->isKeyFrame) { MS_DUMP_CLEAN(indentation + 1, ""); MS_DUMP_CLEAN( indentation + 1, " spatialLayers: %u", this->templateDependencyStructure->spatialLayers); MS_DUMP_CLEAN( indentation + 1, " temporalLayers: %u", this->templateDependencyStructure->temporalLayers); MS_DUMP_CLEAN( indentation + 1, " templateIdOffset: %u", this->templateDependencyStructure->templateIdOffset); MS_DUMP_CLEAN( indentation + 1, " decodeTargetCount: %u", this->templateDependencyStructure->decodeTargetCount); MS_DUMP_CLEAN(indentation + 2, ""); for (const auto& layer : this->templateDependencyStructure->templateLayers) { MS_DUMP_CLEAN(indentation + 3, ""); MS_DUMP_CLEAN(indentation + 3, " spatialLayerId: %u", layer.spatialLayer); MS_DUMP_CLEAN(indentation + 3, " temporalLayerId: %u", layer.temporalLayer); std::string dtis; for (const auto& dti : layer.decodeTargetIndications) { dtis += dtiToString[dti]; } MS_DUMP_CLEAN(indentation + 3, " decodeTargetIndications: %s", dtis.c_str()); std::string fdiffs; for (const auto& fdiff : layer.frameDiffs) { if (!fdiffs.empty()) { fdiffs += ","; } fdiffs += std::to_string(fdiff); } MS_DUMP_CLEAN(indentation + 3, " frameDiffs: %s", fdiffs.c_str()); std::string fdiffChains; for (const auto& fdiffChain : layer.frameDiffChains) { if (!fdiffChains.empty()) { fdiffChains += ","; } fdiffChains += std::to_string(fdiffChain); } MS_DUMP_CLEAN(indentation + 3, " frameDiffChains: %s", fdiffChains.c_str()); MS_DUMP_CLEAN(indentation + 3, ""); } MS_DUMP_CLEAN(indentation + 2, ""); MS_DUMP_CLEAN(indentation + 1, ""); } if (this->activeDecodeTargetsBitmask.has_value()) { MS_DUMP_CLEAN( indentation + 1, "activeDecodeTargetsBitmask: %s", std::bitset<32>(this->activeDecodeTargetsBitmask.value()).to_string().c_str()); } MS_DUMP_CLEAN(indentation, ""); } void DependencyDescriptor::UpdateListener(DependencyDescriptor::Listener* listener) { this->listener = listener; } bool DependencyDescriptor::ReadMandatoryDescriptorFields() { MS_TRACE(); if (this->bitStream.GetLeftBits() < 24) { return false; } this->startOfFrame = this->bitStream.GetBit(); this->endOfFrame = this->bitStream.GetBit(); this->frameDependencyTemplateId = this->bitStream.GetBits(6); this->frameNumber = this->bitStream.GetBits(16); return true; } bool DependencyDescriptor::ReadExtendedDescriptorFields() { MS_TRACE(); if (this->bitStream.GetLeftBits() < 5) { return false; } auto templateDependencyStructurePresentFlag = this->bitStream.GetBit(); auto activeDecodeTargetLayersPresentFlag = this->bitStream.GetBit(); // Advance 3 positios due to non interesting fields. bitStream.SkipBits(3); if (templateDependencyStructurePresentFlag) { if (!ReadTemplateDependencyStructure()) { return false; } this->activeDecodeTargetsBitmask = (1 << this->templateDependencyStructure->decodeTargetCount) - 1; } if (activeDecodeTargetLayersPresentFlag) { if (this->bitStream.GetLeftBits() < this->templateDependencyStructure->decodeTargetCount) { return false; } this->activeDecodeTargetsBitmask = this->bitStream.GetBits(this->templateDependencyStructure->decodeTargetCount); } return true; } bool DependencyDescriptor::ReadTemplateDependencyStructure() { MS_TRACE(); if (this->bitStream.GetLeftBits() < 11) { return false; } this->templateDependencyStructure->templateIdOffset = this->bitStream.GetBits(6); this->templateDependencyStructure->decodeTargetCount = this->bitStream.GetBits(5) + 1; if (!ReadTemplateLayers()) { return false; } if (!ReadTemplateDecodeTargetIndications()) { return false; } if (!ReadTemplateFrameDiffs()) { return false; } if (!ReadTemplateFrameDiffChains()) { return false; } return true; } bool DependencyDescriptor::ReadTemplateLayers() { MS_TRACE(); uint8_t temporalId = 0; uint8_t spatialId = 0; uint32_t nextLayerIdc = 0; this->templateDependencyStructure->templateLayers.clear(); // Set the key frame flag. this->isKeyFrame = true; do { this->templateDependencyStructure->templateLayers.emplace_back(spatialId, temporalId); if (this->bitStream.GetLeftBits() < 2) { return false; } nextLayerIdc = this->bitStream.GetBits(2); // nextLayerIdc == 0, same spatialId and temporalId. if (nextLayerIdc == 1) { temporalId++; } else if (nextLayerIdc == 2) { temporalId = 0; spatialId++; } } while (nextLayerIdc != 3); this->templateDependencyStructure->spatialLayers = spatialId; this->templateDependencyStructure->temporalLayers = temporalId; return true; } bool DependencyDescriptor::ReadTemplateDecodeTargetIndications() { MS_TRACE(); auto templateCount = this->templateDependencyStructure->templateLayers.size(); for (size_t templateIndex = 0; templateIndex < templateCount; ++templateIndex) { for (uint8_t dtIndex = 0; dtIndex < this->templateDependencyStructure->decodeTargetCount; ++dtIndex) { if (this->bitStream.GetLeftBits() < 2) { return false; } this->templateDependencyStructure->templateLayers[templateIndex] .decodeTargetIndications.push_back( static_cast(this->bitStream.GetBits(2))); } } return true; } bool DependencyDescriptor::ReadTemplateFrameDiffs() { MS_TRACE(); auto templateCount = this->templateDependencyStructure->templateLayers.size(); for (size_t templateIndex = 0; templateIndex < templateCount; ++templateIndex) { if (this->bitStream.GetLeftBits() < 1) { return false; } bool followsFlag = this->bitStream.GetBit(); while (followsFlag) { if (this->bitStream.GetLeftBits() < 5) { return false; } const uint8_t fdiff = this->bitStream.GetBits(4) + 1; this->templateDependencyStructure->templateLayers[templateIndex].frameDiffs.push_back( fdiff); followsFlag = this->bitStream.GetBit(); } } return true; } bool DependencyDescriptor::ReadTemplateFrameDiffChains() { MS_TRACE(); auto chainCount = this->bitStream.ReadNs(this->templateDependencyStructure->decodeTargetCount + 1); if (!chainCount.has_value()) { return false; } if (chainCount == 0) { return true; } for (uint8_t dtIndex = 0; dtIndex < this->templateDependencyStructure->decodeTargetCount; ++dtIndex) { auto chain = this->bitStream.ReadNs(chainCount.value()); if (!chain.has_value()) { return false; } this->decodeTargetProtectedBy.push_back(chain.value()); } auto templateCount = this->templateDependencyStructure->templateLayers.size(); for (size_t templateIndex = 0; templateIndex < templateCount; ++templateIndex) { for (uint32_t chainIndex = 0; chainIndex < chainCount; ++chainIndex) { if (this->bitStream.GetLeftBits() < 4) { return false; } this->templateDependencyStructure->templateLayers[templateIndex].frameDiffChains.push_back( this->bitStream.GetBits(4)); } } return true; } bool DependencyDescriptor::ReadFrameDependencyDefinition() { MS_TRACE(); const uint8_t templateIndex = (this->frameDependencyTemplateId + 64 - this->templateDependencyStructure->templateIdOffset) % 64; if (this->templateDependencyStructure->templateLayers.size() <= templateIndex) { MS_WARN_DEV("invalid template index %u", templateIndex); return false; } this->templateId = templateIndex; // Retrieve spatial and temporal layers. this->spatialLayer = this->templateDependencyStructure->templateLayers[this->templateId].spatialLayer; this->temporalLayer = this->templateDependencyStructure->templateLayers[this->templateId].temporalLayer; return true; } const uint8_t* DependencyDescriptor::Serialize(uint8_t& len) { MS_TRACE(); MS_ASSERT(!this->isKeyFrame, "serialization of key frames is not supported"); this->bitStream.Reset(); WriteMandatoryDescriptorFields(); WriteExtendedDescriptorFields(); len = std::ceil((this->bitStream.GetOffset() + 7) >> 3); return this->bitStream.GetData(); } bool DependencyDescriptor::WriteMandatoryDescriptorFields() { MS_TRACE(); this->bitStream.PutBit(this->startOfFrame ? 1 : 0); this->bitStream.PutBit(this->endOfFrame ? 1 : 0); this->bitStream.PutBits(6, this->frameDependencyTemplateId); this->bitStream.PutBits(16, this->frameNumber); return true; } bool DependencyDescriptor::WriteExtendedDescriptorFields() { MS_TRACE(); // Template dependency structure present flag. this->bitStream.PutBit(0); // Active decode targets present flag. this->bitStream.PutBit(this->activeDecodeTargetsBitmask.has_value() ? 1 : 0); // Custom dtis flag. this->bitStream.PutBit(0); // Custom fdiffs flag. this->bitStream.PutBit(0); // Custom chains flag. this->bitStream.PutBit(0); // NOTE: Write template dependency structure if ever needed. // Active Decode Targets if (this->activeDecodeTargetsBitmask.has_value()) { this->bitStream.PutBits( this->templateDependencyStructure->decodeTargetCount, this->activeDecodeTargetsBitmask.value()); } return true; } bool DependencyDescriptor::UpdateActiveDecodeTargets( uint32_t maxSpatialLayer, uint32_t maxTemporalLayer) { MS_TRACE(); // We don't update active decode targets for key frames, // as by definition they enable all decode targets. if (this->isKeyFrame) { this->listener->OnDependencyDescriptorUpdated( this->bitStream.GetData(), this->bitStream.GetLength()); return true; } auto availableSpatialLayers = this->templateDependencyStructure->spatialLayers; auto availableTemporalLayers = this->templateDependencyStructure->temporalLayers; MS_ASSERT( maxSpatialLayer <= availableSpatialLayers, "maxSpatialLayer must be less than or equal to availableSpatialLayers"); MS_ASSERT( maxTemporalLayer <= availableTemporalLayers, "maxTemporalLayer must be less than or equal to availableTemporalLayers"); uint32_t activeDecodeTargetsBitmask = 0; size_t bitIndex = 0; for (uint32_t spatialLayer = 0; spatialLayer <= maxSpatialLayer; ++spatialLayer) { for (uint32_t temporalLayer = 0; temporalLayer <= availableTemporalLayers; ++temporalLayer) { if (temporalLayer <= maxTemporalLayer) { // Set a 1. activeDecodeTargetsBitmask |= (1 << bitIndex); } else { // Set a 0. activeDecodeTargetsBitmask &= ~(1 << bitIndex); } bitIndex += 1; } } this->activeDecodeTargetsBitmask = activeDecodeTargetsBitmask; uint8_t len; const auto* data = this->Serialize(len); this->listener->OnDependencyDescriptorUpdated(data, len); return true; } } // namespace Codecs } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/Codecs/H264.cpp ================================================ #define MS_CLASS "RTC::RTP::Codecs::H264" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/Codecs/H264.hpp" #include "Logger.hpp" #include "Utils.hpp" namespace RTC { namespace RTP { namespace Codecs { /* Class methods. */ H264::PayloadDescriptor* H264::Parse( const uint8_t* data, size_t len, Codecs::DependencyDescriptor* dependencyDescriptor) { MS_TRACE(); std::unique_ptr payloadDescriptor(new PayloadDescriptor()); if (dependencyDescriptor) { // Read fields. payloadDescriptor->startOfFrame = dependencyDescriptor->startOfFrame; payloadDescriptor->endOfFrame = dependencyDescriptor->endOfFrame; payloadDescriptor->spatialLayer = dependencyDescriptor->spatialLayer; payloadDescriptor->temporalLayer = dependencyDescriptor->temporalLayer; payloadDescriptor->isKeyFrame = dependencyDescriptor->isKeyFrame; } else { payloadDescriptor->isKeyFrame = IsKeyFrame(data, len); } return payloadDescriptor.release(); } bool H264::IsKeyFrame(const uint8_t* data, size_t len) { MS_TRACE(); if (len < 2) { MS_WARN_DEV("ignoring payload with length < 2"); return false; } const uint8_t nal = *data & 0x1F; switch (nal) { // Single NAL unit packet. // IDR (instantaneous decoding picture). case 7: { return true; } // Aggreation packet. // STAP-A. case 24: { size_t offset{ 1 }; len -= 1; // Iterate NAL units. while (len >= 3) { auto naluSize = Utils::Byte::Get2Bytes(data, offset); const uint8_t subnal = *(data + offset + sizeof(naluSize)) & 0x1F; if (subnal == 7) { return true; } // Check if there is room for the indicated NAL unit size. if (len < (naluSize + sizeof(naluSize))) { break; } offset += naluSize + sizeof(naluSize); len -= naluSize + sizeof(naluSize); } break; } // Aggreation packet. // FU-A, FU-B. case 28: case 29: { const uint8_t subnal = *(data + 1) & 0x1F; const uint8_t startBit = *(data + 1) & 0x80; if (subnal == 7 && startBit == 128) { return true; } break; } default:; } return false; } void H264::ProcessRtpPacket( RTP::Packet* packet, std::unique_ptr& templateDependencyStructure) { MS_TRACE(); auto* data = packet->GetPayload(); auto len = packet->GetPayloadLength(); std::unique_ptr dependencyDescriptor; // Read dependency descriptor. packet->ReadDependencyDescriptor(dependencyDescriptor, templateDependencyStructure); PayloadDescriptor* payloadDescriptor = H264::Parse(data, len, dependencyDescriptor.get()); if (!payloadDescriptor) { return; } auto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor); packet->SetPayloadDescriptorHandler(payloadDescriptorHandler); } /* Instance methods. */ void H264::PayloadDescriptor::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN( indentation, " startOfFrame:%" PRIu8 "|endOfFrame:%" PRIu8, this->startOfFrame, this->endOfFrame); MS_DUMP_CLEAN(indentation, " spatialLayer:%" PRIu8, this->spatialLayer); MS_DUMP_CLEAN(indentation, " temporalLayer:%" PRIu8, this->temporalLayer); MS_DUMP_CLEAN(indentation, " isKeyFrame: %s", this->isKeyFrame ? "true" : "false"); MS_DUMP_CLEAN(indentation, ""); } H264::PayloadDescriptorHandler::PayloadDescriptorHandler(H264::PayloadDescriptor* payloadDescriptor) { MS_TRACE(); this->payloadDescriptor.reset(payloadDescriptor); } bool H264::PayloadDescriptorHandler::Process( Codecs::EncodingContext* encodingContext, RTP::Packet* /*packet*/, bool& /*marker*/) { MS_TRACE(); auto* context = static_cast(encodingContext); MS_ASSERT(context->GetTargetTemporalLayer() >= 0, "target temporal layer cannot be -1"); if (this->payloadDescriptor->temporalLayer > context->GetTargetTemporalLayer()) { return false; } // Update/fix current temporal layer. if (this->payloadDescriptor->temporalLayer > context->GetCurrentTemporalLayer()) { context->SetCurrentTemporalLayer(this->payloadDescriptor->temporalLayer); } if (context->GetCurrentTemporalLayer() > context->GetTargetTemporalLayer()) { context->SetCurrentTemporalLayer(context->GetTargetTemporalLayer()); } return true; } } // namespace Codecs } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/Codecs/Opus.cpp ================================================ #define MS_CLASS "RTC::RTP::Codecs::Opus" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/Codecs/Opus.hpp" #include "Logger.hpp" namespace RTC { namespace RTP { namespace Codecs { /* Class methods. */ Opus::PayloadDescriptor* Opus::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < 1) { MS_WARN_DEV("ignoring empty payload"); return nullptr; } std::unique_ptr payloadDescriptor(new PayloadDescriptor()); const uint8_t byte = data[0]; payloadDescriptor->stereo = (byte >> 2) & 0x01; payloadDescriptor->code = byte & 0x03; switch (payloadDescriptor->code) { case 0: case 1: { // In code 0 and 1 packets, DTX is determined by total length = 1 (TOC // byte only). if (len == 1) { payloadDescriptor->isDtx = true; } break; } case 2: { // As per spec, a 1-byte code 2 packet is always invalid. if (len == 1) { MS_WARN_DEV("ignoring invalid payload (1)"); return nullptr; } // In code 2 packets, DTX is determined by total length = 2 (TOC byte // only). Per spec, the only valid 2-byte code 2 packet is one where // the length of both frames is zero. if (len == 2) { payloadDescriptor->isDtx = true; } break; } case 3: { // As per spec, a 1-byte code 3 packet is always invalid. if (len == 1) { MS_WARN_DEV("ignoring invalid payload (2)"); return nullptr; } // A code 3 packet can never be DTX. break; } default:; } return payloadDescriptor.release(); } void Opus::ProcessRtpPacket(RTP::Packet* packet) { MS_TRACE(); auto* data = packet->GetPayload(); auto len = packet->GetPayloadLength(); PayloadDescriptor* payloadDescriptor = Opus::Parse(data, len); if (!payloadDescriptor) { return; } auto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor); packet->SetPayloadDescriptorHandler(payloadDescriptorHandler); } /* Instance methods. */ void Opus::PayloadDescriptor::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " stereo: %" PRIu8, this->stereo); MS_DUMP_CLEAN(indentation, " code: %" PRIu8, this->code); MS_DUMP_CLEAN(indentation, " is dtx: %s", this->isDtx ? "true" : "false"); MS_DUMP_CLEAN(indentation, ""); } Opus::PayloadDescriptorHandler::PayloadDescriptorHandler(Opus::PayloadDescriptor* payloadDescriptor) { MS_TRACE(); this->payloadDescriptor.reset(payloadDescriptor); } bool Opus::PayloadDescriptorHandler::Process( Codecs::EncodingContext* encodingContext, RTP::Packet* /*packet*/, bool& /*marker*/) { MS_TRACE(); auto* context = static_cast(encodingContext); return !(this->payloadDescriptor->isDtx && context->GetIgnoreDtx()); }; } // namespace Codecs } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/Codecs/VP8.cpp ================================================ #define MS_CLASS "RTC::RTP::Codecs::VP8" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/Codecs/VP8.hpp" #include "Logger.hpp" #include // std::memcpy() namespace RTC { namespace RTP { namespace Codecs { /* Class methods. */ VP8::PayloadDescriptor* VP8::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < 1) { MS_WARN_DEV("ignoring empty payload"); return nullptr; } std::unique_ptr payloadDescriptor(new PayloadDescriptor()); size_t offset{ 0 }; uint8_t byte = data[offset]; payloadDescriptor->extended = (byte >> 7) & 0x01; payloadDescriptor->nonReference = (byte >> 5) & 0x01; payloadDescriptor->start = (byte >> 4) & 0x01; payloadDescriptor->partitionIndex = byte & 0x07; if (payloadDescriptor->extended) { if (len < ++offset + 1) { MS_WARN_DEV("ignoring invalid payload (2)"); return nullptr; } byte = data[offset]; payloadDescriptor->i = (byte >> 7) & 0x01; payloadDescriptor->l = (byte >> 6) & 0x01; payloadDescriptor->t = (byte >> 5) & 0x01; payloadDescriptor->k = (byte >> 4) & 0x01; } if (payloadDescriptor->i) { if (len < ++offset + 1) { MS_WARN_DEV("ignoring invalid payload (3)"); return nullptr; } byte = data[offset]; if ((byte >> 7) & 0x01) { if (len < ++offset + 1) { MS_WARN_DEV("ignoring invalid payload (4)"); return nullptr; } payloadDescriptor->hasTwoBytesPictureId = true; payloadDescriptor->pictureId = (byte & 0x7F) << 8; payloadDescriptor->pictureId += data[offset]; } else { payloadDescriptor->hasOneBytePictureId = true; payloadDescriptor->pictureId = byte & 0x7F; } payloadDescriptor->hasPictureId = true; } if (payloadDescriptor->l) { if (len < ++offset + 1) { MS_WARN_DEV("ignoring invalid payload (5)"); return nullptr; } payloadDescriptor->hasTl0PictureIndex = true; payloadDescriptor->tl0PictureIndex = data[offset]; } if (payloadDescriptor->t || payloadDescriptor->k) { if (len < ++offset + 1) { MS_WARN_DEV("ignoring invalid payload (6)"); return nullptr; } byte = data[offset]; payloadDescriptor->hasTlIndex = true; payloadDescriptor->tlIndex = (byte >> 6) & 0x03; payloadDescriptor->y = (byte >> 5) & 0x01; payloadDescriptor->keyIndex = byte & 0x1F; } if ( // NOLINTNEXTLINE(bugprone-inc-dec-in-conditions) (len >= ++offset + 1) && payloadDescriptor->start && payloadDescriptor->partitionIndex == 0 && (!(data[offset] & 0x01)) // Inverse Keyframe bit. ) { payloadDescriptor->isKeyFrame = true; } return payloadDescriptor.release(); } void VP8::ProcessRtpPacket(RTP::Packet* packet) { MS_TRACE(); auto* data = packet->GetPayload(); auto len = packet->GetPayloadLength(); PayloadDescriptor* payloadDescriptor = VP8::Parse(data, len); if (!payloadDescriptor) { return; } auto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor); packet->SetPayloadDescriptorHandler(payloadDescriptorHandler); // Modify the RtpPacket payload in order to always have two byte pictureId. if (payloadDescriptor->hasOneBytePictureId) { // Shift the RTP payload one byte from the begining of the pictureId field. packet->ShiftPayload(2, 1); // Set the two byte pictureId marker bit. data[2] = 0x80; // Update the payloadDescriptor. payloadDescriptor->hasOneBytePictureId = false; payloadDescriptor->hasTwoBytesPictureId = true; } } /* Instance methods. */ void VP8::PayloadDescriptor::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN( indentation, " i:%" PRIu8 "|l:%" PRIu8 "|t:%" PRIu8 "|k:%" PRIu8, this->i, this->l, this->t, this->k); MS_DUMP_CLEAN(indentation, " extended: %" PRIu8, this->extended); MS_DUMP_CLEAN(indentation, " nonReference: %" PRIu8, this->nonReference); MS_DUMP_CLEAN(indentation, " start: %" PRIu8, this->start); MS_DUMP_CLEAN(indentation, " partitionIndex: %" PRIu8, this->partitionIndex); MS_DUMP_CLEAN(indentation, " pictureId: %" PRIu16, this->pictureId); MS_DUMP_CLEAN(indentation, " tl0PictureIndex: %" PRIu8, this->tl0PictureIndex); MS_DUMP_CLEAN(indentation, " tlIndex: %" PRIu8, this->tlIndex); MS_DUMP_CLEAN(indentation, " y: %" PRIu8, this->y); MS_DUMP_CLEAN(indentation, " keyIndex: %" PRIu8, this->keyIndex); MS_DUMP_CLEAN(indentation, " isKeyFrame: %s", this->isKeyFrame ? "true" : "false"); MS_DUMP_CLEAN(indentation, " hasPictureId: %s", this->hasPictureId ? "true" : "false"); MS_DUMP_CLEAN( indentation, " hasOneBytePictureId: %s", this->hasOneBytePictureId ? "true" : "false"); MS_DUMP_CLEAN( indentation, " hasTwoBytesPictureId: %s", this->hasTwoBytesPictureId ? "true" : "false"); MS_DUMP_CLEAN( indentation, " hasTl0PictureIndex: %s", this->hasTl0PictureIndex ? "true" : "false"); MS_DUMP_CLEAN(indentation, " hasTlIndex: %s", this->hasTlIndex ? "true" : "false"); MS_DUMP_CLEAN(indentation, ""); } void VP8::PayloadDescriptor::Encode(uint8_t* data, uint16_t pictureId, uint8_t tl0PictureIndex) const { MS_TRACE(); if (data == nullptr) { MS_ABORT("data is nullptr"); } // Nothing to do. if (!this->extended) { return; } data += 2; if (this->i) { if (this->hasTwoBytesPictureId) { uint16_t netPictureId = htons(pictureId); std::memcpy(data, &netPictureId, 2); data[0] |= 0x80; data += 2; } else if (this->hasOneBytePictureId) { *data = pictureId; data++; if (pictureId > 127) { MS_DEBUG_TAG(rtp, "casting pictureId value to one byte"); } } } if (this->l) { *data = tl0PictureIndex; } } void VP8::PayloadDescriptor::Encode(uint8_t* data) const { MS_TRACE(); if (this->encoder == std::nullopt) { return; } this->encoder->Encode(data, this); } void VP8::PayloadDescriptor::Restore(uint8_t* data) const { MS_TRACE(); if (this->hasPictureId && this->hasTl0PictureIndex) { Encode(data, this->pictureId, this->tl0PictureIndex); } } void VP8::PayloadDescriptor::Encoder::Encode( uint8_t* data, const PayloadDescriptor* payloadDescriptor) const { payloadDescriptor->Encode( data, this->encodingData.pictureId, this->encodingData.tl0PictureIndex); } VP8::PayloadDescriptorHandler::PayloadDescriptorHandler(VP8::PayloadDescriptor* payloadDescriptor) { MS_TRACE(); this->payloadDescriptor.reset(payloadDescriptor); } bool VP8::PayloadDescriptorHandler::Process( Codecs::EncodingContext* encodingContext, RTP::Packet* packet, bool& /*marker*/) { MS_TRACE(); auto* context = static_cast(encodingContext); MS_ASSERT(context->GetTargetTemporalLayer() >= 0, "target temporal layer cannot be -1"); // Check if the payload should contain temporal layer info. if (context->GetTemporalLayers() > 1 && !this->payloadDescriptor->hasTlIndex) { MS_WARN_DEV("stream is supposed to have >1 temporal layers but does not have TlIndex field"); } // Check whether pictureId and tl0PictureIndex sync is required. if ( context->syncRequired && this->payloadDescriptor->hasPictureId && this->payloadDescriptor->hasTl0PictureIndex) { context->pictureIdManager.Sync(this->payloadDescriptor->pictureId - 1); context->tl0PictureIndexManager.Sync(this->payloadDescriptor->tl0PictureIndex - 1); context->syncRequired = false; } // Incremental pictureId. Check the temporal layer. if ( this->payloadDescriptor->hasPictureId && this->payloadDescriptor->hasTlIndex && this->payloadDescriptor->hasTl0PictureIndex && !RTC::SeqManager::IsSeqLowerThan( this->payloadDescriptor->pictureId, context->pictureIdManager.GetMaxInput())) { // Drop if: // - Temporal layer is higher than target. // - Temporal layer is higher than current and sync flag is not set. if ( this->payloadDescriptor->tlIndex > context->GetTargetTemporalLayer() || (this->payloadDescriptor->tlIndex > context->GetCurrentTemporalLayer() && !this->payloadDescriptor->y)) { context->pictureIdManager.Drop(this->payloadDescriptor->pictureId); if (this->payloadDescriptor->tlIndex == 0) { context->tl0PictureIndexManager.Drop(this->payloadDescriptor->tl0PictureIndex); } return false; } } // Update pictureId and tl0PictureIndex values. uint16_t pictureId; uint8_t tl0PictureIndex; // Do not send a dropped pictureId. if ( this->payloadDescriptor->hasPictureId && !context->pictureIdManager.Input(this->payloadDescriptor->pictureId, pictureId)) { return false; } // Do not send a dropped tl0PictureIndex. if ( this->payloadDescriptor->hasTl0PictureIndex && !context->tl0PictureIndexManager.Input( this->payloadDescriptor->tl0PictureIndex, tl0PictureIndex)) { return false; } // Update/fix current temporal layer. if (this->payloadDescriptor->hasTlIndex && this->payloadDescriptor->tlIndex == context->GetTargetTemporalLayer()) { context->SetCurrentTemporalLayer(this->payloadDescriptor->tlIndex); } else if (!this->payloadDescriptor->hasTlIndex) { context->SetCurrentTemporalLayer(0); } if (context->GetCurrentTemporalLayer() > context->GetTargetTemporalLayer()) { context->SetCurrentTemporalLayer(context->GetTargetTemporalLayer()); } // Do not send tlIndex higher than current one. if (this->payloadDescriptor->tlIndex > context->GetCurrentTemporalLayer()) { return false; } if (this->payloadDescriptor->hasPictureId && this->payloadDescriptor->hasTl0PictureIndex) { // Store the encoding data for retransmissions. this->payloadDescriptor->CreateEncoder({ pictureId, tl0PictureIndex }); this->payloadDescriptor->Encode(packet->GetPayload()); } return true; }; void VP8::PayloadDescriptorHandler::Encode( RTP::Packet* packet, Codecs::PayloadDescriptor::Encoder* encoder) { MS_TRACE(); auto* vp8Encoder = static_cast(encoder); vp8Encoder->Encode(packet->GetPayload(), this->payloadDescriptor.get()); } void VP8::PayloadDescriptorHandler::Restore(RTP::Packet* packet) { MS_TRACE(); if (this->payloadDescriptor->hasPictureId && this->payloadDescriptor->hasTl0PictureIndex) { this->payloadDescriptor->Restore(packet->GetPayload()); } } } // namespace Codecs } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/Codecs/VP9.cpp ================================================ #define MS_CLASS "RTC::RTP::Codecs::VP9" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/Codecs/VP9.hpp" #include "Logger.hpp" namespace RTC { namespace RTP { namespace Codecs { /* Class methods. */ VP9::PayloadDescriptor* VP9::Parse(const uint8_t* data, size_t len) { MS_TRACE(); if (len < 1) { MS_WARN_DEV("ignoring empty payload"); return nullptr; } std::unique_ptr payloadDescriptor(new PayloadDescriptor()); size_t offset{ 0 }; uint8_t byte = data[offset]; payloadDescriptor->i = (byte >> 7) & 0x01; payloadDescriptor->p = (byte >> 6) & 0x01; payloadDescriptor->l = (byte >> 5) & 0x01; payloadDescriptor->f = (byte >> 4) & 0x01; payloadDescriptor->b = (byte >> 3) & 0x01; payloadDescriptor->e = (byte >> 2) & 0x01; payloadDescriptor->v = (byte >> 1) & 0x01; if (payloadDescriptor->i) { if (len < ++offset + 1) { MS_WARN_DEV("ignoring invalid payload (1)"); return nullptr; } byte = data[offset]; if (byte >> 7 & 0x01) { if (len < ++offset + 1) { MS_WARN_DEV("ignoring invalid payload (2)"); return nullptr; } payloadDescriptor->pictureId = (byte & 0x7F) << 8; payloadDescriptor->pictureId += data[offset]; payloadDescriptor->hasTwoBytesPictureId = true; } else { payloadDescriptor->pictureId = byte & 0x7F; payloadDescriptor->hasOneBytePictureId = true; } payloadDescriptor->hasPictureId = true; } if (payloadDescriptor->l) { if (len < ++offset + 1) { MS_WARN_DEV("ignoring invalid payload (3)"); return nullptr; } byte = data[offset]; payloadDescriptor->interLayerDependency = byte & 0x01; payloadDescriptor->switchingUpPoint = byte >> 4 & 0x01; payloadDescriptor->slIndex = byte >> 1 & 0x07; payloadDescriptor->tlIndex = byte >> 5 & 0x07; payloadDescriptor->hasSlIndex = true; payloadDescriptor->hasTlIndex = true; if (len < ++offset + 1) { MS_WARN_DEV("ignoring invalid payload (4)"); return nullptr; } // Read TL0PICIDX if flexible mode is unset. if (!payloadDescriptor->f) { payloadDescriptor->tl0PictureIndex = data[offset]; payloadDescriptor->hasTl0PictureIndex = true; } } if (!payloadDescriptor->p && payloadDescriptor->b && payloadDescriptor->slIndex == 0) { payloadDescriptor->isKeyFrame = true; } return payloadDescriptor.release(); } void VP9::ProcessRtpPacket(RTP::Packet* packet) { MS_TRACE(); auto* data = packet->GetPayload(); auto len = packet->GetPayloadLength(); PayloadDescriptor* payloadDescriptor = VP9::Parse(data, len); if (!payloadDescriptor) { return; } if (payloadDescriptor->isKeyFrame) { MS_DEBUG_DEV( "key frame [spatialLayer:%" PRIu8 ", temporalLayer:%" PRIu8 "]", packet->GetSpatialLayer(), packet->GetTemporalLayer()); } auto* payloadDescriptorHandler = new PayloadDescriptorHandler(payloadDescriptor); packet->SetPayloadDescriptorHandler(payloadDescriptorHandler); } /* Instance methods. */ void VP9::PayloadDescriptor::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN( indentation, " i:%" PRIu8 "|p:%" PRIu8 "|l:%" PRIu8 "|f:%" PRIu8 "|b:%" PRIu8 "|e:%" PRIu8 "|v:%" PRIu8, this->i, this->p, this->l, this->f, this->b, this->e, this->v); MS_DUMP_CLEAN(indentation, " pictureId: %" PRIu16, this->pictureId); MS_DUMP_CLEAN(indentation, " slIndex: %" PRIu8, this->slIndex); MS_DUMP_CLEAN(indentation, " tlIndex: %" PRIu8, this->tlIndex); MS_DUMP_CLEAN(indentation, " tl0PictureIndex: %" PRIu8, this->tl0PictureIndex); MS_DUMP_CLEAN(indentation, " interLayerDependency: %" PRIu8, this->interLayerDependency); MS_DUMP_CLEAN(indentation, " switchingUpPoint: %" PRIu8, this->switchingUpPoint); MS_DUMP_CLEAN(indentation, " isKeyFrame: %s", this->isKeyFrame ? "true" : "false"); MS_DUMP_CLEAN(indentation, " hasPictureId: %s", this->hasPictureId ? "true" : "false"); MS_DUMP_CLEAN( indentation, " hasOneBytePictureId: %s", this->hasOneBytePictureId ? "true" : "false"); MS_DUMP_CLEAN( indentation, " hasTwoBytesPictureId: %s", this->hasTwoBytesPictureId ? "true" : "false"); MS_DUMP_CLEAN( indentation, " hasTl0PictureIndex: %s", this->hasTl0PictureIndex ? "true" : "false"); MS_DUMP_CLEAN(indentation, " hasSlIndex: %s", this->hasSlIndex ? "true" : "false"); MS_DUMP_CLEAN(indentation, " hasTlIndex: %s", this->hasTlIndex ? "true" : "false"); MS_DUMP_CLEAN(indentation, ""); } VP9::PayloadDescriptorHandler::PayloadDescriptorHandler(VP9::PayloadDescriptor* payloadDescriptor) { MS_TRACE(); this->payloadDescriptor.reset(payloadDescriptor); } bool VP9::PayloadDescriptorHandler::Process( Codecs::EncodingContext* encodingContext, RTP::Packet* /*packet*/, bool& marker) { MS_TRACE(); auto* context = static_cast(encodingContext); MS_ASSERT(context->GetTargetSpatialLayer() >= 0, "target spatial layer cannot be -1"); MS_ASSERT(context->GetTargetTemporalLayer() >= 0, "target temporal layer cannot be -1"); auto packetSpatialLayer = GetSpatialLayer(); auto packetTemporalLayer = GetTemporalLayer(); auto tmpSpatialLayer = context->GetCurrentSpatialLayer(); auto tmpTemporalLayer = context->GetCurrentTemporalLayer(); // If packet spatial or temporal layer is higher than maximum announced // one, drop the packet. if (packetSpatialLayer >= context->GetSpatialLayers() || packetTemporalLayer >= context->GetTemporalLayers()) { MS_WARN_TAG( rtp, "too high packet layers %" PRIu8 ":%" PRIu8, packetSpatialLayer, packetTemporalLayer); return false; } // Check whether pictureId sync is required. if (context->syncRequired && this->payloadDescriptor->hasPictureId) { context->pictureIdManager.Sync(this->payloadDescriptor->pictureId - 1); context->syncRequired = false; } const bool isOldPacket = (this->payloadDescriptor->hasPictureId && RTC::SeqManager::IsSeqLowerThan( this->payloadDescriptor->pictureId, context->pictureIdManager.GetMaxInput())); if (!isOldPacket) { // Upgrade current spatial layer if needed. if (context->GetTargetSpatialLayer() > context->GetCurrentSpatialLayer()) { if (this->payloadDescriptor->isKeyFrame) { MS_DEBUG_DEV( "upgrading tmpSpatialLayer from %" PRIu16 " to %" PRIu16 " (packet:%" PRIu8 ":%" PRIu8 ")", context->GetCurrentSpatialLayer(), context->GetTargetSpatialLayer(), packetSpatialLayer, packetTemporalLayer); tmpSpatialLayer = context->GetTargetSpatialLayer(); tmpTemporalLayer = 0; // Just in case. } } // Downgrade current spatial layer if needed. else if (context->GetTargetSpatialLayer() < context->GetCurrentSpatialLayer()) { // In K-SVC we must wait for a keyframe. if (context->IsKSvc()) { if (this->payloadDescriptor->isKeyFrame) { MS_DEBUG_DEV( "downgrading tmpSpatialLayer from %" PRIu16 " to %" PRIu16 " (packet:%" PRIu8 ":%" PRIu8 ") after keyframe (K-SVC)", context->GetCurrentSpatialLayer(), context->GetTargetSpatialLayer(), packetSpatialLayer, packetTemporalLayer); tmpSpatialLayer = context->GetTargetSpatialLayer(); tmpTemporalLayer = 0; // Just in case. } } // In full SVC we do not need a keyframe. else { if (packetSpatialLayer == context->GetTargetSpatialLayer() && this->payloadDescriptor->e) { MS_DEBUG_DEV( "downgrading tmpSpatialLayer from %" PRIu16 " to %" PRIu16 " (packet:%" PRIu8 ":%" PRIu8 ") without keyframe (full SVC)", context->GetCurrentSpatialLayer(), context->GetTargetSpatialLayer(), packetSpatialLayer, packetTemporalLayer); tmpSpatialLayer = context->GetTargetSpatialLayer(); tmpTemporalLayer = 0; // Just in case. } } } } // Filter spatial layers that are either // * higher than current one // * different than the current one when KSVC is enabled and this is not a keyframe // (interframe p bit = 1) const uint16_t spatialLayerForPictureId = isOldPacket ? context->GetSpatialLayerForPictureId(this->payloadDescriptor->pictureId) : tmpSpatialLayer; if ( packetSpatialLayer > spatialLayerForPictureId || (context->IsKSvc() && this->payloadDescriptor->p && packetSpatialLayer != spatialLayerForPictureId)) { return false; } // Check and handle temporal layer (unless old packet). if (!isOldPacket) { // Upgrade current temporal layer if needed. if (context->GetTargetTemporalLayer() > context->GetCurrentTemporalLayer()) { if ( packetTemporalLayer >= context->GetCurrentTemporalLayer() + 1 && (context->GetCurrentTemporalLayer() == -1 || this->payloadDescriptor->switchingUpPoint) && this->payloadDescriptor->b) { MS_DEBUG_DEV( "upgrading tmpTemporalLayer from %" PRIu16 " to %" PRIu8 " (packet:%" PRIu8 ":%" PRIu8 ")", context->GetCurrentTemporalLayer(), packetTemporalLayer, packetSpatialLayer, packetTemporalLayer); tmpTemporalLayer = packetTemporalLayer; } } // Downgrade current temporal layer if needed. else if (context->GetTargetTemporalLayer() < context->GetCurrentTemporalLayer()) { if (packetTemporalLayer == context->GetTargetTemporalLayer() && this->payloadDescriptor->e) { MS_DEBUG_DEV( "downgrading tmpTemporalLayer from %" PRIu16 " to %" PRIu16 " (packet:%" PRIu8 ":%" PRIu8 ")", context->GetCurrentTemporalLayer(), context->GetTargetTemporalLayer(), packetSpatialLayer, packetTemporalLayer); tmpTemporalLayer = context->GetTargetTemporalLayer(); } } } // Filter temporal layers higher than current one. const uint16_t temporalLayerForPictureId = isOldPacket ? context->GetTemporalLayerForPictureId(this->payloadDescriptor->pictureId) : tmpTemporalLayer; if (packetTemporalLayer > temporalLayerForPictureId) { return false; } // Set marker bit if needed. if (packetSpatialLayer == tmpSpatialLayer && this->payloadDescriptor->e) { marker = true; } // Update the pictureId manager. if (this->payloadDescriptor->hasPictureId) { uint16_t pictureId; context->pictureIdManager.Input(this->payloadDescriptor->pictureId, pictureId); } // Update current spatial layer if needed. if (tmpSpatialLayer != context->GetCurrentSpatialLayer()) { context->SetCurrentSpatialLayer(tmpSpatialLayer, this->payloadDescriptor->pictureId); } // Update current temporal layer if needed. if (tmpTemporalLayer != context->GetCurrentTemporalLayer()) { context->SetCurrentTemporalLayer(tmpTemporalLayer, this->payloadDescriptor->pictureId); } return true; } } // namespace Codecs } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/Packet.cpp ================================================ #define MS_CLASS "RTC::RTP::Packet" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/Packet.hpp" #ifdef MS_RTC_LOGGER_RTP #endif #include "DepLibUV.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/Consts.hpp" #include // std::memmove(), std::memset() #include // std::ostream_iterator #include // std::ostringstream namespace RTC { namespace RTP { /* Class variables. */ thread_local uint32_t Packet::nextMediasoupPacketId{ Utils::Crypto::GetRandomUInt( 0, std::numeric_limits::max() / 2) }; /* Class methods. */ bool Packet::IsRtp(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); return ( bufferLength >= Packet::FixedHeaderMinLength && // @see RFC 7983. (buffer[0] > 127 && buffer[0] < 192) && // RTP Version must be 2. (buffer[0] >> 6) == 2); } Packet* Packet::Parse(const uint8_t* buffer, size_t packetLength, size_t bufferLength) { MS_TRACE(); if (packetLength > bufferLength) { MS_THROW_TYPE_ERROR( "packetLength (%zu bytes) cannot be bigger than bufferLength (%zu bytes)", packetLength, bufferLength); } if (!Packet::IsRtp(buffer, packetLength)) { MS_WARN_TAG(rtp, "not a RTP Packet"); return nullptr; } auto* packet = new Packet(const_cast(buffer), bufferLength); packet->SetLength(packetLength); if (!packet->Validate(/*storeExtensions*/ true)) { delete packet; return nullptr; } return packet; } Packet* Packet::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (!Packet::IsRtp(buffer, bufferLength)) { MS_WARN_TAG(rtp, "not a RTP Packet"); return nullptr; } auto* packet = new Packet(const_cast(buffer), bufferLength); packet->SetLength(bufferLength); if (!packet->Validate(/*storeExtensions*/ true)) { delete packet; return nullptr; } return packet; } Packet* Packet::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Packet::FixedHeaderMinLength) { MS_THROW_TYPE_ERROR("no space for fixed header"); } auto* packet = new Packet(buffer, bufferLength); auto* fixedHeader = packet->GetFixedHeaderPointer(); fixedHeader->version = 2; fixedHeader->padding = 0; fixedHeader->extension = 0; fixedHeader->csrcCount = 0; fixedHeader->marker = 0; fixedHeader->payloadType = 0; fixedHeader->sequenceNumber = 0; fixedHeader->timestamp = 0; fixedHeader->ssrc = 0; // No need to invoke SetLength() since constructor invoked it with // minimum Packet length. return packet; } uint32_t Packet::GetNextMediasoupPacketId() { MS_TRACE(); return Packet::nextMediasoupPacketId++; } /* Instance methods. */ Packet::Packet(uint8_t* buffer, size_t bufferLength) : Serializable(buffer, bufferLength) { MS_TRACE(); SetLength(Packet::FixedHeaderMinLength); #ifdef MS_RTC_LOGGER_RTP // Initialize logger. // NOTE: Here we use DepLibUV directly since `this->logger` doesn't // have any purpose during tests. this->logger.timestamp = DepLibUV::GetTimeMs(); this->logger.recvRtpTimestamp = GetTimestamp(); this->logger.recvSeqNumber = GetSequenceNumber(); #endif } Packet::~Packet() { MS_TRACE(); } void Packet::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " length: %zu (buffer length: %zu)", GetLength(), GetBufferLength()); MS_DUMP_CLEAN(indentation, " sequence number: %" PRIu16, GetSequenceNumber()); MS_DUMP_CLEAN(indentation, " timestamp: %" PRIu32, GetTimestamp()); MS_DUMP_CLEAN(indentation, " marker: %s", HasMarker() ? "true" : "false"); MS_DUMP_CLEAN(indentation, " payload type: %" PRIu8, GetPayloadType()); MS_DUMP_CLEAN(indentation, " ssrc: %" PRIu32, GetSsrc()); MS_DUMP_CLEAN(indentation, " csrcs: %s", HasCsrcs() ? "true" : "false"); if (HasHeaderExtension()) { MS_DUMP_CLEAN( indentation, " header extension: id:%" PRIu16 ", value length:%zu", GetHeaderExtensionId(), GetHeaderExtensionValueLength()); } if (HasExtensions()) { std::vector extIds; std::ostringstream extIdsStream; if (HasOneByteExtensions()) { for (const auto offset : this->oneByteExtensions) { if (offset == -1) { continue; } auto* extension = reinterpret_cast(GetHeaderExtensionValue() + offset); extIds.push_back( "{id:" + std::to_string(extension->id) + ", len:" + std::to_string(extension->len + 1) + "}"); } } else { extIds.reserve(this->twoBytesExtensions.size()); for (const auto& kv : this->twoBytesExtensions) { const auto offset = kv.second; if (offset == -1) { continue; } auto* extension = reinterpret_cast(GetHeaderExtensionValue() + offset); extIds.push_back( "{id:" + std::to_string(extension->id) + ", len:" + std::to_string(extension->len) + "}"); } } if (!extIds.empty()) { std::copy( extIds.begin(), extIds.end() - 1, std::ostream_iterator(extIdsStream, ", ")); extIdsStream << extIds.back(); MS_DUMP_CLEAN( indentation, " RFC5285 extensions (%s): %s", HasOneByteExtensions() ? "One-Byte" : "Two-Bytes", extIdsStream.str().c_str()); } MS_DUMP_CLEAN(indentation + 1, ""); { std::string mid; if (ReadMid(mid)) { MS_DUMP_CLEAN( indentation + 1, " mid: id:%" PRIu8 ", value:'%s'", this->headerExtensionIds.mid, mid.c_str()); } } { std::string rid; if (ReadRid(rid)) { MS_DUMP_CLEAN( indentation + 1, " rid: id:%" PRIu8 ", value:'%s'", this->headerExtensionIds.rid, rid.c_str()); } } { std::string rrid; if (ReadRid(rrid)) { MS_DUMP_CLEAN( indentation + 1, " rrid: id:%" PRIu8 ", value:'%s'", this->headerExtensionIds.rrid, rrid.c_str()); } } { uint32_t absSendtime; if (ReadAbsSendTime(absSendtime)) { MS_DUMP_CLEAN( indentation + 1, " absSendTime: id:%" PRIu8 ", value:%" PRIu32, this->headerExtensionIds.absSendTime, absSendtime); } } { uint16_t wideSeqNumber{ 0 }; if (ReadTransportWideCc01(wideSeqNumber)) { MS_DUMP_CLEAN( indentation + 1, " transportWideCc01: id:%" PRIu8 ", value:%" PRIu16, this->headerExtensionIds.transportWideCc01, wideSeqNumber); } } { uint8_t volume{ 0 }; bool voice{ false }; if (ReadSsrcAudioLevel(volume, voice)) { MS_DUMP_CLEAN( indentation + 1, " ssrcAudioLevel: id:%" PRIu8 ", volume:%" PRIu8 ", voice:%s", this->headerExtensionIds.ssrcAudioLevel, volume, voice ? "true" : "false"); } } { uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.dependencyDescriptor, extenLen); if (extenValue) { MS_DUMP_CLEAN( indentation + 1, " dependencyDescriptor: id:%" PRIu8 ", length:%" PRIu8, this->headerExtensionIds.dependencyDescriptor, extenLen); } } { bool camera{ false }; bool flip{ false }; uint16_t rotation{ 0 }; if (ReadVideoOrientation(camera, flip, rotation)) { MS_DUMP_CLEAN( indentation + 1, " videoOrientation: id:%" PRIu8 ", camera:%s, flip:%s, rotation:%" PRIu16, this->headerExtensionIds.videoOrientation, camera ? "true" : "false", flip ? "true" : "false", rotation); } } { uint64_t absCaptureTimestamp{ 0 }; int64_t estimatedCaptureClockOffset{ 0 }; if (ReadAbsCaptureTime(absCaptureTimestamp, estimatedCaptureClockOffset)) { MS_DUMP_CLEAN( indentation + 1, " absCaptureTime: id:%" PRIu8 ", absCaptureTimestamp:%" PRIu64 ", estimatedCaptureClockOffset:%" PRId64, this->headerExtensionIds.absCaptureTime, absCaptureTimestamp, estimatedCaptureClockOffset); } } { uint16_t minDelay{ 0 }; uint16_t maxDelay{ 0 }; if (ReadPlayoutDelay(minDelay, maxDelay)) { MS_DUMP_CLEAN( indentation + 1, " playoutDelay: id:%" PRIu8 ", minDelay:%" PRIu16 ", maxDelay:%" PRIu16, this->headerExtensionIds.playoutDelay, minDelay, maxDelay); } } { uint32_t mediasoupPacketId{ 0 }; if (ReadMediasoupPacketId(mediasoupPacketId)) { MS_DUMP_CLEAN( indentation + 1, " mediasoupPacketId: id:%" PRIu8 ", mediasoupPacketId:%" PRIu32, this->headerExtensionIds.mediasoupPacketId, mediasoupPacketId); } } MS_DUMP_CLEAN(indentation + 1, ""); } MS_DUMP_CLEAN(indentation, " payload length: %zu", GetPayloadLength()); MS_DUMP_CLEAN(indentation, " padding length: %" PRIu8, GetPaddingLength()); MS_DUMP_CLEAN(indentation, " padded to 4 bytes: %s", IsPaddedTo4Bytes() ? "yes" : "no"); if (this->payloadDescriptorHandler) { MS_DUMP_CLEAN(indentation + 1, ""); MS_DUMP_CLEAN(indentation + 1, " key frame: %s", IsKeyFrame() ? "true" : "false"); MS_DUMP_CLEAN(indentation + 1, " spatial layer: %" PRIu8, GetSpatialLayer()); MS_DUMP_CLEAN(indentation + 1, " temporal layer: %" PRIu8, GetTemporalLayer()); #ifdef MS_DUMP_RTP_PAYLOAD_DESCRIPTOR this->payloadDescriptorHandler->Dump(indentation + 2); #endif MS_DUMP_CLEAN(indentation + 1, ""); } MS_DUMP_CLEAN(indentation, ""); } Packet* Packet::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedPacket = new Packet(buffer, bufferLength); Serializable::CloneInto(clonedPacket); // Clone Extension containers. clonedPacket->oneByteExtensions = this->oneByteExtensions; clonedPacket->twoBytesExtensions = this->twoBytesExtensions; // Clone Extension ids. clonedPacket->headerExtensionIds = this->headerExtensionIds; // Assign the payload descriptor handler. clonedPacket->payloadDescriptorHandler = this->payloadDescriptorHandler; if (this->payloadDescriptorHandler) { clonedPacket->payloadDescriptorHandler->RtpPacketChanged(clonedPacket); } return clonedPacket; } flatbuffers::Offset Packet::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add mid. std::string mid; ReadMid(mid); // Add rid. std::string rid; ReadRid(rid); // Add rrid. std::string rrid; ReadRid(rrid); // Add wideSequenceNumber. uint16_t wideSequenceNumber{ 0 }; bool wideSequenceNumberSet{ false }; if (ReadTransportWideCc01(wideSequenceNumber)) { wideSequenceNumberSet = true; } return FBS::RtpPacket::CreateDumpDirect( builder, this->GetPayloadType(), this->GetSequenceNumber(), this->GetTimestamp(), this->HasMarker(), this->GetSsrc(), this->IsKeyFrame(), this->GetLength(), this->GetPayloadLength(), this->GetSpatialLayer(), this->GetTemporalLayer(), mid.empty() ? nullptr : mid.c_str(), rid.empty() ? nullptr : rid.c_str(), rrid.empty() ? nullptr : rrid.c_str(), wideSequenceNumberSet ? flatbuffers::Optional(wideSequenceNumber) : flatbuffers::nullopt); } void Packet::SetPayloadType(uint8_t payloadType) { MS_TRACE(); GetFixedHeaderPointer()->payloadType = payloadType; } void Packet::SetMarker(bool marker) { MS_TRACE(); GetFixedHeaderPointer()->marker = marker; } void Packet::SetSequenceNumber(uint16_t seq) { MS_TRACE(); GetFixedHeaderPointer()->sequenceNumber = htons(seq); } void Packet::SetTimestamp(uint32_t timestamp) { MS_TRACE(); GetFixedHeaderPointer()->timestamp = htonl(timestamp); } void Packet::SetSsrc(uint32_t ssrc) { MS_TRACE(); GetFixedHeaderPointer()->ssrc = htonl(ssrc); } void Packet::RemoveHeaderExtension() { MS_TRACE(); if (!HasHeaderExtension()) { return; } // Clear One-Byte and Two-Bytes Extensions. std::fill(std::begin(this->oneByteExtensions), std::end(this->oneByteExtensions), -1); this->twoBytesExtensions.clear(); const auto headerExtensionLength = GetHeaderExtensionLength(); auto* payload = GetPayloadPointer(); const auto payloadLength = GetPayloadLength(); const auto paddingLength = GetPaddingLength(); // Update Packet length. // NOTE: This throws if given length is higher than buffer length. SetLength(GetLength() - headerExtensionLength); // Unset the Header Extension flag. GetFixedHeaderPointer()->extension = 0; // Shift the payload. std::memmove(payload - headerExtensionLength, payload, payloadLength + paddingLength); } void Packet::SetExtensions(ExtensionsType type, const std::vector& extensions) { MS_TRACE(); // Clear One-Byte and Two-Bytes Extensions. std::fill(std::begin(this->oneByteExtensions), std::end(this->oneByteExtensions), -1); this->twoBytesExtensions.clear(); // Reset Extension ids. this->headerExtensionIds = {}; const auto hadHeaderExtension = HasHeaderExtension(); const auto previousHeaderExtensionValueLength = GetHeaderExtensionValueLength(); // If no explicit ExtensionType is given then select the best one based // on given Extensions. if (type == ExtensionsType::Auto) { uint8_t highestId{ 0 }; uint8_t highestLen{ 0 }; for (const auto& extension : extensions) { highestId = std::max(extension.id, highestId); highestLen = std::max(extension.len, highestLen); } type = highestId <= 14 && highestLen > 0 && highestLen <= 16 ? ExtensionsType::OneByte : ExtensionsType::TwoBytes; MS_DEBUG_DEV( "using %" PRIu8 " byte(s) extensions [highestId:%" PRIu8 ", highestLen:%" PRIu8 "]", type, highestId, highestLen); } // If One-Byte is requested and the Packet already has One-Byte Extensions, // keep the Header Extension id. if (type == ExtensionsType::OneByte && HasOneByteExtensions()) { // Nothing to do. } // If Two-Bytes is requested and the Packet already has Two-Bytes Extensions, // keep the Header Extension id. else if (type == ExtensionsType::TwoBytes && HasTwoBytesExtensions()) { // Nothing to do. } // Otherwise, if there is Header Extension of non matching type, modify its id. else if (hadHeaderExtension) { if (type == ExtensionsType::OneByte) { GetHeaderExtensionPointer()->id = htons(0xBEDE); } else if (type == ExtensionsType::TwoBytes) { GetHeaderExtensionPointer()->id = htons(0b0001000000000000); } } // Calculate total length required for all Extensions (with padding if needed). size_t extensionsLength{ 0 }; if (type == ExtensionsType::OneByte) { for (const auto& extension : extensions) { if (extension.id == 0) { MS_THROW_TYPE_ERROR("invalid Extension with id 0"); } else if (extension.id > 14) { MS_THROW_TYPE_ERROR( "invalid Extension with id %" PRIu8 " > 14 when using One-Byte Extensions", extension.id); } else if (extension.len == 0) { MS_THROW_TYPE_ERROR( "invalid Extension with id %" PRIu8 " and length 0 when using One-Byte Extensions", extension.id); } else if (extension.len > 16) { MS_THROW_TYPE_ERROR( "invalid Extension with id %" PRIu8 " and length %" PRIu8 " when using One-Byte Extensions", extension.id, extension.len); } extensionsLength += (1 + extension.len); } } else if (type == ExtensionsType::TwoBytes) { for (const auto& extension : extensions) { if (extension.id == 0) { MS_THROW_TYPE_ERROR("invalid Extension with id 0"); } extensionsLength += (2 + extension.len); } } auto paddedExtensionsLength = Utils::Byte::PadTo4Bytes(extensionsLength); const size_t extensionsPaddingLength = paddedExtensionsLength - extensionsLength; // Calculate the number of bytes to shift (may be negative if the Packet // already had Header Extension). int16_t shift{ 0 }; if (hadHeaderExtension) { shift = static_cast(paddedExtensionsLength - previousHeaderExtensionValueLength); } else { shift = 4 + static_cast(paddedExtensionsLength); } auto* payload = GetPayloadPointer(); const auto payloadLength = GetPayloadLength(); const auto paddingLength = GetPaddingLength(); if (hadHeaderExtension && shift != 0) { // Update Packet length. // NOTE: This throws if given length is higher than buffer length. SetLength(GetLength() + shift); // Update the Header Extension length. GetHeaderExtensionPointer()->len = htons(paddedExtensionsLength / 4); // Shift the payload. std::memmove(payload + shift, payload, payloadLength + paddingLength); } else if (!hadHeaderExtension) { // Update Packet length. // NOTE: This throws if given length is higher than buffer length. SetLength(GetLength() + shift); // Set the Header Extension flag. GetFixedHeaderPointer()->extension = 1; auto* headerExtension = GetHeaderExtensionPointer(); // Shift the payload. // NOTE: We need to move payload before code below, otherwise we would // override written bytes later. std::memmove(payload + shift, payload, payloadLength + paddingLength); // Set the Header Extension id. if (type == ExtensionsType::OneByte) { headerExtension->id = htons(0xBEDE); } else if (type == ExtensionsType::TwoBytes) { headerExtension->id = htons(0b0001000000000000); } // Set the Header Extension length. headerExtension->len = htons(paddedExtensionsLength / 4); } const uint8_t* extensionsStart = GetHeaderExtensionValue(); auto* ptr = const_cast(extensionsStart); if (type == ExtensionsType::OneByte) { for (const auto& extension : extensions) { // Store the One-Byte Extension offset in the array. // `-1` because we have 14 elements total 0..13 and `id` is in the // range 1..14. this->oneByteExtensions[extension.id - 1] = ptr - extensionsStart; *ptr = (extension.id << 4) | ((extension.len - 1) & 0x0F); ++ptr; std::memmove(ptr, extension.value, extension.len); ptr += extension.len; } } else if (type == ExtensionsType::TwoBytes) { for (const auto& extension : extensions) { // Store the Two-Bytes Extension offset in the map. this->twoBytesExtensions[extension.id] = ptr - extensionsStart; *ptr = extension.id; ++ptr; *ptr = extension.len; ++ptr; std::memmove(ptr, extension.value, extension.len); ptr += extension.len; } } for (size_t i = 0; i < extensionsPaddingLength; ++i) { *ptr = 0; ++ptr; } // Assign Extension ids. for (const auto& extension : extensions) { switch (extension.type) { case RTC::RtpHeaderExtensionUri::Type::MID: { this->headerExtensionIds.mid = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID: { this->headerExtensionIds.rid = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID: { this->headerExtensionIds.rrid = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME: { this->headerExtensionIds.absSendTime = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01: { this->headerExtensionIds.transportWideCc01 = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL: { this->headerExtensionIds.ssrcAudioLevel = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR: { this->headerExtensionIds.dependencyDescriptor = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION: { this->headerExtensionIds.videoOrientation = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET: { this->headerExtensionIds.timeOffset = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME: { this->headerExtensionIds.absCaptureTime = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY: { this->headerExtensionIds.playoutDelay = extension.id; break; } case RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID: { this->headerExtensionIds.mediasoupPacketId = extension.id; break; } } } } void Packet::AssignExtensionIds(RTP::HeaderExtensionIds& headerExtensionIds) { MS_TRACE(); // Reset Extension ids. this->headerExtensionIds = headerExtensionIds; } bool Packet::ReadMid(std::string& mid) const { MS_TRACE(); if (this->headerExtensionIds.mid == 0) { return false; } uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.mid, extenLen); if (!extenValue || extenLen == 0) { return false; } mid.assign(reinterpret_cast(extenValue), static_cast(extenLen)); return true; } bool Packet::UpdateMid(const std::string& mid) { MS_TRACE(); uint8_t extenLen; uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.mid, extenLen); if (!extenValue) { return false; } const size_t midLen = mid.length(); // Here we assume that there is MidRtpExtensionMaxLength available bytes, // even if now they are padding bytes. if (midLen > RTC::Consts::MidRtpExtensionMaxLength) { MS_ERROR( "no enough space for MID value [MidRtpExtensionMaxLength:%" PRIu8 ", mid:'%s']", RTC::Consts::MidRtpExtensionMaxLength, mid.c_str()); return false; } std::memcpy(extenValue, mid.c_str(), midLen); SetExtensionLength(this->headerExtensionIds.mid, midLen); return true; } bool Packet::ReadRid(std::string& rid) const { MS_TRACE(); if (this->headerExtensionIds.rid == 0 && this->headerExtensionIds.rrid == 0) { return false; } // First try with the RID id then with the Repaired RID id. uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.rid, extenLen); if (extenValue && extenLen > 0) { rid.assign(reinterpret_cast(extenValue), static_cast(extenLen)); return true; } extenValue = GetExtensionValue(this->headerExtensionIds.rrid, extenLen); if (extenValue && extenLen > 0) { rid.assign(reinterpret_cast(extenValue), static_cast(extenLen)); return true; } return false; } bool Packet::ReadAbsSendTime(uint32_t& absSendtime) const { MS_TRACE(); if (this->headerExtensionIds.absSendTime == 0) { return false; } uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.absSendTime, extenLen); if (!extenValue || extenLen != 3u) { return false; } absSendtime = Utils::Byte::Get3Bytes(extenValue, 0); return true; } bool Packet::UpdateAbsSendTime(uint64_t ms) const { MS_TRACE(); uint8_t extenLen; uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.absSendTime, extenLen); if (!extenValue || extenLen != 3u) { return false; } auto absSendTime = Utils::Time::TimeMsToAbsSendTime(ms); Utils::Byte::Set3Bytes(extenValue, 0, absSendTime); return true; } bool Packet::ReadTransportWideCc01(uint16_t& wideSeqNumber) const { MS_TRACE(); if (this->headerExtensionIds.transportWideCc01 == 0) { return false; } uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.transportWideCc01, extenLen); if (!extenValue || extenLen != 2u) { return false; } wideSeqNumber = Utils::Byte::Get2Bytes(extenValue, 0); return true; } bool Packet::UpdateTransportWideCc01(uint16_t wideSeqNumber) const { MS_TRACE(); uint8_t extenLen; uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.transportWideCc01, extenLen); if (!extenValue || extenLen != 2u) { return false; } Utils::Byte::Set2Bytes(extenValue, 0, wideSeqNumber); return true; } bool Packet::ReadSsrcAudioLevel(uint8_t& volume, bool& voice) const { MS_TRACE(); if (this->headerExtensionIds.ssrcAudioLevel == 0) { return false; } uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.ssrcAudioLevel, extenLen); if (!extenValue || extenLen != 1u) { return false; } volume = Utils::Byte::Get1Byte(extenValue, 0); voice = (volume & (1 << 7)) != 0; volume &= ~(1 << 7); return true; } bool Packet::ReadDependencyDescriptor( std::unique_ptr& dependencyDescriptor, std::unique_ptr& templateDependencyStructure) const { MS_TRACE(); if (this->headerExtensionIds.dependencyDescriptor == 0) { return false; } uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.dependencyDescriptor, extenLen); auto* value = Codecs::DependencyDescriptor::Parse( extenValue, extenLen, const_cast(this), templateDependencyStructure); if (!value) { return false; } dependencyDescriptor.reset(value); return true; } bool Packet::UpdateDependencyDescriptor(const uint8_t* data, size_t len) { MS_TRACE(); uint8_t extenLen; uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.dependencyDescriptor, extenLen); if (!extenValue) { MS_WARN_TAG(rtp, "dependency description not found"); return false; } std::memcpy(extenValue, data, len); SetExtensionLength(this->headerExtensionIds.dependencyDescriptor, len); return true; } bool Packet::ReadVideoOrientation(bool& camera, bool& flip, uint16_t& rotation) const { MS_TRACE(); if (this->headerExtensionIds.videoOrientation == 0) { return false; } uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.videoOrientation, extenLen); if (!extenValue || extenLen != 1u) { return false; } const uint8_t cvoByte = Utils::Byte::Get1Byte(extenValue, 0); const uint8_t cameraValue = ((cvoByte & 0b00001000) >> 3); const uint8_t flipValue = ((cvoByte & 0b00000100) >> 2); const uint8_t rotationValue = (cvoByte & 0b00000011); camera = cameraValue != 0; flip = flipValue != 0; // Using counter clockwise values. switch (rotationValue) { case 3: { rotation = 270; break; } case 2: { rotation = 180; break; } case 1: { rotation = 90; break; } default: { rotation = 0; } } return true; } bool Packet::ReadAbsCaptureTime( uint64_t& absCaptureTimestamp, int64_t& estimatedCaptureClockOffset) const { MS_TRACE(); if (this->headerExtensionIds.absCaptureTime == 0) { return false; } uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.absCaptureTime, extenLen); // Extension value can be 8 or 16 bytes depending on whether it contains // estimated capture clock offset or not. // // https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time if (!extenValue || (extenLen != 8u && extenLen != 16u)) { return false; } absCaptureTimestamp = Utils::Byte::Get8Bytes(extenValue, 0); if (extenLen == 16) { estimatedCaptureClockOffset = static_cast(Utils::Byte::Get8Bytes(extenValue, 8)); } else { estimatedCaptureClockOffset = 0; } return true; } bool Packet::ReadPlayoutDelay(uint16_t& minDelay, uint16_t& maxDelay) const { MS_TRACE(); if (this->headerExtensionIds.playoutDelay == 0) { return false; } uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.playoutDelay, extenLen); if (extenLen != 3) { return false; } const uint32_t v = Utils::Byte::Get3Bytes(extenValue, 0); minDelay = v >> 12u; maxDelay = v & 0xFFFu; return true; } bool Packet::ReadMediasoupPacketId(uint32_t& mediasoupPacketId) const { MS_TRACE(); if (this->headerExtensionIds.mediasoupPacketId == 0) { return false; } uint8_t extenLen; const uint8_t* extenValue = GetExtensionValue(this->headerExtensionIds.mediasoupPacketId, extenLen); if (extenLen != 4u) { return false; } mediasoupPacketId = Utils::Byte::Get4Bytes(extenValue, 0); return true; } void Packet::SetPayload(const uint8_t* payload, size_t payloadLength) { MS_TRACE(); if (!payload && payloadLength > 0) { MS_THROW_TYPE_ERROR("invalid payloadLength %zu without payload", payloadLength); } const auto previousLength = GetLength(); const auto previousPayloadLength = GetPayloadLength(); const auto previousPaddingLength = GetPaddingLength(); const auto newLength = previousLength - previousPayloadLength - previousPaddingLength + payloadLength; // Set the new Packet total length. // NOTE: This throws if given length is higher than buffer length. SetLength(newLength); // Unset padding flag. GetFixedHeaderPointer()->padding = 0; if (payload) { std::memmove(GetPayloadPointer(), payload, payloadLength); } } void Packet::SetPayloadLength(size_t payloadLength) { MS_TRACE(); const auto previousLength = GetLength(); const auto previousPayloadLength = GetPayloadLength(); const auto previousPaddingLength = GetPaddingLength(); const auto newLength = previousLength - previousPayloadLength - previousPaddingLength + payloadLength; // Set the new Packet total length. // NOTE: This throws if given length is higher than buffer length. SetLength(newLength); // Unset padding flag. GetFixedHeaderPointer()->padding = 0; } void Packet::ShiftPayload(size_t payloadOffset, int32_t delta) { MS_TRACE(); if (delta == 0) { return; } auto* payload = GetPayloadPointer(); const auto payloadLength = GetPayloadLength(); const auto absDelta = delta < 0 ? static_cast(-(int64_t)delta) : static_cast(delta); if (payloadOffset >= payloadLength) { MS_THROW_TYPE_ERROR( "payloadOffset (%zu) is bigger than payload length (%zu)", payloadOffset, payloadLength); } else if (delta < 0 && absDelta > (payloadLength - payloadOffset)) { MS_THROW_TYPE_ERROR("negative delta (%" PRIi32 ") too big", delta); } // Remove padding (if any). if (HasPadding()) { SetPaddingLength(0); } if (delta > 0) { // Update Packet length. // NOTE: This throws if given length is higher than buffer length. SetLength(GetLength() + delta); std::memmove( payload + payloadOffset + delta, payload + payloadOffset, payloadLength - payloadOffset); } else { // Update Packet length. // NOTE: This throws if given length is higher than buffer length. SetLength(GetLength() - absDelta); std::memmove( payload + payloadOffset, payload + payloadOffset + absDelta, payloadLength - payloadOffset - absDelta); } } void Packet::SetPaddingLength(uint8_t paddingLength) { MS_TRACE(); const auto previousLength = GetLength(); const auto previousPaddingLength = GetPaddingLength(); const auto newLength = previousLength - previousPaddingLength + paddingLength; // Set the new Packet total length. // NOTE: This throws if given length is higher than buffer length. SetLength(newLength); if (paddingLength > 0) { GetFixedHeaderPointer()->padding = 1; Utils::Byte::Set1Byte(const_cast(GetBuffer()), GetLength() - 1, paddingLength); } else { GetFixedHeaderPointer()->padding = 0; } } void Packet::PadTo4Bytes() { MS_TRACE(); const auto previousLength = GetLength(); const auto previousPaddingLength = GetPaddingLength(); const auto newNotPaddedLength = previousLength - previousPaddingLength; const auto newPaddedLength = Utils::Byte::PadTo4Bytes(newNotPaddedLength); if (newPaddedLength == previousLength) { return; } // Set the new Packet total length. // NOTE: This throws if given length is higher than buffer length. SetLength(newPaddedLength); const auto newPaddingLength = newPaddedLength - newNotPaddedLength; if (newPaddingLength > 0) { GetFixedHeaderPointer()->padding = 1; Utils::Byte::Set1Byte(const_cast(GetBuffer()), GetLength() - 1, newPaddingLength); } else { GetFixedHeaderPointer()->padding = 0; } } void Packet::RtxEncode(uint8_t payloadType, uint32_t ssrc, uint16_t seq) { MS_TRACE(); // Remove padding (if any). if (HasPadding()) { // NOTE: This must be called before SetLength() method below. SetPaddingLength(0); } // Update Packet length. // NOTE: This throws if given length is higher than buffer length. SetLength(GetLength() + 2); // Rewrite the payload type. SetPayloadType(payloadType); // Rewrite the SSRC. SetSsrc(ssrc); auto* payload = GetPayloadPointer(); const auto payloadLength = GetPayloadLength(); // Write the original sequence number at the begining of the payload. std::memmove(payload + 2, payload, payloadLength); Utils::Byte::Set2Bytes(payload, 0, GetSequenceNumber()); // Rewrite the sequence number. SetSequenceNumber(seq); } bool Packet::RtxDecode(uint8_t payloadType, uint32_t ssrc) { MS_TRACE(); auto* payload = GetPayloadPointer(); const auto payloadLength = GetPayloadLength(); // NOTE: libwebrtc sends some RTX packets with no payload when the stream // is started. Just ignore them. if (payloadLength < 2) { return false; } // Rewrite the payload type. SetPayloadType(payloadType); // Rewrite the sequence number. SetSequenceNumber(Utils::Byte::Get2Bytes(payload, 0)); // Rewrite the SSRC. SetSsrc(ssrc); // Shift the payload to its original place. std::memmove(payload, payload + 2, payloadLength - 2); // Remove padding (if any). if (HasPadding()) { // NOTE: This must be called before SetLength() method below. SetPaddingLength(0); } // Update Packet length. SetLength(GetLength() - 2); return true; } void Packet::SetPayloadDescriptorHandler(Codecs::PayloadDescriptorHandler* payloadDescriptorHandler) { MS_TRACE(); this->payloadDescriptorHandler.reset(payloadDescriptorHandler); } bool Packet::ProcessPayload(Codecs::EncodingContext* context, bool& marker) { MS_TRACE(); if (!this->payloadDescriptorHandler) { return true; } return this->payloadDescriptorHandler->Process(context, this, marker); } std::unique_ptr Packet::GetPayloadEncoder() const { MS_TRACE(); if (!this->payloadDescriptorHandler) { return nullptr; } return this->payloadDescriptorHandler->GetEncoder(); } void Packet::EncodePayload(Codecs::PayloadDescriptor::Encoder* encoder) { MS_TRACE(); if (!this->payloadDescriptorHandler) { return; } this->payloadDescriptorHandler->Encode(this, encoder); } void Packet::RestorePayload() { MS_TRACE(); if (!this->payloadDescriptorHandler) { return; } this->payloadDescriptorHandler->Restore(this); } bool Packet::Validate(bool storeExtensions) { MS_TRACE(); // Here we are at the beginning of the Packet. const auto* ptr = const_cast(GetBuffer()); if (GetVersion() != 2) { MS_WARN_TAG(rtp, "invalid Packet, version must be 2"); return false; } ptr += Packet::FixedHeaderMinLength; // Here we are at the beginning of the optional CCRS list. if (HasCsrcs()) { auto csrcsLength = GetCsrcCount(); if (GetLength() < static_cast(ptr - GetBuffer()) + csrcsLength) { MS_WARN_TAG(rtp, "invalid Packet, not enough space for the announced CSRC list"); return false; } ptr += csrcsLength; } // Here we are at the beginning of the optional Header Extension. if (HasHeaderExtension()) { // The Header Extension is at least 4 bytes. if (GetLength() < static_cast(ptr - GetBuffer()) + 4) { MS_WARN_TAG(rtp, "invalid Packet, not enough space for the announced Header Extension"); return false; } const auto headerExtensionLength = GetHeaderExtensionLength(); if (GetLength() < static_cast(ptr - GetBuffer()) + headerExtensionLength) { MS_WARN_TAG( rtp, "invalid Packet, not enough space for the announced Header Extension value"); return false; } if (!ParseExtensions(storeExtensions)) { MS_WARN_TAG(rtp, "invalid Packet, invalid Extensions"); return false; } ptr += headerExtensionLength; } // Here we are at the beginning of the optional payload. const auto payloadLength = GetPayloadLength(); const auto paddingLength = GetPaddingLength(); const auto availablePayloadAndPaddingLength = GetLength() - (GetPayloadPointer() - GetBuffer()); if (payloadLength + paddingLength != availablePayloadAndPaddingLength) { MS_WARN_TAG(rtp, "invalid Packet, not enough space for announced padding"); return false; } if (HasPadding() && paddingLength == 0) { MS_WARN_TAG(rtp, "invalid Packet, padding byte cannot be 0"); return false; } ptr += availablePayloadAndPaddingLength; // Here we are at the end of the Packet. MS_ASSERT( static_cast(ptr - GetBuffer()) == GetLength(), "Packet computed length does not match its assigned length"); return true; } bool Packet::ParseExtensions(bool storeExtensions) { MS_TRACE(); if (HasOneByteExtensions()) { const uint8_t* extensionsStart = GetHeaderExtensionValue(); const uint8_t* extensionsEnd = extensionsStart + GetHeaderExtensionValueLength(); auto* ptr = const_cast(extensionsStart); // One-Byte Extensions cannot have length 0. while (ptr < extensionsEnd) { const auto* extension = reinterpret_cast(ptr); const uint8_t id = extension->id; // NOTE: In One-Byte Extensions, announced value must be incremented // by 1. const size_t len = extension->len + 1; // id=0 means alignment. if (id == 0) { ++ptr; } // id=15 in One-Byte extensions means "stop parsing here". else if (id == 15) { break; } // Valid Extension id. else { if (ptr + 1 + len > extensionsEnd) { MS_WARN_TAG( rtp, "not enough space for the announced value of the One-Byte Extension with id %" PRIu8, id); return false; } if (storeExtensions) { // Store the One-Byte Extension offset in the array. // `-1` because we have 14 elements total 0..13 and `id` is in the // range 1..14. this->oneByteExtensions[id - 1] = ptr - extensionsStart; } ptr += (1 + len); } // Counting padding bytes. while (ptr < extensionsEnd && *ptr == 0) { ++ptr; } } return true; } else if (HasTwoBytesExtensions()) { const uint8_t* extensionsStart = GetHeaderExtensionValue(); const uint8_t* extensionsEnd = extensionsStart + GetHeaderExtensionValueLength(); // ptr points to the Extension id field (1 byte). // ptr+1 points to the length field (1 byte, can have value 0). auto* ptr = const_cast(extensionsStart); // Two-Byte Extensions can have length 0. while (ptr + 1 < extensionsEnd) { const auto* extension = reinterpret_cast(ptr); const uint8_t id = extension->id; const size_t len = extension->len; // id=0 means alignment. if (id == 0) { ++ptr; } // Valid Extension id. else { if (ptr + 2 + len > extensionsEnd) { MS_WARN_TAG( rtp, "not enough space for the announced value of the Two-Bytes Extension with id %" PRIu8, id); return false; } if (storeExtensions) { // Store the Two-Bytes Extension offset in the map. this->twoBytesExtensions[id] = ptr - extensionsStart; } ptr += (2 + len); } // Counting padding bytes. while (ptr < extensionsEnd && *ptr == 0) { ++ptr; } } return true; } // If there is no Header Extension of if there is but it doesn't conform // to RFC 8285 Extensions, then this is ok. else { return true; } } void Packet::SetExtensionLength(uint8_t id, uint8_t len) { MS_TRACE(); MS_ASSERT(id > 0, "id cannot be 0"); if (HasOneByteExtensions()) { // `-1` because we have 14 elements total 0..13 and `id` is in the // range 1..14. const auto offset = this->oneByteExtensions[id - 1]; MS_ASSERT(offset != -1, "extension with id %" PRIu8 " not found", id); auto* extension = reinterpret_cast(GetHeaderExtensionValue() + offset); // In One-Byte Extensions value length 0 means 1. const auto currentLen = extension->len + 1; // Fill with 0's if new length is minor. if (len < currentLen) { std::memset(extension->value + len, 0, currentLen - len); } extension->len = len - 1; } else if (HasTwoBytesExtensions()) { const auto it = this->twoBytesExtensions.find(id); MS_ASSERT(it != this->twoBytesExtensions.end(), "extension with id %" PRIu8 " not found", id); const auto offset = it->second; auto* extension = reinterpret_cast(GetHeaderExtensionValue() + offset); const auto currentLen = extension->len; // Fill with 0's if new length is minor. if (len < currentLen) { std::memset(extension->value + len, 0, currentLen - len); } extension->len = len; } } void Packet::OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) { MS_TRACE(); UpdateDependencyDescriptor(data, len); } } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/ProbationGenerator.cpp ================================================ #define MS_CLASS "RTC::RTP::ProbationGenerator" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/ProbationGenerator.hpp" #include "Logger.hpp" #include "Utils.hpp" #include "RTC/RtpDictionaries.hpp" #include // std::memcpy(), std::memset() #include namespace RTC { namespace RTP { /* Static. */ static thread_local uint8_t ProbationPacketBuffer[ProbationGenerator::ProbationPacketMaxLength]; static constexpr size_t ProbationPacketExtensionsBufferLength{ 200 }; alignas(4) static thread_local uint8_t ProbationPacketExtensionsBuffer[ProbationPacketExtensionsBufferLength]; // 8 bytes, same as RTC::Consts::MidRtpExtensionMaxLength. static const std::string MidValue{ "probator" }; /* Instance methods. */ ProbationGenerator::ProbationGenerator() { MS_TRACE(); // Trick to only fill the padding with zeroes once. static thread_local bool mustInitializePayload{ true }; if (mustInitializePayload) { std::memset(ProbationPacketBuffer, 0x00, sizeof(ProbationPacketBuffer)); mustInitializePayload = false; } // Create the probation RTP Packet. this->probationPacket.reset( RTP::Packet::Factory(ProbationPacketBuffer, sizeof(ProbationPacketBuffer))); // Sex fixed codec payload type. this->probationPacket->SetPayloadType(ProbationGenerator::PayloadType); // Set fixed SSRC. this->probationPacket->SetSsrc(ProbationGenerator::Ssrc); // Set random initial RTP seq number. this->probationPacket->SetSequenceNumber(Utils::Crypto::GetRandomUInt(0, 65535)); // Set random initial RTP timestamp. this->probationPacket->SetTimestamp(Utils::Crypto::GetRandomUInt(0, 4294967295)); // Add BWE related RTP header extensions. std::vector extensions; uint8_t extenLen; uint8_t* bufferPtr{ ProbationPacketExtensionsBuffer }; // Add urn:ietf:params:rtp-hdrext:sdes:mid. { extenLen = MidValue.size(); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::MID, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::MID), /*len*/ extenLen, /*value*/ bufferPtr); std::memcpy(bufferPtr, MidValue.c_str(), extenLen); bufferPtr += extenLen; } // Add http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time. // NOTE: Just the corresponding id and space for its value. { extenLen = 3u; extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME), /*len*/ extenLen, /*value*/ bufferPtr); bufferPtr += extenLen; } // Add http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01. // NOTE: Just the corresponding id and space for its value. { extenLen = 2u; extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01, /*id*/ static_cast(RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01), /*len*/ extenLen, /*value*/ bufferPtr); // Not needed since this is the latest added extension. // bufferPtr += extenLen; } // Set the extensions into the Packet using One-Byte format. this->probationPacket->SetExtensions(RTP::Packet::ExtensionsType::OneByte, extensions); this->probationPacketMinLength = this->probationPacket->GetLength(); } ProbationGenerator::~ProbationGenerator() { MS_TRACE(); } /** * This method maybe called with desired `len` higher than typical RTP * packet mas length. That's ok since the caller will iterate and call * this method again until it satisfies the total desired `len`. */ RTP::Packet* ProbationGenerator::GetNextPacket(size_t len) { MS_TRACE(); // Pad given length to 4 bytes. len = Utils::Byte::PadTo4Bytes(len); // Make the Packet length fit into our available limits. if (len > ProbationGenerator::ProbationPacketMaxLength) { len = ProbationGenerator::ProbationPacketMaxLength; } else if (len < this->probationPacketMinLength) { len = this->probationPacketMinLength; } // Just send up to StepNumPackets per step. // Increase RTP seq number and timestamp. const auto seq = this->probationPacket->GetSequenceNumber() + 1; const auto timestamp = this->probationPacket->GetTimestamp() + 20; const size_t payloadLength = len - this->probationPacketMinLength; this->probationPacket->SetSequenceNumber(seq); this->probationPacket->SetTimestamp(timestamp); // Set payload length. this->probationPacket->SetPayloadLength(payloadLength); return this->probationPacket.get(); } } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/RetransmissionBuffer.cpp ================================================ #define MS_CLASS "RTC::RTP::RetransmissionBuffer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/RetransmissionBuffer.hpp" #include "Logger.hpp" #include "Utils.hpp" #include "RTC/SeqManager.hpp" namespace RTC { namespace RTP { /* Class methods. */ RetransmissionBuffer::Item* RetransmissionBuffer::FillItem( RetransmissionBuffer::Item* item, RTP::Packet* packet, const RTP::SharedPacket& sharedPacket) { MS_TRACE(); // Store original packet and some extra info into the item. // // NOTE: sharedPacket could be empty at this point but it's ok since the // Consumer will fill it. item->sharedPacket = sharedPacket; item->encoder = packet->GetPayloadEncoder(); item->ssrc = packet->GetSsrc(); item->sequenceNumber = packet->GetSequenceNumber(); item->timestamp = packet->GetTimestamp(); item->marker = packet->HasMarker(); return item; } /* Instance methods. */ RetransmissionBuffer::RetransmissionBuffer( uint16_t maxItems, uint32_t maxRetransmissionDelayMs, uint32_t clockRate) : maxItems(maxItems), maxRetransmissionDelayMs(maxRetransmissionDelayMs), clockRate(clockRate) { MS_TRACE(); MS_ASSERT(maxItems > 0u, "maxItems must be greater than 0"); } RetransmissionBuffer::~RetransmissionBuffer() { MS_TRACE(); Clear(); } void RetransmissionBuffer::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN( indentation, " buffer [size:%zu, maxSize:%" PRIu16 "]", this->buffer.size(), this->maxItems); if (!this->buffer.empty()) { const auto* oldestItem = GetOldest(); const auto* newestItem = GetNewest(); MS_DUMP_CLEAN( indentation, " oldest item [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", oldestItem->sequenceNumber, oldestItem->timestamp); MS_DUMP_CLEAN( indentation, " newest item [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", newestItem->sequenceNumber, newestItem->timestamp); MS_DUMP_CLEAN( indentation, " buffer window: %" PRIu32 "ms", static_cast(newestItem->timestamp * 1000 / this->clockRate) - static_cast(oldestItem->timestamp * 1000 / this->clockRate)); } MS_DUMP_CLEAN(indentation, ""); } RetransmissionBuffer::Item* RetransmissionBuffer::Get(uint16_t seq) const { MS_TRACE(); const auto* oldestItem = GetOldest(); if (!oldestItem) { return nullptr; } if (RTC::SeqManager::IsSeqLowerThan(seq, oldestItem->sequenceNumber)) { return nullptr; } const uint16_t idx = seq - oldestItem->sequenceNumber; if (static_cast(idx) > this->buffer.size() - 1) { return nullptr; } return this->buffer.at(idx); } /** * This method tries to insert given packet into the buffer. Here we assume * that packet seq number is legitimate according to the content of the buffer. * We discard the packet if too old and also discard it if its timestamp does * not properly fit (by ensuring that elements in the buffer are not only * ordered by increasing seq but also that their timestamp are incremental). */ bool RetransmissionBuffer::Insert(RTP::Packet* packet, const RTP::SharedPacket& sharedPacket) { MS_TRACE(); const auto ssrc = packet->GetSsrc(); const auto seq = packet->GetSequenceNumber(); const auto timestamp = packet->GetTimestamp(); MS_DEBUG_DEV("packet [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", seq, timestamp); // Buffer is empty, so just insert new item. if (this->buffer.empty()) { MS_DEBUG_DEV("buffer empty [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", seq, timestamp); auto* item = new Item(); this->buffer.push_back(RetransmissionBuffer::FillItem(item, packet, sharedPacket)); return true; } auto* oldestItem = GetOldest(); auto* newestItem = GetNewest(); // Special case: Received packet has lower seq than newest packet in the // buffer, however its timestamp is higher. If so, clear the whole buffer. if ( RTC::SeqManager::IsSeqLowerThan(seq, newestItem->sequenceNumber) && Utils::Number::IsHigherThan(timestamp, newestItem->timestamp)) { MS_WARN_TAG( rtp, "packet has lower seq but higher timestamp than newest packet in the buffer, emptying the buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 ", timestamp:%" PRIu32 "]", ssrc, seq, timestamp); Clear(); auto* item = new Item(); this->buffer.push_back(RetransmissionBuffer::FillItem(item, packet, sharedPacket)); return true; } // Clear too old packets in the buffer. // NOTE: Here we must consider the case in which, due for example to huge // packet loss, received packet has higher timestamp but "older" seq number // than the newest packet in the buffer and, if so, use it to clear too old // packets rather than the newest packet in the buffer. auto newestTimestamp = Utils::Number::IsHigherThan(timestamp, newestItem->timestamp) ? timestamp : newestItem->timestamp; // ClearTooOldByTimestamp() returns true if at least one packet has been // removed from the front. if (ClearTooOldByTimestamp(newestTimestamp)) { // Buffer content has been modified so we must check it again. if (this->buffer.empty()) { MS_WARN_TAG( rtp, "buffer empty after clearing too old packets [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", seq, timestamp); auto* item = new Item(); this->buffer.push_back(RetransmissionBuffer::FillItem(item, packet, sharedPacket)); return true; } oldestItem = GetOldest(); newestItem = GetNewest(); } MS_ASSERT(oldestItem != nullptr, "oldest item doesn't exist"); MS_ASSERT(newestItem != nullptr, "newest item doesn't exist"); // Packet arrived in order (its seq is higher than seq of the newest stored // packet) so will become the newest one in the buffer. if (RTC::SeqManager::IsSeqHigherThan(seq, newestItem->sequenceNumber)) { MS_DEBUG_DEV("packet in order [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", seq, timestamp); // Ensure that the timestamp of the packet is equal or higher than the // timestamp of the newest stored packet. if (Utils::Number::IsLowerThan(timestamp, newestItem->timestamp)) { MS_WARN_TAG( rtp, "packet has higher seq but lower timestamp than newest packet in the buffer, discarding it [ssrc:%" PRIu32 ", seq:%" PRIu16 ", timestamp:%" PRIu32 "]", ssrc, seq, timestamp); return false; } // Calculate how many blank slots it would be necessary to add when // pushing new item to the back of the buffer. uint16_t numBlankSlots = seq - newestItem->sequenceNumber - 1; // We may have to remove oldest items not to exceed the maximum size of // the buffer. if (this->buffer.size() + numBlankSlots + 1 > this->maxItems) { const uint16_t numItemsToRemove = this->buffer.size() + numBlankSlots + 1 - this->maxItems; // If num of items to be removed exceed buffer size minus one (needed to // allocate current packet) then we must clear the entire buffer. if (numItemsToRemove > this->buffer.size() - 1) { MS_WARN_TAG( rtp, "packet has too high seq and forces buffer emptying [ssrc:%" PRIu32 ", seq:%" PRIu16 ", timestamp:%" PRIu32 "]", ssrc, seq, timestamp); numBlankSlots = 0u; Clear(); } else { MS_DEBUG_DEV( "calling RemoveOldest(%" PRIu16 ") [bufferSize:%zu, numBlankSlots:%" PRIu16 ", maxItems:%" PRIu16 "]", numItemsToRemove, this->buffer.size(), numBlankSlots, this->maxItems); RemoveOldest(numItemsToRemove); } } // Push blank slots to the back. for (uint16_t i{ 0u }; i < numBlankSlots; ++i) { this->buffer.push_back(nullptr); } // Push the packet, which becomes the newest one in the buffer. auto* item = new Item(); this->buffer.push_back(RetransmissionBuffer::FillItem(item, packet, sharedPacket)); } // Packet arrived out order and its seq is less than seq of the oldest // stored packet, so will become the oldest one in the buffer. else if (RTC::SeqManager::IsSeqLowerThan(seq, oldestItem->sequenceNumber)) { MS_DEBUG_DEV( "packet out of order and older than oldest packet in the buffer [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", seq, timestamp); // Ensure that packet is not too old to be stored. if (IsTooOldTimestamp(timestamp, newestItem->timestamp)) { MS_WARN_DEV( "packet's timestamp too old, discarding it [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", seq, timestamp); return false; } // Ensure that the timestamp of the packet is equal or less than the // timestamp of the oldest stored packet. if (Utils::Number::IsHigherThan(timestamp, oldestItem->timestamp)) { MS_WARN_TAG( rtp, "packet has lower seq but higher timestamp than oldest packet in the buffer, discarding it [ssrc:%" PRIu32 ", seq:%" PRIu16 ", timestamp:%" PRIu32 "]", ssrc, seq, timestamp); return false; } // Calculate how many blank slots it would be necessary to add when // pushing new item to the fton of the buffer. const uint16_t numBlankSlots = oldestItem->sequenceNumber - seq - 1; // If adding this packet (and needed blank slots) to the front makes the // buffer exceed its max size, discard this packet. if (this->buffer.size() + numBlankSlots + 1 > this->maxItems) { MS_WARN_TAG( rtp, "discarding received old packet to not exceed max buffer size [ssrc:%" PRIu32 ", seq:%" PRIu16 ", timestamp:%" PRIu32 "]", ssrc, seq, timestamp); return false; } // Push blank slots to the front. for (uint16_t i{ 0u }; i < numBlankSlots; ++i) { this->buffer.push_front(nullptr); } // Insert the packet, which becomes the oldest one in the buffer. auto* item = new Item(); this->buffer.push_front(RetransmissionBuffer::FillItem(item, packet, sharedPacket)); } // Otherwise packet must be inserted between oldest and newest stored items // so there is already an allocated slot for it. else { MS_DEBUG_DEV( "packet out of order and in between oldest and newest packets in the buffer [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", seq, timestamp); // Let's check if an item already exist in same position. If so, assume // it's duplicated. auto* item = Get(seq); if (item) { MS_DEBUG_DEV( "packet already in the buffer, discarding [seq:%" PRIu16 ", timestamp:%" PRIu32 "]", seq, timestamp); return false; } // idx is the intended position of the received packet in the buffer. const uint16_t idx = seq - oldestItem->sequenceNumber; // Validate that packet timestamp is equal or higher than the timestamp of // the immediate older packet (if any). for (int32_t idx2 = idx - 1; idx2 >= 0; --idx2) { const auto* olderItem = this->buffer.at(idx2); // Blank slot, continue. if (!olderItem) { continue; } // We are done. if (timestamp >= olderItem->timestamp) { break; } else { MS_WARN_TAG( rtp, "packet timestamp is lower than timestamp of immediate older packet in the buffer, discarding it [ssrc:%" PRIu32 ", seq:%" PRIu16 ", timestamp:%" PRIu32 "]", ssrc, seq, timestamp); return false; } } // Validate that packet timestamp is equal or less than the timestamp of // the immediate newer packet (if any). for (size_t idx2 = idx + 1; idx2 < this->buffer.size(); ++idx2) { const auto* newerItem = this->buffer.at(idx2); // Blank slot, continue. if (!newerItem) { continue; } // We are done. if (timestamp <= newerItem->timestamp) { break; } else { MS_WARN_TAG( rtp, "packet timestamp is higher than timestamp of immediate newer packet in the buffer, discarding it [ssrc:%" PRIu32 ", seq:%" PRIu16 ", timestamp:%" PRIu32 "]", ssrc, seq, timestamp); return false; } } // Store the packet. item = new Item(); this->buffer[idx] = RetransmissionBuffer::FillItem(item, packet, sharedPacket); } MS_ASSERT( this->buffer.size() <= this->maxItems, "buffer contains %zu items (more than %" PRIu16 " max items)", this->buffer.size(), this->maxItems); return true; } void RetransmissionBuffer::Clear() { MS_TRACE(); for (auto* item : this->buffer) { if (!item) { continue; } // Reset the stored item (decrease RTP packet shared pointer counter). item->Reset(); delete item; } this->buffer.clear(); } RetransmissionBuffer::Item* RetransmissionBuffer::GetOldest() const { MS_TRACE(); if (this->buffer.empty()) { return nullptr; } return this->buffer.front(); } RetransmissionBuffer::Item* RetransmissionBuffer::GetNewest() const { MS_TRACE(); if (this->buffer.empty()) { return nullptr; } return this->buffer.back(); } void RetransmissionBuffer::RemoveOldest() { MS_TRACE(); if (this->buffer.empty()) { return; } auto* item = this->buffer.front(); // Reset the stored item (decrease RTP packet shared pointer counter). item->Reset(); delete item; this->buffer.pop_front(); MS_DEBUG_DEV("removed 1 item from the front"); // Remove all nullptr elements from the beginning of the buffer. // NOTE: Calling front on an empty container is undefined. size_t numItemsRemoved{ 0u }; while (!this->buffer.empty() && this->buffer.front() == nullptr) { this->buffer.pop_front(); ++numItemsRemoved; } if (numItemsRemoved) { MS_DEBUG_DEV("removed %zu blank slots from the front", numItemsRemoved); } } void RetransmissionBuffer::RemoveOldest(uint16_t numItems) { MS_TRACE(); MS_ASSERT( numItems <= this->buffer.size(), "attempting to remove more items than current buffer size [numItems:%" PRIu16 ", bufferSize:%zu]", numItems, this->buffer.size()); const size_t intendedBufferSize = this->buffer.size() - numItems; while (this->buffer.size() > intendedBufferSize) { RemoveOldest(); } } bool RetransmissionBuffer::ClearTooOldByTimestamp(uint32_t newestTimestamp) { MS_TRACE(); const RetransmissionBuffer::Item* oldestItem{ nullptr }; bool itemsRemoved{ false }; // Go through all buffer items starting with the first and free all items // that contain too old packets. while ((oldestItem = GetOldest())) { if (IsTooOldTimestamp(oldestItem->timestamp, newestTimestamp)) { RemoveOldest(); itemsRemoved = true; } // If current oldest stored packet is not too old, exit the loop since we // know that packets stored after it are guaranteed to be newer. else { break; } } return itemsRemoved; } bool RetransmissionBuffer::IsTooOldTimestamp(uint32_t timestamp, uint32_t newestTimestamp) const { MS_TRACE(); if (Utils::Number::IsHigherThan(timestamp, newestTimestamp)) { return false; } const int64_t diffTs = newestTimestamp - timestamp; return static_cast(diffTs * 1000 / this->clockRate) > this->maxRetransmissionDelayMs; } void RetransmissionBuffer::Item::Reset() { MS_TRACE(); // NOTE: Here we MUST NOT call this->sharedPacket.Reset() because that // would affect all copies of this SharedRtpPacket by removing their stored // packet. We have to replace it entirely. this->sharedPacket = RTP::SharedPacket(); this->ssrc = 0u; this->sequenceNumber = 0u; this->timestamp = 0u; this->resentAtMs = 0u; this->sentTimes = 0u; } } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/RtpStream.cpp ================================================ #define MS_CLASS "RTC::RTP::RtpStream" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/RtpStream.hpp" #include "Logger.hpp" #include "Utils.hpp" namespace RTC { namespace RTP { /* Static. */ static constexpr uint16_t MaxDropout{ 3000 }; static constexpr uint16_t MaxMisorder{ 1500 }; static constexpr uint32_t RtpSeqMod{ 1 << 16 }; static constexpr size_t ScoreHistogramLength{ 24 }; /* Instance methods. */ RtpStream::RtpStream( RTP::RtpStream::Listener* listener, SharedInterface* shared, RTP::RtpStream::Params& params, uint8_t initialScore) : listener(listener), shared(shared), params(params), score(initialScore), activeSinceMs(this->shared->GetTimeMs()) { MS_TRACE(); } RtpStream::~RtpStream() { MS_TRACE(); delete this->rtxStream; this->rtxStream = nullptr; } flatbuffers::Offset RtpStream::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add params. auto params = this->params.FillBuffer(builder); // Add rtxStream. flatbuffers::Offset rtxStream; if (HasRtx()) { rtxStream = this->rtxStream->FillBuffer(builder); } return FBS::RtpStream::CreateDump(builder, params, this->score, rtxStream); } flatbuffers::Offset RtpStream::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); const uint64_t nowMs = this->shared->GetTimeMs(); const auto mediaKind = this->params.mimeType.type == RTC::RtpCodecMimeType::Type::AUDIO ? FBS::RtpParameters::MediaKind::AUDIO : FBS::RtpParameters::MediaKind::VIDEO; auto baseStats = FBS::RtpStream::CreateBaseStatsDirect( builder, nowMs, this->params.ssrc, mediaKind, this->params.mimeType.ToString().c_str(), this->packetsLost, this->fractionLost, this->jitter, this->packetsDiscarded, this->packetsRetransmitted, this->packetsRepaired, this->nackCount, this->nackPacketCount, this->pliCount, this->firCount, !this->params.rid.empty() ? this->params.rid.c_str() : nullptr, this->params.rtxSsrc ? flatbuffers::Optional(this->params.rtxSsrc) : flatbuffers::nullopt, this->rtxStream ? this->rtxStream->GetPacketsDiscarded() : 0, this->rtt > 0.0f ? this->rtt : 0, this->score); return FBS::RtpStream::CreateStats( builder, FBS::RtpStream::StatsData::BaseStats, baseStats.Union()); } void RtpStream::SetRtx(uint8_t payloadType, uint32_t ssrc) { MS_TRACE(); this->params.rtxPayloadType = payloadType; this->params.rtxSsrc = ssrc; if (HasRtx()) { delete this->rtxStream; this->rtxStream = nullptr; } // Set RTX stream params. RTP::RtxStream::Params params; params.ssrc = ssrc; params.payloadType = payloadType; params.mimeType.type = GetMimeType().type; params.mimeType.subtype = RTC::RtpCodecMimeType::Subtype::RTX; params.clockRate = GetClockRate(); params.rrid = GetRid(); params.cname = GetCname(); // Tell the RtpCodecMimeType to update its string based on current type and subtype. params.mimeType.UpdateMimeType(); this->rtxStream = new RTP::RtxStream(this->shared, params); } bool RtpStream::ReceiveStreamPacket(const RTP::Packet* packet) { MS_TRACE(); const uint16_t seq = packet->GetSequenceNumber(); // If this is the first packet seen, initialize stuff. if (!this->started) { InitSeq(seq); this->started = true; this->maxSeq = seq - 1; this->maxPacketTs = packet->GetTimestamp(); this->maxPacketMs = this->shared->GetTimeMs(); } // If not a valid packet ignore it. if (!UpdateSeq(packet)) { MS_WARN_TAG( rtp, "invalid packet [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); return false; } // Update highest seen RTP timestamp. if (Utils::Number::IsHigherThan(packet->GetTimestamp(), this->maxPacketTs)) { this->maxPacketTs = packet->GetTimestamp(); this->maxPacketMs = this->shared->GetTimeMs(); } return true; } void RtpStream::ResetScore(uint8_t score, bool notify) { MS_TRACE(); this->scores.clear(); if (this->score != score) { auto previousScore = this->score; this->score = score; // If previous score was 0 (and new one is not 0) then update activeSinceMs. if (previousScore == 0u) { this->activeSinceMs = this->shared->GetTimeMs(); } // Notify the listener. if (notify) { this->listener->OnRtpStreamScore(this, score, previousScore); } } } bool RtpStream::UpdateSeq(const RTP::Packet* packet) { MS_TRACE(); const uint16_t seq = packet->GetSequenceNumber(); const uint16_t udelta = seq - this->maxSeq; // If the new packet sequence number is greater than the max seen but not // "so much bigger", accept it. // NOTE: udelta also handles the case of a new cycle, this is: // maxSeq:65536, seq:0 => udelta:1 if (udelta < MaxDropout) { // In order, with permissible gap. if (seq < this->maxSeq) { // Sequence number wrapped: count another 64K cycle. this->cycles += RtpSeqMod; } this->maxSeq = seq; // Timestamp moved backwards despite in-order sequence number. Likely // caused by prolonged Producer inactivity (e.g., overnight pause). if (Utils::Number::IsLowerThan(packet->GetTimestamp(), this->maxPacketTs)) { MS_DEBUG_TAG( rtp, "timestamp moved backwards, updating [ssrc:%" PRIu32 ", seq:%" PRIu16 ", old maxPacketTs:%" PRIu32 ", new maxPacketTs:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), this->maxPacketTs, packet->GetTimestamp()); this->maxPacketTs = packet->GetTimestamp(); this->maxPacketMs = this->shared->GetTimeMs(); } } // Too old packet received (older than the allowed misorder). // Or too new packet (more than acceptable dropout). else if (udelta <= RtpSeqMod - MaxMisorder) { // The sequence number made a very large jump. If two sequential packets // arrive, accept the latter. if (seq == this->badSeq) { // Two sequential packets. Assume that the other side restarted without // telling us so just re-sync (i.e., pretend this was the first packet). MS_WARN_TAG( rtp, "too bad sequence number, re-syncing RTP [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); InitSeq(seq); this->maxPacketTs = packet->GetTimestamp(); this->maxPacketMs = this->shared->GetTimeMs(); // Notify the subclass about it. UserOnSequenceNumberReset(); } else { MS_WARN_TAG( rtp, "bad sequence number, ignoring packet [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); this->badSeq = (seq + 1) & (RtpSeqMod - 1); // Packet discarded due to late or early arriving. this->packetsDiscarded++; return false; } } // Acceptable misorder. else { // Do nothing. } return true; } void RtpStream::UpdateScore(uint8_t score) { MS_TRACE(); // Add the score into the histogram. if (this->scores.size() == ScoreHistogramLength) { this->scores.erase(this->scores.begin()); } auto previousScore = this->score; // Compute new effective score taking into accout entries in the histogram. this->scores.push_back(score); /* * Scoring mechanism is a weighted average. * * The more recent the score is, the more weight it has. * The oldest score has a weight of 1 and subsequent scores weight is * increased by one sequentially. * * Ie: * - scores: [1,2,3,4] * - this->scores = ((1) + (2+2) + (3+3+3) + (4+4+4+4)) / 10 = 2.8 => 3 */ size_t weight{ 0 }; size_t samples{ 0 }; size_t totalScore{ 0 }; for (auto score : this->scores) { weight++; samples += weight; totalScore += weight * score; } // clang-tidy "thinks" that this can lead to division by zero but we are // smarter. // NOLINTNEXTLINE(clang-analyzer-core.DivideZero) this->score = static_cast(std::round(static_cast(totalScore) / samples)); // Call the listener if the global score has changed. if (this->score != previousScore) { MS_DEBUG_TAG( score, "[added score:%" PRIu8 ", previous computed score:%" PRIu8 ", new computed score:%" PRIu8 "] (calling listener)", score, previousScore, this->score); // If previous score was 0 (and new one is not 0) then update activeSinceMs. if (previousScore == 0u) { this->activeSinceMs = this->shared->GetTimeMs(); } this->listener->OnRtpStreamScore(this, this->score, previousScore); } else { #if MS_LOG_DEV_LEVEL == 3 MS_DEBUG_TAG( score, "[added score:%" PRIu8 ", previous computed score:%" PRIu8 ", new computed score:%" PRIu8 "] (no change)", score, previousScore, this->score); #endif } } void RtpStream::PacketRetransmitted(const RTP::Packet* /*packet*/) { MS_TRACE(); this->packetsRetransmitted++; } void RtpStream::PacketRepaired(const RTP::Packet* /*packet*/) { MS_TRACE(); this->packetsRepaired++; } inline void RtpStream::InitSeq(uint16_t seq) { MS_TRACE(); // Initialize/reset RTP counters. this->baseSeq = seq; this->maxSeq = seq; this->badSeq = RtpSeqMod + 1; // So seq == badSeq is false. } flatbuffers::Offset RtpStream::Params::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::RtpStream::CreateParamsDirect( builder, this->encodingIdx, this->ssrc, this->payloadType, this->mimeType.ToString().c_str(), this->clockRate, this->rid.c_str(), this->cname.c_str(), this->rtxSsrc != 0 ? flatbuffers::Optional(this->rtxSsrc) : flatbuffers::nullopt, this->rtxSsrc != 0 ? flatbuffers::Optional(this->rtxPayloadType) : flatbuffers::nullopt, this->useNack, this->usePli, this->useFir, this->useInBandFec, this->useDtx, this->spatialLayers, this->temporalLayers); } } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/RtpStreamRecv.cpp ================================================ #define MS_CLASS "RTC::RTP::RtpStreamRecv" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/RtpStreamRecv.hpp" #include "Logger.hpp" #include "Utils.hpp" #include "RTC/RTP/Codecs/Tools.hpp" namespace RTC { namespace RTP { /* Static. */ static constexpr uint64_t InactivityCheckInterval{ 1500u }; // In ms. static constexpr uint64_t InactivityCheckIntervalWithDtx{ 5000u }; // In ms. /* TransmissionCounter methods. */ RtpStreamRecv::TransmissionCounter::TransmissionCounter( SharedInterface* shared, uint8_t spatialLayers, uint8_t temporalLayers, size_t windowSize) { MS_TRACE(); // Reserve vectors capacity. this->spatialLayerCounters = std::vector>(spatialLayers); for (auto& spatialLayerCounter : this->spatialLayerCounters) { for (uint8_t tIdx{ 0u }; tIdx < temporalLayers; ++tIdx) { spatialLayerCounter.emplace_back(shared, /*ignorePaddingOnlyPackets*/ true, windowSize); } } } void RtpStreamRecv::TransmissionCounter::Update(const RTP::Packet* packet) { MS_TRACE(); auto spatialLayer = packet->GetSpatialLayer(); auto temporalLayer = packet->GetTemporalLayer(); // Sanity check. Do not allow spatial layers higher than defined. spatialLayer = std::min(static_cast(spatialLayer), this->spatialLayerCounters.size() - 1); // Sanity check. Do not allow temporal layers higher than defined. temporalLayer = std::min(static_cast(temporalLayer), this->spatialLayerCounters[0].size() - 1); auto& counter = this->spatialLayerCounters[spatialLayer][temporalLayer]; counter.Update(packet); } uint32_t RtpStreamRecv::TransmissionCounter::GetBitrate(uint64_t nowMs) { MS_TRACE(); uint32_t rate{ 0u }; for (auto& spatialLayerCounter : this->spatialLayerCounters) { for (auto& temporalLayerCounter : spatialLayerCounter) { rate += temporalLayerCounter.GetBitrate(nowMs); } } return rate; } uint32_t RtpStreamRecv::TransmissionCounter::GetBitrate( uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) { MS_TRACE(); MS_ASSERT(spatialLayer < this->spatialLayerCounters.size(), "spatialLayer too high"); MS_ASSERT( temporalLayer < this->spatialLayerCounters[spatialLayer].size(), "temporalLayer too high"); // Return 0 if specified layers are not being received. auto& counter = this->spatialLayerCounters[spatialLayer][temporalLayer]; if (counter.GetBitrate(nowMs) == 0) { return 0u; } uint32_t rate{ 0u }; // Iterate all temporal layers of spatial layers previous to the given one. for (uint8_t sIdx{ 0u }; sIdx < spatialLayer; ++sIdx) { for (size_t tIdx{ 0u }; tIdx < this->spatialLayerCounters[sIdx].size(); ++tIdx) { auto& temporalLayerCounter = this->spatialLayerCounters[sIdx][tIdx]; rate += temporalLayerCounter.GetBitrate(nowMs); } } // Add the given spatial layer with up to the given temporal layer. for (uint8_t tIdx{ 0u }; tIdx <= temporalLayer; ++tIdx) { auto& temporalLayerCounter = this->spatialLayerCounters[spatialLayer][tIdx]; rate += temporalLayerCounter.GetBitrate(nowMs); } return rate; } uint32_t RtpStreamRecv::TransmissionCounter::GetSpatialLayerBitrate( uint64_t nowMs, uint8_t spatialLayer) { MS_TRACE(); MS_ASSERT(spatialLayer < this->spatialLayerCounters.size(), "spatialLayer too high"); uint32_t rate{ 0u }; for (size_t tIdx{ 0u }; tIdx < this->spatialLayerCounters[spatialLayer].size(); ++tIdx) { auto& temporalLayerCounter = this->spatialLayerCounters[spatialLayer][tIdx]; rate += temporalLayerCounter.GetBitrate(nowMs); } return rate; } uint32_t RtpStreamRecv::TransmissionCounter::GetLayerBitrate( uint64_t nowMs, uint8_t spatialLayer, uint8_t temporalLayer) { MS_TRACE(); MS_ASSERT(spatialLayer < this->spatialLayerCounters.size(), "spatialLayer too high"); MS_ASSERT( temporalLayer < this->spatialLayerCounters[spatialLayer].size(), "temporalLayer too high"); auto& counter = this->spatialLayerCounters[spatialLayer][temporalLayer]; return counter.GetBitrate(nowMs); } size_t RtpStreamRecv::TransmissionCounter::GetPacketCount() const { MS_TRACE(); size_t packetCount{ 0u }; for (const auto& spatialLayerCounter : this->spatialLayerCounters) { for (const auto& temporalLayerCounter : spatialLayerCounter) { packetCount += temporalLayerCounter.GetPacketCount(); } } return packetCount; } size_t RtpStreamRecv::TransmissionCounter::GetBytes() const { MS_TRACE(); size_t bytes{ 0u }; for (const auto& spatialLayerCounter : this->spatialLayerCounters) { for (const auto& temporalLayerCounter : spatialLayerCounter) { bytes += temporalLayerCounter.GetBytes(); } } return bytes; } /* Instance methods. */ RtpStreamRecv::RtpStreamRecv( RTP::RtpStreamRecv::Listener* listener, SharedInterface* shared, RTP::RtpStream::Params& params, uint32_t sendNackDelayMs, bool useRtpInactivityCheck) : RTP::RtpStream::RtpStream(listener, shared, params, 10), sendNackDelayMs(sendNackDelayMs), useRtpInactivityCheck(useRtpInactivityCheck), transmissionCounter( shared, params.spatialLayers, params.temporalLayers, this->params.useDtx ? 6000 : 2500), mediaTransmissionCounter(shared, /*ignorePaddingOnlyPackets*/ true) { MS_TRACE(); if (this->params.useNack) { this->nackGenerator.reset(new RTC::NackGenerator(this, this->shared, this->sendNackDelayMs)); } this->inactive = false; if (this->useRtpInactivityCheck) { // Run the RTP inactivity periodic timer (use a different timeout if DTX is // enabled). this->inactivityCheckPeriodicTimer = this->shared->CreateTimer(this); this->inactivityCheckPeriodicTimer->Start( this->params.useDtx ? InactivityCheckIntervalWithDtx : InactivityCheckInterval); } } RtpStreamRecv::~RtpStreamRecv() { MS_TRACE(); // Close the RTP inactivity check periodic timer. delete this->inactivityCheckPeriodicTimer; this->inactivityCheckPeriodicTimer = nullptr; } flatbuffers::Offset RtpStreamRecv::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); const uint64_t nowMs = this->shared->GetTimeMs(); auto baseStats = RTP::RtpStream::FillBufferStats(builder); std::vector> bitrateByLayer; if (GetSpatialLayers() > 1 || GetTemporalLayers() > 1) { for (uint8_t sIdx = 0; sIdx < GetSpatialLayers(); ++sIdx) { for (uint8_t tIdx = 0; tIdx < GetTemporalLayers(); ++tIdx) { auto layer = std::to_string(sIdx) + "." + std::to_string(tIdx); bitrateByLayer.emplace_back( FBS::RtpStream::CreateBitrateByLayerDirect( builder, layer.c_str(), GetBitrate(nowMs, sIdx, tIdx))); } } } auto stats = FBS::RtpStream::CreateRecvStatsDirect( builder, baseStats, this->transmissionCounter.GetPacketCount(), this->transmissionCounter.GetBytes(), this->transmissionCounter.GetBitrate(nowMs), &bitrateByLayer); return FBS::RtpStream::CreateStats(builder, FBS::RtpStream::StatsData::RecvStats, stats.Union()); } bool RtpStreamRecv::ReceivePacket(RTP::Packet* packet) { MS_TRACE(); // Call the parent method. if (!RTP::RtpStream::ReceiveStreamPacket(packet)) { MS_WARN_TAG(rtp, "packet discarded"); return false; } // Process the packet at codec level. if (packet->GetPayloadType() == GetPayloadType()) { RTP::Codecs::Tools::ProcessRtpPacket(packet, GetMimeType(), this->templateDependencyStructure); } // Pass the packet to the NackGenerator. if (this->params.useNack) { // If there is RTX just provide the NackGenerator with the packet. if (HasRtx()) { this->nackGenerator->ReceivePacket(packet, /*isRecovered*/ false); } // If there is no RTX and NackGenerator returns true it means that it // was a NACKed packet. else if (this->nackGenerator->ReceivePacket(packet, /*isRecovered*/ false)) { // Mark the packet as retransmitted and repaired. RTP::RtpStream::PacketRetransmitted(packet); RTP::RtpStream::PacketRepaired(packet); } } // Calculate Jitter. CalculateJitter(packet->GetTimestamp()); // Increase transmission counter. this->transmissionCounter.Update(packet); // Increase media transmission counter. this->mediaTransmissionCounter.Update(packet); // Padding only packet, do not consider it for stream activation. if (packet->GetPayloadLength() == 0) { return true; } // Not inactive anymore. if (this->inactive) { this->inactive = false; ResetScore(10, /*notify*/ true); } // Restart the inactivityCheckPeriodicTimer. if (this->inactivityCheckPeriodicTimer) { this->inactivityCheckPeriodicTimer->Restart(); } return true; } bool RtpStreamRecv::ReceiveRtxPacket(RTP::Packet* packet) { MS_TRACE(); if (!this->params.useNack) { MS_WARN_TAG(rtx, "NACK not supported"); return false; } MS_ASSERT(packet->GetSsrc() == this->params.rtxSsrc, "invalid ssrc on RTX packet"); // Check that the payload type corresponds to the one negotiated. if (packet->GetPayloadType() != this->params.rtxPayloadType) { MS_WARN_TAG( rtx, "ignoring RTX packet with invalid payload type [ssrc:%" PRIu32 ", seq:%" PRIu16 ", pt:%" PRIu8 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetPayloadType()); return false; } if (HasRtx()) { if (!this->rtxStream->ReceivePacket(packet)) { MS_WARN_TAG(rtx, "RTX packet discarded"); return false; } } #if MS_LOG_DEV_LEVEL == 3 // Get the RTX packet sequence number for logging purposes. auto rtxSeq = packet->GetSequenceNumber(); #endif // Get the original RTP packet. if (!packet->RtxDecode(this->params.payloadType, this->params.ssrc)) { MS_DEBUG_DEV( "ignoring empty RTX packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", pt:%" PRIu8 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetPayloadType()); return false; } MS_DEBUG_DEV( "received RTX packet [ssrc:%" PRIu32 ", seq:%" PRIu16 "] recovering original [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", this->params.rtxSsrc, rtxSeq, packet->GetSsrc(), packet->GetSequenceNumber()); // If not a valid packet ignore it. if (!RTP::RtpStream::UpdateSeq(packet)) { MS_WARN_TAG( rtx, "invalid RTX packet [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); return false; } // Process the packet at codec level. if (packet->GetPayloadType() == GetPayloadType()) { RTP::Codecs::Tools::ProcessRtpPacket(packet, GetMimeType(), this->templateDependencyStructure); } // Mark the packet as retransmitted. RTP::RtpStream::PacketRetransmitted(packet); // Pass the packet to the NackGenerator and return true just if this was a // NACKed packet or a RTX packet containing a non yet seen original RTP // packet. if (this->nackGenerator->ReceivePacket(packet, /*isRecovered*/ true)) { // Mark the packet as repaired. RTP::RtpStream::PacketRepaired(packet); // Increase transmission counter. this->transmissionCounter.Update(packet); // Padding only packet, do not consider it for stream activation. if (packet->GetPayloadLength() == 0) { return true; } // Not inactive anymore. if (this->inactive) { this->inactive = false; ResetScore(10, /*notify*/ true); } // Restart the inactivityCheckPeriodicTimer. if (this->inactivityCheckPeriodicTimer) { this->inactivityCheckPeriodicTimer->Restart(); } return true; } return false; } RTC::RTCP::ReceiverReport* RtpStreamRecv::GetRtcpReceiverReport() { MS_TRACE(); uint8_t worstRemoteFractionLost{ 0 }; if (this->params.useInBandFec) { // Notify the listener so we'll get the worst remote fraction lost. static_cast(this->listener) ->OnRtpStreamNeedWorstRemoteFractionLost(this, worstRemoteFractionLost); if (worstRemoteFractionLost > 0) { MS_DEBUG_TAG(rtcp, "using worst remote fraction lost:%" PRIu8, worstRemoteFractionLost); } } auto* report = new RTC::RTCP::ReceiverReport(); report->SetSsrc(GetSsrc()); const int32_t prevPacketsLost = this->packetsLost; // Calculate Packets Expected and Lost. auto expected = GetExpectedPackets(); if (expected > this->mediaTransmissionCounter.GetPacketCount()) { this->packetsLost = expected - this->mediaTransmissionCounter.GetPacketCount(); } else { this->packetsLost = 0; } // Calculate Fraction Lost. const uint32_t expectedInterval = expected - this->expectedPrior; this->expectedPrior = expected; const uint32_t receivedInterval = this->mediaTransmissionCounter.GetPacketCount() - this->receivedPrior; this->receivedPrior = this->mediaTransmissionCounter.GetPacketCount(); const int32_t lostInterval = expectedInterval - receivedInterval; if (expectedInterval == 0 || lostInterval <= 0) { this->fractionLost = 0; } else { this->fractionLost = std::round((static_cast(lostInterval << 8) / expectedInterval)); } // Worst remote fraction lost is not worse than local one. if (worstRemoteFractionLost <= this->fractionLost) { this->reportedPacketsLost += (this->packetsLost - prevPacketsLost); report->SetTotalLost(this->reportedPacketsLost); report->SetFractionLost(this->fractionLost); } else { // Recalculate packetsLost. const uint32_t newLostInterval = (worstRemoteFractionLost * expectedInterval) >> 8; this->reportedPacketsLost += newLostInterval; report->SetTotalLost(this->reportedPacketsLost); report->SetFractionLost(worstRemoteFractionLost); } // Fill the rest of the report. report->SetLastSeq(static_cast(this->maxSeq) + this->cycles); report->SetJitter(this->jitter); if (this->lastSrReceived != 0) { // Get delay in milliseconds. auto delayMs = static_cast(this->shared->GetTimeMs() - this->lastSrReceived); // Express delay in units of 1/65536 seconds. uint32_t dlsr = (delayMs / 1000) << 16; dlsr |= uint32_t{ (delayMs % 1000) * 65536 / 1000 }; report->SetDelaySinceLastSenderReport(dlsr); report->SetLastSenderReport(this->lastSrTimestamp); } else { report->SetDelaySinceLastSenderReport(0); report->SetLastSenderReport(0); } return report; } RTC::RTCP::ReceiverReport* RtpStreamRecv::GetRtxRtcpReceiverReport() { MS_TRACE(); if (HasRtx()) { return this->rtxStream->GetRtcpReceiverReport(); } return nullptr; } void RtpStreamRecv::ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report) { MS_TRACE(); this->lastSrReceived = this->shared->GetTimeMs(); this->lastSrTimestamp = report->GetNtpSec() << 16; this->lastSrTimestamp += report->GetNtpFrac() >> 16; // Update info about last Sender Report. Utils::Time::Ntp ntp{}; // NOLINT(cppcoreguidelines-pro-type-member-init) ntp.seconds = report->GetNtpSec(); ntp.fractions = report->GetNtpFrac(); this->lastSenderReportNtpMs = Utils::Time::Ntp2TimeMs(ntp); this->lastSenderReportTs = report->GetRtpTs(); // Update the score with the current RR. UpdateScore(); } void RtpStreamRecv::ReceiveRtxRtcpSenderReport(RTC::RTCP::SenderReport* report) { MS_TRACE(); if (HasRtx()) { this->rtxStream->ReceiveRtcpSenderReport(report); } } void RtpStreamRecv::ReceiveRtcpXrDelaySinceLastRr(RTC::RTCP::DelaySinceLastRr::SsrcInfo* ssrcInfo) { MS_TRACE(); /* Calculate RTT. */ // Get the NTP representation of the current timestamp. const uint64_t nowMs = this->shared->GetTimeMs(); auto ntp = Utils::Time::TimeMs2Ntp(nowMs); // Get the compact NTP representation of the current timestamp. uint32_t compactNtp = (ntp.seconds & 0x0000FFFF) << 16; compactNtp |= (ntp.fractions & 0xFFFF0000) >> 16; const uint32_t lastRr = ssrcInfo->GetLastReceiverReport(); const uint32_t dlrr = ssrcInfo->GetDelaySinceLastReceiverReport(); // RTT in 1/2^16 second fractions. uint32_t rtt{ 0 }; // If no Receiver Extended Report was received by the remote endpoint yet, // ignore lastRr and dlrr values in the Sender Extended Report. if (lastRr && dlrr && (compactNtp > dlrr + lastRr)) { rtt = compactNtp - dlrr - lastRr; } // RTT in milliseconds. this->rtt = static_cast(rtt >> 16) * 1000; this->rtt += (static_cast(rtt & 0x0000FFFF) / 65536) * 1000; // Avoid negative RTT value since it doesn't make sense. this->rtt = std::max(this->rtt, 0.0f); // Tell it to the NackGenerator. if (this->params.useNack) { this->nackGenerator->UpdateRtt(static_cast(this->rtt)); } } void RtpStreamRecv::RequestKeyFrame() { MS_TRACE(); if (this->params.usePli) { MS_DEBUG_2TAGS(rtcp, rtx, "sending PLI [ssrc:%" PRIu32 "]", GetSsrc()); // Sender SSRC should be 0 since there is no media sender involved, but // some implementations like gstreamer will fail to process it otherwise. RTC::RTCP::FeedbackPsPliPacket packet(GetSsrc(), GetSsrc()); packet.Serialize(RTC::RTCP::SerializationBuffer); this->pliCount++; // Notify the listener. static_cast(this->listener) ->OnRtpStreamSendRtcpPacket(this, &packet); } else if (this->params.useFir) { MS_DEBUG_2TAGS(rtcp, rtx, "sending FIR [ssrc:%" PRIu32 "]", GetSsrc()); // Sender SSRC should be 0 since there is no media sender involved, but // some implementations like gstreamer will fail to process it otherwise. RTC::RTCP::FeedbackPsFirPacket packet(GetSsrc(), GetSsrc()); auto* item = new RTC::RTCP::FeedbackPsFirItem(GetSsrc(), ++this->firSeqNumber); packet.AddItem(item); packet.Serialize(RTC::RTCP::SerializationBuffer); this->firCount++; // Notify the listener. static_cast(this->listener) ->OnRtpStreamSendRtcpPacket(this, &packet); } } void RtpStreamRecv::Pause() { MS_TRACE(); if (this->inactivityCheckPeriodicTimer) { this->inactivityCheckPeriodicTimer->Stop(); } if (this->params.useNack) { this->nackGenerator->Reset(); } // Reset jitter. this->transit = 0; this->jitter = 0; } void RtpStreamRecv::Resume() { MS_TRACE(); if (this->inactivityCheckPeriodicTimer && !this->inactive) { this->inactivityCheckPeriodicTimer->Restart(); } } void RtpStreamRecv::CalculateJitter(uint32_t rtpTimestamp) { MS_TRACE(); if (GetClockRate() == 0u) { return; } // NOTE: Based on https://github.com/versatica/mediasoup/issues/1018. auto transit = static_cast((this->shared->GetTimeMs() * GetClockRate() / 1000) - rtpTimestamp); int d = transit - this->transit; // First transit calculation, save and return. if (this->transit == 0) { this->transit = transit; return; } this->transit = transit; if (d < 0) { d = -d; } this->jitter += (1. / 16.) * (static_cast(d) - this->jitter); } void RtpStreamRecv::UpdateScore() { MS_TRACE(); // Calculate number of packets expected in this interval. const auto totalExpected = GetExpectedPackets(); const uint32_t expected = totalExpected - this->expectedPriorScore; this->expectedPriorScore = totalExpected; // Calculate number of packets received in this interval. const auto totalReceived = this->mediaTransmissionCounter.GetPacketCount(); const uint32_t received = totalReceived - this->receivedPriorScore; this->receivedPriorScore = totalReceived; // Calculate number of packets lost in this interval. uint32_t lost; if (expected < received) { lost = 0; } else { lost = expected - received; } // Calculate number of packets repaired in this interval. const auto totalRepaired = this->packetsRepaired; uint32_t repaired = totalRepaired - this->repairedPriorScore; this->repairedPriorScore = totalRepaired; // Calculate number of packets retransmitted in this interval. const auto totatRetransmitted = this->packetsRetransmitted; uint32_t retransmitted = totatRetransmitted - this->retransmittedPriorScore; this->retransmittedPriorScore = totatRetransmitted; if (this->inactive) { return; } // We didn't expect more packets to come. if (expected == 0) { RTP::RtpStream::UpdateScore(10); return; } lost = std::min(lost, received); if (repaired > lost) { if (HasRtx()) { repaired = lost; retransmitted -= repaired - lost; } else { lost = repaired; } } #if MS_LOG_DEV_LEVEL == 3 MS_DEBUG_TAG( score, "[totalExpected:%" PRIu32 ", totalReceived:%zu, totalRepaired:%zu", totalExpected, totalReceived, totalRepaired); MS_DEBUG_TAG( score, "fixed values [expected:%" PRIu32 ", received:%" PRIu32 ", lost:%" PRIu32 ", repaired:%" PRIu32 ", retransmitted:%" PRIu32, expected, received, lost, repaired, retransmitted); #endif auto repairedRatio = static_cast(repaired) / static_cast(received); auto repairedWeight = std::pow(1 / (repairedRatio + 1), 4); MS_ASSERT(retransmitted >= repaired, "repaired packets cannot be more than retransmitted ones"); if (retransmitted > 0) { repairedWeight *= static_cast(repaired) / retransmitted; } lost -= repaired * repairedWeight; auto deliveredRatio = static_cast(received - lost) / static_cast(received); auto score = static_cast(std::round(std::pow(deliveredRatio, 4) * 10)); #if MS_LOG_DEV_LEVEL == 3 MS_DEBUG_TAG( score, "[deliveredRatio:%f, repairedRatio:%f, repairedWeight:%f, new lost:%" PRIu32 ", score:%" PRIu8 "]", deliveredRatio, repairedRatio, repairedWeight, lost, score); #endif // Call the parent method for update score. RTP::RtpStream::UpdateScore(score); } void RtpStreamRecv::UserOnSequenceNumberReset() { MS_TRACE(); // Nothing to do. } inline void RtpStreamRecv::OnTimer(TimerHandleInterface* timer) { MS_TRACE(); if (timer == this->inactivityCheckPeriodicTimer) { this->inactive = true; if (GetScore() != 0) { MS_WARN_2TAGS( rtp, score, "RTP inactivity detected, resetting score to 0 [ssrc:%" PRIu32 "]", GetSsrc()); } ResetScore(0, /*notify*/ true); } } inline void RtpStreamRecv::OnNackGeneratorNackRequired(const std::vector& seqNumbers) { MS_TRACE(); MS_ASSERT(this->params.useNack, "NACK required but not supported"); MS_DEBUG_TAG( rtx, "triggering NACK [ssrc:%" PRIu32 ", first seq:%" PRIu16 ", num packets:%zu]", this->params.ssrc, seqNumbers[0], seqNumbers.size()); RTC::RTCP::FeedbackRtpNackPacket packet(0, GetSsrc()); auto it = seqNumbers.begin(); const auto end = seqNumbers.end(); size_t numPacketsRequested{ 0 }; while (it != end) { uint16_t seq; uint16_t bitmask{ 0 }; seq = *it; ++it; while (it != end) { const uint16_t shift = *it - seq - 1; if (shift > 15) { break; } bitmask |= (1 << shift); ++it; } auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(seq, bitmask); packet.AddItem(nackItem); numPacketsRequested += nackItem->CountRequestedPackets(); } // Ensure that the RTCP packet fits into the RTCP buffer. if (packet.GetSize() > RTC::RTCP::SerializationBufferSize) { MS_WARN_TAG(rtx, "cannot send RTCP NACK packet, size too big (%zu bytes)", packet.GetSize()); return; } this->nackCount++; this->nackPacketCount += numPacketsRequested; packet.Serialize(RTC::RTCP::SerializationBuffer); // Notify the listener. static_cast(this->listener)->OnRtpStreamSendRtcpPacket(this, &packet); } inline void RtpStreamRecv::OnNackGeneratorKeyFrameRequired() { MS_TRACE(); MS_DEBUG_TAG(rtx, "requesting key frame [ssrc:%" PRIu32 "]", this->params.ssrc); RequestKeyFrame(); } } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/RtpStreamSend.cpp ================================================ #define MS_CLASS "RTC::RTP::RtpStreamSend" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/RtpStreamSend.hpp" #ifdef MS_LIBURING_SUPPORTED #include "DepLibUring.hpp" #endif #include "Logger.hpp" #include "Utils.hpp" #include "RTC/Consts.hpp" #include "RTC/RtpDictionaries.hpp" #include namespace RTC { namespace RTP { /* Static. */ // Limit max number of items in the retransmission buffer. static constexpr size_t RetransmissionBufferMaxItems{ 2500u }; // 17: 16 bit mask + the initial sequence number. static constexpr size_t MaxRequestedPackets{ 17u }; static thread_local std::vector RetransmissionContainer( MaxRequestedPackets + 1); static constexpr uint32_t DefaultRtt{ 100u }; /* Class Static. */ const uint32_t RtpStreamSend::MaxRetransmissionDelayForVideoMs{ 2000u }; const uint32_t RtpStreamSend::MaxRetransmissionDelayForAudioMs{ 1000u }; /* Instance methods. */ RtpStreamSend::RtpStreamSend( RTP::RtpStreamSend::Listener* listener, SharedInterface* shared, RTP::RtpStream::Params& params, std::string& mid) : RTP::RtpStream::RtpStream(listener, shared, params, 10), mid(mid), transmissionCounter(shared, /*ignorePaddingOnlyPackets*/ true) { MS_TRACE(); if (this->params.useNack) { uint32_t maxRetransmissionDelayMs{ 0 }; switch (params.mimeType.type) { case RTC::RtpCodecMimeType::Type::VIDEO: { maxRetransmissionDelayMs = RtpStreamSend::MaxRetransmissionDelayForVideoMs; break; } case RTC::RtpCodecMimeType::Type::AUDIO: { maxRetransmissionDelayMs = RtpStreamSend::MaxRetransmissionDelayForAudioMs; break; } } this->retransmissionBuffer = new RTC::RTP::RetransmissionBuffer( RetransmissionBufferMaxItems, maxRetransmissionDelayMs, params.clockRate); } } RtpStreamSend::~RtpStreamSend() { MS_TRACE(); // Delete retransmission buffer. delete this->retransmissionBuffer; this->retransmissionBuffer = nullptr; } flatbuffers::Offset RtpStreamSend::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); const uint64_t nowMs = this->shared->GetTimeMs(); auto baseStats = RTP::RtpStream::FillBufferStats(builder); auto stats = FBS::RtpStream::CreateSendStats( builder, baseStats, this->transmissionCounter.GetPacketCount(), this->transmissionCounter.GetBytes(), this->transmissionCounter.GetBitrate(nowMs)); return FBS::RtpStream::CreateStats(builder, FBS::RtpStream::StatsData::SendStats, stats.Union()); } void RtpStreamSend::SetRtx(uint8_t payloadType, uint32_t ssrc) { MS_TRACE(); RTP::RtpStream::SetRtx(payloadType, ssrc); this->rtxSeq = Utils::Crypto::GetRandomUInt(0u, 0xFFFF); } RtpStreamSend::ReceivePacketResult RtpStreamSend::ReceivePacket( RTP::Packet* packet, const RTP::SharedPacket& sharedPacket) { MS_TRACE(); MS_ASSERT( packet->GetSsrc() == this->params.ssrc, "RTP packet SSRC does not match the encodings SSRC"); // Call the parent method. if (!RtpStream::ReceiveStreamPacket(packet)) { return ReceivePacketResult::DISCARDED; } bool stored{ false }; // If NACK is enabled, store the packet into the buffer. if (this->retransmissionBuffer) { // Check if the packet is already stored. if (this->retransmissionBuffer->Get(packet->GetSequenceNumber())) { // Packet already stored, do not resend it since the receiver will // fail decrypting it and this packet will be considered not received // in the RTCP transport feedback affecting the bandwidth estimation. MS_DEBUG_DEV( "packet already stored in retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); return ReceivePacketResult::DISCARDED; } stored = this->retransmissionBuffer->Insert(packet, sharedPacket); } // Increase transmission counter. this->transmissionCounter.Update(packet); return stored ? ReceivePacketResult::ACCEPTED_AND_STORED : ReceivePacketResult::ACCEPTED_AND_NOT_STORED; } void RtpStreamSend::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) { MS_TRACE(); this->nackCount++; #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Activate liburing usage. DepLibUring::SetActive(); } #endif for (auto it = nackPacket->Begin(); it != nackPacket->End(); ++it) { const RTC::RTCP::FeedbackRtpNackItem* item = *it; this->nackPacketCount += item->CountRequestedPackets(); FillRetransmissionContainer(item->GetPacketId(), item->GetLostPacketBitmask()); for (auto* item : RetransmissionContainer) { if (!item) { break; } MS_ASSERT( item->sharedPacket.HasPacket(), "item in retransmission container doesn't contain a packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", timestamp:%" PRIu32 "]", item->ssrc, item->sequenceNumber, item->timestamp); auto* packet = item->sharedPacket.GetPacket(); // Keep the values of the original packet received by the Consumer. auto origSsrc = packet->GetSsrc(); auto origSeq = packet->GetSequenceNumber(); auto origTimestamp = packet->GetTimestamp(); auto origMarker = packet->HasMarker(); std::string origMid; // Put correct info into the packet. packet->SetSsrc(item->ssrc); packet->SetSequenceNumber(item->sequenceNumber); packet->SetTimestamp(item->timestamp); packet->SetMarker(item->marker); if (item->encoder != nullptr) { packet->EncodePayload(item->encoder.get()); } // Update MID RTP extension value. if (!this->mid.empty()) { packet->ReadMid(origMid); packet->UpdateMid(this->mid); } // If we use RTX, encode it. if (HasRtx()) { // Increment RTX seq. this->rtxSeq++; packet->RtxEncode(this->params.rtxPayloadType, this->params.rtxSsrc, this->rtxSeq); } // Retransmit the packet. static_cast(this->listener) ->OnRtpStreamRetransmitRtpPacket(this, packet); // Mark the packet as retransmitted. RTP::RtpStream::PacketRetransmitted(packet); // Mark the packet as repaired (only if this is the first retransmission). if (item->sentTimes == 1) { RTP::RtpStream::PacketRepaired(packet); } // If we use RTX, restore it. if (HasRtx()) { // Restore the packet. packet->RtxDecode(RtpStream::GetPayloadType(), item->ssrc); } // Restore MID. if (!this->mid.empty()) { packet->UpdateMid(origMid); } // Restore payload. if (item->encoder != nullptr) { packet->RestorePayload(); } // Restore RTP header fields. packet->SetSsrc(origSsrc); packet->SetSequenceNumber(origSeq); packet->SetTimestamp(origTimestamp); packet->SetMarker(origMarker); } } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Submit all prepared submission entries. DepLibUring::Submit(); } #endif } void RtpStreamSend::ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType) { MS_TRACE(); switch (messageType) { case RTC::RTCP::FeedbackPs::MessageType::PLI: { this->pliCount++; break; } case RTC::RTCP::FeedbackPs::MessageType::FIR: { this->firCount++; break; } default:; } } void RtpStreamSend::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) { MS_TRACE(); /* Calculate RTT. */ // Get the NTP representation of the current timestamp. const uint64_t nowMs = this->shared->GetTimeMs(); auto ntp = Utils::Time::TimeMs2Ntp(nowMs); // Get the compact NTP representation of the current timestamp. uint32_t compactNtp = (ntp.seconds & 0x0000FFFF) << 16; compactNtp |= (ntp.fractions & 0xFFFF0000) >> 16; const uint32_t lastSr = report->GetLastSenderReport(); const uint32_t dlsr = report->GetDelaySinceLastSenderReport(); // RTT in 1/2^16 second fractions. uint32_t rtt{ 0 }; // If no Sender Report was received by the remote endpoint yet, ignore lastSr // and dlsr values in the Receiver Report. if (lastSr && dlsr && (compactNtp > dlsr + lastSr)) { rtt = compactNtp - dlsr - lastSr; } // RTT in milliseconds. this->rtt = static_cast(rtt >> 16) * 1000; this->rtt += (static_cast(rtt & 0x0000FFFF) / 65536) * 1000; // Avoid negative RTT value since it doesn't make sense. this->rtt = std::max(this->rtt, 0.0f); this->packetsLost = report->GetTotalLost(); this->fractionLost = report->GetFractionLost(); this->jitter = static_cast(report->GetJitter()); // Update the score with the received RR. UpdateScore(report); } void RtpStreamSend::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) { MS_TRACE(); this->lastRrReceivedMs = this->shared->GetTimeMs(); this->lastRrTimestamp = report->GetNtpSec() << 16; this->lastRrTimestamp += report->GetNtpFrac() >> 16; } RTC::RTCP::SenderReport* RtpStreamSend::GetRtcpSenderReport(uint64_t nowMs) { MS_TRACE(); if (this->transmissionCounter.GetPacketCount() == 0u) { return nullptr; } auto ntp = Utils::Time::TimeMs2Ntp(nowMs); auto* report = new RTC::RTCP::SenderReport(); // Calculate TS difference between now and maxPacketMs. auto diffMs = nowMs - this->maxPacketMs; auto diffTs = diffMs * GetClockRate() / 1000; report->SetSsrc(GetSsrc()); report->SetPacketCount(this->transmissionCounter.GetPacketCount()); report->SetOctetCount(this->transmissionCounter.GetBytes()); report->SetNtpSec(ntp.seconds); report->SetNtpFrac(ntp.fractions); report->SetRtpTs(this->maxPacketTs + diffTs); // Update info about last Sender Report. this->lastSenderReportNtpMs = nowMs; this->lastSenderReportTs = this->maxPacketTs + diffTs; return report; } RTC::RTCP::DelaySinceLastRr::SsrcInfo* RtpStreamSend::GetRtcpXrDelaySinceLastRrSsrcInfo(uint64_t nowMs) { MS_TRACE(); if (this->lastRrReceivedMs == 0u) { return nullptr; } // Get delay in milliseconds. auto delayMs = static_cast(nowMs - this->lastRrReceivedMs); // Express delay in units of 1/65536 seconds. uint32_t dlrr = (delayMs / 1000) << 16; dlrr |= uint32_t{ (delayMs % 1000) * 65536 / 1000 }; auto* ssrcInfo = new RTC::RTCP::DelaySinceLastRr::SsrcInfo(); ssrcInfo->SetSsrc(GetSsrc()); ssrcInfo->SetDelaySinceLastReceiverReport(dlrr); ssrcInfo->SetLastReceiverReport(this->lastRrTimestamp); return ssrcInfo; } RTC::RTCP::SdesChunk* RtpStreamSend::GetRtcpSdesChunk() { MS_TRACE(); const auto& cname = GetCname(); auto* sdesChunk = new RTC::RTCP::SdesChunk(GetSsrc()); auto* sdesItem = new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, cname.size(), cname.c_str()); sdesChunk->AddItem(sdesItem); return sdesChunk; } void RtpStreamSend::Pause() { MS_TRACE(); // Clear retransmission buffer. if (this->retransmissionBuffer) { this->retransmissionBuffer->Clear(); } // Reset jitter. this->jitter = 0; } void RtpStreamSend::Resume() { MS_TRACE(); } uint32_t RtpStreamSend::GetBitrate( uint64_t /*nowMs*/, uint8_t /*spatialLayer*/, uint8_t /*temporalLayer*/) { MS_TRACE(); MS_ABORT("invalid method call"); } uint32_t RtpStreamSend::GetSpatialLayerBitrate(uint64_t /*nowMs*/, uint8_t /*spatialLayer*/) { MS_TRACE(); MS_ABORT("invalid method call"); } uint32_t RtpStreamSend::GetLayerBitrate( uint64_t /*nowMs*/, uint8_t /*spatialLayer*/, uint8_t /*temporalLayer*/) { MS_TRACE(); MS_ABORT("invalid method call"); } // This method looks for the requested RTP packets and inserts them into the // RetransmissionContainer vector (and sets to null the next position). // // If RTX is used the stored packet will be RTX encoded now (if not already // encoded in a previous resend). // // NOTE: This method doesn't verify whether requested stored packet is too // old, why? Because, if we verified it, we would do it by comparing its // timestamp with the newest one in the retransmission buffer. However we // already clean old packets upon receipt of any new packet (see Insert() // method in RetransmissionBuffer class). void RtpStreamSend::FillRetransmissionContainer(uint16_t seq, uint16_t bitmask) { MS_TRACE(); // Ensure the container's first element is 0. RetransmissionContainer[0] = nullptr; // If NACK is not supported, exit. if (!this->retransmissionBuffer) { MS_WARN_TAG(rtx, "NACK not supported"); return; } // Look for each requested packet. const uint64_t nowMs = this->shared->GetTimeMs(); const uint16_t rtt = (this->rtt > 0.0f ? this->rtt : DefaultRtt); uint16_t currentSeq = seq; bool requested{ true }; size_t containerIdx{ 0 }; // Variables for debugging. const uint16_t origBitmask = bitmask; uint16_t sentBitmask{ 0b0000000000000000 }; bool isFirstPacket{ true }; bool firstPacketSent{ false }; uint8_t bitmaskCounter{ 0 }; while (requested || bitmask != 0) { bool sent = false; if (requested) { auto* item = this->retransmissionBuffer->Get(currentSeq); // Packet not found. if (!item) { // Do nothing. } // Don't resent the packet if it was resent in the last RTT ms. else if (item->resentAtMs != 0u && nowMs - item->resentAtMs <= static_cast(rtt)) { MS_DEBUG_TAG( rtx, "ignoring retransmission for a packet already resent in the last RTT ms " "[seq:%" PRIu16 ", rtt:%" PRIu16 "]", item->sequenceNumber, rtt); } // Stored packet is valid for retransmission. Resend it. else { // Save when this packet was resent. item->resentAtMs = nowMs; // Increase the number of times this packet was sent. item->sentTimes++; // Store the item in the container and then increment its index. RetransmissionContainer[containerIdx++] = item; sent = true; if (isFirstPacket) { firstPacketSent = true; } } } requested = (bitmask & 1) != 0; bitmask >>= 1; currentSeq++; if (!isFirstPacket) { sentBitmask |= (sent ? 1 : 0) << bitmaskCounter; bitmaskCounter++; } else { isFirstPacket = false; } } // If not all the requested packets was sent, log it. if (!firstPacketSent || origBitmask != sentBitmask) { MS_WARN_DEV( "could not resend all packets [seq:%" PRIu16 ", first:%s, " "bitmask:" MS_UINT16_TO_BINARY_PATTERN ", sent bitmask:" MS_UINT16_TO_BINARY_PATTERN "]", seq, firstPacketSent ? "yes" : "no", MS_UINT16_TO_BINARY(origBitmask), MS_UINT16_TO_BINARY(sentBitmask)); } else { MS_DEBUG_DEV( "all packets resent [seq:%" PRIu16 ", bitmask:" MS_UINT16_TO_BINARY_PATTERN "]", seq, MS_UINT16_TO_BINARY(origBitmask)); } // Set the next container element to null. RetransmissionContainer[containerIdx] = nullptr; } void RtpStreamSend::UpdateScore(RTC::RTCP::ReceiverReport* report) { MS_TRACE(); // Calculate number of packets sent in this interval. auto totalSent = this->transmissionCounter.GetPacketCount(); auto sent = totalSent - this->sentPriorScore; this->sentPriorScore = totalSent; // Calculate number of packets lost in this interval. const int32_t totalLost = report->GetTotalLost() > 0 ? report->GetTotalLost() : 0; uint32_t lost; if (totalLost < this->lostPriorScore) { lost = 0; } else { lost = totalLost - this->lostPriorScore; } this->lostPriorScore = totalLost; // Calculate number of packets repaired in this interval. auto totalRepaired = this->packetsRepaired; uint32_t repaired = totalRepaired - this->repairedPriorScore; this->repairedPriorScore = totalRepaired; // Calculate number of packets retransmitted in this interval. auto totatRetransmitted = this->packetsRetransmitted; const uint32_t retransmitted = totatRetransmitted - this->retransmittedPriorScore; this->retransmittedPriorScore = totatRetransmitted; // We didn't send any packet. if (sent == 0) { RTP::RtpStream::UpdateScore(10); return; } lost = std::min(lost, sent); repaired = std::min(repaired, lost); #if MS_LOG_DEV_LEVEL == 3 MS_DEBUG_TAG( score, "[totalSent:%zu, totalLost:%" PRIi32 ", totalRepaired:%zu", totalSent, totalLost, totalRepaired); MS_DEBUG_TAG( score, "fixed values [sent:%zu, lost:%" PRIu32 ", repaired:%" PRIu32 ", retransmitted:%" PRIu32, sent, lost, repaired, retransmitted); #endif auto repairedRatio = static_cast(repaired) / static_cast(sent); auto repairedWeight = std::pow(1 / (repairedRatio + 1), 4); MS_ASSERT(retransmitted >= repaired, "repaired packets cannot be more than retransmitted ones"); if (retransmitted > 0) { repairedWeight *= static_cast(repaired) / retransmitted; } lost -= repaired * repairedWeight; auto deliveredRatio = static_cast(sent - lost) / static_cast(sent); auto score = static_cast(std::round(std::pow(deliveredRatio, 4) * 10)); #if MS_LOG_DEV_LEVEL == 3 MS_DEBUG_TAG( score, "[deliveredRatio:%f, repairedRatio:%f, repairedWeight:%f, new lost:%" PRIu32 ", score:%" PRIu8 "]", deliveredRatio, repairedRatio, repairedWeight, lost, score); #endif // Call the parent method for update score. RTP::RtpStream::UpdateScore(score); } void RtpStreamSend::UserOnSequenceNumberReset() { MS_TRACE(); // Clear retransmission buffer. if (this->retransmissionBuffer) { this->retransmissionBuffer->Clear(); } } } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/RtxStream.cpp ================================================ #define MS_CLASS "RTC::RTP::RtxStream" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/RtxStream.hpp" #include "Logger.hpp" #include "Utils.hpp" namespace RTC { namespace RTP { /* Static. */ static constexpr uint16_t MaxDropout{ 3000 }; static constexpr uint16_t MaxMisorder{ 1500 }; static constexpr uint32_t RtpSeqMod{ 1 << 16 }; /* Instance methods. */ RtxStream::RtxStream(SharedInterface* shared, RTP::RtxStream::Params& params) : shared(shared), params(params) { MS_TRACE(); MS_ASSERT( params.mimeType.subtype == RTC::RtpCodecMimeType::Subtype::RTX, "mimeType.subtype is not RTX"); } RtxStream::~RtxStream() { MS_TRACE(); } flatbuffers::Offset RtxStream::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add params. auto params = this->params.FillBuffer(builder); return FBS::RtxStream::CreateRtxDump(builder, params); } bool RtxStream::ReceivePacket(const RTP::Packet* packet) { MS_TRACE(); const uint16_t seq = packet->GetSequenceNumber(); // If this is the first packet seen, initialize stuff. if (!this->started) { InitSeq(seq); this->started = true; this->maxSeq = seq - 1; this->maxPacketTs = packet->GetTimestamp(); this->maxPacketMs = this->shared->GetTimeMs(); } // If not a valid packet ignore it. if (!UpdateSeq(packet)) { MS_WARN_TAG( rtx, "invalid packet [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); return false; } // Update highest seen RTP timestamp. if (Utils::Number::IsHigherThan(packet->GetTimestamp(), this->maxPacketTs)) { this->maxPacketTs = packet->GetTimestamp(); this->maxPacketMs = this->shared->GetTimeMs(); } // Increase packet count. this->packetsCount++; return true; } RTC::RTCP::ReceiverReport* RtxStream::GetRtcpReceiverReport() { MS_TRACE(); auto* report = new RTC::RTCP::ReceiverReport(); report->SetSsrc(GetSsrc()); const int32_t prevPacketsLost = this->packetsLost; // Calculate Packets Expected and Lost. auto expected = GetExpectedPackets(); if (expected > this->packetsCount) { this->packetsLost = expected - this->packetsCount; } else { this->packetsLost = 0u; } // Calculate Fraction Lost. const uint32_t expectedInterval = expected - this->expectedPrior; this->expectedPrior = expected; const uint32_t receivedInterval = this->packetsCount - this->receivedPrior; this->receivedPrior = this->packetsCount; const int32_t lostInterval = expectedInterval - receivedInterval; if (expectedInterval == 0 || lostInterval <= 0) { this->fractionLost = 0; } else { this->fractionLost = std::round((static_cast(lostInterval << 8) / expectedInterval)); } this->reportedPacketsLost += (this->packetsLost - prevPacketsLost); report->SetTotalLost(this->reportedPacketsLost); report->SetFractionLost(this->fractionLost); // Fill the rest of the report. report->SetLastSeq(static_cast(this->maxSeq) + this->cycles); // NOTE: Do not calculate any jitter. report->SetJitter(0); if (this->lastSrReceived != 0) { // Get delay in milliseconds. const uint32_t delayMs = this->shared->GetTimeMs() - this->lastSrReceived; // Express delay in units of 1/65536 seconds. uint32_t dlsr = (delayMs / 1000) << 16; dlsr |= uint32_t{ (delayMs % 1000) * 65536 / 1000 }; report->SetDelaySinceLastSenderReport(dlsr); report->SetLastSenderReport(this->lastSrTimestamp); } else { report->SetDelaySinceLastSenderReport(0); report->SetLastSenderReport(0); } return report; } void RtxStream::ReceiveRtcpSenderReport(RTC::RTCP::SenderReport* report) { MS_TRACE(); this->lastSrReceived = this->shared->GetTimeMs(); this->lastSrTimestamp = report->GetNtpSec() << 16; this->lastSrTimestamp += report->GetNtpFrac() >> 16; } bool RtxStream::UpdateSeq(const RTP::Packet* packet) { MS_TRACE(); const uint16_t seq = packet->GetSequenceNumber(); const uint16_t udelta = seq - this->maxSeq; // If the new packet sequence number is greater than the max seen but not // "so much bigger", accept it. // NOTE: udelta also handles the case of a new cycle, this is: // maxSeq:65536, seq:0 => udelta:1 if (udelta < MaxDropout) { // In order, with permissible gap. if (seq < this->maxSeq) { // Sequence number wrapped: count another 64K cycle. this->cycles += RtpSeqMod; } this->maxSeq = seq; } // Too old packet received (older than the allowed misorder). // Or to new packet (more than acceptable dropout). else if (udelta <= RtpSeqMod - MaxMisorder) { // The sequence number made a very large jump. If two sequential packets // arrive, accept the latter. if (seq == this->badSeq) { // Two sequential packets. Assume that the other side restarted without // telling us so just re-sync (i.e., pretend this was the first packet). MS_WARN_TAG( rtx, "too bad sequence number, re-syncing RTP [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); InitSeq(seq); this->maxPacketTs = packet->GetTimestamp(); this->maxPacketMs = this->shared->GetTimeMs(); } else { MS_WARN_TAG( rtx, "bad sequence number, ignoring packet [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber()); this->badSeq = (seq + 1) & (RtpSeqMod - 1); // Packet discarded due to late or early arriving. this->packetsDiscarded++; return false; } } // Acceptable misorder. else { // Do nothing. } return true; } inline void RtxStream::InitSeq(uint16_t seq) { MS_TRACE(); // Initialize/reset RTP counters. this->baseSeq = seq; this->maxSeq = seq; this->badSeq = RtpSeqMod + 1; // So seq == badSeq is false. } flatbuffers::Offset RtxStream::Params::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::RtxStream::CreateParamsDirect( builder, this->ssrc, this->payloadType, this->mimeType.ToString().c_str(), this->clockRate, this->rrid.c_str(), this->cname.c_str()); } } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RTP/SharedPacket.cpp ================================================ #define MS_CLASS "RTC::RTP::SharedPacket" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RTP/SharedPacket.hpp" #include "Logger.hpp" #include "RTC/Serializable.hpp" #include // std::align_val_t{ namespace RTC { namespace RTP { /* Static. */ // When cloning a RTP packet, a buffer is allocated for it and its length is // the length of the Packet plus this value (in bytes). static constexpr size_t PacketBufferLengthIncrement{ 100 }; // Callback to pass to every cloned RTP Packet to deallocate its buffer once // the Packet releases its buffer (for example when the Packet is destroyed). static thread_local Serializable::BufferReleasedListener PacketBufferReleasedListener = // NOLINTNEXTLINE(misc-unused-parameters, readability-non-const-parameter) [](const Serializable* packet, uint8_t* buffer) { // NOTE: Needed since we allocated it using ::operator delete[](buffer, std::align_val_t{ 4 }); #ifdef MS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE SharedPacket::allocatedMemory -= packet->GetBufferLength(); MS_DUMP_CLEAN( 0, "[worker.pid:%" PRIu64 "] [RTC::RTP::SharedPacket] memory deallocated [packet buffer:%zu, total allocated memory:%" PRIu64 "]", Logger::Pid, packet->GetBufferLength(), SharedPacket::allocatedMemory); #endif }; /* Class variables. */ #ifdef MS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE thread_local uint64_t SharedPacket::allocatedMemory{ 0 }; #endif /* Instance methods. */ SharedPacket::SharedPacket() : sharedPtr(std::make_shared>(nullptr)) { MS_TRACE(); } SharedPacket::SharedPacket(RTP::Packet* packet) : sharedPtr(std::make_shared>(nullptr)) { MS_TRACE(); if (packet) { StorePacket(packet); } } void SharedPacket::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " has packet: %s", HasPacket() ? "yes" : "no"); if (HasPacket()) { const auto* packet = GetPacket(); packet->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } void SharedPacket::Assign(RTP::Packet* packet) { MS_TRACE(); if (packet) { StorePacket(packet); } else { this->sharedPtr->reset(nullptr); } } void SharedPacket::Reset() { MS_TRACE(); this->sharedPtr->reset(nullptr); } void SharedPacket::AssertSamePacket(const RTP::Packet* otherPacket) const { MS_TRACE(); const auto* packet = GetPacket(); if (!packet && !otherPacket) { return; } else if (packet && !otherPacket) { MS_ABORT("there is a packet in sharedPacket but given otherPacket doesn't have value"); } else if (!packet && otherPacket) { MS_ABORT("there is no packet in sharedPacket but given otherPacket has value"); } else { MS_ASSERT( packet->GetSsrc() == otherPacket->GetSsrc(), "SSRC %" PRIu32 " in packet in sharedPacket != SSRC %" PRIu32 " in otherPacket", packet->GetSsrc(), otherPacket->GetSsrc()); MS_ASSERT( packet->GetSequenceNumber() == otherPacket->GetSequenceNumber(), "seq %" PRIu16 " in packet in sharedPacket != seq %" PRIu16 " in otherPacket", packet->GetSequenceNumber(), otherPacket->GetSequenceNumber()); MS_ASSERT( packet->GetTimestamp() == otherPacket->GetTimestamp(), "timestamp %" PRIu16 " in packet in sharedPacket != timestamp %" PRIu16 " in otherPacket", packet->GetTimestamp(), otherPacket->GetTimestamp()); MS_ASSERT( packet->GetLength() == otherPacket->GetLength(), "length %zu of packet in sharedPacket != length %zu of otherPacket", packet->GetLength(), otherPacket->GetLength()); } } void SharedPacket::StorePacket(RTP::Packet* packet) { MS_TRACE(); const size_t bufferLength = packet->GetLength() + PacketBufferLengthIncrement; // NOTE: Buffer must be 4-byte aligned since RTP packet parsing casts it to // structs (e.g. FixedHeader, HeaderExtension) that require 4-byte alignment. auto* buffer = static_cast(::operator new[](bufferLength, std::align_val_t{ 4 })); auto* clonedPacket = packet->Clone(buffer, bufferLength); // Set a listener in the Packet to deallocate its buffer once the Packet // is destroyed or releases its internal buffer. clonedPacket->SetBufferReleasedListener(std::addressof(PacketBufferReleasedListener)); this->sharedPtr->reset(clonedPacket); #ifdef MS_DUMP_RTP_SHARED_PACKET_MEMORY_USAGE SharedPacket::allocatedMemory += bufferLength; MS_DUMP_CLEAN( 0, "[worker.pid:%" PRIu64 "] [RTC::RTP::SharedPacket] memory allocated [packet buffer:%zu, total allocated memory:%" PRIu64 "]", Logger::Pid, clonedPacket->GetBufferLength(), SharedPacket::allocatedMemory); #endif } } // namespace RTP } // namespace RTC ================================================ FILE: worker/src/RTC/RateCalculator.cpp ================================================ #define MS_CLASS "RTC::RateCalculator" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RateCalculator.hpp" #include "Logger.hpp" #include "Utils.hpp" #include // std::trunc() #include // std::memset() namespace RTC { RateCalculator::RateCalculator(size_t windowSizeMs, float scale, uint16_t windowItems) : windowSizeMs(windowSizeMs), scale(scale), windowItems(windowItems), itemSizeMs(std::max(windowSizeMs / windowItems, size_t{ 1 })) { MS_TRACE(); this->buffer.resize(windowItems); std::memset( static_cast(std::addressof(this->buffer.front())), 0, sizeof(BufferItem) * this->buffer.size()); } void RateCalculator::Update(size_t size, uint64_t nowMs) { MS_TRACE(); // Ignore too old data. Should never happen. if (this->oldestItemStartTime.has_value() && Utils::Number::IsLowerThan(nowMs, *this->oldestItemStartTime)) { MS_WARN_DEV("nowMs < this->oldestItemStartTime, should never happen"); return; } // Increase bytes. this->bytes += size; RemoveOldData(nowMs); // If the elapsed time from the newest item start time is greater than the // item size (in milliseconds), increase the item index. if ( this->newestItemIndex < 0 || !this->newestItemStartTime.has_value() || Utils::Number::IsHigherOrEqualThan( nowMs - *this->newestItemStartTime, this->itemSizeMs)) { this->newestItemIndex++; this->newestItemStartTime = nowMs; if (this->newestItemIndex >= this->windowItems) { MS_DEBUG_DEV("this->newestItemIndex >= this->windowItems, setting this->newestItemIndex = 0"); this->newestItemIndex = 0; } // Advance oldestItemIndex if buffer is full. // NOTE: This avoids a crash: // https://github.com/versatica/mediasoup/issues/1316 if (this->newestItemIndex == this->oldestItemIndex && this->oldestItemIndex != -1) { if (++this->oldestItemIndex >= this->windowItems) { this->oldestItemIndex = 0; } } MS_ASSERT( this->newestItemIndex != this->oldestItemIndex || this->oldestItemIndex == -1, "newest index overlaps with the oldest one [newestItemIndex:%" PRIi32 ", oldestItemIndex:%" PRIi32 "]", this->newestItemIndex, this->oldestItemIndex); // Set the newest item. BufferItem& item = this->buffer[this->newestItemIndex]; item.count = size; item.time = nowMs; } else { // Update the newest item. BufferItem& item = this->buffer[this->newestItemIndex]; item.count += size; } // Set the oldest item index and time, if not set. if (this->oldestItemIndex < 0) { MS_DEBUG_DEV( "this->oldestItemIndex < 0, setting this->oldestItemIndex and this->oldestItemStartTime"); this->oldestItemIndex = this->newestItemIndex; this->oldestItemStartTime = nowMs; } this->totalCount += size; // Reset lastRate and lastTime so GetRate() will calculate rate again even // if called with same now in the same loop iteration. this->lastRate = 0; this->lastTime = std::nullopt; } uint32_t RateCalculator::GetRate(uint64_t nowMs) { MS_TRACE(); if (this->lastTime.has_value() && nowMs == *this->lastTime) { MS_DEBUG_DEV("nowMs == this->lastTime, early return"); return this->lastRate; } RemoveOldData(nowMs); const float scale = this->scale / this->windowSizeMs; this->lastTime = nowMs; this->lastRate = static_cast(std::trunc((this->totalCount * scale) + 0.5f)); return this->lastRate; } void RateCalculator::Reset() { MS_TRACE(); std::memset( static_cast(std::addressof(this->buffer.front())), 0, sizeof(BufferItem) * this->buffer.size()); this->newestItemStartTime = std::nullopt; this->newestItemIndex = -1; this->oldestItemStartTime = std::nullopt; this->oldestItemIndex = -1; this->totalCount = 0u; this->lastRate = 0u; this->lastTime = std::nullopt; } void RateCalculator::RemoveOldData(uint64_t nowMs) { MS_TRACE(); if (!this->oldestItemStartTime.has_value()) { return; } // No item set. if (this->newestItemIndex < 0 || this->oldestItemIndex < 0) { return; } const uint64_t newOldestTime = nowMs - this->windowSizeMs; // Oldest item already removed. if (Utils::Number::IsLowerThan(newOldestTime, *this->oldestItemStartTime)) { return; } // A whole window size time has elapsed since last entry. Reset the buffer. if ( this->newestItemStartTime.has_value() && Utils::Number::IsHigherOrEqualThan(newOldestTime, *this->newestItemStartTime)) { MS_DEBUG_DEV("newOldestTime >= this->newestItemStartTime, resetting the buffer"); Reset(); return; } while (Utils::Number::IsHigherOrEqualThan(newOldestTime, *this->oldestItemStartTime)) { BufferItem& oldestItem = this->buffer[this->oldestItemIndex]; this->totalCount -= oldestItem.count; oldestItem.count = 0u; oldestItem.time = 0u; if (++this->oldestItemIndex >= this->windowItems) { this->oldestItemIndex = 0; } const BufferItem& newOldestItem = this->buffer[this->oldestItemIndex]; this->oldestItemStartTime = newOldestItem.time; } } void RtpDataCounter::Update(const RTC::RTP::Packet* packet) { this->packets++; if (!this->ignorePaddingOnlyPackets || packet->GetPayloadLength() > 0) { const uint64_t nowMs = this->shared->GetTimeMs(); this->rate.Update(packet->GetLength(), nowMs); } } } // namespace RTC ================================================ FILE: worker/src/RTC/Router.cpp ================================================ #define MS_CLASS "RTC::Router" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/Router.hpp" #ifdef MS_LIBURING_SUPPORTED #include "DepLibUring.hpp" #endif #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/ActiveSpeakerObserver.hpp" #include "RTC/AudioLevelObserver.hpp" #include "RTC/DirectTransport.hpp" #include "RTC/PipeTransport.hpp" #include "RTC/PlainTransport.hpp" #include "RTC/RTP/SharedPacket.hpp" #include "RTC/WebRtcTransport.hpp" namespace RTC { /* Instance methods. */ Router::Router(SharedInterface* shared, const std::string& id, Listener* listener) : id(id), shared(shared), listener(listener) { MS_TRACE(); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*ChannelNotificationHandler*/ nullptr); } Router::~Router() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); // Close all Transports. for (auto& kv : this->mapTransports) { auto* transport = kv.second; delete transport; } this->mapTransports.clear(); // Close all RtpObservers. for (auto& kv : this->mapRtpObservers) { auto* rtpObserver = kv.second; delete rtpObserver; } this->mapRtpObservers.clear(); // Clear other maps. this->mapProducerConsumers.clear(); this->mapConsumerProducer.clear(); this->mapProducerRtpObservers.clear(); this->mapProducers.clear(); this->mapDataProducerDataConsumers.clear(); this->mapDataConsumerDataProducer.clear(); this->mapDataProducers.clear(); } flatbuffers::Offset Router::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add transportIds. std::vector> transportIds; transportIds.reserve(this->mapTransports.size()); for (const auto& kv : this->mapTransports) { const auto& transportId = kv.first; transportIds.push_back(builder.CreateString(transportId)); } // Add rtpObserverIds. std::vector> rtpObserverIds; rtpObserverIds.reserve(this->mapRtpObservers.size()); for (const auto& kv : this->mapRtpObservers) { const auto& rtpObserverId = kv.first; rtpObserverIds.push_back(builder.CreateString(rtpObserverId)); } // Add mapProducerIdConsumerIds. std::vector> mapProducerIdConsumerIds; mapProducerIdConsumerIds.reserve(this->mapProducerConsumers.size()); for (const auto& kv : this->mapProducerConsumers) { auto* producer = kv.first; const auto& consumers = kv.second; std::vector> consumerIds; consumerIds.reserve(consumers.size()); for (auto* consumer : consumers) { consumerIds.emplace_back(builder.CreateString(consumer->id)); } mapProducerIdConsumerIds.emplace_back( FBS::Common::CreateStringStringArrayDirect(builder, producer->id.c_str(), &consumerIds)); } // Add mapConsumerIdProducerId. std::vector> mapConsumerIdProducerId; mapConsumerIdProducerId.reserve(this->mapConsumerProducer.size()); for (const auto& kv : this->mapConsumerProducer) { auto* consumer = kv.first; auto* producer = kv.second; mapConsumerIdProducerId.emplace_back( FBS::Common::CreateStringStringDirect(builder, consumer->id.c_str(), producer->id.c_str())); } // Add mapProducerIdObserverIds. std::vector> mapProducerIdObserverIds; mapProducerIdObserverIds.reserve(this->mapProducerRtpObservers.size()); for (const auto& kv : this->mapProducerRtpObservers) { auto* producer = kv.first; const auto& rtpObservers = kv.second; std::vector> observerIds; observerIds.reserve(rtpObservers.size()); for (auto* rtpObserver : rtpObservers) { observerIds.emplace_back(builder.CreateString(rtpObserver->id)); } mapProducerIdObserverIds.emplace_back( FBS::Common::CreateStringStringArrayDirect(builder, producer->id.c_str(), &observerIds)); } // Add mapDataProducerIdDataConsumerIds. std::vector> mapDataProducerIdDataConsumerIds; mapDataProducerIdDataConsumerIds.reserve(this->mapDataProducerDataConsumers.size()); for (const auto& kv : this->mapDataProducerDataConsumers) { auto* dataProducer = kv.first; const auto& dataConsumers = kv.second; std::vector> dataConsumerIds; dataConsumerIds.reserve(dataConsumers.size()); for (auto* dataConsumer : dataConsumers) { dataConsumerIds.emplace_back(builder.CreateString(dataConsumer->id)); } mapDataProducerIdDataConsumerIds.emplace_back( FBS::Common::CreateStringStringArrayDirect( builder, dataProducer->id.c_str(), &dataConsumerIds)); } // Add mapDataConsumerIdDataProducerId. std::vector> mapDataConsumerIdDataProducerId; mapDataConsumerIdDataProducerId.reserve(this->mapDataConsumerDataProducer.size()); for (const auto& kv : this->mapDataConsumerDataProducer) { auto* dataConsumer = kv.first; auto* dataProducer = kv.second; mapDataConsumerIdDataProducerId.emplace_back( FBS::Common::CreateStringStringDirect( builder, dataConsumer->id.c_str(), dataProducer->id.c_str())); } return FBS::Router::CreateDumpResponseDirect( builder, this->id.c_str(), &transportIds, &rtpObserverIds, &mapProducerIdConsumerIds, &mapConsumerIdProducerId, &mapProducerIdObserverIds, &mapDataProducerIdDataConsumerIds, &mapDataConsumerIdDataProducerId); } void Router::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::ROUTER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Router_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::ROUTER_CREATE_WEBRTCTRANSPORT: { const auto* body = request->data->body_as(); auto transportId = body->transportId()->str(); // This may throw. CheckNoTransport(transportId); // This may throw. auto* webRtcTransport = new RTC::WebRtcTransport(this->shared, transportId, this, body->options()); // Insert into the map. this->mapTransports[transportId] = webRtcTransport; MS_DEBUG_DEV("WebRtcTransport created [transportId:%s]", transportId.c_str()); auto dumpOffset = webRtcTransport->FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::WebRtcTransport_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::ROUTER_CREATE_WEBRTCTRANSPORT_WITH_SERVER: { const auto* body = request->data->body_as(); auto transportId = body->transportId()->str(); // This may throw. CheckNoTransport(transportId); const auto* options = body->options(); const auto* listenInfo = options->listen_as(); auto webRtcServerId = listenInfo->webRtcServerId()->str(); auto* webRtcServer = this->listener->OnRouterNeedWebRtcServer(this, webRtcServerId); if (!webRtcServer) { MS_THROW_ERROR("wrong webRtcServerId (no associated WebRtcServer found)"); } auto iceCandidates = webRtcServer->GetIceCandidates( options->enableUdp(), options->enableTcp(), options->preferUdp(), options->preferTcp()); // This may throw. auto* webRtcTransport = new RTC::WebRtcTransport( this->shared, transportId, this, webRtcServer, iceCandidates, options); // Insert into the map. this->mapTransports[transportId] = webRtcTransport; MS_DEBUG_DEV( "WebRtcTransport with WebRtcServer created [transportId:%s]", transportId.c_str()); auto dumpOffset = webRtcTransport->FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::WebRtcTransport_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::ROUTER_CREATE_PLAINTRANSPORT: { const auto* body = request->data->body_as(); auto transportId = body->transportId()->str(); // This may throw. CheckNoTransport(transportId); auto* plainTransport = new RTC::PlainTransport(this->shared, transportId, this, body->options()); // Insert into the map. this->mapTransports[transportId] = plainTransport; MS_DEBUG_DEV("PlainTransport created [transportId:%s]", transportId.c_str()); auto dumpOffset = plainTransport->FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::PlainTransport_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::ROUTER_CREATE_PIPETRANSPORT: { const auto* body = request->data->body_as(); auto transportId = body->transportId()->str(); // This may throw. CheckNoTransport(transportId); auto* pipeTransport = new RTC::PipeTransport(this->shared, transportId, this, body->options()); // Insert into the map. this->mapTransports[transportId] = pipeTransport; MS_DEBUG_DEV("PipeTransport created [transportId:%s]", transportId.c_str()); auto dumpOffset = pipeTransport->FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::PipeTransport_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::ROUTER_CREATE_DIRECTTRANSPORT: { const auto* body = request->data->body_as(); auto transportId = body->transportId()->str(); // This may throw. CheckNoTransport(transportId); auto* directTransport = new RTC::DirectTransport(this->shared, transportId, this, body->options()); // Insert into the map. this->mapTransports[transportId] = directTransport; MS_DEBUG_DEV("DirectTransport created [transportId:%s]", transportId.c_str()); auto dumpOffset = directTransport->FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::DirectTransport_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::ROUTER_CREATE_ACTIVESPEAKEROBSERVER: { const auto* body = request->data->body_as(); auto rtpObserverId = body->rtpObserverId()->str(); // This may throw. CheckNoRtpObserver(rtpObserverId); auto* activeSpeakerObserver = new RTC::ActiveSpeakerObserver(this->shared, rtpObserverId, this, body->options()); // Insert into the map. this->mapRtpObservers[rtpObserverId] = activeSpeakerObserver; MS_DEBUG_DEV("ActiveSpeakerObserver created [rtpObserverId:%s]", rtpObserverId.c_str()); request->Accept(); break; } case Channel::ChannelRequest::Method::ROUTER_CREATE_AUDIOLEVELOBSERVER: { const auto* body = request->data->body_as(); auto rtpObserverId = body->rtpObserverId()->str(); // This may throw. CheckNoRtpObserver(rtpObserverId); auto* audioLevelObserver = new RTC::AudioLevelObserver(this->shared, rtpObserverId, this, body->options()); // Insert into the map. this->mapRtpObservers[rtpObserverId] = audioLevelObserver; MS_DEBUG_DEV("AudioLevelObserver created [rtpObserverId:%s]", rtpObserverId.c_str()); request->Accept(); break; } case Channel::ChannelRequest::Method::ROUTER_CLOSE_TRANSPORT: { const auto* body = request->data->body_as(); auto transportId = body->transportId()->str(); // This may throw. RTC::Transport* transport = GetTransportById(transportId); // Tell the Transport to close all its Producers and Consumers so it will // notify us about their closures. transport->CloseProducersAndConsumers(); // Remove it from the map. this->mapTransports.erase(transport->id); MS_DEBUG_DEV("Transport closed [transportId:%s]", transport->id.c_str()); // Delete it. delete transport; request->Accept(); break; } case Channel::ChannelRequest::Method::ROUTER_CLOSE_RTPOBSERVER: { const auto* body = request->data->body_as(); auto rtpObserverId = body->rtpObserverId()->str(); // This may throw. RTC::RtpObserver* rtpObserver = GetRtpObserverById(rtpObserverId); // Remove it from the map. this->mapRtpObservers.erase(rtpObserver->id); // Iterate all entries in mapProducerRtpObservers and remove the closed one. for (auto& kv : this->mapProducerRtpObservers) { auto& rtpObservers = kv.second; rtpObservers.erase(rtpObserver); } MS_DEBUG_DEV("RtpObserver closed [rtpObserverId:%s]", rtpObserver->id.c_str()); // Delete it. delete rtpObserver; request->Accept(); break; } default: { MS_THROW_ERROR("unknown method"); } } } void Router::CheckNoTransport(const std::string& transportId) const { if (this->mapTransports.find(transportId) != this->mapTransports.end()) { MS_THROW_ERROR("a Transport with same id already exists"); } } void Router::CheckNoRtpObserver(const std::string& rtpObserverId) const { if (this->mapRtpObservers.find(rtpObserverId) != this->mapRtpObservers.end()) { MS_THROW_ERROR("an RtpObserver with same id already exists"); } } RTC::Transport* Router::GetTransportById(const std::string& transportId) const { MS_TRACE(); auto it = this->mapTransports.find(transportId); if (this->mapTransports.find(transportId) == this->mapTransports.end()) { MS_THROW_ERROR("Transport not found"); } return it->second; } RTC::RtpObserver* Router::GetRtpObserverById(const std::string& rtpObserverId) const { MS_TRACE(); auto it = this->mapRtpObservers.find(rtpObserverId); if (this->mapRtpObservers.find(rtpObserverId) == this->mapRtpObservers.end()) { MS_THROW_ERROR("RtpObserver not found"); } return it->second; } void Router::OnTransportNewProducer(RTC::Transport* /*transport*/, RTC::Producer* producer) { MS_TRACE(); MS_ASSERT( this->mapProducerConsumers.find(producer) == this->mapProducerConsumers.end(), "Producer already present in mapProducerConsumers"); if (this->mapProducers.find(producer->id) != this->mapProducers.end()) { MS_THROW_ERROR("Producer already present in mapProducers [producerId:%s]", producer->id.c_str()); } // Insert the Producer in the maps. this->mapProducers[producer->id] = producer; this->mapProducerConsumers[producer]; this->mapProducerRtpObservers[producer]; } void Router::OnTransportProducerClosed(RTC::Transport* /*transport*/, RTC::Producer* producer) { MS_TRACE(); auto mapProducerConsumersIt = this->mapProducerConsumers.find(producer); auto mapProducersIt = this->mapProducers.find(producer->id); auto mapProducerRtpObserversIt = this->mapProducerRtpObservers.find(producer); MS_ASSERT( mapProducerConsumersIt != this->mapProducerConsumers.end(), "Producer not present in mapProducerConsumers"); MS_ASSERT(mapProducersIt != this->mapProducers.end(), "Producer not present in mapProducers"); MS_ASSERT( mapProducerRtpObserversIt != this->mapProducerRtpObservers.end(), "Producer not present in mapProducerRtpObservers"); // Close all Consumers associated to the closed Producer. auto& consumers = mapProducerConsumersIt->second; // NOTE: While iterating the set of Consumers, we call ProducerClosed() on each // one, which will end calling Router::OnTransportConsumerProducerClosed(), // which will remove the Consumer from mapConsumerProducer but won't remove the // closed Consumer from the set of Consumers in mapProducerConsumers (here will // erase the complete entry in that map). for (auto* consumer : consumers) { // Call consumer->ProducerClosed() so the Consumer will notify the Node process, // will notify its Transport, and its Transport will delete the Consumer. consumer->ProducerClosed(); } // Tell all RtpObservers that the Producer has been closed. auto& rtpObservers = mapProducerRtpObserversIt->second; for (auto* rtpObserver : rtpObservers) { rtpObserver->RemoveProducer(producer); } // Remove the Producer from the maps. this->mapProducers.erase(mapProducersIt); this->mapProducerConsumers.erase(mapProducerConsumersIt); this->mapProducerRtpObservers.erase(mapProducerRtpObserversIt); } void Router::OnTransportProducerPaused(RTC::Transport* /*transport*/, RTC::Producer* producer) { MS_TRACE(); auto& consumers = this->mapProducerConsumers.at(producer); for (auto* consumer : consumers) { consumer->ProducerPaused(); } auto it = this->mapProducerRtpObservers.find(producer); if (it != this->mapProducerRtpObservers.end()) { auto& rtpObservers = it->second; for (auto* rtpObserver : rtpObservers) { rtpObserver->ProducerPaused(producer); } } } void Router::OnTransportProducerResumed(RTC::Transport* /*transport*/, RTC::Producer* producer) { MS_TRACE(); auto& consumers = this->mapProducerConsumers.at(producer); for (auto* consumer : consumers) { consumer->ProducerResumed(); } auto it = this->mapProducerRtpObservers.find(producer); if (it != this->mapProducerRtpObservers.end()) { auto& rtpObservers = it->second; for (auto* rtpObserver : rtpObservers) { rtpObserver->ProducerResumed(producer); } } } void Router::OnTransportProducerNewRtpStream( RTC::Transport* /*transport*/, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) { MS_TRACE(); auto& consumers = this->mapProducerConsumers.at(producer); for (auto* consumer : consumers) { consumer->ProducerNewRtpStream(rtpStream, mappedSsrc); } } void Router::OnTransportProducerRtpStreamScore( RTC::Transport* /*transport*/, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) { MS_TRACE(); auto& consumers = this->mapProducerConsumers.at(producer); for (auto* consumer : consumers) { consumer->ProducerRtpStreamScore(rtpStream, score, previousScore); } } void Router::OnTransportProducerRtcpSenderReport( RTC::Transport* /*transport*/, RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first) { MS_TRACE(); auto& consumers = this->mapProducerConsumers.at(producer); for (auto* consumer : consumers) { consumer->ProducerRtcpSenderReport(rtpStream, first); } } void Router::OnTransportProducerRtpPacketReceived( RTC::Transport* /*transport*/, RTC::Producer* producer, RTC::RTP::Packet* packet) { MS_TRACE(); #ifdef MS_RTC_LOGGER_RTP packet->logger.routerId = this->id; #endif auto& consumers = this->mapProducerConsumers.at(producer); if (!consumers.empty()) { // Cloned ref-counted packet that will be filled for as long as needed // avoiding multiple allocations unless absolutely necessary. // Clone only happens if needed and only once. RTC::RTP::SharedPacket sharedPacket; #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Activate liburing usage. DepLibUring::SetActive(); } #endif for (auto* consumer : consumers) { // Update MID RTP extension value. const auto& mid = consumer->GetRtpParameters().mid; if (!mid.empty()) { packet->UpdateMid(mid); } consumer->SendRtpPacket(packet, sharedPacket); // Assert that, if this Consumer filled sharedPacket or it was already // filled, both packet and sharedPacket are the very same RTP packet. if (sharedPacket.HasPacket()) { sharedPacket.AssertSamePacket(packet); } } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Submit all prepared submission entries. DepLibUring::Submit(); } #endif } auto it = this->mapProducerRtpObservers.find(producer); if (it != this->mapProducerRtpObservers.end()) { auto& rtpObservers = it->second; for (auto* rtpObserver : rtpObservers) { rtpObserver->ReceiveRtpPacket(producer, packet); } } } void Router::OnTransportNeedWorstRemoteFractionLost( RTC::Transport* /*transport*/, RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) { MS_TRACE(); auto& consumers = this->mapProducerConsumers.at(producer); for (auto* consumer : consumers) { consumer->NeedWorstRemoteFractionLost(mappedSsrc, worstRemoteFractionLost); } } void Router::OnTransportNewConsumer( RTC::Transport* /*transport*/, RTC::Consumer* consumer, const std::string& producerId) { MS_TRACE(); auto mapProducersIt = this->mapProducers.find(producerId); if (mapProducersIt == this->mapProducers.end()) { MS_THROW_ERROR("Producer not found [producerId:%s]", producerId.c_str()); } auto* producer = mapProducersIt->second; auto mapProducerConsumersIt = this->mapProducerConsumers.find(producer); MS_ASSERT( mapProducerConsumersIt != this->mapProducerConsumers.end(), "Producer not present in mapProducerConsumers"); MS_ASSERT( this->mapConsumerProducer.find(consumer) == this->mapConsumerProducer.end(), "Consumer already present in mapConsumerProducer"); // Update the Consumer status based on the Producer status. if (producer->IsPaused()) { consumer->ProducerPaused(); } // Insert the Consumer in the maps. auto& consumers = mapProducerConsumersIt->second; consumers.insert(consumer); this->mapConsumerProducer[consumer] = producer; // Get all streams in the Producer and provide the Consumer with them. for (const auto& kv : producer->GetRtpStreams()) { auto* rtpStream = kv.first; const uint32_t mappedSsrc = kv.second; consumer->ProducerRtpStream(rtpStream, mappedSsrc); } // Provide the Consumer with the scores of all streams in the Producer. consumer->ProducerRtpStreamScores(producer->GetRtpStreamScores()); } void Router::OnTransportConsumerClosed(RTC::Transport* /*transport*/, RTC::Consumer* consumer) { MS_TRACE(); // NOTE: // This callback is called when the Consumer has been closed but its Producer // remains alive, so the entry in mapProducerConsumers still exists and must // be removed. auto mapConsumerProducerIt = this->mapConsumerProducer.find(consumer); MS_ASSERT( mapConsumerProducerIt != this->mapConsumerProducer.end(), "Consumer not present in mapConsumerProducer"); // Get the associated Producer. auto* producer = mapConsumerProducerIt->second; MS_ASSERT( this->mapProducerConsumers.find(producer) != this->mapProducerConsumers.end(), "Producer not present in mapProducerConsumers"); // Remove the Consumer from the set of Consumers of the Producer. auto& consumers = this->mapProducerConsumers.at(producer); consumers.erase(consumer); // Remove the Consumer from the map. this->mapConsumerProducer.erase(mapConsumerProducerIt); } void Router::OnTransportConsumerProducerClosed(RTC::Transport* /*transport*/, RTC::Consumer* consumer) { MS_TRACE(); // NOTE: // This callback is called when the Consumer has been closed because its // Producer was closed, so the entry in mapProducerConsumers has already been // removed. auto mapConsumerProducerIt = this->mapConsumerProducer.find(consumer); MS_ASSERT( mapConsumerProducerIt != this->mapConsumerProducer.end(), "Consumer not present in mapConsumerProducer"); // Remove the Consumer from the map. this->mapConsumerProducer.erase(mapConsumerProducerIt); } void Router::OnTransportConsumerKeyFrameRequested( RTC::Transport* /*transport*/, RTC::Consumer* consumer, uint32_t mappedSsrc) { MS_TRACE(); auto* producer = this->mapConsumerProducer.at(consumer); producer->RequestKeyFrame(mappedSsrc); } void Router::OnTransportNewDataProducer(RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer) { MS_TRACE(); MS_ASSERT( this->mapDataProducerDataConsumers.find(dataProducer) == this->mapDataProducerDataConsumers.end(), "DataProducer already present in mapDataProducerDataConsumers"); if (this->mapDataProducers.find(dataProducer->id) != this->mapDataProducers.end()) { MS_THROW_ERROR( "DataProducer already present in mapDataProducers [dataProducerId:%s]", dataProducer->id.c_str()); } // Insert the DataProducer in the maps. this->mapDataProducers[dataProducer->id] = dataProducer; this->mapDataProducerDataConsumers[dataProducer]; } void Router::OnTransportDataProducerClosed(RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer) { MS_TRACE(); auto mapDataProducerDataConsumersIt = this->mapDataProducerDataConsumers.find(dataProducer); auto mapDataProducersIt = this->mapDataProducers.find(dataProducer->id); MS_ASSERT( mapDataProducerDataConsumersIt != this->mapDataProducerDataConsumers.end(), "DataProducer not present in mapDataProducerDataConsumers"); MS_ASSERT( mapDataProducersIt != this->mapDataProducers.end(), "DataProducer not present in mapDataProducers"); // Close all DataConsumers associated to the closed DataProducer. auto& dataConsumers = mapDataProducerDataConsumersIt->second; // NOTE: While iterating the set of DataConsumers, we call DataProducerClosed() // on each one, which will end calling // Router::OnTransportDataConsumerDataProducerClosed(), which will remove the // DataConsumer from mapDataConsumerDataProducer but won't remove the closed // DataConsumer from the set of DataConsumers in mapDataProducerDataConsumers // (here will erase the complete entry in that map). for (auto* dataConsumer : dataConsumers) { // Call dataConsumer->DataProducerClosed() so the DataConsumer will notify the Node // process, will notify its Transport, and its Transport will delete the DataConsumer. dataConsumer->DataProducerClosed(); } // Remove the DataProducer from the maps. this->mapDataProducers.erase(mapDataProducersIt); this->mapDataProducerDataConsumers.erase(mapDataProducerDataConsumersIt); } void Router::OnTransportDataProducerPaused(RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer) { MS_TRACE(); auto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer); for (auto* dataConsumer : dataConsumers) { dataConsumer->DataProducerPaused(); } } void Router::OnTransportDataProducerResumed( RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer) { MS_TRACE(); auto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer); for (auto* dataConsumer : dataConsumers) { dataConsumer->DataProducerResumed(); } } // TODO: SCTP: Remove when we migrate to the new SCTP stack. void Router::OnTransportDataProducerMessageReceived( RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer, const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel) { MS_TRACE(); auto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer); if (!dataConsumers.empty()) { #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Activate liburing usage. // The effective sending could be synchronous, thus we would send those // messages within a single system call. DepLibUring::SetActive(); } #endif for (auto* dataConsumer : dataConsumers) { dataConsumer->SendMessage(msg, len, ppid, subchannels, requiredSubchannel); } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Submit all prepared submission entries. DepLibUring::Submit(); } #endif } } void Router::OnTransportDataProducerMessageReceived( RTC::Transport* /*transport*/, RTC::DataProducer* dataProducer, RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel) { MS_TRACE(); auto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer); if (!dataConsumers.empty()) { #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Activate liburing usage. // The effective sending could be synchronous, thus we would send those // messages within a single system call. DepLibUring::SetActive(); } #endif const auto numDataConsumers = dataConsumers.size(); for (auto* dataConsumer : dataConsumers) { const uint16_t streamId = (dataConsumer->GetType() == DataConsumer::Type::SCTP ? dataConsumer->GetSctpStreamParameters().streamId : 0); if (numDataConsumers == 1) { // We must update the Message`s `streamId` for each destination // DataConsumer. // NOTE: clang-tidy doesn't understand that we are only doing this // once in the original `message`. // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) message.SetStreamId(streamId); dataConsumer->SendMessage(std::move(message), subchannels, requiredSubchannel); } // NOTE: Here we are cloning the Message before passing it to each // DataConsumer because each DataConsumer will pass std::move(message) // internally to its SCTP Association. else { auto clonedMessage = message.Clone(); // We must update the Message`s `streamId` for each destination // DataConsumer. clonedMessage.SetStreamId(streamId); dataConsumer->SendMessage(std::move(clonedMessage), subchannels, requiredSubchannel); } } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Submit all prepared submission entries. DepLibUring::Submit(); } #endif } } void Router::OnTransportNewDataConsumer( RTC::Transport* /*transport*/, RTC::DataConsumer* dataConsumer, std::string& dataProducerId) { MS_TRACE(); auto mapDataProducersIt = this->mapDataProducers.find(dataProducerId); if (mapDataProducersIt == this->mapDataProducers.end()) { MS_THROW_ERROR("DataProducer not found [dataProducerId:%s]", dataProducerId.c_str()); } auto* dataProducer = mapDataProducersIt->second; auto mapDataProducerDataConsumersIt = this->mapDataProducerDataConsumers.find(dataProducer); MS_ASSERT( mapDataProducerDataConsumersIt != this->mapDataProducerDataConsumers.end(), "DataProducer not present in mapDataProducerDataConsumers"); MS_ASSERT( this->mapDataConsumerDataProducer.find(dataConsumer) == this->mapDataConsumerDataProducer.end(), "DataConsumer already present in mapDataConsumerDataProducer"); // Update the DataConsumer status based on the DataProducer status. if (dataProducer->IsPaused()) { dataConsumer->DataProducerPaused(); } // Insert the DataConsumer in the maps. auto& dataConsumers = mapDataProducerDataConsumersIt->second; dataConsumers.insert(dataConsumer); this->mapDataConsumerDataProducer[dataConsumer] = dataProducer; } void Router::OnTransportDataConsumerClosed(RTC::Transport* /*transport*/, RTC::DataConsumer* dataConsumer) { MS_TRACE(); // NOTE: // This callback is called when the DataConsumer has been closed but its DataProducer // remains alive, so the entry in mapDataProducerDataConsumers still exists and must // be removed. auto mapDataConsumerDataProducerIt = this->mapDataConsumerDataProducer.find(dataConsumer); MS_ASSERT( mapDataConsumerDataProducerIt != this->mapDataConsumerDataProducer.end(), "DataConsumer not present in mapDataConsumerDataProducer"); // Get the associated DataProducer. auto* dataProducer = mapDataConsumerDataProducerIt->second; MS_ASSERT( this->mapDataProducerDataConsumers.find(dataProducer) != this->mapDataProducerDataConsumers.end(), "DataProducer not present in mapDataProducerDataConsumers"); // Remove the DataConsumer from the set of DataConsumers of the DataProducer. auto& dataConsumers = this->mapDataProducerDataConsumers.at(dataProducer); dataConsumers.erase(dataConsumer); // Remove the DataConsumer from the map. this->mapDataConsumerDataProducer.erase(mapDataConsumerDataProducerIt); } void Router::OnTransportDataConsumerDataProducerClosed( RTC::Transport* /*transport*/, RTC::DataConsumer* dataConsumer) { MS_TRACE(); // NOTE: // This callback is called when the DataConsumer has been closed because its // DataProducer was closed, so the entry in mapDataProducerDataConsumers has already // been removed. auto mapDataConsumerDataProducerIt = this->mapDataConsumerDataProducer.find(dataConsumer); MS_ASSERT( mapDataConsumerDataProducerIt != this->mapDataConsumerDataProducer.end(), "DataConsumer not present in mapDataConsumerDataProducer"); // Remove the DataConsumer from the map. this->mapDataConsumerDataProducer.erase(mapDataConsumerDataProducerIt); } void Router::OnTransportListenServerClosed(RTC::Transport* transport) { MS_TRACE(); MS_ASSERT( this->mapTransports.find(transport->id) != this->mapTransports.end(), "Transport not present in mapTransports"); // Tell the Transport to close all its Producers and Consumers so it will // notify us about their closures. transport->CloseProducersAndConsumers(); // Remove it from the map. this->mapTransports.erase(transport->id); // Delete it. delete transport; } void Router::OnRtpObserverAddProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer) { // Add to the map. this->mapProducerRtpObservers[producer].insert(rtpObserver); } void Router::OnRtpObserverRemoveProducer(RTC::RtpObserver* rtpObserver, RTC::Producer* producer) { // Remove from the map. this->mapProducerRtpObservers[producer].erase(rtpObserver); } RTC::Producer* Router::RtpObserverGetProducer( RTC::RtpObserver* /* rtpObserver */, const std::string& id) { auto it = this->mapProducers.find(id); if (it == this->mapProducers.end()) { MS_THROW_ERROR("Producer not found"); } RTC::Producer* producer = it->second; return producer; } } // namespace RTC ================================================ FILE: worker/src/RTC/RtcLogger.cpp ================================================ #define MS_CLASS "RTC::RtcLogger" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RtcLogger.hpp" #include "Logger.hpp" #include namespace RTC { namespace RtcLogger { // clang-format off const absl::flat_hash_map RtpPacket::DiscardReason2String = { { RtpPacket::DiscardReason::NONE, "None" }, { RtpPacket::DiscardReason::PRODUCER_NOT_FOUND, "ProducerNotFound" }, { RtpPacket::DiscardReason::RECV_RTP_STREAM_NOT_FOUND, "RecvRtpStreamNotFound" }, { RtpPacket::DiscardReason::RECV_RTP_STREAM_DISCARDED, "RecvRtpStreamDiscarded" }, { RtpPacket::DiscardReason::RECV_RTP_RTX_STREAM_DISCARDED, "RecvRtpRtxStreamDiscarded" }, { RtpPacket::DiscardReason::CONSUMER_INACTIVE, "ConsumerInactive" }, { RtpPacket::DiscardReason::INVALID_TARGET_LAYER, "InvalidTargetLayer" }, { RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE, "UnsupportedPayloadType" }, { RtpPacket::DiscardReason::NOT_A_KEYFRAME, "NotAKeyframe" }, { RtpPacket::DiscardReason::EMPTY_PAYLOAD, "EmptyPayload" }, { RtpPacket::DiscardReason::SPATIAL_LAYER_MISMATCH, "SpatialLayerMismatch" }, { RtpPacket::DiscardReason::PACKET_PREVIOUS_TO_SPATIAL_LAYER_SWITCH, "PacketPreviousToSpatialLayerSwitch" }, { RtpPacket::DiscardReason::DROPPED_BY_CODEC, "DroppedByCodec" }, { RtpPacket::DiscardReason::TOO_HIGH_TIMESTAMP_EXTRA_NEEDED, "TooHighTimestampExtraNeeded"}, { RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED, "SendRtpStreamDiscarded"} }; // clang-format on void RtpPacket::Sent() { MS_TRACE(); this->discarded = false; Log(); Clear(); } void RtpPacket::Discarded(DiscardReason discardReason) { MS_TRACE(); this->discarded = true; this->discardReason = discardReason; Log(); Clear(); } void RtpPacket::Log() const { MS_TRACE(); std::stringstream ss; ss << "{"; ss << "\"timestamp\": " << this->timestamp; if (!this->recvTransportId.empty()) { ss << R"(, "recvTransportId": ")" << this->recvTransportId << "\""; } if (!this->sendTransportId.empty()) { ss << R"(, "sendTransportId": ")" << this->sendTransportId << "\""; } if (!this->routerId.empty()) { ss << R"(, "routerId": ")" << this->routerId << "\""; } if (!this->producerId.empty()) { ss << R"(, "producerId": ")" << this->producerId << "\""; } if (!this->consumerId.empty()) { ss << R"(, "consumerId": ")" << this->consumerId << "\""; } ss << ", \"recvRtpTimestamp\": " << this->recvRtpTimestamp; ss << ", \"sendRtpTimestamp\": " << this->sendRtpTimestamp; ss << ", \"recvSeqNumber\": " << this->recvSeqNumber; ss << ", \"sendSeqNumber\": " << this->sendSeqNumber; ss << ", \"discarded\": " << (this->discarded ? "true" : "false"); ss << ", \"discardReason\": '" << RtpPacket::DiscardReason2String.at(this->discardReason) << "'"; ss << "}"; MS_DUMP_CLEAN(0, "%s", ss.str().c_str()); } void RtpPacket::Clear() { MS_TRACE(); this->sendTransportId = {}; this->routerId = {}; this->producerId = {}; this->sendSeqNumber = { 0 }; this->discarded = { false }; this->discardReason = { DiscardReason::NONE }; } } // namespace RtcLogger } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/Parameters.cpp ================================================ #define MS_CLASS "RTC::Parameters" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/Parameters.hpp" #include "Logger.hpp" namespace RTC { /* Instance methods. */ std::vector> Parameters::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); std::vector> parameters; for (const auto& kv : this->mapKeyValues) { const auto& key = kv.first; const auto& value = kv.second; flatbuffers::Offset parameter; switch (value.type) { case Value::Type::BOOLEAN: { auto valueOffset = FBS::RtpParameters::CreateBoolean(builder, value.booleanValue); parameter = FBS::RtpParameters::CreateParameterDirect( builder, key.c_str(), FBS::RtpParameters::Value::Boolean, valueOffset.Union()); break; } case Value::Type::INTEGER: { auto valueOffset = FBS::RtpParameters::CreateInteger32(builder, value.integerValue); parameters.emplace_back( FBS::RtpParameters::CreateParameterDirect( builder, key.c_str(), FBS::RtpParameters::Value::Integer32, valueOffset.Union())); break; } case Value::Type::DOUBLE: { auto valueOffset = FBS::RtpParameters::CreateDouble(builder, value.doubleValue); parameters.emplace_back( FBS::RtpParameters::CreateParameterDirect( builder, key.c_str(), FBS::RtpParameters::Value::Double, valueOffset.Union())); break; } case Value::Type::STRING: { auto valueOffset = FBS::RtpParameters::CreateStringDirect(builder, value.stringValue.c_str()); parameters.emplace_back( FBS::RtpParameters::CreateParameterDirect( builder, key.c_str(), FBS::RtpParameters::Value::String, valueOffset.Union())); break; } case Value::Type::ARRAY_OF_INTEGERS: { auto valueOffset = FBS::RtpParameters::CreateInteger32ArrayDirect(builder, &value.arrayOfIntegers); parameters.emplace_back( FBS::RtpParameters::CreateParameterDirect( builder, key.c_str(), FBS::RtpParameters::Value::Integer32Array, valueOffset.Union())); break; } } } return parameters; } void Parameters::Set(const flatbuffers::Vector>* data) { MS_TRACE(); for (const auto* parameter : *data) { const auto key = parameter->name()->str(); switch (parameter->value_type()) { case FBS::RtpParameters::Value::Boolean: { this->mapKeyValues.emplace(key, Value(parameter->value_as_Boolean()->value() != 0)); break; } case FBS::RtpParameters::Value::Integer32: { this->mapKeyValues.emplace(key, Value(parameter->value_as_Integer32()->value())); break; } case FBS::RtpParameters::Value::Double: { this->mapKeyValues.emplace(key, Value(parameter->value_as_Double()->value())); break; } case FBS::RtpParameters::Value::String: { this->mapKeyValues.emplace(key, Value(parameter->value_as_String()->value()->str())); break; } case FBS::RtpParameters::Value::Integer32Array: { this->mapKeyValues.emplace(key, Value(parameter->value_as_Integer32Array()->value())); break; } default:; // Just ignore other value types. } } } bool Parameters::HasBoolean(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); if (it == this->mapKeyValues.end()) { return false; } const auto& value = it->second; return value.type == Value::Type::BOOLEAN; } bool Parameters::HasInteger(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); if (it == this->mapKeyValues.end()) { return false; } const auto& value = it->second; return value.type == Value::Type::INTEGER; } bool Parameters::HasPositiveInteger(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); if (it == this->mapKeyValues.end()) { return false; } const auto& value = it->second; return value.type == Value::Type::INTEGER && value.integerValue >= 0; } bool Parameters::HasDouble(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); if (it == this->mapKeyValues.end()) { return false; } const auto& value = it->second; return value.type == Value::Type::DOUBLE; } bool Parameters::HasString(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); if (it == this->mapKeyValues.end()) { return false; } const auto& value = it->second; return value.type == Value::Type::STRING; } bool Parameters::HasArrayOfIntegers(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); if (it == this->mapKeyValues.end()) { return false; } const auto& value = it->second; return value.type == Value::Type::ARRAY_OF_INTEGERS; } bool Parameters::IncludesInteger(const std::string& key, int32_t integer) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); if (it == this->mapKeyValues.end()) { return false; } const auto& value = it->second; const auto& array = value.arrayOfIntegers; return std::find(array.begin(), array.end(), integer) != array.end(); } bool Parameters::GetBoolean(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); MS_ASSERT(it != this->mapKeyValues.end(), "key does not exist [key:%s]", key.c_str()); const auto& value = it->second; return value.booleanValue; } int32_t Parameters::GetInteger(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); MS_ASSERT(it != this->mapKeyValues.end(), "key does not exist [key:%s]", key.c_str()); const auto& value = it->second; return value.integerValue; } double Parameters::GetDouble(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); MS_ASSERT(it != this->mapKeyValues.end(), "key does not exist [key:%s]", key.c_str()); const auto& value = it->second; return value.doubleValue; } const std::string& Parameters::GetString(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); MS_ASSERT(it != this->mapKeyValues.end(), "key does not exist [key:%s]", key.c_str()); const auto& value = it->second; return value.stringValue; } const std::vector& Parameters::GetArrayOfIntegers(const std::string& key) const { MS_TRACE(); auto it = this->mapKeyValues.find(key); MS_ASSERT(it != this->mapKeyValues.end(), "key does not exist [key:%s]", key.c_str()); const auto& value = it->second; return value.arrayOfIntegers; } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/RtcpFeedback.cpp ================================================ #define MS_CLASS "RTC::RtcpFeedback" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "RTC/RtpDictionaries.hpp" namespace RTC { /* Instance methods. */ RtcpFeedback::RtcpFeedback(const FBS::RtpParameters::RtcpFeedback* data) { MS_TRACE(); this->type = data->type()->str(); if (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtcpFeedback::VT_PARAMETER)) { this->parameter = data->parameter()->str(); } } flatbuffers::Offset RtcpFeedback::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::RtpParameters::CreateRtcpFeedbackDirect( builder, this->type.c_str(), this->parameter.empty() ? nullptr : this->parameter.c_str()); } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/RtcpParameters.cpp ================================================ #define MS_CLASS "RTC::RtcpParameters" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "RTC/RtpDictionaries.hpp" namespace RTC { /* Instance methods. */ RtcpParameters::RtcpParameters(const FBS::RtpParameters::RtcpParameters* data) { MS_TRACE(); // cname is optional. if (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtcpParameters::VT_CNAME)) { this->cname = data->cname()->str(); } // reducedSize is optional, default value is true. this->reducedSize = data->reducedSize(); } flatbuffers::Offset RtcpParameters::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::RtpParameters::CreateRtcpParametersDirect( builder, this->cname.c_str(), this->reducedSize); } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/RtpCodecMimeType.cpp ================================================ #define MS_CLASS "RTC::RtpCodecMimeType" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "RTC/RtpDictionaries.hpp" namespace RTC { /* Class variables. */ // clang-format off const absl::flat_hash_map RtpCodecMimeType::String2Type = { { "audio", RtpCodecMimeType::Type::AUDIO }, { "video", RtpCodecMimeType::Type::VIDEO } }; const absl::flat_hash_map RtpCodecMimeType::Type2String = { { RtpCodecMimeType::Type::AUDIO, "audio" }, { RtpCodecMimeType::Type::VIDEO, "video" } }; const absl::flat_hash_map RtpCodecMimeType::String2Subtype = { // Audio codecs: { "opus", RtpCodecMimeType::Subtype::OPUS }, { "multiopus", RtpCodecMimeType::Subtype::MULTIOPUS }, { "pcma", RtpCodecMimeType::Subtype::PCMA }, { "pcmu", RtpCodecMimeType::Subtype::PCMU }, { "isac", RtpCodecMimeType::Subtype::ISAC }, { "g722", RtpCodecMimeType::Subtype::G722 }, { "ilbc", RtpCodecMimeType::Subtype::ILBC }, { "silk", RtpCodecMimeType::Subtype::SILK }, // Video codecs: { "vp8", RtpCodecMimeType::Subtype::VP8 }, { "vp9", RtpCodecMimeType::Subtype::VP9 }, { "h264", RtpCodecMimeType::Subtype::H264 }, { "av1", RtpCodecMimeType::Subtype::AV1 }, // Complementary codecs: { "cn", RtpCodecMimeType::Subtype::CN }, { "telephone-event", RtpCodecMimeType::Subtype::TELEPHONE_EVENT }, // Feature codecs: { "rtx", RtpCodecMimeType::Subtype::RTX }, { "ulpfec", RtpCodecMimeType::Subtype::ULPFEC }, { "flexfec", RtpCodecMimeType::Subtype::FLEXFEC }, { "x-ulpfecuc", RtpCodecMimeType::Subtype::X_ULPFECUC }, { "red", RtpCodecMimeType::Subtype::RED } }; const absl::flat_hash_map RtpCodecMimeType::Subtype2String = { // Audio codecs: { RtpCodecMimeType::Subtype::OPUS, "opus" }, { RtpCodecMimeType::Subtype::MULTIOPUS, "multiopus" }, { RtpCodecMimeType::Subtype::PCMA, "PCMA" }, { RtpCodecMimeType::Subtype::PCMU, "PCMU" }, { RtpCodecMimeType::Subtype::ISAC, "ISAC" }, { RtpCodecMimeType::Subtype::G722, "G722" }, { RtpCodecMimeType::Subtype::ILBC, "iLBC" }, { RtpCodecMimeType::Subtype::SILK, "SILK" }, // Video codecs: { RtpCodecMimeType::Subtype::VP8, "VP8" }, { RtpCodecMimeType::Subtype::VP9, "VP9" }, { RtpCodecMimeType::Subtype::H264, "H264" }, { RtpCodecMimeType::Subtype::AV1, "AV1" }, // Complementary codecs: { RtpCodecMimeType::Subtype::CN, "CN" }, { RtpCodecMimeType::Subtype::TELEPHONE_EVENT, "telephone-event" }, // Feature codecs: { RtpCodecMimeType::Subtype::RTX, "rtx" }, { RtpCodecMimeType::Subtype::ULPFEC, "ulpfec" }, { RtpCodecMimeType::Subtype::FLEXFEC, "flexfec" }, { RtpCodecMimeType::Subtype::X_ULPFECUC, "x-ulpfecuc" }, { RtpCodecMimeType::Subtype::RED, "red" } }; // clang-format on /* Instance methods. */ void RtpCodecMimeType::SetMimeType(const std::string& mimeType) { MS_TRACE(); auto slashPos = mimeType.find('/'); if (slashPos == std::string::npos || slashPos == 0 || slashPos == mimeType.length() - 1) { MS_THROW_TYPE_ERROR("wrong codec MIME"); } std::string type = mimeType.substr(0, slashPos); std::string subtype = mimeType.substr(slashPos + 1); // Force lowcase names. Utils::String::ToLowerCase(type); Utils::String::ToLowerCase(subtype); // Set MIME type. { auto it = RtpCodecMimeType::String2Type.find(type); if (it == RtpCodecMimeType::String2Type.end()) { MS_THROW_TYPE_ERROR("unknown codec MIME type '%s'", type.c_str()); } this->type = it->second; } // Set MIME subtype. { auto it = RtpCodecMimeType::String2Subtype.find(subtype); if (it == RtpCodecMimeType::String2Subtype.end()) { MS_THROW_TYPE_ERROR("unknown codec MIME subtype '%s'", subtype.c_str()); } this->subtype = it->second; } // Set mimeType. this->mimeType = RtpCodecMimeType::Type2String.at(this->type) + "/" + RtpCodecMimeType::Subtype2String.at(this->subtype); } void RtpCodecMimeType::UpdateMimeType() { MS_TRACE(); // Set mimeType. this->mimeType = RtpCodecMimeType::Type2String.at(this->type) + "/" + RtpCodecMimeType::Subtype2String.at(this->subtype); } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/RtpCodecParameters.cpp ================================================ #define MS_CLASS "RTC::RtpCodecParameters" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/RtpDictionaries.hpp" namespace RTC { /* Instance methods. */ RtpCodecParameters::RtpCodecParameters(const FBS::RtpParameters::RtpCodecParameters* data) { MS_TRACE(); // Set MIME field. // This may throw. this->mimeType.SetMimeType(data->mimeType()->str()); // payloadType. this->payloadType = data->payloadType(); // clockRate. this->clockRate = data->clockRate(); // channels is optional. if (auto channels = data->channels(); channels.has_value()) { this->channels = channels.value(); } // parameters is optional. if (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpCodecParameters::VT_PARAMETERS)) { this->parameters.Set(data->parameters()); } // rtcpFeedback is optional. if (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpCodecParameters::VT_RTCPFEEDBACK)) { this->rtcpFeedback.reserve(data->rtcpFeedback()->size()); for (const auto* entry : *data->rtcpFeedback()) { this->rtcpFeedback.emplace_back(entry); } } // Check codec. CheckCodec(); } flatbuffers::Offset RtpCodecParameters::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); auto parameters = this->parameters.FillBuffer(builder); std::vector> rtcpFeedback; rtcpFeedback.reserve(this->rtcpFeedback.size()); for (const auto& fb : this->rtcpFeedback) { rtcpFeedback.emplace_back(fb.FillBuffer(builder)); } return FBS::RtpParameters::CreateRtpCodecParametersDirect( builder, this->mimeType.ToString().c_str(), this->payloadType, this->clockRate, this->channels > 1 ? flatbuffers::Optional(this->channels) : flatbuffers::nullopt, ¶meters, &rtcpFeedback); } inline void RtpCodecParameters::CheckCodec() const { MS_TRACE(); static const std::string AptString{ "apt" }; // Check per MIME parameters and set default values. switch (this->mimeType.subtype) { case RTC::RtpCodecMimeType::Subtype::RTX: { // A RTX codec must have 'apt' parameter. if (!this->parameters.HasPositiveInteger(AptString)) { MS_THROW_TYPE_ERROR("missing apt parameter in RTX codec"); } break; } default:; } } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/RtpEncodingParameters.cpp ================================================ #define MS_CLASS "RTC::RtpEncodingParameters" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/RtpDictionaries.hpp" #include #include namespace RTC { /* Instance methods. */ RtpEncodingParameters::RtpEncodingParameters(const FBS::RtpParameters::RtpEncodingParameters* data) { MS_TRACE(); // ssrc is optional. if (auto ssrc = data->ssrc(); ssrc.has_value()) { this->ssrc = ssrc.value(); } // rid is optional. if (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpEncodingParameters::VT_RID)) { this->rid = data->rid()->str(); } // codecPayloadType is optional. if (auto codecPayloadType = data->codecPayloadType(); codecPayloadType.has_value()) { this->codecPayloadType = codecPayloadType.value(); this->hasCodecPayloadType = true; } // rtx is optional. if (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpEncodingParameters::VT_RTX)) { this->rtx = RtpRtxParameters(data->rtx()); this->hasRtx = true; } // maxBitrate is optional. if (auto maxBitrate = data->maxBitrate(); maxBitrate.has_value()) { this->maxBitrate = maxBitrate.value(); } // dtx is optional, default is false. this->dtx = data->dtx(); // scalabilityMode is optional. if (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpEncodingParameters::VT_SCALABILITYMODE)) { const std::string scalabilityMode = data->scalabilityMode()->str(); static const std::regex ScalabilityModeRegex( "^[LS]([1-9]\\d{0,1})T([1-9]\\d{0,1})(_KEY)?.*", std::regex_constants::ECMAScript); std::smatch match; std::regex_match(scalabilityMode, match, ScalabilityModeRegex); if (!match.empty()) { this->scalabilityMode = scalabilityMode; try { this->spatialLayers = std::stoul(match[1].str()); this->temporalLayers = std::stoul(match[2].str()); this->ksvc = match.size() >= 4 && match[3].str() == "_KEY"; } catch (std::exception& e) { MS_THROW_TYPE_ERROR("invalid scalabilityMode: %s", e.what()); } } } } flatbuffers::Offset RtpEncodingParameters::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::RtpParameters::CreateRtpEncodingParametersDirect( builder, this->ssrc != 0u ? flatbuffers::Optional(this->ssrc) : flatbuffers::nullopt, !this->rid.empty() ? this->rid.c_str() : nullptr, this->hasCodecPayloadType ? flatbuffers::Optional(this->codecPayloadType) : flatbuffers::nullopt, this->hasRtx ? this->rtx.FillBuffer(builder) : 0u, this->dtx, this->scalabilityMode.c_str()); } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/RtpHeaderExtensionParameters.cpp ================================================ #define MS_CLASS "RTC::RtpHeaderExtensionParameters" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/RtpDictionaries.hpp" namespace RTC { /* Instance methods. */ RtpHeaderExtensionParameters::RtpHeaderExtensionParameters( const FBS::RtpParameters::RtpHeaderExtensionParameters* const data) { MS_TRACE(); // Get the type. this->type = RTC::RtpHeaderExtensionUri::TypeFromFbs(data->uri()); this->id = data->id(); // Don't allow id 0. if (this->id == 0u) { MS_THROW_TYPE_ERROR("invalid id 0"); } // encrypt is false by default. this->encrypt = data->encrypt(); // parameters is optional. if (flatbuffers::IsFieldPresent(data, FBS::RtpParameters::RtpHeaderExtensionParameters::VT_PARAMETERS)) { this->parameters.Set(data->parameters()); } } flatbuffers::Offset RtpHeaderExtensionParameters::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); auto parameters = this->parameters.FillBuffer(builder); return FBS::RtpParameters::CreateRtpHeaderExtensionParametersDirect( builder, RTC::RtpHeaderExtensionUri::TypeToFbs(this->type), this->id, this->encrypt, ¶meters); } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/RtpHeaderExtensionUri.cpp ================================================ #define MS_CLASS "RTC::RtpHeaderExtensionUri" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RtpDictionaries.hpp" namespace RTC { /* Class methods. */ RtpHeaderExtensionUri::Type RtpHeaderExtensionUri::TypeFromFbs( FBS::RtpParameters::RtpHeaderExtensionUri uri) { switch (uri) { case FBS::RtpParameters::RtpHeaderExtensionUri::Mid: { return RtpHeaderExtensionUri::Type::MID; } case FBS::RtpParameters::RtpHeaderExtensionUri::RtpStreamId: { return RtpHeaderExtensionUri::Type::RTP_STREAM_ID; } case FBS::RtpParameters::RtpHeaderExtensionUri::RepairRtpStreamId: { return RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID; } case FBS::RtpParameters::RtpHeaderExtensionUri::AbsSendTime: { return RtpHeaderExtensionUri::Type::ABS_SEND_TIME; } case FBS::RtpParameters::RtpHeaderExtensionUri::TransportWideCcDraft01: { return RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01; } case FBS::RtpParameters::RtpHeaderExtensionUri::SsrcAudioLevel: { return RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL; } case FBS::RtpParameters::RtpHeaderExtensionUri::VideoOrientation: { return RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION; } case FBS::RtpParameters::RtpHeaderExtensionUri::TimeOffset: { return RtpHeaderExtensionUri::Type::TIME_OFFSET; } case FBS::RtpParameters::RtpHeaderExtensionUri::PlayoutDelay: { return RtpHeaderExtensionUri::Type::PLAYOUT_DELAY; } case FBS::RtpParameters::RtpHeaderExtensionUri::AbsCaptureTime: { return RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME; } case FBS::RtpParameters::RtpHeaderExtensionUri::DependencyDescriptor: { return RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR; } case FBS::RtpParameters::RtpHeaderExtensionUri::MediasoupPacketId: { return RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID; } NO_DEFAULT_GCC(); } } FBS::RtpParameters::RtpHeaderExtensionUri RtpHeaderExtensionUri::TypeToFbs( RtpHeaderExtensionUri::Type uri) { switch (uri) { case RtpHeaderExtensionUri::Type::MID: { return FBS::RtpParameters::RtpHeaderExtensionUri::Mid; } case RtpHeaderExtensionUri::Type::RTP_STREAM_ID: { return FBS::RtpParameters::RtpHeaderExtensionUri::RtpStreamId; } case RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID: { return FBS::RtpParameters::RtpHeaderExtensionUri::RepairRtpStreamId; } case RtpHeaderExtensionUri::Type::ABS_SEND_TIME: { return FBS::RtpParameters::RtpHeaderExtensionUri::AbsSendTime; } case RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01: { return FBS::RtpParameters::RtpHeaderExtensionUri::TransportWideCcDraft01; } case RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL: { return FBS::RtpParameters::RtpHeaderExtensionUri::SsrcAudioLevel; } case RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR: { return FBS::RtpParameters::RtpHeaderExtensionUri::DependencyDescriptor; } case RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION: { return FBS::RtpParameters::RtpHeaderExtensionUri::VideoOrientation; } case RtpHeaderExtensionUri::Type::TIME_OFFSET: { return FBS::RtpParameters::RtpHeaderExtensionUri::TimeOffset; } case RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME: { return FBS::RtpParameters::RtpHeaderExtensionUri::AbsCaptureTime; } case RtpHeaderExtensionUri::Type::PLAYOUT_DELAY: { return FBS::RtpParameters::RtpHeaderExtensionUri::PlayoutDelay; } case RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID: { return FBS::RtpParameters::RtpHeaderExtensionUri::MediasoupPacketId; } NO_DEFAULT_GCC(); } } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/RtpParameters.cpp ================================================ #define MS_CLASS "RTC::RtpParameters" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/RTP/Codecs/Tools.hpp" #include "RTC/RtpDictionaries.hpp" #include namespace RTC { /* Class variables. */ // clang-format off const absl::flat_hash_map RtpParameters::Type2String = { { RtpParameters::Type::SIMPLE, "simple" }, { RtpParameters::Type::SIMULCAST, "simulcast" }, { RtpParameters::Type::SVC, "svc" }, { RtpParameters::Type::PIPE, "pipe" } }; // clang-format on /* Class methods. */ std::optional RtpParameters::GetType(const RtpParameters& rtpParameters) { MS_TRACE(); std::optional type; if (rtpParameters.encodings.size() == 1) { const auto& encoding = rtpParameters.encodings[0]; const auto* mediaCodec = rtpParameters.GetCodecForEncoding(const_cast(encoding)); if (encoding.spatialLayers > 1 || encoding.temporalLayers > 1) { if (RTC::RTP::Codecs::Tools::IsValidTypeForCodec(RtpParameters::Type::SVC, mediaCodec->mimeType)) { type.emplace(RtpParameters::Type::SVC); } else if ( RTC::RTP::Codecs::Tools::IsValidTypeForCodec( RtpParameters::Type::SIMULCAST, mediaCodec->mimeType)) { type.emplace(RtpParameters::Type::SIMULCAST); } } else { type.emplace(RtpParameters::Type::SIMPLE); } } else if (rtpParameters.encodings.size() > 1) { type.emplace(RtpParameters::Type::SIMULCAST); } return type; } const std::string& RtpParameters::GetTypeString(RtpParameters::Type type) { MS_TRACE(); return RtpParameters::Type2String.at(type); } FBS::RtpParameters::Type RtpParameters::TypeToFbs(RtpParameters::Type type) { MS_TRACE(); switch (type) { case Type::SIMPLE: { return FBS::RtpParameters::Type::SIMPLE; } case Type::SIMULCAST: { return FBS::RtpParameters::Type::SIMULCAST; } case Type::SVC: { return FBS::RtpParameters::Type::SVC; } case Type::PIPE: { return FBS::RtpParameters::Type::PIPE; } NO_DEFAULT_GCC(); } } /* Instance methods. */ RtpParameters::RtpParameters(const FBS::RtpParameters::RtpParameters* data) { MS_TRACE(); // mid is optional. if (data->mid()) { this->mid = data->mid()->str(); } this->codecs.reserve(data->codecs()->size()); for (const auto* entry : *data->codecs()) { // This may throw due the constructor of RTC::RtpCodecParameters. this->codecs.emplace_back(entry); } if (this->codecs.empty()) { MS_THROW_TYPE_ERROR("empty codecs"); } this->encodings.reserve(data->encodings()->size()); for (const auto* entry : *data->encodings()) { // This may throw due the constructor of RTC::RtpEncodingParameters. this->encodings.emplace_back(entry); } if (this->encodings.empty()) { MS_THROW_TYPE_ERROR("empty encodings"); } this->headerExtensions.reserve(data->headerExtensions()->size()); for (const auto* entry : *data->headerExtensions()) { // This may throw due the constructor of RTC::RtpHeaderExtensionParameters. this->headerExtensions.emplace_back(entry); } // This may throw. this->rtcp = RTC::RtcpParameters(data->rtcp()); // msid is optional. if (data->msid()) { this->msid = data->msid()->str(); } // Validate RTP parameters. ValidateCodecs(); ValidateEncodings(); } flatbuffers::Offset RtpParameters::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add codecs. std::vector> codecs; codecs.reserve(this->codecs.size()); for (const auto& codec : this->codecs) { codecs.emplace_back(codec.FillBuffer(builder)); } // Add encodings. std::vector> encodings; encodings.reserve(this->encodings.size()); for (const auto& encoding : this->encodings) { encodings.emplace_back(encoding.FillBuffer(builder)); } // Add headerExtensions. std::vector> headerExtensions; headerExtensions.reserve(this->headerExtensions.size()); for (const auto& headerExtension : this->headerExtensions) { headerExtensions.emplace_back(headerExtension.FillBuffer(builder)); } // Add rtcp. flatbuffers::Offset rtcp; rtcp = this->rtcp.FillBuffer(builder); return FBS::RtpParameters::CreateRtpParametersDirect( builder, this->mid.c_str(), &codecs, &headerExtensions, &encodings, rtcp, this->msid.c_str()); } const RTC::RtpCodecParameters* RtpParameters::GetCodecForEncoding(RtpEncodingParameters& encoding) const { MS_TRACE(); const uint8_t payloadType = encoding.codecPayloadType; auto it = this->codecs.begin(); for (; it != this->codecs.end(); ++it) { const auto& codec = *it; if (codec.payloadType == payloadType) { return std::addressof(codec); } } // This should never happen. if (it == this->codecs.end()) { MS_ABORT("no valid codec payload type for the given encoding"); } return nullptr; } const RTC::RtpCodecParameters* RtpParameters::GetRtxCodecForEncoding(RtpEncodingParameters& encoding) const { MS_TRACE(); static const std::string AptString{ "apt" }; const uint8_t payloadType = encoding.codecPayloadType; for (const auto& codec : this->codecs) { if (codec.mimeType.IsFeatureCodec() && codec.parameters.GetInteger(AptString) == payloadType) { return std::addressof(codec); } } return nullptr; } void RtpParameters::ValidateCodecs() { MS_TRACE(); static const std::string AptString{ "apt" }; absl::flat_hash_set payloadTypes; for (auto& codec : this->codecs) { if (payloadTypes.find(codec.payloadType) != payloadTypes.end()) { MS_THROW_TYPE_ERROR("duplicated payloadType"); } payloadTypes.insert(codec.payloadType); switch (codec.mimeType.subtype) { // A RTX codec must have 'apt' parameter pointing to a non RTX codec. case RTC::RtpCodecMimeType::Subtype::RTX: { // NOTE: RtpCodecParameters already asserted that there is apt parameter. const int32_t apt = codec.parameters.GetInteger(AptString); auto it = this->codecs.begin(); for (; it != this->codecs.end(); ++it) { const auto& codec = *it; if (static_cast(codec.payloadType) == apt) { if (codec.mimeType.subtype == RTC::RtpCodecMimeType::Subtype::RTX) { MS_THROW_TYPE_ERROR("apt in RTX codec points to a RTX codec"); } else if (codec.mimeType.subtype == RTC::RtpCodecMimeType::Subtype::ULPFEC) { MS_THROW_TYPE_ERROR("apt in RTX codec points to a ULPFEC codec"); } else if (codec.mimeType.subtype == RTC::RtpCodecMimeType::Subtype::FLEXFEC) { MS_THROW_TYPE_ERROR("apt in RTX codec points to a FLEXFEC codec"); } else { break; } } } if (it == this->codecs.end()) { MS_THROW_TYPE_ERROR("apt in RTX codec points to a non existing codec"); } break; } default:; } } } void RtpParameters::ValidateEncodings() { uint8_t firstMediaPayloadType{ 0 }; { auto it = this->codecs.begin(); for (; it != this->codecs.end(); ++it) { auto& codec = *it; // Must be a media codec. if (codec.mimeType.IsMediaCodec()) { firstMediaPayloadType = codec.payloadType; break; } } if (it == this->codecs.end()) { MS_THROW_TYPE_ERROR("no media codecs found"); } } // Iterate all the encodings, set the first payloadType in all of them with // codecPayloadType unset, and check that others point to a media codec. // // Also, don't allow multiple SVC spatial layers into an encoding if there // are more than one encoding (simulcast). for (auto& encoding : this->encodings) { if (encoding.spatialLayers > 1 && this->encodings.size() > 1) { MS_THROW_TYPE_ERROR( "cannot use both simulcast and encodings with multiple SVC spatial layers"); } if (!encoding.hasCodecPayloadType) { encoding.codecPayloadType = firstMediaPayloadType; encoding.hasCodecPayloadType = true; } else { auto it = this->codecs.begin(); for (; it != this->codecs.end(); ++it) { const auto& codec = *it; if (codec.payloadType == encoding.codecPayloadType) { // Must be a media codec. if (codec.mimeType.IsMediaCodec()) { break; } MS_THROW_TYPE_ERROR("invalid codecPayloadType"); } } if (it == this->codecs.end()) { MS_THROW_TYPE_ERROR("unknown codecPayloadType"); } } } } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpDictionaries/RtpRtxParameters.cpp ================================================ #define MS_CLASS "RTC::RtpRtxParameters" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "RTC/RtpDictionaries.hpp" namespace RTC { /* Instance methods. */ RtpRtxParameters::RtpRtxParameters(const FBS::RtpParameters::Rtx* data) { MS_TRACE(); this->ssrc = data->ssrc(); } flatbuffers::Offset RtpRtxParameters::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::RtpParameters::CreateRtx(builder, this->ssrc); } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpListener.cpp ================================================ #define MS_CLASS "RTC::RtpListener" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RtpListener.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/Producer.hpp" namespace RTC { /* Instance methods. */ flatbuffers::Offset RtpListener::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add ssrcTable. std::vector> ssrcTable; for (const auto& kv : this->ssrcTable) { auto ssrc = kv.first; auto* producer = kv.second; ssrcTable.emplace_back( FBS::Common::CreateUint32StringDirect(builder, ssrc, producer->id.c_str())); } // Add midTable. std::vector> midTable; for (const auto& kv : this->midTable) { const auto& mid = kv.first; auto* producer = kv.second; midTable.emplace_back( FBS::Common::CreateStringStringDirect(builder, mid.c_str(), producer->id.c_str())); } // Add ridTable. std::vector> ridTable; for (const auto& kv : this->ridTable) { const auto& rid = kv.first; auto* producer = kv.second; ridTable.emplace_back( FBS::Common::CreateStringStringDirect(builder, rid.c_str(), producer->id.c_str())); } return FBS::Transport::CreateRtpListenerDirect(builder, &ssrcTable, &midTable, &ridTable); } void RtpListener::AddProducer(RTC::Producer* producer) { MS_TRACE(); const auto& rtpParameters = producer->GetRtpParameters(); // Add entries into the ssrcTable. for (const auto& encoding : rtpParameters.encodings) { uint32_t ssrc; // Check encoding.ssrc. ssrc = encoding.ssrc; if (ssrc != 0u) { if (this->ssrcTable.find(ssrc) == this->ssrcTable.end()) { this->ssrcTable[ssrc] = producer; } else { RemoveProducer(producer); MS_THROW_ERROR("ssrc already exists in RTP listener [ssrc:%" PRIu32 "]", ssrc); } } // Check encoding.rtx.ssrc. ssrc = encoding.rtx.ssrc; if (ssrc != 0u) { if (this->ssrcTable.find(ssrc) == this->ssrcTable.end()) { this->ssrcTable[ssrc] = producer; } else { RemoveProducer(producer); MS_THROW_ERROR("RTX ssrc already exists in RTP listener [ssrc:%" PRIu32 "]", ssrc); } } } // Add entries into midTable. if (!rtpParameters.mid.empty()) { const auto& mid = rtpParameters.mid; if (this->midTable.find(mid) == this->midTable.end()) { this->midTable[mid] = producer; } else { RemoveProducer(producer); MS_THROW_ERROR("MID already exists in RTP listener [mid:%s]", mid.c_str()); } } // Add entries into ridTable. for (const auto& encoding : rtpParameters.encodings) { const auto& rid = encoding.rid; if (rid.empty()) { continue; } if (this->ridTable.find(rid) == this->ridTable.end()) { this->ridTable[rid] = producer; } // Just fail if no MID is given. else if (rtpParameters.mid.empty()) { RemoveProducer(producer); MS_THROW_ERROR("RID already exists in RTP listener and no MID is given [rid:%s]", rid.c_str()); } } } void RtpListener::RemoveProducer(RTC::Producer* producer) { MS_TRACE(); // Remove from the listener tables all entries pointing to the Producer. for (auto it = this->ssrcTable.begin(); it != this->ssrcTable.end();) { if (it->second == producer) { it = this->ssrcTable.erase(it); } else { ++it; } } for (auto it = this->midTable.begin(); it != this->midTable.end();) { if (it->second == producer) { it = this->midTable.erase(it); } else { ++it; } } for (auto it = this->ridTable.begin(); it != this->ridTable.end();) { if (it->second == producer) { it = this->ridTable.erase(it); } else { ++it; } } } RTC::Producer* RtpListener::GetProducer(const RTC::RTP::Packet* packet) { MS_TRACE(); // First lookup into the SSRC table. { auto it = this->ssrcTable.find(packet->GetSsrc()); if (it != this->ssrcTable.end()) { auto* producer = it->second; return producer; } } bool hasMid{ false }; std::string mid; std::string rid; // Otherwise lookup into the MID table. if (packet->ReadMid(mid)) { hasMid = true; auto it = this->midTable.find(mid); if (it != this->midTable.end()) { auto* producer = it->second; // Fill the ssrc table. MS_DEBUG_DEV( "inserting entry in ssrcTable [mid:%s, ssrc:%" PRIu32 ", producerId:%s]", mid.c_str(), packet->GetSsrc(), producer->id.c_str()); // NOTE: Here we may override an existing key with same SSRC but it's ok. if (this->ssrcTable.find(packet->GetSsrc()) != this->ssrcTable.end()) { MS_WARN_TAG( rtp, "a Producer with ssrc %" PRIu32 " already exists in the ssrcTable, overriding it anyway [mid:%s]", packet->GetSsrc(), mid.c_str()); } this->ssrcTable[packet->GetSsrc()] = producer; return producer; } } // Otherwise lookup into the RID table but only do it if the packet doesn't // have MID extension. if (!hasMid && packet->ReadRid(rid)) { auto it = this->ridTable.find(rid); if (it != this->ridTable.end()) { auto* producer = it->second; // Fill the ssrc table. MS_DEBUG_DEV( "inserting entry in ssrcTable [rid:%s, ssrc:%" PRIu32 ", producerId:%s]", rid.c_str(), packet->GetSsrc(), producer->id.c_str()); // NOTE: Here we may override an existing key with same SSRC but it's ok. if (this->ssrcTable.find(packet->GetSsrc()) != this->ssrcTable.end()) { MS_WARN_TAG( rtp, "a Producer with ssrc %" PRIu32 " already exists in the ssrcTable, overriding it anyway [rid:%s]", packet->GetSsrc(), rid.c_str()); } this->ssrcTable[packet->GetSsrc()] = producer; return producer; } } return nullptr; } RTC::Producer* RtpListener::GetProducer(uint32_t ssrc) const { MS_TRACE(); // Lookup into the SSRC table. auto it = this->ssrcTable.find(ssrc); if (it != this->ssrcTable.end()) { auto* producer = it->second; return producer; } return nullptr; } } // namespace RTC ================================================ FILE: worker/src/RTC/RtpObserver.cpp ================================================ #define MS_CLASS "RTC::RtpObserver" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/RtpObserver.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { /* Instance methods. */ RtpObserver::RtpObserver( SharedInterface* shared, const std::string& id, RTC::RtpObserver::Listener* listener) : id(id), shared(shared), listener(listener) { MS_TRACE(); } RtpObserver::~RtpObserver() { MS_TRACE(); } void RtpObserver::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::RTPOBSERVER_PAUSE: { this->Pause(); request->Accept(); break; } case Channel::ChannelRequest::Method::RTPOBSERVER_RESUME: { this->Resume(); request->Accept(); break; } case Channel::ChannelRequest::Method::RTPOBSERVER_ADD_PRODUCER: { const auto* body = request->data->body_as(); auto producerId = body->producerId()->str(); RTC::Producer* producer = this->listener->RtpObserverGetProducer(this, producerId); this->AddProducer(producer); this->listener->OnRtpObserverAddProducer(this, producer); request->Accept(); break; } case Channel::ChannelRequest::Method::RTPOBSERVER_REMOVE_PRODUCER: { const auto* body = request->data->body_as(); auto producerId = body->producerId()->str(); RTC::Producer* producer = this->listener->RtpObserverGetProducer(this, producerId); this->RemoveProducer(producer); // Remove from the map. this->listener->OnRtpObserverRemoveProducer(this, producer); request->Accept(); break; } default: { MS_THROW_ERROR("unknown method '%s'", request->methodCStr); } } } void RtpObserver::Pause() { MS_TRACE(); if (this->paused) { return; } this->paused = true; Paused(); } void RtpObserver::Resume() { MS_TRACE(); if (!this->paused) { return; } this->paused = false; Resumed(); } } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/association/Association.cpp ================================================ #define MS_CLASS "RTC::SCTP::Association" // TODO: SCTP: COMMENT #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/association/Association.hpp" #include "Logger.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp" #include "RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp" #include "RTC/SCTP/packet/parameters/StateCookieParameter.hpp" #include "RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp" #include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp" #include // std::numeric_limits() #include // std::ostringstream #include #include // std::is_same_v namespace RTC { namespace SCTP { /* Static. */ alignas(4) static thread_local uint8_t PacketFactoryBuffer[65536]; // @see https://tools.ietf.org/html/rfc9260#section-5.1 constexpr uint32_t MinVerificationTag{ 1 }; constexpr uint32_t MaxVerificationTag{ std::numeric_limits::max() }; // @see https://tools.ietf.org/html/rfc9260#section-3.3.2 constexpr uint32_t MinInitialTsn{ 0 }; constexpr uint32_t MaxInitialTsn{ std::numeric_limits::max() }; constexpr uint64_t MaxTieTag{ std::numeric_limits::max() }; /* Instance methods. */ Association::Association( const SctpOptions& sctpOptions, AssociationListenerInterface* listener, SharedInterface* shared) : sctpOptions(sctpOptions), // Our `listener` member is a `AssociationListenerDeferrer` which takes // `listener` argument as constructor argument. associationListenerDeferrer(listener), shared(shared), packetSender(this, this->associationListenerDeferrer), sendQueue( this->associationListenerDeferrer, sctpOptions.mtu, sctpOptions.defaultStreamPriority, sctpOptions.totalBufferedAmountLowThreshold), t1InitTimer(this->shared->CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = this, .label = "sctp-t1-init", .baseTimeoutMs = sctpOptions.t1InitTimeoutMs, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL, .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs, .maxRestarts = sctpOptions.maxInitRetransmissions, })), t1CookieTimer(this->shared->CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = this, .label = "sctp-t1-cookie", .baseTimeoutMs = sctpOptions.t1CookieTimeoutMs, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL, .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs, .maxRestarts = sctpOptions.maxInitRetransmissions })), t2ShutdownTimer(this->shared->CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = this, .label = "sctp-t2-shutdown", .baseTimeoutMs = sctpOptions.t2ShutdownTimeoutMs, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL, .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs, .maxRestarts = sctpOptions.maxRetransmissions })), maxPacketLength(Utils::Byte::PadDownTo4Bytes(this->sctpOptions.mtu)) { MS_TRACE(); } Association::~Association() { MS_TRACE(); } void Association::Dump(int indentation) const { MS_TRACE(); const auto stateStringView = Association::StateToString(this->state); const auto associationStateStringView = Types::AssociationStateToString(GetAssociationState()); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN( indentation, " association state: %.*s (internal state: %.*s)", static_cast(associationStateStringView.size()), associationStateStringView.data(), static_cast(stateStringView.size()), stateStringView.data()); if (this->tcb) { this->tcb->Dump(indentation + 1); } const auto metrics = GetMetrics(); if (metrics.has_value()) { metrics->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } flatbuffers::Offset Association::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::SctpParameters::CreateSctpParameters( builder, // Add port. this->sctpOptions.sourcePort, // Add OS. // TODO: SCTP: We should put here current value which may be different after // negotiation with peer and reconfig. this->sctpOptions.announcedMaxOutboundStreams, // Add MIS. // TODO: SCTP: We should put here current value which may be different after // negotiation with peer and reconfig. this->sctpOptions.announcedMaxInboundStreams, // Add maxMessageSize. this->sctpOptions.maxSendMessageSize, // Add sendBufferSize. this->sctpOptions.maxSendBufferSize, // Add sctpBufferedAmountLowThreshold. this->sctpOptions.totalBufferedAmountLowThreshold, // Add isDataChannel. // TODO: SCTP: Have a member for this. // TODO: SCTP: So remove this hardcoded `true`. /*isDataChannel*/ true); } Types::AssociationState Association::GetAssociationState() const { MS_TRACE(); switch (this->state) { case State::NEW: { return Types::AssociationState::NEW; } case State::CLOSED: { return Types::AssociationState::CLOSED; } case State::COOKIE_WAIT: case State::COOKIE_ECHOED: { return Types::AssociationState::CONNECTING; } case State::ESTABLISHED: { return Types::AssociationState::CONNECTED; } case State::SHUTDOWN_PENDING: case State::SHUTDOWN_SENT: case State::SHUTDOWN_RECEIVED: case State::SHUTDOWN_ACK_SENT: { return Types::AssociationState::SHUTTING_DOWN; } NO_DEFAULT_GCC(); } } void Association::MayConnect() { MS_TRACE(); // Just run the SCTP stack if our state is 'new'. // Notice that once MayConnect() is called (and the code below is executed), // SCTP state will no longer be "NEW". if (this->state != State::NEW) { MS_DEBUG_DEV("internal Association state is not NEW, ignoring"); return; } // If we haven't received any SCTP packet yet and the transport is not // ready for SCTP traffic, don't do anything. if (this->privateMetrics.rxPacketsCount == 0 && !this->associationListenerDeferrer.OnAssociationIsTransportReadyForSctp()) { MS_DEBUG_DEV( "no SCTP data has been received yet and transport is not ready for SCTP traffic, ignoring"); return; } MS_DEBUG_DEV("invoking Connect()"); Connect(); } void Association::Connect() { MS_TRACE(); // NOTE: We only accept NEW state here so once the Association is closed // it cannot be reused. However there is no real technical reason for it. if (this->state != State::NEW) { const auto stateStringView = Association::StateToString(this->state); MS_WARN_TAG( sctp, "cannot initiate the Association since internal state is not NEW but %.*s", static_cast(stateStringView.size()), stateStringView.data()); return; } const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); this->preTcb.localVerificationTag = Utils::Crypto::GetRandomUInt(MinVerificationTag, MaxVerificationTag); this->preTcb.localInitialTsn = Utils::Crypto::GetRandomUInt(MinInitialTsn, MaxInitialTsn); SendInitChunk(); this->t1InitTimer->Start(); SetState(State::COOKIE_WAIT, "Connect() called"); AssertIsConsistent(); this->associationListenerDeferrer.OnAssociationConnecting(); } void Association::Shutdown() { MS_TRACE(); if (this->state == State::NEW || this->state == State::CLOSED) { AssertIsConsistent(); return; } const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "Upon receipt of the SHUTDOWN primitive from its upper layer, the // endpoint enters the SHUTDOWN-PENDING state and remains there until all // outstanding data has been acknowledged by its peer." if (this->tcb) { // TODO: dcsctp: Remove this check, as it just hides the problem that the // Association can transition from ShutdownSent to ShutdownPending, or // from ShutdownAckSent to ShutdownPending, which is illegal. // // @see https://issues.webrtc.org/issues/42222897 if ( this->state != State::SHUTDOWN_SENT && this->state != State::SHUTDOWN_ACK_SENT && this->state != State::SHUTDOWN_RECEIVED && this->state != State::SHUTDOWN_PENDING) { this->t1InitTimer->Stop(); this->t1CookieTimer->Stop(); // NOTE: We need to set state before calling method below. SetState(State::SHUTDOWN_PENDING, "Shutdown() called"); MaySendShutdownOrShutdownAckChunk(); } } // Association closed before even starting to connect, or during the // initial connection phase. There is no outstanding data, so the // Association can just be closed (stopping any timers, if any), as this // is the application's intention when calling Shutdown(). else { InternalClose(Types::ErrorKind::SUCCESS, ""); } AssertIsConsistent(); } void Association::Close() { MS_TRACE(); if (this->state == State::NEW || this->state == State::CLOSED) { AssertIsConsistent(); return; } const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); if (this->tcb) { auto packet = this->tcb->CreatePacket(); auto* abortAssociationChunk = packet->BuildChunkInPlace(); // NOTE: Don't set bit T in the ABORT chunk since TCB knows the // Verification Tag expected by the remote. auto* userInitiatedAbortErrorCause = abortAssociationChunk->BuildErrorCauseInPlace(); userInitiatedAbortErrorCause->SetUpperLayerAbortReason("Close() called"); userInitiatedAbortErrorCause->Consolidate(); abortAssociationChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); } InternalClose(Types::ErrorKind::SUCCESS, ""); AssertIsConsistent(); } std::optional Association::GetMetrics() const { if (!this->tcb) { return std::nullopt; } const size_t packetPayloadLength = this->sctpOptions.mtu - Packet::CommonHeaderLength - DataChunk::DataChunkHeaderLength; AssociationMetrics metrics{ .txPacketsCount = this->privateMetrics.txPacketsCount, .txMessagesCount = this->privateMetrics.txMessagesCount, .rxPacketsCount = this->privateMetrics.rxPacketsCount, .rxMessagesCount = this->privateMetrics.rxMessagesCount, .rtxPacketsCount = this->tcb->GetRetransmissionQueue().GetRtxPacketsCount(), .rtxBytesCount = this->tcb->GetRetransmissionQueue().GetRtxBytesCount(), .cwndBytes = this->tcb->GetCwnd(), .srttMs = this->tcb->GetCurrentSrttMs(), .unackDataCount = this->tcb->GetRetransmissionQueue().GetUnackedItems() + ((this->sendQueue.GetTotalBufferedAmount() + packetPayloadLength - 1) / packetPayloadLength), .peerRwndBytes = static_cast(this->tcb->GetRetransmissionQueue().GetRwnd()), .peerImplementation = this->privateMetrics.peerImplementation, .negotiatedMaxOutboundStreams = this->privateMetrics.negotiatedMaxOutboundStreams, .negotiatedMaxInboundStreams = this->privateMetrics.negotiatedMaxInboundStreams, .usesPartialReliability = this->privateMetrics.usesPartialReliability, .usesMessageInterleaving = this->privateMetrics.usesMessageInterleaving, .usesReConfig = this->privateMetrics.usesReConfig, .usesZeroChecksum = this->privateMetrics.usesZeroChecksum, }; return metrics; } uint16_t Association::GetStreamPriority(uint16_t streamId) const { MS_TRACE(); return this->sendQueue.GetStreamPriority(streamId); } void Association::SetStreamPriority(uint16_t streamId, uint16_t priority) { MS_TRACE(); this->sendQueue.SetStreamPriority(streamId, priority); } void Association::SetMaxSendMessageSize(size_t maxMessageSize) { MS_TRACE(); this->sctpOptions.maxSendMessageSize = maxMessageSize; } size_t Association::GetStreamBufferedAmount(uint16_t streamId) const { MS_TRACE(); return this->sendQueue.GetStreamBufferedAmount(streamId); } size_t Association::GetStreamBufferedAmountLowThreshold(uint16_t streamId) const { MS_TRACE(); return this->sendQueue.GetStreamBufferedAmountLowThreshold(streamId); } void Association::SetBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) { MS_TRACE(); this->sendQueue.SetStreamBufferedAmountLowThreshold(streamId, bytes); } Types::ResetStreamsStatus Association::ResetStreams(std::span outboundStreamIds) { MS_TRACE(); const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); if (!this->tcb) { this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::WRONG_SEQUENCE, "cannot reset outbound streams as the association is not connected"); return Types::ResetStreamsStatus::NOT_CONNECTED; } if (!this->tcb->GetNegotiatedCapabilities().reConfig) { this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::UNSUPPORTED_OPERATION, "cannot reset outbound streams as the remote doesn't support it"); return Types::ResetStreamsStatus::NOT_SUPPORTED; } this->tcb->GetStreamResetHandler().ResetStreams(outboundStreamIds); MaySendResetStreamsRequest(); AssertIsConsistent(); return Types::ResetStreamsStatus::PERFORMED; } Types::SendMessageStatus Association::SendMessage( Message message, const SendMessageOptions& sendMessageOptions) { MS_TRACE(); const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); const auto status = InternalSendMessageCheck(message, sendMessageOptions); if (status != Types::SendMessageStatus::SUCCESS) { return status; } const uint64_t nowMs = this->shared->GetTimeMs(); this->privateMetrics.txMessagesCount++; this->sendQueue.AddMessage(nowMs, std::move(message), sendMessageOptions); if (this->tcb) { this->tcb->SendBufferedPackets(nowMs); } AssertIsConsistent(); return Types::SendMessageStatus::SUCCESS; } std::vector Association::SendManyMessages( std::span messages, const SendMessageOptions& sendMessageOptions) { MS_TRACE(); const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); const uint64_t nowMs = this->shared->GetTimeMs(); std::vector statuses; statuses.reserve(messages.size()); for (auto& message : messages) { const auto status = InternalSendMessageCheck(message, sendMessageOptions); statuses.push_back(status); if (status != Types::SendMessageStatus::SUCCESS) { continue; } this->privateMetrics.txMessagesCount++; this->sendQueue.AddMessage(nowMs, std::move(message), sendMessageOptions); } if (this->tcb) { this->tcb->SendBufferedPackets(nowMs); } AssertIsConsistent(); return statuses; } void Association::ReceiveSctpData(const uint8_t* data, size_t len) { MS_TRACE(); // TODO: SCTP: For testing purposes. Must be removed. { MS_DUMP("<<< received SCTP packet:"); const auto* packet = RTC::SCTP::Packet::Parse(data, len); if (packet) { packet->Dump(); delete packet; } else { MS_ABORT("RTC::SCTP::Packet::Parse() failed to parse received SCTP data"); } } this->privateMetrics.rxPacketsCount++; // If we are received SCTP data from the remote peer it means that we may // initiate the SCTP association (if not already connected). MayConnect(); // NOTE: It's important to create the deferrer here, otherwise it may // happen that MayConnect() ends calling to Connect() so we end with two // nested deferreds (and hence an assertion). const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); std::unique_ptr receivedPacket{ Packet::Parse(data, len) }; if (!receivedPacket) { MS_WARN_TAG(sctp, "failed to parse received SCTP packet"); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "failed to parse received SCTP packet"); AssertIsConsistent(); return; } if (!ValidateReceivedPacket(receivedPacket.get())) { MS_WARN_TAG(sctp, "Packet verification failed, discarded"); return; } MaySendShutdownOnPacketReceived(receivedPacket.get()); for (auto it = receivedPacket->ChunksBegin(); it != receivedPacket->ChunksEnd(); ++it) { const auto* receivedChunk = *it; if (!HandleReceivedChunk(receivedPacket.get(), receivedChunk)) { break; } } if (this->tcb) { this->tcb->GetDataTracker().ObservePacketEnd(); this->tcb->MaySendSackChunk(); } AssertIsConsistent(); } void Association::InternalClose(Types::ErrorKind errorKind, const std::string_view& message) { MS_TRACE(); if (this->state != State::NEW && this->state != State::CLOSED) { this->t1InitTimer->Stop(); this->t1CookieTimer->Stop(); this->t2ShutdownTimer->Stop(); this->tcb = nullptr; } const auto prevState = this->state; SetState(State::CLOSED, message); if (prevState == State::COOKIE_WAIT || prevState == State::COOKIE_ECHOED) { if (errorKind == Types::ErrorKind::SUCCESS) { this->associationListenerDeferrer.OnAssociationClosed(errorKind, message); } else { this->associationListenerDeferrer.OnAssociationFailed(errorKind, message); } } else { this->associationListenerDeferrer.OnAssociationClosed(errorKind, message); } } void Association::SetState(State state, const std::string_view& message) { MS_TRACE(); const auto stateStringView = Association::StateToString(state); if (state == this->state) { MS_WARN_DEV( "SCTP Association internal state is already %.*s (message:\"%.*s\")", static_cast(stateStringView.size()), stateStringView.data(), static_cast(message.size()), message.data()); return; } const auto previousStateStringView = Association::StateToString(this->state); MS_DEBUG_TAG( sctp, "SCTP Association internal state changed from %.*s to %.*s (message:\"%.*s\")", static_cast(previousStateStringView.size()), previousStateStringView.data(), static_cast(stateStringView.size()), stateStringView.data(), static_cast(message.size()), message.data()); this->state = state; } void Association::AddCapabilitiesParametersToInitOrInitAckChunk(AnyInitChunk* chunk) const { MS_TRACE(); auto* supportedExtensionsParameter = chunk->BuildParameterInPlace(); supportedExtensionsParameter->AddChunkType(Chunk::ChunkType::RE_CONFIG); if (this->sctpOptions.enablePartialReliability) { supportedExtensionsParameter->AddChunkType(Chunk::ChunkType::FORWARD_TSN); } if (this->sctpOptions.enableMessageInterleaving) { supportedExtensionsParameter->AddChunkType(Chunk::ChunkType::I_DATA); supportedExtensionsParameter->AddChunkType(Chunk::ChunkType::I_FORWARD_TSN); } supportedExtensionsParameter->Consolidate(); if (this->sctpOptions.enablePartialReliability) { const auto* forwardTsnSupportedParameter = chunk->BuildParameterInPlace(); forwardTsnSupportedParameter->Consolidate(); } if ( this->sctpOptions.zeroChecksumAlternateErrorDetectionMethod != ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE) { auto* zeroChecksumAcceptableParameter = chunk->BuildParameterInPlace(); zeroChecksumAcceptableParameter->SetAlternateErrorDetectionMethod( this->sctpOptions.zeroChecksumAlternateErrorDetectionMethod); zeroChecksumAcceptableParameter->Consolidate(); } } void Association::CreateTransmissionControlBlock( uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities) { MS_TRACE(); this->tcb = std::make_unique( this->associationListenerDeferrer, this->sctpOptions, this->shared, this->sendQueue, this->packetSender, localVerificationTag, remoteVerificationTag, localInitialTsn, remoteInitialTsn, remoteAdvertisedReceiverWindowCredit, tieTag, negotiatedCapabilities, this->maxPacketLength, [this]() { return this->state == State::ESTABLISHED; }); this->privateMetrics.negotiatedMaxOutboundStreams = negotiatedCapabilities.negotiatedMaxOutboundStreams; this->privateMetrics.negotiatedMaxInboundStreams = negotiatedCapabilities.negotiatedMaxInboundStreams; this->privateMetrics.usesPartialReliability = negotiatedCapabilities.partialReliability; this->privateMetrics.usesMessageInterleaving = negotiatedCapabilities.messageInterleaving; this->privateMetrics.usesReConfig = negotiatedCapabilities.reConfig; this->privateMetrics.usesZeroChecksum = negotiatedCapabilities.zeroChecksum; } std::unique_ptr Association::CreatePacket() const { MS_TRACE(); return CreatePacketWithVerificationTag(0); } std::unique_ptr Association::CreatePacketWithVerificationTag(uint32_t verificationTag) const { MS_TRACE(); auto packet = std::unique_ptr{ Packet::Factory(PacketFactoryBuffer, this->maxPacketLength) }; packet->SetSourcePort(this->sctpOptions.sourcePort); packet->SetDestinationPort(this->sctpOptions.destinationPort); packet->SetVerificationTag(verificationTag); return packet; } void Association::SendInitChunk() { MS_TRACE(); auto packet = CreatePacket(); // Insert an INIT Chunk in the Packet. auto* initChunk = packet->BuildChunkInPlace(); initChunk->SetInitiateTag(this->preTcb.localVerificationTag); initChunk->SetAdvertisedReceiverWindowCredit(this->sctpOptions.maxReceiverWindowBufferSize); initChunk->SetNumberOfOutboundStreams(this->sctpOptions.announcedMaxOutboundStreams); initChunk->SetNumberOfInboundStreams(this->sctpOptions.announcedMaxInboundStreams); initChunk->SetInitialTsn(this->preTcb.localInitialTsn); // Insert capabilities related Parameters in the INIT Chunk. AddCapabilitiesParametersToInitOrInitAckChunk(initChunk); initChunk->Consolidate(); // https://datatracker.ietf.org/doc/html/rfc9653#section-5.2 // // "When a sender sends a packet containing an INIT chunk, it MUST include // a correct CRC32c checksum in the packet containing the INIT chunk." this->packetSender.SendPacket(packet.get()); } void Association::SendShutdownChunk() { MS_TRACE(); AssertHasTcb(); auto packet = this->tcb->CreatePacket(); auto* shutdownChunk = packet->BuildChunkInPlace(); shutdownChunk->SetCumulativeTsnAck(this->tcb->GetDataTracker().GetLastCumulativeAckedTsn()); shutdownChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); } void Association::SendShutdownAckChunk() { MS_TRACE(); AssertHasTcb(); auto packet = this->tcb->CreatePacket(); const auto* shutdownAckChunk = packet->BuildChunkInPlace(); shutdownAckChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); this->t2ShutdownTimer->SetBaseTimeoutMs(this->tcb->GetCurrentRtoMs()); this->t2ShutdownTimer->Start(); } void Association::MaySendShutdownOrShutdownAckChunk() { MS_TRACE(); AssertHasTcb(); if (this->tcb->GetRetransmissionQueue().GetUnackedItems() != 0) { return; } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "Once all its outstanding data has been acknowledged, the endpoint // sends a SHUTDOWN chunk to its peer, including in the Cumulative TSN Ack // field the last sequential TSN it has received from the peer. It SHOULD // then start the T2-shutdown timer and enter the SHUTDOWN-SENT state." if (this->state == State::SHUTDOWN_PENDING) { SendShutdownChunk(); this->t2ShutdownTimer->SetBaseTimeoutMs(this->tcb->GetCurrentRtoMs()); this->t2ShutdownTimer->Start(); SetState(State::SHUTDOWN_SENT, "no more outstanding data"); } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "If the receiver of the SHUTDOWN chunk has no more outstanding DATA // chunks, the SHUTDOWN chunk receiver MUST send a SHUTDOWN ACK chunk and // start a T2-shutdown timer of its own, entering the SHUTDOWN-ACK-SENT // state. If the timer expires, the endpoint MUST resend the SHUTDOWN ACK // chunk." else if (this->state == State::SHUTDOWN_RECEIVED) { SendShutdownAckChunk(); SetState(State::SHUTDOWN_ACK_SENT, "no more outstanding data"); } } void Association::MaySendShutdownOnPacketReceived(const Packet* receivedPacket) { MS_TRACE(); if (this->state != State::SHUTDOWN_SENT) { return; } AssertHasTcb(); // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "While in the SHUTDOWN-SENT state, the SHUTDOWN chunk sender MUST // immediately respond to each received packet containing one or more // DATA chunks with a SHUTDOWN chunk and restart the T2-shutdown timer." // // @remarks // - This also applies to I-DATA chunks. const bool hasDataChunk = std::find_if( receivedPacket->ChunksBegin(), receivedPacket->ChunksEnd(), [](const Chunk* chunk) { return chunk->GetType() == Chunk::ChunkType::DATA || chunk->GetType() == Chunk::ChunkType::I_DATA; }) != receivedPacket->ChunksEnd(); if (hasDataChunk) { SendShutdownChunk(); this->t2ShutdownTimer->SetBaseTimeoutMs(this->tcb->GetCurrentRtoMs()); this->t2ShutdownTimer->Start(); } } void Association::MaySendResetStreamsRequest() { MS_TRACE(); AssertHasTcb(); if (this->tcb->GetStreamResetHandler().ShouldSendStreamResetRequest()) { auto packet = this->tcb->CreatePacket(); this->tcb->GetStreamResetHandler().AddStreamResetRequest(packet.get()); this->packetSender.SendPacket(packet.get()); } } void Association::MayDeliverMessages() { MS_TRACE(); AssertHasTcb(); while (std::optional message = this->tcb->GetReassemblyQueue().GetNextMessage()) { this->privateMetrics.rxMessagesCount++; this->associationListenerDeferrer.OnAssociationMessageReceived(*std::move(message)); } } Types::SendMessageStatus Association::InternalSendMessageCheck( const Message& message, const SendMessageOptions& sendMessageOptions) { MS_TRACE(); const auto lifecycleId = sendMessageOptions.lifecycleId; if (message.GetPayloadLength() == 0) { if (lifecycleId.has_value()) { this->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value()); } this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PROTOCOL_VIOLATION, "cannot send empty message"); return Types::SendMessageStatus::ERROR_MESSAGE_EMPTY; } else if (message.GetPayloadLength() > this->sctpOptions.maxSendMessageSize) { if (lifecycleId.has_value()) { this->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value()); } this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PROTOCOL_VIOLATION, "cannot send too large message"); return Types::SendMessageStatus::ERROR_MESSAGE_TOO_LARGE; } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "An endpoint SHOULD reject any new data request from its upper layer // if it is in the SHUTDOWN-PENDING, SHUTDOWN-SENT, SHUTDOWN-RECEIVED, or // SHUTDOWN-ACK-SENT state." else if ( this->state == State::SHUTDOWN_PENDING || this->state == State::SHUTDOWN_SENT || this->state == State::SHUTDOWN_RECEIVED || this->state == State::SHUTDOWN_ACK_SENT) { if (lifecycleId.has_value()) { this->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value()); } this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::WRONG_SEQUENCE, "cannot send message as the association is shutting down"); return Types::SendMessageStatus::ERROR_SHUTTING_DOWN; } else if ( this->sendQueue.GetTotalBufferedAmount() >= this->sctpOptions.maxSendBufferSize || this->sendQueue.GetStreamBufferedAmount(message.GetStreamId()) >= this->sctpOptions.perStreamSendQueueLimit) { if (lifecycleId.has_value()) { this->associationListenerDeferrer.OnAssociationLifecycleMessageEnd(lifecycleId.value()); } this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::RESOURCE_EXHAUSTION, "cannot send message as the send queue is full"); return Types::SendMessageStatus::ERROR_RESOURCE_EXHAUSTION; } return Types::SendMessageStatus::SUCCESS; } bool Association::ValidateReceivedPacket(const Packet* receivedPacket) { MS_TRACE(); const uint32_t localVerificationTag = this->tcb ? this->tcb->GetLocalVerificationTag() : 0; // https://datatracker.ietf.org/doc/html/rfc9260#section-8.5.1 // // "When an endpoint receives an SCTP packet with the Verification Tag // set to 0, it SHOULD verify that the packet contains only an INIT // chunk. Otherwise, the receiver MUST silently discard the packet." if (receivedPacket->GetVerificationTag() == 0) { if (receivedPacket->GetChunksCount() == 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::INIT) { return true; } else { MS_WARN_TAG( sctp, "Packet with Verification Tag 0 must have a single Chunk and it must be an INIT Chunk, packet discarded"); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "packet with Verification Tag 0 must have a single chunk and it must be an INIT chunk"); return false; } } // https://datatracker.ietf.org/doc/html/rfc9260#section-8.5.1 // // "The receiver of an ABORT chunk MUST accept the packet if the // Verification Tag field of the packet matches its own tag and the T bit // is not set OR if it is set to its Peer's Tag and the T bit is set in // the Chunk Flags. Otherwise, the receiver MUST silently discard the // packet and take no further action." if (receivedPacket->GetChunksCount() == 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::ABORT) { const auto* abortAssociationChunk = static_cast(receivedPacket->GetChunkAt(0)); // We cannot verify the Verification Tag so assume it's okey. if (abortAssociationChunk->GetT() && !this->tcb) { return true; } else if ( (!abortAssociationChunk->GetT() && receivedPacket->GetVerificationTag() == localVerificationTag) || (abortAssociationChunk->GetT() && receivedPacket->GetVerificationTag() == this->tcb->GetRemoteVerificationTag())) { return true; } else { MS_WARN_TAG( sctp, "ABORT Chunk Verification Tag %" PRIu32 " is wrong, packet discarded", receivedPacket->GetVerificationTag()); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "packet with ABORT chunk has invalid Verification Tag"); return false; } } if (receivedPacket->GetChunksCount() >= 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::INIT_ACK) { if (receivedPacket->GetVerificationTag() == this->preTcb.localVerificationTag) { return true; } else { MS_WARN_TAG( sctp, "INIT_ACK Chunk Verification Tag %" PRIu32 " (should be %" PRIu32 ")", receivedPacket->GetVerificationTag(), this->preTcb.localVerificationTag); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "packet with INIT_ACK chunk has invalid Verification Tag"); return false; } } // https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.4 // // This is handled in HandleReceivedCookieEchoChunk(). if (receivedPacket->GetChunksCount() >= 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::COOKIE_ECHO) { return true; } // https://datatracker.ietf.org/doc/html/rfc9260#section-8.5.1 // // "The receiver of a SHUTDOWN COMPLETE shall accept the packet if the // Verification Tag field of the packet matches its own tag and the T bit is // not set OR if it is set to its peer's tag and the T bit is set in the // Chunk Flags. Otherwise, the receiver MUST silently discard the packet // and take no further action." if (receivedPacket->GetChunksCount() == 1 && receivedPacket->GetChunkAt(0)->GetType() == Chunk::ChunkType::SHUTDOWN_COMPLETE) { const auto* shutdownCompleteChunk = static_cast(receivedPacket->GetChunkAt(0)); // We cannot verify the Verification Tag so assume it's okey. if (shutdownCompleteChunk->GetT() && !this->tcb) { return true; } else if ( (!shutdownCompleteChunk->GetT() && receivedPacket->GetVerificationTag() == localVerificationTag) || (shutdownCompleteChunk->GetT() && receivedPacket->GetVerificationTag() == this->tcb->GetRemoteVerificationTag())) { return true; } else { MS_WARN_TAG( sctp, "SHUTDOWN_COMPLETE Chunk Verification Tag %" PRIu32 " is wrong, packet discarded", receivedPacket->GetVerificationTag()); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "packet with SHUTDOWN_COMPLETE chunk has invalid Verification Tag"); return false; } } // https://datatracker.ietf.org/doc/html/rfc9260#section-8.5 // // "When receiving an SCTP packet, the endpoint MUST ensure that the // value in the Verification Tag field of the received SCTP packet // matches its own tag. If the received Verification Tag value does not // match the receiver's own tag value, the receiver MUST silently discard // the packet and MUST NOT process it any further, except for those cases // listed in Section 8.5.1 below." if (receivedPacket->GetVerificationTag() == localVerificationTag) { return true; } else { MS_WARN_TAG( sctp, "invalid Verification Tag %" PRIu32 " (should be %" PRIu32 ")", receivedPacket->GetVerificationTag(), localVerificationTag); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "packet has invalid Verification Tag"); return false; } } bool Association::HandleReceivedChunk(const Packet* receivedPacket, const Chunk* receivedChunk) { MS_TRACE(); switch (receivedChunk->GetType()) { case Chunk::ChunkType::INIT: { HandleReceivedInitChunk(receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::INIT_ACK: { HandleReceivedInitAckChunk(receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::COOKIE_ECHO: { HandleReceivedCookieEchoChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::COOKIE_ACK: { HandleReceivedCookieAckChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::SHUTDOWN: { HandleReceivedShutdownChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::SHUTDOWN_ACK: { HandleReceivedShutdownAckChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::SHUTDOWN_COMPLETE: { HandleReceivedShutdownCompleteChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::OPERATION_ERROR: { HandleReceivedOperationErrorChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::ABORT: { HandleReceivedAbortAssociationChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::HEARTBEAT_REQUEST: { HandleReceivedHeartbeatRequestChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::HEARTBEAT_ACK: { HandleReceivedHeartbeatAckChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::RE_CONFIG: { HandleReceivedReConfigChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::FORWARD_TSN: { HandleReceivedForwardTsnChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::I_FORWARD_TSN: { HandleReceivedIForwardTsnChunk( receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::DATA: { HandleReceivedDataChunk(receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::I_DATA: { HandleReceivedIDataChunk(receivedPacket, static_cast(receivedChunk)); break; } case Chunk::ChunkType::SACK: { HandleReceivedSackChunk(receivedPacket, static_cast(receivedChunk)); break; } default: { return HandleReceivedUnknownChunk( receivedPacket, static_cast(receivedChunk)); } } return true; } void Association::HandleReceivedInitChunk( const Packet* /*receivedPacket*/, const InitChunk* receivedInitChunk) { MS_TRACE(); // https://datatracker.ietf.org/doc/html/rfc9260#section-3.3.2 // // "If the value of the Initiate Tag in a received INIT chunk is found to // be 0, the receiver MUST silently discard the packet." if (receivedInitChunk->GetInitiateTag() == 0) { MS_WARN_TAG(sctp, "invalid value 0 in Initiate Tagin received INIT Chunk, discarded"); return; } // https://datatracker.ietf.org/doc/html/rfc9260#section-3.3.2 // // "A receiver of an INIT chunk with the OS value set to 0 MUST discard // the packet, SHOULD send a packet in response containing an ABORT chunk // and using the Initiate Tag as the Verification Tag." // // "A receiver of an INIT chunk with the MIS value set to 0 MUST discard // the packet, SHOULD send a packet in response containing an ABORT chunk // and using the Initiate Tag as the Verification Tag." else if ( receivedInitChunk->GetNumberOfOutboundStreams() == 0 or receivedInitChunk->GetNumberOfInboundStreams() == 0) { MS_WARN_TAG( sctp, "invalidNumber of Outbound Streams or Number of Inbound Streams in received INIT Chunk, aborting Association"); auto packet = CreatePacketWithVerificationTag(0); auto* abortAssociationChunk = packet->BuildChunkInPlace(); // NOTE: We are not setting the Verification Tag expected by the peer // so must set be T to 1. abortAssociationChunk->SetT(true); auto* protocolViolationErrorCause = abortAssociationChunk->BuildErrorCauseInPlace(); protocolViolationErrorCause->SetAdditionalInformation( "invalid value 0 in Number of Outbound Streams or Number of Inbound Streams in received INIT chunk"); protocolViolationErrorCause->Consolidate(); abortAssociationChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); InternalClose(Types::ErrorKind::PROTOCOL_VIOLATION, "received invalid INIT chunk"); return; } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "If an endpoint is in the SHUTDOWN-ACK-SENT state and receives an INIT // chunk (e.g., if the SHUTDOWN COMPLETE chunk was lost) with source and // destination transport addresses (either in the IP addresses or in the // INIT chunk) that belong to this association, it SHOULD discard the // INIT chunk and retransmit the SHUTDOWN ACK chunk." if (this->state == State::SHUTDOWN_ACK_SENT) { MS_DEBUG_TAG( sctp, "INIT Chunk received in SHUTDOWN_ACK_SENT state, retransmitting SHUTDOWN_ACK Chunk"); SendShutdownAckChunk(); return; } uint64_t tieTag{ 0 }; uint32_t localVerificationTag; uint32_t localInitialTsn; switch (this->state) { case State::NEW: { MS_DEBUG_TAG(sctp, "INIT Chunk received in NEW state (normal scenario)"); localVerificationTag = Utils::Crypto::GetRandomUInt(MinVerificationTag, MaxVerificationTag); localInitialTsn = Utils::Crypto::GetRandomUInt(MinInitialTsn, MaxInitialTsn); break; } case State::CLOSED: { MS_WARN_TAG(sctp, "ignoring INIT Chunk received in CLOSED state)"); } // https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.1 // // "This usually indicates an initialization collision, i.e., each // endpoint is attempting, at about the same time, to establish an // association with the other endpoint. Upon receipt of an INIT chunk // in the COOKIE-WAIT state, an endpoint MUST respond with an INIT ACK // chunk using the same parameters it sent in its original INIT chunk // (including its Initiate Tag, unchanged)." case State::COOKIE_WAIT: case State::COOKIE_ECHOED: { MS_DEBUG_TAG(sctp, "INIT Chunk received after sending INIT Chunk (collision, no problem)"); localVerificationTag = this->preTcb.localVerificationTag; localInitialTsn = this->preTcb.localInitialTsn; break; } // https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.2 // // "The outbound SCTP packet containing this INIT ACK chunk MUST carry // a Verification Tag value equal to the Initiate Tag found in the // unexpected INIT chunk. And the INIT ACK chunk MUST contain a new // Initiate Tag (randomly generated; see Section 5.3.1). Other // parameters for the endpoint SHOULD be copied from the existing // parameters of the association (e.g., number of outbound streams) // into the INIT ACK chunk and cookie." default: { AssertHasTcb(); MS_DEBUG_TAG(sctp, "INIT Chunk received (probably peer restarted)"); localVerificationTag = Utils::Crypto::GetRandomUInt(MinVerificationTag, MaxVerificationTag); localInitialTsn = Utils::Crypto::GetRandomUInt(MinInitialTsn, MaxInitialTsn); tieTag = this->tcb->GetTieTag(); } } MS_DEBUG_TAG( sctp, "initiating Association [localVerificationTag:%" PRIu32 ", localInitialTsn:%" PRIu32 ", remoteVerificationTag:%" PRIu32 ", remoteInitialTsn:%" PRIu32 "]", localVerificationTag, localInitialTsn, receivedInitChunk->GetInitiateTag(), receivedInitChunk->GetInitialTsn()); /* Send a Packet with an INIT_ACK Chunk. */ auto packet = CreatePacketWithVerificationTag(receivedInitChunk->GetInitiateTag()); // Insert an INIT_ACK Chunk in the Packet. auto* initAckChunk = packet->BuildChunkInPlace(); initAckChunk->SetInitiateTag(localVerificationTag); initAckChunk->SetAdvertisedReceiverWindowCredit(this->sctpOptions.maxReceiverWindowBufferSize); initAckChunk->SetNumberOfOutboundStreams(this->sctpOptions.announcedMaxOutboundStreams); initAckChunk->SetNumberOfInboundStreams(this->sctpOptions.announcedMaxInboundStreams); initAckChunk->SetInitialTsn(localInitialTsn); // Insert a StateCookieParameter in the INIT_ACK Chunk. auto* stateCookieParameter = initAckChunk->BuildParameterInPlace(); const auto negotiatedCapabilities = NegotiatedCapabilities::Factory(this->sctpOptions, receivedInitChunk); // Write the StateCookie in place in the Parameter. stateCookieParameter->WriteStateCookieInPlace( localVerificationTag, receivedInitChunk->GetInitiateTag(), localInitialTsn, receivedInitChunk->GetInitialTsn(), receivedInitChunk->GetAdvertisedReceiverWindowCredit(), tieTag, negotiatedCapabilities); stateCookieParameter->Consolidate(); // Insert capabilities related Parameters in the INIT_ACK Chunk. AddCapabilitiesParametersToInitOrInitAckChunk(initAckChunk); initAckChunk->Consolidate(); // If the peer has signaled that it supports Zero Checksum, INIT-ACK can // then have its checksum as zero. this->packetSender.SendPacket( packet.get(), /*writeChecksum*/ !negotiatedCapabilities.zeroChecksum); } void Association::HandleReceivedInitAckChunk( const Packet* /*receivedPacket*/, const InitAckChunk* receivedInitAckChunk) { MS_TRACE(); // https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.3 // // "If an INIT ACK chunk is received by an endpoint in any state other // than the COOKIE-WAIT or CLOSED state, the endpoint SHOULD discard the // INIT ACK chunk." if (this->state != State::COOKIE_WAIT) { MS_DEBUG_TAG(sctp, "ignoring received INIT_ACK Chunk when not in COOKIE_WAIT state"); return; } const auto* stateCookieParameter = receivedInitAckChunk->GetFirstParameterOfType(); if (!stateCookieParameter || !stateCookieParameter->GetCookie()) { MS_WARN_TAG( sctp, "ignoring received INIT_ACK Chunk without StateCookieParameter or without Cookie"); auto packet = CreatePacketWithVerificationTag(this->preTcb.localVerificationTag); auto* abortAssociationChunk = packet->BuildChunkInPlace(); // NOTE: We are not setting the Verification Tag expected by the peer // so must set be T to 1. abortAssociationChunk->SetT(true); auto* protocolViolationErrorCause = abortAssociationChunk->BuildErrorCauseInPlace(); protocolViolationErrorCause->SetAdditionalInformation( "INIT_ACK without State Cookie Parameter or without Cookie"); protocolViolationErrorCause->Consolidate(); abortAssociationChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); InternalClose( Types::ErrorKind::PROTOCOL_VIOLATION, "received INIT_ACK chunk doesn't contain a Cookie"); return; } this->privateMetrics.peerImplementation = StateCookie::DetermineSctpImplementation( stateCookieParameter->GetCookie(), stateCookieParameter->GetCookieLength()); this->t1InitTimer->Stop(); const auto negotiatedCapabilities = NegotiatedCapabilities::Factory(this->sctpOptions, receivedInitAckChunk); // If the Association is re-established (peer restarted, but re-used old // Association), make sure that all message identifiers are reset and any // partly sent message is re-sent in full. The same is true when the // Association is closed and later re-opened, which never happens in // WebRTC, but is a valid operation on the SCTP level. this->sendQueue.Reset(); CreateTransmissionControlBlock( this->preTcb.localVerificationTag, receivedInitAckChunk->GetInitiateTag(), this->preTcb.localInitialTsn, receivedInitAckChunk->GetInitialTsn(), receivedInitAckChunk->GetAdvertisedReceiverWindowCredit(), /*tieTag*/ Utils::Crypto::GetRandomUInt(0, MaxTieTag), negotiatedCapabilities); SetState(State::COOKIE_ECHOED, "INIT_ACK received"); // The Association isn't fully established just yet. Store the stat // cookie in the TCB. std::vector remoteStateCookie( stateCookieParameter->GetCookie(), stateCookieParameter->GetCookie() + stateCookieParameter->GetCookieLength()); this->tcb->SetRemoteStateCookie(std::move(remoteStateCookie)); const uint64_t nowMs = this->shared->GetTimeMs(); this->tcb->SendBufferedPackets(nowMs); this->t1CookieTimer->Start(); this->associationListenerDeferrer.OnAssociationConnecting(); } void Association::HandleReceivedCookieEchoChunk( const Packet* receivedPacket, const CookieEchoChunk* receivedCookieEchoChunk) { MS_TRACE(); if (!receivedCookieEchoChunk->HasCookie()) { MS_WARN_TAG(sctp, "ignoring received COOKIE_ECHO Chunk without Cookie"); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "received COOKIE_ECHO Chunk without Cookie"); return; } std::unique_ptr cookie{ StateCookie::Parse( receivedCookieEchoChunk->GetCookie(), receivedCookieEchoChunk->GetCookieLength()) }; if (!cookie) { MS_WARN_TAG(sctp, "failed to parse Cookie in received COOKIE_ECHO Chunk"); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "received COOKIE_ECHO Chunk with invalid Cookie"); return; } if (this->tcb) { if (!HandleReceivedCookieEchoChunkWithTcb(receivedPacket, cookie.get())) { return; } } else { if (receivedPacket->GetVerificationTag() != cookie->GetLocalVerificationTag()) { MS_WARN_TAG(sctp, "received COOKIE_ECHO Chunk with invalid Verification Tag"); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "received COOKIE_ECHO Chunk with invalid Verification Tag"); return; } } this->t1InitTimer->Stop(); this->t1CookieTimer->Stop(); if (this->state != State::ESTABLISHED) { if (this->tcb) { this->tcb->ClearRemoteStateCookie(); } SetState(State::ESTABLISHED, "COOKIE_ECHO received"); this->associationListenerDeferrer.OnAssociationConnected(); } if (!this->tcb) { // If the Association is re-established (peer restarted, but re-used old // Association), make sure that all message identifiers are reset and any // partly sent message is re-sent in full. The same is true when the // Association is closed and later re-opened, which never happens in // WebRTC, but is a valid operation on the SCTP level. this->sendQueue.Reset(); CreateTransmissionControlBlock( cookie->GetLocalVerificationTag(), cookie->GetRemoteVerificationTag(), cookie->GetLocalInitialTsn(), cookie->GetRemoteInitialTsn(), cookie->GetRemoteAdvertisedReceiverWindowCredit(), /*tieTag*/ Utils::Crypto::GetRandomUInt(0, MaxTieTag), cookie->GetNegotiatedCapabilities()); } // https://datatracker.ietf.org/doc/html/rfc9260#section-5.1 // // "A COOKIE ACK chunk MAY be bundled with any pending DATA chunks (and/or // SACK chunks), but the COOKIE ACK chunk MUST be the first chunk in the // packet." const uint64_t nowMs = this->shared->GetTimeMs(); this->tcb->SendBufferedPackets(nowMs, /*addCookieAckChunk*/ true); } bool Association::HandleReceivedCookieEchoChunkWithTcb( const Packet* receivedPacket, const StateCookie* cookie) { MS_TRACE(); MS_DEBUG_DEV("handling COOKIE_ECHO with TCB"); AssertHasTcb(); // https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.4 // // "Handle a COOKIE ECHO Chunk When a TCB Exists" // // "A) In this case, the peer might have restarted." if ( receivedPacket->GetVerificationTag() != this->tcb->GetLocalVerificationTag() && cookie->GetRemoteVerificationTag() != this->tcb->GetRemoteVerificationTag() && cookie->GetTieTag() == this->tcb->GetTieTag()) { // "If the endpoint is in the SHUTDOWN-ACK-SENT state and recognizes // that the peer has restarted (Action A), it MUST NOT set up a new // association but instead resend the SHUTDOWN ACK chunk and send an // ERROR chunk with a "Cookie Received While Shutting Down" error cause // to its peer." if (this->state == State::SHUTDOWN_ACK_SENT) { auto packet = CreatePacketWithVerificationTag(cookie->GetRemoteVerificationTag()); const auto* shutdownAckChunk = packet->BuildChunkInPlace(); shutdownAckChunk->Consolidate(); auto* operationErrorChunk = packet->BuildChunkInPlace(); const auto* cookieReceivedWhileShuttingDownErrorCause = operationErrorChunk->BuildErrorCauseInPlace(); cookieReceivedWhileShuttingDownErrorCause->Consolidate(); operationErrorChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::WRONG_SEQUENCE, "received COOKIE_ECHO while shutting down"); return false; } MS_DEBUG_DEV("received COOKIE_ECHO indicating a restarted peer"); this->tcb = nullptr; this->associationListenerDeferrer.OnAssociationRestarted(); } // "B) In this case, both sides might be attempting to start an association // at about the same time, but the peer endpoint sent its INIT chunk after // responding to the local endpoint's INIT chunk." else if ( receivedPacket->GetVerificationTag() == this->tcb->GetLocalVerificationTag() && cookie->GetRemoteVerificationTag() != this->tcb->GetRemoteVerificationTag()) { // TODO: dcsctp: Handle the case in which remote Verification Tag is 0? MS_DEBUG_DEV("received COOKIE_ECHO indicating simultaneous associations"); this->tcb = nullptr; } // "C) In this case, the local endpoint's cookie has arrived late. Before // it arrived, the local endpoint sent an INIT chunk and received an INIT // ACK chunk and finally sent a COOKIE ECHO chunk with the peer's same tag // but a new tag of its own. The cookie SHOULD be silently discarded. The // endpoint SHOULD NOT change states and SHOULD leave any timers running." else if ( receivedPacket->GetVerificationTag() != this->tcb->GetLocalVerificationTag() && cookie->GetRemoteVerificationTag() == this->tcb->GetRemoteVerificationTag() && cookie->GetTieTag() == this->tcb->GetTieTag()) { MS_DEBUG_DEV("received COOKIE_ECHO indicating a late COOKIE_ECHO, discarding"); return false; } // "D) When both local and remote tags match, the endpoint SHOULD enter // the ESTABLISHED state if it is in the COOKIE_ECHOED state. It SHOULD // stop any T1-cookie timer that is running and send a COOKIE ACK chunk." else if ( receivedPacket->GetVerificationTag() == this->tcb->GetLocalVerificationTag() && cookie->GetRemoteVerificationTag() == this->tcb->GetRemoteVerificationTag()) { MS_DEBUG_DEV( "received duplicate COOKIE_ECHO, probably because of peer not receiving COOKIE_ACK and retransmitting COOKIE_ECHO"); } return true; } void Association::HandleReceivedCookieAckChunk( const Packet* /*receivedPacket*/, const CookieAckChunk* /*receivedCookieAckChunk*/) { MS_TRACE(); // https://datatracker.ietf.org/doc/html/rfc9260#section-5.2.5 // // "At any state other than COOKIE_ECHOED, an endpoint SHOULD silently // discard a received COOKIE ACK chunk." if (this->state != State::COOKIE_ECHOED) { MS_DEBUG_DEV("received COOKIE_ACK not in COOKIE_ECHOED state, discarding"); return; } AssertHasTcb(); this->t1CookieTimer->Stop(); this->tcb->ClearRemoteStateCookie(); SetState(State::ESTABLISHED, "COOKIE_ACK received"); const uint64_t nowMs = this->shared->GetTimeMs(); this->tcb->SendBufferedPackets(nowMs); this->associationListenerDeferrer.OnAssociationConnected(); } void Association::HandleReceivedShutdownChunk( const Packet* /*receivedPacket*/, const ShutdownChunk* /*receivedShutdownChunk*/) { MS_TRACE(); switch (this->state) { case State::NEW: case State::CLOSED: { break; } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "If a SHUTDOWN chunk is received in the COOKIE-WAIT or COOKIE ECHOED // state, the SHUTDOWN chunk SHOULD be silently discarded." case State::COOKIE_WAIT: case State::COOKIE_ECHOED: { break; } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "If an endpoint is in the SHUTDOWN-SENT state and receives a SHUTDOWN // chunk from its peer, the endpoint SHOULD respond immediately with a // SHUTDOWN ACK chunk to its peer and move into the SHUTDOWN-ACK-SENT // state, restarting its T2-shutdown timer. case State::SHUTDOWN_SENT: { SendShutdownAckChunk(); SetState(State::SHUTDOWN_ACK_SENT, "SHUTDOWN received"); break; } case State::SHUTDOWN_ACK_SENT: { SendShutdownAckChunk(); break; } case State::SHUTDOWN_RECEIVED: { break; } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "Upon reception of the SHUTDOWN chunk, the peer endpoint does the // following: // - enter the SHUTDOWN-RECEIVED state, // - stop accepting new data from its SCTP user, and // - verify, by checking the Cumulative TSN Ack field of the chunk, // that all its outstanding DATA chunks have been received by the // SHUTDOWN chunk sender." default: { MS_DEBUG_DEV("received SHUTDOWN, shutting down the Association"); SetState(State::SHUTDOWN_RECEIVED, "SHUTDOWN received"); MaySendShutdownOrShutdownAckChunk(); } } } void Association::HandleReceivedShutdownAckChunk( const Packet* receivedPacket, const ShutdownAckChunk* /*receivedShutdownAckChunk*/) { MS_TRACE(); switch (this->state) { // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "Upon the receipt of the SHUTDOWN ACK chunk, the sender of the // SHUTDOWN chunk MUST stop the T2-shutdown timer, send a SHUTDOWN // COMPLETE chunk to its peer, and remove all record of the // association." case State::SHUTDOWN_SENT: case State::SHUTDOWN_ACK_SENT: { auto packet = this->tcb->CreatePacket(); const auto* shutdownCompleteChunk = packet->BuildChunkInPlace(); // NOTE: Don't set bit T in the SHUTDOWN_COMPLETE chunk since TCB // knows the Verification Tag expected by the remote. shutdownCompleteChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); InternalClose(Types::ErrorKind::SUCCESS, ""); break; } // https://datatracker.ietf.org/doc/html/rfc9260#section-8.5.1 // // "If the receiver is in COOKIE-ECHOED or COOKIE-WAIT state, the // procedures in Section 8.4 SHOULD be followed; in other words, it is // treated as an OOTB packet." // // https://datatracker.ietf.org/doc/html/rfc9260#section-8.4 // // "If the packet contains a SHUTDOWN ACK chunk, the receiver SHOULD // respond to the sender of the OOTB packet with a SHUTDOWN COMPLETE // chunk. When sending the SHUTDOWN COMPLETE chunk, the receiver of the // OOTB packet MUST fill in the Verification Tag field of the outbound // packet with the Verification Tag received in the SHUTDOWN ACK chunk // and set the T bit in the Chunk Flags to indicate that the // Verification Tag is reflected." default: { auto packet = this->CreatePacketWithVerificationTag(receivedPacket->GetVerificationTag()); auto* shutdownCompleteChunk = packet->BuildChunkInPlace(); shutdownCompleteChunk->SetT(true); shutdownCompleteChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); } } } void Association::HandleReceivedShutdownCompleteChunk( const Packet* /*receivedPacket*/, const ShutdownCompleteChunk* /*receivedShutdownCompleteChunk*/) { MS_TRACE(); if (this->state != State::SHUTDOWN_ACK_SENT) { return; } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "Upon reception of the SHUTDOWN COMPLETE chunk, the endpoint verifies // that it is in the SHUTDOWN-ACK-SENT state; if it is not, the chunk // SHOULD be discarded. If the endpoint is in the SHUTDOWN-ACK-SENT state, // the endpoint SHOULD stop the T2-shutdown timer and remove all knowledge // of the association (and thus the association enters the CLOSED state)." InternalClose(Types::ErrorKind::SUCCESS, ""); } void Association::HandleReceivedOperationErrorChunk( const Packet* /*receivedPacket*/, const OperationErrorChunk* receivedOperationErrorChunk) { MS_TRACE(); std::string errorCausesStr; errorCausesStr.reserve(50); for (auto it = receivedOperationErrorChunk->ErrorCausesBegin(); it != receivedOperationErrorChunk->ErrorCausesEnd(); ++it) { const auto* errorCause = *it; if (!errorCausesStr.empty()) { errorCausesStr.append(", "); } errorCausesStr.append(errorCause->ToString()); } if (!this->tcb) { MS_DEBUG_TAG( sctp, "received OPERATION_ERROR Chunk on a Association with no TCB, ignoring: %s", errorCausesStr.c_str()); return; } MS_WARN_TAG(sctp, "received OPERATION_ERROR Chunk: %s", errorCausesStr.c_str()); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PEER_REPORTED, errorCausesStr); } void Association::HandleReceivedAbortAssociationChunk( const Packet* /*receivedPacket*/, const AbortAssociationChunk* receivedAbortAssociationChunk) { MS_TRACE(); std::string errorCausesStr; errorCausesStr.reserve(50); for (auto it = receivedAbortAssociationChunk->ErrorCausesBegin(); it != receivedAbortAssociationChunk->ErrorCausesEnd(); ++it) { const auto* errorCause = *it; if (!errorCausesStr.empty()) { errorCausesStr.append(", "); } errorCausesStr.append(errorCause->ToString()); } if (!this->tcb) { MS_DEBUG_TAG( sctp, "received ABORT Chunk on a Association with no TCB, ignoring: %s", errorCausesStr.c_str()); return; } MS_WARN_TAG(sctp, "received ABORT Chunk, closing Association: %s", errorCausesStr.c_str()); InternalClose(Types::ErrorKind::PEER_REPORTED, errorCausesStr); } void Association::HandleReceivedHeartbeatRequestChunk( const Packet* /*receivedPacket*/, const HeartbeatRequestChunk* receivedHeartbeatRequestChunk) { MS_TRACE(); if (!ValidateHasTcb()) { return; } this->tcb->GetHeartbeatHandler().HandleReceivedHeartbeatRequestChunk( receivedHeartbeatRequestChunk); } void Association::HandleReceivedHeartbeatAckChunk( const Packet* /*receivedPacket*/, const HeartbeatAckChunk* receivedHeartbeatAckChunk) { MS_TRACE(); if (!ValidateHasTcb()) { return; } this->tcb->GetHeartbeatHandler().HandleReceivedHeartbeatAckChunk(receivedHeartbeatAckChunk); } void Association::HandleReceivedReConfigChunk( const Packet* /*receivedPacket*/, const ReConfigChunk* receivedReConfigChunk) { MS_TRACE(); if (!ValidateHasTcb()) { return; } this->tcb->GetStreamResetHandler().HandleReceivedReConfigChunk(receivedReConfigChunk); // Handling this response may result in outgoing stream resets finishing // (either successfully or with failure). If there still are pending // streams that were waiting for this request to finish, continue // resetting them. MaySendResetStreamsRequest(); // If a response was processed, pending to-be-reset streams may now have // become unpaused. Try to send more DATA/I_DATA chunks. const uint64_t nowMs = this->shared->GetTimeMs(); this->tcb->SendBufferedPackets(nowMs); // If it leaves "deferred reset processing", there may be chunks to // deliver that were queued while waiting for the stream to reset. MayDeliverMessages(); } void Association::HandleReceivedForwardTsnChunk( const Packet* receivedPacket, const ForwardTsnChunk* receivedForwardTsnChunk) { MS_TRACE(); HandleReceivedAnyForwardTsnChunk(receivedPacket, receivedForwardTsnChunk); } void Association::HandleReceivedIForwardTsnChunk( const Packet* receivedPacket, const IForwardTsnChunk* receivedIForwardTsnChunk) { MS_TRACE(); HandleReceivedAnyForwardTsnChunk(receivedPacket, receivedIForwardTsnChunk); } void Association::HandleReceivedAnyForwardTsnChunk( const Packet* /*receivedPacket*/, const AnyForwardTsnChunk* receivedAnyForwardTsnChunk) { MS_TRACE(); if (!ValidateHasTcb()) { return; } if (!this->tcb->GetNegotiatedCapabilities().partialReliability) { auto packet = this->tcb->CreatePacket(); auto* abortAssociationChunk = packet->BuildChunkInPlace(); // NOTE: Don't set bit T in the ABORT chunk since TCB knows the // Verification Tag expected by the remote. auto* protocolViolationErrorCause = abortAssociationChunk->BuildErrorCauseInPlace(); protocolViolationErrorCause->SetAdditionalInformation( "FORWARD-TSN or I_FORWARD-TSN chunk received but partial reliability is not negotiated"); protocolViolationErrorCause->Consolidate(); abortAssociationChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PROTOCOL_VIOLATION, "received FORWARD-TSN or I-FORWARD-TSN chunk but partial reliability is not negotiated"); return; } if (this->tcb->GetDataTracker().HandleForwardTsn(receivedAnyForwardTsnChunk->GetNewCumulativeTsn())) { this->tcb->GetReassemblyQueue().HandleForwardTsn( receivedAnyForwardTsnChunk->GetNewCumulativeTsn(), receivedAnyForwardTsnChunk->GetSkippedStreams()); } // A forward TSN (for ordered streams) may allow messages to be delivered. MayDeliverMessages(); } void Association::HandleReceivedDataChunk( const Packet* receivedPacket, const DataChunk* receivedDataChunk) { MS_TRACE(); HandleReceivedAnyDataChunk(receivedPacket, receivedDataChunk); } void Association::HandleReceivedIDataChunk( const Packet* receivedPacket, const IDataChunk* receivedIDataChunk) { MS_TRACE(); HandleReceivedAnyDataChunk(receivedPacket, receivedIDataChunk); } void Association::HandleReceivedAnyDataChunk( const Packet* /*receivedPacket*/, const AnyDataChunk* receivedAnyDataChunk) { MS_TRACE(); if (!ValidateHasTcb()) { return; } const uint32_t tsn = receivedAnyDataChunk->GetTsn(); const bool immediateAck = receivedAnyDataChunk->GetI(); if (receivedAnyDataChunk->GetUserDataPayloadLength() == 0) { auto packet = this->tcb->CreatePacket(); auto* operationErrorChunk = packet->BuildChunkInPlace(); auto* noUserDataErrorCause = operationErrorChunk->BuildErrorCauseInPlace(); noUserDataErrorCause->SetTsn(tsn); noUserDataErrorCause->Consolidate(); operationErrorChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PROTOCOL_VIOLATION, "received DATA or I-DATA chunk with no user data"); return; } MS_DEBUG_DEV( "data received [data length:%" PRIu16 ", queue size:%zu, watermark:%zu, full:%s, above:%s]", receivedAnyDataChunk->GetUserDataPayloadLength(), this->tcb->GetReassemblyQueue().GetQueuedBytes(), this->tcb->GetReassemblyQueue().GetWatermarkBytes(), this->tcb->GetReassemblyQueue().IsFull() ? "yes" : "no", this->tcb->GetReassemblyQueue().IsAboveWatermark() ? "yes" : "no"); if (this->tcb->GetReassemblyQueue().IsFull()) { // If the reassembly queue is full but there are assembled messages // waiting to be pulled, we can't do anything with this data except drop // it, and hope the upper layer drains the accumulated messages soon. if (this->tcb->GetReassemblyQueue().HasMessages()) { MS_WARN_TAG(sctp, "received data rejected because reassembly queue is full"); return; } // If the reassembly queue is full and there's no messages waiting, // there is nothing that can be done. The specification only allows // dropping gap-ack-blocks, and that's not likely to help as the // Association has been trying to fill gaps since the watermark was // reached. else { auto packet = this->tcb->CreatePacket(); auto* abortAssociationChunk = packet->BuildChunkInPlace(); // NOTE: Don't set bit T in the ABORT chunk since TCB knows the // Verification Tag expected by the remote. auto* outOfResourceErrorCause = abortAssociationChunk->BuildErrorCauseInPlace(); outOfResourceErrorCause->Consolidate(); abortAssociationChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); InternalClose(Types::ErrorKind::RESOURCE_EXHAUSTION, "reassembly queue is exhausted"); return; } } // If the reassembly queue is above its high watermark, only accept data // chunks that increase its cumulative ack tsn in an attempt to fill gaps // to deliver messages. if (this->tcb->GetReassemblyQueue().IsAboveWatermark()) { MS_WARN_TAG(sctp, "reassembly queue is above watermark"); if (!this->tcb->GetDataTracker().WillIncreaseCumAckTsn(tsn)) { MS_WARN_TAG(sctp, "reassembly queue is above watermark"); this->tcb->GetDataTracker().ForceImmediateSack(); return; } } if (!this->tcb->GetDataTracker().IsTsnValid(tsn)) { MS_WARN_TAG(sctp, "data rejected because of failing TSN validity"); return; } if (this->tcb->GetDataTracker().Observe(tsn, immediateAck)) { // NOTE: Here we are passing an UserData r-value created and returned by // receivedAnyDataChunk->MakeUserData() so there is only one copy here. // And ReassemblyQueue::AddData() will std::move() it internally. this->tcb->GetReassemblyQueue().AddData(tsn, receivedAnyDataChunk->MakeUserData()); MayDeliverMessages(); } } void Association::HandleReceivedSackChunk( const Packet* /*receivedPacket*/, const SackChunk* receivedSackChunk) { MS_TRACE(); if (!ValidateHasTcb()) { return; } const uint64_t nowMs = this->shared->GetTimeMs(); if (this->tcb->GetRetransmissionQueue().HandleReceivedSackChunk(nowMs, receivedSackChunk)) { MaySendShutdownOrShutdownAckChunk(); // Receiving an ACK may make the Association go into fast recovery mode. // // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4 // // "If not in Fast Recovery, determine how many of the earliest (i.e., // lowest TSN) DATA chunks marked for retransmission will fit into a // single packet, subject to constraint of the PMTU of the destination // transport address to which the packet is being sent. Call this value // K. Retransmit those K DATA chunks in a single packet. When a Fast // Retransmit is being performed, the sender SHOULD ignore the value of // cwnd and SHOULD NOT delay retransmission for this single packet." this->tcb->MaySendFastRetransmit(); // Receiving an ACK will decrease outstanding bytes (maybe now below // cwnd?) or indicate packet loss that may result in sending FORWARD-TSN. this->tcb->SendBufferedPackets(nowMs); } else { MS_WARN_TAG( sctp, "dropping received out-of-order SACK [TSN:%" PRIu32 "]", receivedSackChunk->GetCumulativeTsnAck()); } } bool Association::HandleReceivedUnknownChunk( const Packet* /*receivedPacket*/, const UnknownChunk* receivedUnknownChunk) { MS_TRACE(); const auto action = receivedUnknownChunk->GetActionForUnknownChunkType(); const auto skipProcessing = action == Chunk::ActionForUnknownChunkType::SKIP || action == Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT; const auto reportError = action == Chunk::ActionForUnknownChunkType::STOP_AND_REPORT || action == Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT; if (skipProcessing) { MS_WARN_TAG( sctp, "Chunk with unknown type %" PRIu8 " received, skipping further processing of Chunks in the Packet", static_cast(receivedUnknownChunk->GetType())); } else { MS_DEBUG_TAG( sctp, "ignoring received Chunk with unknown type %" PRIu8, static_cast(receivedUnknownChunk->GetType())); } if (reportError) { this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "unknown chunk with type indicating it should be reported"); // If there is TCB (we need correct remote verification tag) send an // OPERATION_ERROR Chunk with a Unrecognized Chunk Type Error Cause. if (this->tcb) { auto packet = this->tcb->CreatePacket(); auto* operationErrorChunk = packet->BuildChunkInPlace(); auto* unrecognizedChunkTypeErrorCause = operationErrorChunk->BuildErrorCauseInPlace(); unrecognizedChunkTypeErrorCause->SetUnrecognizedChunk( receivedUnknownChunk->GetBuffer(), receivedUnknownChunk->GetLength()); unrecognizedChunkTypeErrorCause->Consolidate(); operationErrorChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); } } return !skipProcessing; } void Association::OnT1InitTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) { MS_TRACE(); const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); AssertState(State::COOKIE_WAIT); if (this->t1InitTimer->IsRunning()) { SendInitChunk(); } else { InternalClose(Types::ErrorKind::TOO_MANY_RETRIES, "no INIT_ACK chunk received"); } AssertIsConsistent(); } void Association::OnT1CookieTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) { MS_TRACE(); const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); AssertState(State::COOKIE_ECHOED); if (this->t1CookieTimer->IsRunning()) { const uint64_t nowMs = this->shared->GetTimeMs(); this->tcb->SendBufferedPackets(nowMs); } else { InternalClose(Types::ErrorKind::TOO_MANY_RETRIES, "no COOKIE_ACK chunk received"); } AssertIsConsistent(); } void Association::OnT2ShutdownTimer(uint64_t& baseTimeoutMs, bool& /*stop*/) { MS_TRACE(); AssertState(State::SHUTDOWN_SENT, State::SHUTDOWN_ACK_SENT); AssertHasTcb(); const AssociationListenerDeferrer::ScopedDeferrer deferrer(this->associationListenerDeferrer); // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "An endpoint SHOULD limit the number of retransmissions of the // SHUTDOWN chunk to the protocol parameter 'Association.Max.Retrans'. If // this threshold is exceeded, the endpoint SHOULD destroy the TCB and // SHOULD report the peer endpoint unreachable to the upper layer (and // thus the association enters the CLOSED state)." if (!this->t2ShutdownTimer->IsRunning()) { auto packet = this->tcb->CreatePacket(); auto* abortAssociationChunk = packet->BuildChunkInPlace(); // NOTE: Don't set bit T in the ABORT chunk since TCB knows the // Verification Tag expected by the remote. auto* userInitiatedAbortErrorCause = abortAssociationChunk->BuildErrorCauseInPlace(); userInitiatedAbortErrorCause->SetUpperLayerAbortReason( "too many retransmissions of SHUTDOWN chunk"); userInitiatedAbortErrorCause->Consolidate(); abortAssociationChunk->Consolidate(); this->packetSender.SendPacket(packet.get()); InternalClose(Types::ErrorKind::TOO_MANY_RETRIES, "no SHUTDOWN_ACK chunk received"); AssertIsConsistent(); return; } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "the SHUTDOWN chunk receiver MUST send a SHUTDOWN ACK chunk and start // a T2-shutdown timer of its own, entering the SHUTDOWN-ACK-SENT state. // If the timer expires, the endpoint MUST resend the SHUTDOWN ACK chunk." if (this->state == State::SHUTDOWN_ACK_SENT) { SendShutdownAckChunk(); } // https://datatracker.ietf.org/doc/html/rfc9260#section-9.2 // // "It SHOULD then start the T2-shutdown timer and enter the SHUTDOWN-SENT // state. If the timer expires, the endpoint MUST resend the SHUTDOWN // chunk with the updated last sequential TSN received from its peer." else { SendShutdownChunk(); } AssertIsConsistent(); baseTimeoutMs = this->tcb->GetCurrentRtoMs(); } template void Association::AssertState(States... expectedStates) const { MS_TRACE(); static_assert((std::is_same_v && ...), "all arguments must be of type State"); // NOTE: Using fold expression operator. if ((... || (this->state == expectedStates))) { return; } const auto currentStateStringView = Association::StateToString(this->state); std::ostringstream expectedStatesOss; bool firstExpectedState = true; // NOTE: Using fold expression operator. ((expectedStatesOss << (firstExpectedState ? "" : ", ") << Association::StateToString(expectedStates), firstExpectedState = false), ...); auto expectedStatesString = expectedStatesOss.str(); MS_ABORT( "current internal state %.*s does not match any of the given expected states (%s)", static_cast(currentStateStringView.size()), currentStateStringView.data(), expectedStatesString.c_str()); } template void Association::AssertNotState(States... unexpectedStates) const { MS_TRACE(); static_assert((std::is_same_v && ...), "all arguments must be of type State"); // NOTE: Using fold expression operator. if ((... || (this->state == unexpectedStates))) { const auto currentStateStringView = Association::StateToString(this->state); std::ostringstream unexpectedStatesOss; bool firstUnexpectedState = true; // NOTE: Using fold expression operator. ((unexpectedStatesOss << (firstUnexpectedState ? "" : ", ") << Association::StateToString(unexpectedStates), firstUnexpectedState = false), ...); const auto unexpectedStatesString = unexpectedStatesOss.str(); MS_ABORT( "current internal state %.*s matches one of the given unexpected states (%s)", static_cast(currentStateStringView.size()), currentStateStringView.data(), unexpectedStatesString.c_str()); } } bool Association::ValidateHasTcb() { MS_TRACE(); if (this->tcb) { return true; } this->associationListenerDeferrer.OnAssociationError( Types::ErrorKind::NOT_CONNECTED, "received unexpected commands on association that is not connected"); return false; } void Association::AssertHasTcb() const { MS_TRACE(); if (!this->tcb) { MS_ABORT("TCB doesn't exist"); } } void Association::AssertIsConsistent() const { MS_TRACE(); MS_ASSERT( !(this->tcb && this->tcb->GetReassemblyQueue().HasMessages()), "this->tcb && this->tcb->GetReassemblyQueue().HasMessages()"); switch (this->state) { case State::NEW: { MS_ASSERT(!this->tcb, "internal state is NEW but there is TCB"); MS_ASSERT( !this->t1InitTimer->IsRunning(), "internal state is NEW but T1 Init timer is running"); MS_ASSERT( !this->t1CookieTimer->IsRunning(), "internal state is NEW but T1 Cookie timer is running"); MS_ASSERT( !this->t2ShutdownTimer->IsRunning(), "internal state is NEW but T2 Shutdown timer is running"); break; } case State::CLOSED: { MS_ASSERT(!this->tcb, "internal state is CLOSED but there is TCB"); MS_ASSERT( !this->t1InitTimer->IsRunning(), "internal state is CLOSED but T1 Init timer is running"); MS_ASSERT( !this->t1CookieTimer->IsRunning(), "internal state is CLOSED but T1 Cookie timer is running"); MS_ASSERT( !this->t2ShutdownTimer->IsRunning(), "internal state is CLOSED but T2 Shutdown timer is running"); break; } case State::COOKIE_WAIT: { MS_ASSERT(!this->tcb, "internal state is COOKIE_WAIT but there is TCB"); MS_ASSERT( this->t1InitTimer->IsRunning(), "internal state is COOKIE_WAIT but T1 Init timer is not running"); MS_ASSERT( !this->t1CookieTimer->IsRunning(), "internal state is COOKIE_WAIT but T1 Cookie timer is running"); MS_ASSERT( !this->t2ShutdownTimer->IsRunning(), "internal state is COOKIE_WAIT but T2 Shutdown timer is running"); break; } case State::COOKIE_ECHOED: { MS_ASSERT(this->tcb, "internal state is COOKIE_ECHOED but there is no TCB"); MS_ASSERT( !this->t1InitTimer->IsRunning(), "internal state is COOKIE_ECHOED but T1 Init timer is not running"); MS_ASSERT( this->t1CookieTimer->IsRunning(), "internal state is COOKIE_ECHOED but T1 Cookie timer is not running"); MS_ASSERT( !this->t2ShutdownTimer->IsRunning(), "internal state is COOKIE_ECHOED but T2 Shutdown timer is running"); MS_ASSERT( this->tcb->HasRemoteStateCookie(), "internal state is COOKIE_ECHOED but TCB does't have remote state cookie"); break; } case State::ESTABLISHED: { MS_ASSERT(this->tcb, "internal state is ESTABLISHED but there is not TCB"); MS_ASSERT( !this->t1InitTimer->IsRunning(), "internal state is ESTABLISHED but T1 Init timer is running"); MS_ASSERT( !this->t1CookieTimer->IsRunning(), "internal state is ESTABLISHED but T1 Cookie timer is running"); MS_ASSERT( !this->t2ShutdownTimer->IsRunning(), "internal state is ESTABLISHED but T2 Shutdown timer is running"); break; } case State::SHUTDOWN_PENDING: { MS_ASSERT(this->tcb, "internal state is SHUTDOWN_PENDING but there is not TCB"); MS_ASSERT( !this->t1InitTimer->IsRunning(), "internal state is SHUTDOWN_PENDING but T1 Init timer is running"); MS_ASSERT( !this->t1CookieTimer->IsRunning(), "internal state is SHUTDOWN_PENDING but T1 Cookie timer is running"); MS_ASSERT( !this->t2ShutdownTimer->IsRunning(), "internal state is SHUTDOWN_PENDING but T2 Shutdown timer is running"); break; } case State::SHUTDOWN_SENT: { MS_ASSERT(this->tcb, "internal state is SHUTDOWN_SENT but there is not TCB"); MS_ASSERT( !this->t1InitTimer->IsRunning(), "internal state is SHUTDOWN_SENT but T1 Init timer is running"); MS_ASSERT( !this->t1CookieTimer->IsRunning(), "internal state is SHUTDOWN_SENT but T1 Cookie timer is running"); MS_ASSERT( this->t2ShutdownTimer->IsRunning(), "internal state is SHUTDOWN_SENT but T2 Shutdown timer is not running"); break; } case State::SHUTDOWN_RECEIVED: { MS_ASSERT(this->tcb, "internal state is SHUTDOWN_RECEIVED but there is not TCB"); MS_ASSERT( !this->t1InitTimer->IsRunning(), "internal state is SHUTDOWN_RECEIVED but T1 Init timer is running"); MS_ASSERT( !this->t1CookieTimer->IsRunning(), "internal state is SHUTDOWN_RECEIVED but T1 Cookie timer is running"); MS_ASSERT( !this->t2ShutdownTimer->IsRunning(), "internal state is SHUTDOWN_RECEIVED but T2 Shutdown timer is running"); break; } case State::SHUTDOWN_ACK_SENT: { MS_ASSERT(this->tcb, "internal state is SHUTDOWN_ACK_SENT but there is not TCB"); MS_ASSERT( !this->t1InitTimer->IsRunning(), "internal state is SHUTDOWN_ACK_SENT but T1 Init timer is running"); MS_ASSERT( !this->t1CookieTimer->IsRunning(), "internal state is SHUTDOWN_ACK_SENT but T1 Cookie timer is running"); MS_ASSERT( this->t2ShutdownTimer->IsRunning(), "internal state is SHUTDOWN_ACK_SENT but T2 Shutdown timer is not running"); break; } } } void Association::OnPacketSenderPacketSent( PacketSender* /*packetSender*/, const Packet* /*packet*/, bool sent) { MS_TRACE(); if (sent) { this->privateMetrics.txPacketsCount++; } } void Association::OnBackoffTimer( BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) { MS_TRACE(); const auto maxRestarts = backoffTimer->GetMaxRestarts(); MS_DEBUG_TAG( sctp, "%s timer has expired [expìrations:%zu/%s]", backoffTimer->GetLabel().c_str(), backoffTimer->GetExpirationCount(), maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite"); if (backoffTimer == this->t1InitTimer.get()) { OnT1InitTimer(baseTimeoutMs, stop); } else if (backoffTimer == this->t1CookieTimer.get()) { OnT1CookieTimer(baseTimeoutMs, stop); } else if (backoffTimer == this->t2ShutdownTimer.get()) { OnT2ShutdownTimer(baseTimeoutMs, stop); } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/association/AssociationListenerDeferrer.cpp ================================================ #define MS_CLASS "RTC::SCTP::AssociationListenerDeferrer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/association/AssociationListenerDeferrer.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { AssociationListenerDeferrer::ScopedDeferrer::ScopedDeferrer( AssociationListenerDeferrer& listenerDeferrer) : listenerDeferrer(listenerDeferrer) { MS_TRACE(); this->listenerDeferrer.SetReady(); } // NOLINTNEXTLINE(bugprone-exception-escape) AssociationListenerDeferrer::ScopedDeferrer::~ScopedDeferrer() { MS_TRACE(); this->listenerDeferrer.TriggerDeferredCallbacks(); } AssociationListenerDeferrer::AssociationListenerDeferrer(AssociationListenerInterface* innerListener) : innerListener(innerListener) { MS_TRACE(); this->deferredCallbacks.reserve(8); } void AssociationListenerDeferrer::SetReady() { MS_TRACE(); MS_ASSERT(!this->ready, "already ready"); this->ready = true; } void AssociationListenerDeferrer::TriggerDeferredCallbacks() { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->ready = false; if (this->deferredCallbacks.empty()) { return; } // Need to swap stored callbacks here. The caller may call into the library // from within a callback, and that might result in adding new callbacks to // this instance, and the vector can't be modified while iterated on. std::vector> localDeferredCallbacks; // Reserve a small buffer to prevent too much reallocation on growth. localDeferredCallbacks.reserve(8); localDeferredCallbacks.swap(this->deferredCallbacks); for (auto& [callback, data] : localDeferredCallbacks) { callback(std::move(data), this->innerListener); } } bool AssociationListenerDeferrer::OnAssociationSendData(const uint8_t* data, size_t len) { MS_TRACE(); // Will not be deferred but called directly. return this->innerListener->OnAssociationSendData(data, len); } void AssociationListenerDeferrer::OnAssociationConnecting() { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData /*data*/, AssociationListenerInterface* listener) { listener->OnAssociationConnecting(); }, std::monostate{}); } void AssociationListenerDeferrer::OnAssociationConnected() { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData /*data*/, AssociationListenerInterface* listener) { listener->OnAssociationConnected(); }, std::monostate{}); } void AssociationListenerDeferrer::OnAssociationFailed( Types::ErrorKind errorKind, std::string_view errorMessage) { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData data, AssociationListenerInterface* listener) { const Error error = std::get(std::move(data)); listener->OnAssociationFailed(error.errorKind, error.message); }, Error{ .errorKind = errorKind, .message = std::string(errorMessage) }); } void AssociationListenerDeferrer::OnAssociationClosed( Types::ErrorKind errorKind, std::string_view errorMessage) { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData data, AssociationListenerInterface* listener) { const Error error = std::get(std::move(data)); listener->OnAssociationClosed(error.errorKind, error.message); }, Error{ .errorKind = errorKind, .message = std::string(errorMessage) }); } void AssociationListenerDeferrer::OnAssociationRestarted() { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData /*data*/, AssociationListenerInterface* listener) { listener->OnAssociationRestarted(); }, std::monostate{}); } void AssociationListenerDeferrer::OnAssociationError( Types::ErrorKind errorKind, std::string_view errorMessage) { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData data, AssociationListenerInterface* listener) { const Error error = std::get(std::move(data)); listener->OnAssociationError(error.errorKind, error.message); }, Error{ .errorKind = errorKind, .message = std::string(errorMessage) }); } void AssociationListenerDeferrer::OnAssociationMessageReceived(Message message) { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData data, AssociationListenerInterface* listener) { listener->OnAssociationMessageReceived(std::get(std::move(data))); }, std::move(message)); } void AssociationListenerDeferrer::OnAssociationStreamsResetPerformed( std::span outboundStreamIds) { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData data, AssociationListenerInterface* listener) { StreamReset streamReset = std::get(std::move(data)); listener->OnAssociationStreamsResetPerformed(streamReset.streamIds); }, StreamReset{ .streamIds = { outboundStreamIds.begin(), outboundStreamIds.end() } }); } void AssociationListenerDeferrer::OnAssociationStreamsResetFailed( std::span outboundStreamIds, std::string_view errorMessage) { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData data, AssociationListenerInterface* listener) { StreamReset streamReset = std::get(std::move(data)); listener->OnAssociationStreamsResetFailed(streamReset.streamIds, streamReset.errorMessage); }, StreamReset{ .streamIds = { outboundStreamIds.begin(), outboundStreamIds.end() }, .errorMessage = std::string(errorMessage) }); } void AssociationListenerDeferrer::OnAssociationInboundStreamsReset( std::span inboundStreamIds) { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData data, AssociationListenerInterface* listener) { StreamReset streamReset = std::get(std::move(data)); listener->OnAssociationInboundStreamsReset(streamReset.streamIds); }, StreamReset{ .streamIds = { inboundStreamIds.begin(), inboundStreamIds.end() } }); } void AssociationListenerDeferrer::OnAssociationStreamBufferedAmountLow(uint16_t streamId) { MS_TRACE(); ; MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData data, AssociationListenerInterface* listener) { listener->OnAssociationStreamBufferedAmountLow(std::get(std::move(data))); }, streamId); } void AssociationListenerDeferrer::OnAssociationTotalBufferedAmountLow() { MS_TRACE(); MS_ASSERT(this->ready, "not ready"); this->deferredCallbacks.emplace_back( [](CallbackData /*data*/, AssociationListenerInterface* listener) { listener->OnAssociationTotalBufferedAmountLow(); }, std::monostate{}); } bool AssociationListenerDeferrer::OnAssociationIsTransportReadyForSctp() { MS_TRACE(); ; // Will not be deferred but called directly. return this->innerListener->OnAssociationIsTransportReadyForSctp(); } void AssociationListenerDeferrer::OnAssociationLifecycleMessageFullySent(uint64_t lifecycleId) { MS_TRACE(); // Will not be deferred but called directly. this->innerListener->OnAssociationLifecycleMessageFullySent(lifecycleId); } void AssociationListenerDeferrer::OnAssociationLifecycleMessageExpired( uint64_t lifecycleId, bool maybeDelivered) { MS_TRACE(); // Will not be deferred but called directly. this->innerListener->OnAssociationLifecycleMessageExpired(lifecycleId, maybeDelivered); } void AssociationListenerDeferrer::OnAssociationLifecycleMessageDelivered(uint64_t lifecycleId) { MS_TRACE(); // Will not be deferred but called directly. this->innerListener->OnAssociationLifecycleMessageDelivered(lifecycleId); } void AssociationListenerDeferrer::OnAssociationLifecycleMessageEnd(uint64_t lifecycleId) { MS_TRACE(); // Will not be deferred but called directly. this->innerListener->OnAssociationLifecycleMessageEnd(lifecycleId); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/association/HeartbeatHandler.cpp ================================================ #define MS_CLASS "RTC::SCTP::HeartbeatHandler" // TODO: SCTP: COMMENT #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/association/HeartbeatHandler.hpp" #include "Logger.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include namespace RTC { namespace SCTP { /* Static. */ static constexpr int HeartbeatInfoLength{ 8 }; /* Instance methods. */ HeartbeatHandler::HeartbeatHandler( AssociationListenerInterface& associationListener, const SctpOptions& sctpOptions, SharedInterface* shared, TransmissionControlBlockContextInterface* tcbContext) : associationListener(associationListener), sctpOptions(sctpOptions), shared(shared), tcbContext(tcbContext), intervalDurationMs(sctpOptions.heartbeatIntervalMs), intervalDurationShouldIncludeRtt(sctpOptions.heartbeatIntervalIncludeRtt), intervalTimer(this->shared->CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = this, .label = "sctp-heartbeat-interval", .baseTimeoutMs = sctpOptions.initialRtoMs, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL, .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs, .maxRestarts = std::nullopt })), timeoutTimer(this->shared->CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = this, .label = "sctp-heartbeat-timeout", .baseTimeoutMs = sctpOptions.initialRtoMs, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::FIXED, .maxBackoffTimeoutMs = std::nullopt, .maxRestarts = 0 })) { MS_TRACE(); // The interval timer must always be running as long as the association // is up (so when the TCB is created, which is the one that creates the // HeartbeatHandler. RestartTimer(); } HeartbeatHandler::~HeartbeatHandler() { MS_TRACE(); } void HeartbeatHandler::RestartTimer() { MS_TRACE(); // Heartbeating has been disabled. if (this->intervalDurationMs == 0) { return; } if (intervalDurationShouldIncludeRtt) { this->intervalTimer->SetBaseTimeoutMs( this->intervalDurationMs + this->tcbContext->GetCurrentRtoMs()); } else { this->intervalTimer->SetBaseTimeoutMs(this->intervalDurationMs); } this->intervalTimer->Start(); } void HeartbeatHandler::HandleReceivedHeartbeatRequestChunk( const HeartbeatRequestChunk* receivedHeartbeatRequestChunk) { MS_TRACE(); // https://datatracker.ietf.org/doc/html/rfc9260#section-8.3 // // "The receiver of the HEARTBEAT chunk SHOULD immediately respond with a // HEARTBEAT ACK chunk that contains the Heartbeat Information TLV, // together with any other received TLVs, copied unchanged from the // received HEARTBEAT chunk." auto packet = this->tcbContext->CreatePacket(); auto* heartbeatAckChunk = packet->BuildChunkInPlace(); // Here we have to extract all Parameters from receivedHeartbeatRequestChunk // and add them into heartbeatAckChunk. for (auto it = receivedHeartbeatRequestChunk->ParametersBegin(); it != receivedHeartbeatRequestChunk->ParametersEnd(); ++it) { const auto* parameter = *it; heartbeatAckChunk->AddParameter(parameter); } heartbeatAckChunk->Consolidate(); this->tcbContext->SendPacket(packet.get()); } void HeartbeatHandler::HandleReceivedHeartbeatAckChunk( const HeartbeatAckChunk* receivedHeartbeatAckChunk) { MS_TRACE(); this->timeoutTimer->Stop(); const auto* heartbeatInfoParameter = receivedHeartbeatAckChunk->GetFirstParameterOfType(); if (!heartbeatInfoParameter) { this->associationListener.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "ignoring HEARTBEAT_ACK chunk without Heartbeat Info parameter"); return; } const auto* info = heartbeatInfoParameter->GetInfo(); const uint16_t infoLen = heartbeatInfoParameter->GetInfoLength(); if (!info) { this->associationListener.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "ignoring Heartbeat Info parameter without info field"); return; } else if (infoLen != HeartbeatInfoLength) { this->associationListener.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "ignoring Heartbeat Info parameter with wrong length"); return; } const uint64_t createdAtMs = Utils::Byte::Get8Bytes(info, 0); const uint64_t nowMs = this->shared->GetTimeMs(); if (createdAtMs > 0 && createdAtMs <= nowMs) { const uint64_t rttMs = nowMs - createdAtMs; MS_DEBUG_DEV("valid HEARTBEAT_ACK Chunk received, calling ObserveRttMs(%" PRIu64 ")", rttMs); this->tcbContext->ObserveRttMs(rttMs); } else { MS_WARN_DEV( "ignoring received HEARTBEAT_ACK Chunk with invalid info content [createdAtMs:%" PRIu64 ", nowMs:%" PRIu64 "]", createdAtMs, nowMs); } // https://datatracker.ietf.org/doc/html/rfc9260#section-8.1 // // "When a HEARTBEAT ACK chunk is received from the peer endpoint, the // counter SHOULD also be reset." this->tcbContext->ClearTxErrorCounter(); } void HeartbeatHandler::OnIntervalTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) { MS_TRACE(); if (!this->tcbContext->IsAssociationEstablished()) { MS_DEBUG_DEV("won't send HEARTBEAT_REQUEST when SCTP Association is not established"); return; } this->timeoutTimer->SetBaseTimeoutMs(this->tcbContext->GetCurrentRtoMs()); this->timeoutTimer->Start(); alignas(8) uint8_t info[HeartbeatInfoLength]; const uint64_t nowMs = this->shared->GetTimeMs(); Utils::Byte::Set8Bytes(info, 0, nowMs); auto packet = this->tcbContext->CreatePacket(); auto* heartbeatRequestChunk = packet->BuildChunkInPlace(); auto* heartbeatInfoParameter = heartbeatRequestChunk->BuildParameterInPlace(); heartbeatInfoParameter->SetInfo(info, HeartbeatInfoLength); heartbeatInfoParameter->Consolidate(); heartbeatRequestChunk->Consolidate(); MS_DEBUG_DEV("sending HEARTBEAT_REQUEST Chunk with info content [nowMs:%" PRIu64 "]", nowMs); this->tcbContext->SendPacket(packet.get()); } void HeartbeatHandler::OnTimeoutTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) { MS_TRACE(); // Note that the timeout timer is not restarted. It will be started again when // the interval timer expires. MS_ASSERT(!this->timeoutTimer->IsRunning(), "timeout timer shouldn't be running"); this->tcbContext->IncrementTxErrorCounter("hearbeat timeout"); } void HeartbeatHandler::OnBackoffTimer( BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) { MS_TRACE(); const auto maxRestarts = backoffTimer->GetMaxRestarts(); MS_DEBUG_TAG( sctp, "%s timer has expired [expìrations:%zu/%s]", backoffTimer->GetLabel().c_str(), backoffTimer->GetExpirationCount(), maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite"); if (backoffTimer == this->intervalTimer.get()) { OnIntervalTimer(baseTimeoutMs, stop); } else if (backoffTimer == this->timeoutTimer.get()) { OnTimeoutTimer(baseTimeoutMs, stop); } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/association/NegotiatedCapabilities.cpp ================================================ #define MS_CLASS "RTC::SCTP::NegotiatedCapabilities" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/association/NegotiatedCapabilities.hpp" #include "Logger.hpp" #include "RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp" #include "RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp" #include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp" namespace RTC { namespace SCTP { /* Class methods. */ NegotiatedCapabilities NegotiatedCapabilities::Factory( const SctpOptions& sctpOptions, const AnyInitChunk* remoteChunk) { MS_TRACE(); NegotiatedCapabilities negotiatedCapabilities{}; const auto* remoteSupportedExtensionsParameter = remoteChunk->template GetFirstParameterOfType(); const auto* remoteForwardTsnSupportedParameter = remoteChunk->template GetFirstParameterOfType(); const auto* remoteZeroChecksumAcceptableParameter = remoteChunk->template GetFirstParameterOfType(); negotiatedCapabilities.negotiatedMaxOutboundStreams = std::min(sctpOptions.announcedMaxOutboundStreams, remoteChunk->GetNumberOfInboundStreams()); negotiatedCapabilities.negotiatedMaxInboundStreams = std::min(sctpOptions.announcedMaxInboundStreams, remoteChunk->GetNumberOfOutboundStreams()); // Partial Reliability Extension is negotiated if we desire it and // peer announces support via Forward-TSN-Supported Parameter or via // Supported Extensions Parameter. negotiatedCapabilities.partialReliability = sctpOptions.enablePartialReliability && (remoteForwardTsnSupportedParameter || (remoteSupportedExtensionsParameter && remoteSupportedExtensionsParameter->IncludesChunkType(Chunk::ChunkType::FORWARD_TSN))); // Message Interleaving is negotiated if we desire it and peer // announces support via Supported Extensions Parameter. negotiatedCapabilities.messageInterleaving = sctpOptions.enableMessageInterleaving && remoteSupportedExtensionsParameter && remoteSupportedExtensionsParameter->IncludesChunkType(Chunk::ChunkType::I_DATA) && remoteSupportedExtensionsParameter->IncludesChunkType(Chunk::ChunkType::I_FORWARD_TSN); // Stream Re-Configuration is negotiated if peer announces support via // Supported Extensions Parameter. negotiatedCapabilities.reConfig = remoteSupportedExtensionsParameter && remoteSupportedExtensionsParameter->IncludesChunkType(Chunk::ChunkType::RE_CONFIG); // Alternate Error Detection Method for Zero Checksum is negotiated // if we desire it and peer announces the same non-none alternate // error detection method. negotiatedCapabilities.zeroChecksum = sctpOptions.zeroChecksumAlternateErrorDetectionMethod != ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE && remoteZeroChecksumAcceptableParameter && remoteZeroChecksumAcceptableParameter->GetAlternateErrorDetectionMethod() == sctpOptions.zeroChecksumAlternateErrorDetectionMethod; return negotiatedCapabilities; } /* Instance methods. */ void NegotiatedCapabilities::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN( indentation, " negotiated max outbound streams: %" PRIu16, this->negotiatedMaxOutboundStreams); MS_DUMP_CLEAN( indentation, " negotiated max inbound streams: %" PRIu16, this->negotiatedMaxInboundStreams); MS_DUMP_CLEAN(indentation, " partial reliability: %s", this->partialReliability ? "yes" : "no"); MS_DUMP_CLEAN( indentation, " message interleaving: %s", this->messageInterleaving ? "yes" : "no"); MS_DUMP_CLEAN(indentation, " re-config: %s", this->reConfig ? "yes" : "no"); MS_DUMP_CLEAN(indentation, " zero checksum: %s", this->zeroChecksum ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/association/PacketSender.cpp ================================================ #define MS_CLASS "RTC::SCTP::PacketSender" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/association/PacketSender.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { PacketSender::PacketSender(Listener* listener, AssociationListenerInterface& associationListener) : listener(listener), associationListener(associationListener) { MS_TRACE(); } PacketSender::~PacketSender() { MS_TRACE(); } bool PacketSender::SendPacket(Packet* packet, bool writeChecksum) { MS_TRACE(); if (packet->GetChunksCount() == 0) { return false; } if (writeChecksum) { packet->WriteCRC32cChecksum(); } // TODO: SCTP: For testing purposes. Must be removed. { MS_DUMP(">>> sending SCTP packet:"); packet->Dump(); } MS_ASSERT(!packet->NeedsConsolidation(), "cannot send a SCTP packet that needs consolidation"); const bool sent = this->associationListener.OnAssociationSendData(packet->GetBuffer(), packet->GetLength()); this->listener->OnPacketSenderPacketSent(this, packet, sent); if (!sent) { MS_WARN_TAG(sctp, "couldn't send SCTP Packet"); } return sent; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/association/StateCookie.cpp ================================================ #define MS_CLASS "RTC::SCTP::Packet" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/association/StateCookie.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ bool StateCookie::IsMediasoupStateCookie(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength != StateCookie::StateCookieLength) { return false; } if (Utils::Byte::Get8Bytes(buffer, 0) != StateCookie::Magic1) { return false; } auto* negotiatedCapabilitiesField = reinterpret_cast( const_cast(buffer) + StateCookie::NegotiatedCapabilitiesOffset); if (ntohs(negotiatedCapabilitiesField->magic2) != StateCookie::Magic2) { return false; } return true; } StateCookie* StateCookie::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (!StateCookie::IsMediasoupStateCookie(buffer, bufferLength)) { MS_WARN_TAG(sctp, "not a StateCookie generated by mediasoup"); return nullptr; } auto* stateCookie = new StateCookie(const_cast(buffer), bufferLength); return stateCookie; } StateCookie* StateCookie::Factory( uint8_t* buffer, size_t bufferLength, uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities) { MS_TRACE(); // This may throw. StateCookie::Write( buffer, bufferLength, localVerificationTag, remoteVerificationTag, localInitialTsn, remoteInitialTsn, remoteAdvertisedReceiverWindowCredit, tieTag, negotiatedCapabilities); return new StateCookie(buffer, StateCookie::StateCookieLength); } void StateCookie::Write( uint8_t* buffer, size_t bufferLength, uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities) { MS_TRACE(); if (bufferLength < StateCookie::StateCookieLength) { MS_THROW_TYPE_ERROR("buffer too small"); } Utils::Byte::Set8Bytes(buffer, 0, StateCookie::Magic1); Utils::Byte::Set4Bytes(buffer, 8, localVerificationTag); Utils::Byte::Set4Bytes(buffer, 12, remoteVerificationTag); Utils::Byte::Set4Bytes(buffer, 16, localInitialTsn); Utils::Byte::Set4Bytes(buffer, 20, remoteInitialTsn); Utils::Byte::Set4Bytes(buffer, 24, remoteAdvertisedReceiverWindowCredit); Utils::Byte::Set8Bytes(buffer, 28, tieTag); auto* negotiatedCapabilitiesField = reinterpret_cast( buffer + StateCookie::NegotiatedCapabilitiesOffset); negotiatedCapabilitiesField->reserved = 0; negotiatedCapabilitiesField->bitA = negotiatedCapabilities.partialReliability; negotiatedCapabilitiesField->bitB = negotiatedCapabilities.messageInterleaving; negotiatedCapabilitiesField->bitC = negotiatedCapabilities.reConfig; negotiatedCapabilitiesField->bitD = negotiatedCapabilities.zeroChecksum; negotiatedCapabilitiesField->magic2 = htons(StateCookie::Magic2); negotiatedCapabilitiesField->negotiatedMaxOutboundStreams = htons(negotiatedCapabilities.negotiatedMaxOutboundStreams); negotiatedCapabilitiesField->negotiatedMaxInboundStreams = htons(negotiatedCapabilities.negotiatedMaxInboundStreams); } Types::SctpImplementation StateCookie::DetermineSctpImplementation( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < StateCookie::Magic1Length) { return Types::SctpImplementation::UNKNOWN; } const std::string_view magic1(reinterpret_cast(buffer), StateCookie::Magic1Length); if (magic1 == "msworker") { return Types::SctpImplementation::MEDIASOUP; } else if (magic1 == "dcSCTP00") { return Types::SctpImplementation::DCSCTP; } else if (magic1 == "KAME-BSD") { return Types::SctpImplementation::USRSCTP; } else { return Types::SctpImplementation::UNKNOWN; } } /* Instance methods. */ StateCookie::StateCookie(uint8_t* buffer, size_t bufferLength) : Serializable(buffer, bufferLength) { MS_TRACE(); SetLength(StateCookie::StateCookieLength); } StateCookie::~StateCookie() { MS_TRACE(); } void StateCookie::Dump(int indentation) const { MS_TRACE(); auto negotiatedCapabilities = GetNegotiatedCapabilities(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " length: %zu (buffer length: %zu)", GetLength(), GetBufferLength()); MS_DUMP_CLEAN(indentation, " local verification tag: %" PRIu32, GetLocalVerificationTag()); MS_DUMP_CLEAN(indentation, " remote verification tag: %" PRIu32, GetRemoteVerificationTag()); MS_DUMP_CLEAN(indentation, " local initial tsn: %" PRIu32, GetLocalInitialTsn()); MS_DUMP_CLEAN(indentation, " remote initial tsn: %" PRIu32, GetRemoteInitialTsn()); MS_DUMP_CLEAN( indentation, " remote advertised receiver window credit: %" PRIu32, GetRemoteAdvertisedReceiverWindowCredit()); MS_DUMP_CLEAN(indentation, " tie-tag: %" PRIu64, GetTieTag()); negotiatedCapabilities.Dump(indentation + 1); MS_DUMP_CLEAN(indentation, ""); } StateCookie* StateCookie::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedStateCookie = new StateCookie(buffer, bufferLength); Serializable::CloneInto(clonedStateCookie); return clonedStateCookie; } NegotiatedCapabilities StateCookie::GetNegotiatedCapabilities() const { MS_TRACE(); auto* negotiatedCapabilitiesField = GetNegotiatedCapabilitiesField(); NegotiatedCapabilities negotiatedCapabilities; negotiatedCapabilities.negotiatedMaxOutboundStreams = ntohs(negotiatedCapabilitiesField->negotiatedMaxOutboundStreams); negotiatedCapabilities.negotiatedMaxInboundStreams = ntohs(negotiatedCapabilitiesField->negotiatedMaxInboundStreams); negotiatedCapabilities.partialReliability = negotiatedCapabilitiesField->bitA; negotiatedCapabilities.messageInterleaving = negotiatedCapabilitiesField->bitB; negotiatedCapabilities.reConfig = negotiatedCapabilitiesField->bitC; negotiatedCapabilities.zeroChecksum = negotiatedCapabilitiesField->bitD; // NOTE: No need to std::move(). Copy elision (RVO) is used for free in GCC // and clang in C++17 or higher. return negotiatedCapabilities; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/association/StreamResetHandler.cpp ================================================ #define MS_CLASS "RTC::SCTP::StreamResetHandler" // TODO: SCTP: COMMENT #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/association/StreamResetHandler.hpp" #include "Logger.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp" namespace RTC { namespace SCTP { /* Instance methods. */ StreamResetHandler::StreamResetHandler( AssociationListenerInterface& associationListener, SharedInterface* shared, TransmissionControlBlockContextInterface* tcbContext, DataTracker* dataTracker, ReassemblyQueue* reassemblyQueue, RetransmissionQueue* retransmissionQueue) : associationListener(associationListener), shared(shared), tcbContext(tcbContext), dataTracker(dataTracker), reassemblyQueue(reassemblyQueue), retransmissionQueue(retransmissionQueue), reConfigTimer(this->shared->CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = this, .label = "sctp-re-config", .baseTimeoutMs = 0, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL, .maxBackoffTimeoutMs = std::nullopt, .maxRestarts = std::nullopt, })), nextOutgoingReqSeqNbr(tcbContext->GetLocalInitialTsn()), lastProcessedReqSeqNbr( this->incomingReConfigRequestSnUnwrapper.Unwrap(tcbContext->GetRemoteInitialTsn() - 1)), lastProcessedReqResult(ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO) { MS_TRACE(); } StreamResetHandler::~StreamResetHandler() { MS_TRACE(); } void StreamResetHandler::ResetStreams(std::span outgoingStreamIds) { MS_TRACE(); for (const auto streamId : outgoingStreamIds) { this->retransmissionQueue->PrepareResetStream(streamId); } } bool StreamResetHandler::ShouldSendStreamResetRequest() const { MS_TRACE(); // Only send stream resets if there are streams to reset and no current // ongoing request (there can only be one at a time). return !this->currentRequest.has_value() && this->retransmissionQueue->HasStreamsReadyToBeReset(); } void StreamResetHandler::AddStreamResetRequest(Packet* packet) { MS_TRACE(); MS_ASSERT(ShouldSendStreamResetRequest(), "should not send a stream reset request"); this->currentRequest.emplace( this->retransmissionQueue->GetLastAssignedTsn(), this->retransmissionQueue->BeginResetStreams()); this->reConfigTimer->SetBaseTimeoutMs(this->tcbContext->GetCurrentRtoMs()); this->reConfigTimer->Start(); AddReConfigChunk(packet); } void StreamResetHandler::HandleReceivedReConfigChunk(const ReConfigChunk* receivedReConfigChunk) { MS_TRACE(); if (!ValidateReceivedReConfigChunk(receivedReConfigChunk)) { this->associationListener.OnAssociationError( Types::ErrorKind::PARSE_FAILED, "invalid RE-CONFIG command received"); return; } auto packet = this->tcbContext->CreatePacket(); auto* reConfigChunk = packet->BuildChunkInPlace(); for (auto it = receivedReConfigChunk->ParametersBegin(); it != receivedReConfigChunk->ParametersEnd(); ++it) { const auto* parameter = *it; switch (parameter->GetType()) { case Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST: { HandleReceivedOutgoingSsnResetRequestParameter( reinterpret_cast(parameter), reConfigChunk); break; } case Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST: { HandleReceivedIncomingSsnResetRequestParameter( reinterpret_cast(parameter), reConfigChunk); break; } case Parameter::ParameterType::RECONFIGURATION_RESPONSE: { HandleReceivedReconfigurationResponseParameter( reinterpret_cast(parameter)); break; } default:; } } reConfigChunk->Consolidate(); if (reConfigChunk->GetParametersCount() > 0) { this->tcbContext->SendPacket(packet.get()); } } bool StreamResetHandler::ValidateReceivedReConfigChunk(const ReConfigChunk* receivedReConfigChunk) { MS_TRACE(); if (receivedReConfigChunk->GetParametersCount() == 1) { const auto* firstParameter = receivedReConfigChunk->GetParameterAt(0); if ( firstParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST || firstParameter->GetType() == Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST || firstParameter->GetType() == Parameter::ParameterType::SSN_TSN_RESET_REQUEST || firstParameter->GetType() == Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST || firstParameter->GetType() == Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST || firstParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE) { return true; } } else if (receivedReConfigChunk->GetParametersCount() == 2) { const auto* firstParameter = receivedReConfigChunk->GetParameterAt(0); const auto* secondParameter = receivedReConfigChunk->GetParameterAt(1); if ( (firstParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST && secondParameter->GetType() == Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST) || (firstParameter->GetType() == Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST && secondParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST) || (firstParameter->GetType() == Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST && secondParameter->GetType() == Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST) || (firstParameter->GetType() == Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST && secondParameter->GetType() == Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST) || (firstParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE && secondParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST) || (firstParameter->GetType() == Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST && secondParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE) || (firstParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE && secondParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE) || (firstParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE && secondParameter->GetType() == Parameter::ParameterType::RECONFIGURATION_RESPONSE)) { return true; } } MS_WARN_TAG(sctp, "invalid set of RE-CONFIG Parameters"); return false; } void StreamResetHandler::AddReConfigChunk(Packet* packet) { MS_TRACE(); // The `reqSeqNbr` will be empty if the request has never been sent before, // or if it was sent, but the sender responded "in progress", and then the // `reqSeqNbr` will be cleared to re-send with a new number. But if the // request is re-sent due to timeout (re-config timer expiring), the same // `reqSeqNbr` will be used. MS_ASSERT(this->currentRequest.has_value(), "currentRequest optional must have value"); if (this->currentRequest->HasBeenSent()) { this->currentRequest->PrepareToSend(this->nextOutgoingReqSeqNbr); this->nextOutgoingReqSeqNbr = uint32_t{ this->nextOutgoingReqSeqNbr + 1 }; } auto* reConfigChunk = packet->BuildChunkInPlace(); auto* outgoingSsnResetRequestParameter = reConfigChunk->BuildParameterInPlace(); outgoingSsnResetRequestParameter->SetReconfigurationRequestSequenceNumber( this->currentRequest->GetReqSeqNbr()); outgoingSsnResetRequestParameter->SetReconfigurationResponseSequenceNumber( this->currentRequest->GetReqSeqNbr()); outgoingSsnResetRequestParameter->SetSenderLastAssignedTsn( this->currentRequest->GetSenderLastAssignedTsn()); for (const auto& streamId : this->currentRequest->GetStreamIds()) { outgoingSsnResetRequestParameter->AddStreamId(streamId); } outgoingSsnResetRequestParameter->Consolidate(); reConfigChunk->Consolidate(); } StreamResetHandler::ReqSeqNbrValidationResult StreamResetHandler::ValidateReqSeqNbr( StreamResetHandler::UnwrappedReConfigRequestSn reqSeqNbr) { MS_TRACE(); if (reqSeqNbr == this->lastProcessedReqSeqNbr) { return ReqSeqNbrValidationResult::RETRANSMISSION; } else if (reqSeqNbr != this->lastProcessedReqSeqNbr.GetNextValue()) { // Too old, too new, from wrong Association, etc. MS_WARN_TAG(sctp, "bad reqSeqNbr: %" PRIu32, reqSeqNbr.Wrap()); return ReqSeqNbrValidationResult::BAD_SEQUENCE_NUMBER; } else { return ReqSeqNbrValidationResult::VALID; } } void StreamResetHandler::HandleReceivedOutgoingSsnResetRequestParameter( const OutgoingSsnResetRequestParameter* receivedOutgoingSsnResetRequestParameter, ReConfigChunk* reConfigChunk) { MS_TRACE(); const UnwrappedReConfigRequestSn requestSn = this->incomingReConfigRequestSnUnwrapper.Unwrap( receivedOutgoingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber()); const ReqSeqNbrValidationResult validationResult = ValidateReqSeqNbr(requestSn); if (validationResult == ReqSeqNbrValidationResult::BAD_SEQUENCE_NUMBER) { auto* reconfigurationResponseParameter = reConfigChunk->BuildParameterInPlace(); reconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber( receivedOutgoingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber()); reconfigurationResponseParameter->SetResult( ReconfigurationResponseParameter::Result::ERROR_BAD_SEQUENCE_NUMBER); reconfigurationResponseParameter->Consolidate(); return; } // If this is a retransmission of a request that has already been finalized // (i.e., not "In Progress"), just send the previous final response. if ( validationResult == ReqSeqNbrValidationResult::RETRANSMISSION && this->lastProcessedReqResult != ReconfigurationResponseParameter::Result::IN_PROGRESS) { auto* reconfigurationResponseParameter = reConfigChunk->BuildParameterInPlace(); reconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber( receivedOutgoingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber()); reconfigurationResponseParameter->SetResult(this->lastProcessedReqResult); reconfigurationResponseParameter->Consolidate(); return; } // At this point, the request is either brand new, a buggy client sending // a new SN after "In Progress", or a compliant client retransmitting an // "In Progress" request. In all cases, re-evaluate the state. this->lastProcessedReqSeqNbr = requestSn; // TODO: SCTP: Remove (it's just to avoid "private field 'reassemblyQueue' // is not used" wargning). (void)this->reassemblyQueue; if ( this->dataTracker->IsLaterThanCumulativeAckedTsn( receivedOutgoingSsnResetRequestParameter->GetSenderLastAssignedTsn())) { // https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 // // E2) "If the Sender's Last Assigned TSN is greater than the cumulative // acknowledgment point, then the endpoint MUST enter 'deferred reset // processing'." this->reassemblyQueue->EnterDeferredReset( receivedOutgoingSsnResetRequestParameter->GetSenderLastAssignedTsn(), receivedOutgoingSsnResetRequestParameter->GetStreamIds()); // "If the endpoint enters 'deferred reset processing', it MUST put a // Re-configuration Response Parameter into a RE-CONFIG chunk indicating // 'In progress' and MUST send the RE-CONFIG chunk. this->lastProcessedReqResult = ReconfigurationResponseParameter::Result::IN_PROGRESS; MS_DEBUG_DEV( "reset outgoing in progress, sender last assigned tsn %" PRIu32 " not yet reached", receivedOutgoingSsnResetRequestParameter->GetSenderLastAssignedTsn()); } else { // https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 // // E3) If no stream numbers are listed in the parameter, then all incoming // streams MUST be reset to 0 as the next expected SSN. If specific stream // numbers are listed, then only these specific streams MUST be reset to // 0, and all other non-listed SSNs remain unchanged. E4: Any queued TSNs // (queued at step E2) MUST now be released and processed normally. this->reassemblyQueue->ResetStreamsAndLeaveDeferredReset( receivedOutgoingSsnResetRequestParameter->GetStreamIds()); this->associationListener.OnAssociationInboundStreamsReset( receivedOutgoingSsnResetRequestParameter->GetStreamIds()); this->lastProcessedReqResult = ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED; MS_DEBUG_DEV("reset outgoing performed"); MS_DEBUG_DEV( "reset outgoing performed, sender last assigned tsn %" PRIu32 " reached", receivedOutgoingSsnResetRequestParameter->GetSenderLastAssignedTsn()); } auto* reconfigurationResponseParameter = reConfigChunk->BuildParameterInPlace(); reconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber( receivedOutgoingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber()); reconfigurationResponseParameter->SetResult(this->lastProcessedReqResult); reconfigurationResponseParameter->Consolidate(); } void StreamResetHandler::HandleReceivedIncomingSsnResetRequestParameter( const IncomingSsnResetRequestParameter* receivedIncomingSsnResetRequestParameter, ReConfigChunk* reConfigChunk) { MS_TRACE(); const UnwrappedReConfigRequestSn requestSn = this->incomingReConfigRequestSnUnwrapper.Unwrap( receivedIncomingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber()); const ReqSeqNbrValidationResult validationResult = ValidateReqSeqNbr(requestSn); if (validationResult == ReqSeqNbrValidationResult::VALID || validationResult == ReqSeqNbrValidationResult::RETRANSMISSION) { auto* reconfigurationResponseParameter = reConfigChunk->BuildParameterInPlace(); reconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber( receivedIncomingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber()); reconfigurationResponseParameter->SetResult( ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO); reconfigurationResponseParameter->Consolidate(); } else { auto* reconfigurationResponseParameter = reConfigChunk->BuildParameterInPlace(); reconfigurationResponseParameter->SetReconfigurationResponseSequenceNumber( receivedIncomingSsnResetRequestParameter->GetReconfigurationRequestSequenceNumber()); reconfigurationResponseParameter->SetResult( ReconfigurationResponseParameter::Result::ERROR_BAD_SEQUENCE_NUMBER); reconfigurationResponseParameter->Consolidate(); } } void StreamResetHandler::HandleReceivedReconfigurationResponseParameter( const ReconfigurationResponseParameter* receivedReconfigurationResponseParameter) { MS_TRACE(); if ( this->currentRequest.has_value() && this->currentRequest->HasBeenSent() && receivedReconfigurationResponseParameter->GetReconfigurationResponseSequenceNumber() == this->currentRequest->GetReqSeqNbr()) { this->reConfigTimer->Stop(); switch (receivedReconfigurationResponseParameter->GetResult()) { case RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO: case RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED: { MS_DEBUG_DEV( "reset stream success [reqSeqNbr:%" PRIu32 "]", this->currentRequest->GetReqSeqNbr()); this->associationListener.OnAssociationStreamsResetPerformed( this->currentRequest->GetStreamIds()); this->currentRequest = std::nullopt; this->retransmissionQueue->CommitResetStreams(); break; } case RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS: { MS_DEBUG_DEV( "reset stream still pending [reqSeqNbr:%" PRIu32 "]", this->currentRequest->GetReqSeqNbr()); // Force this request to be sent again, but with the same `reqSeqNbr`. this->currentRequest->SetDeferred(true); this->reConfigTimer->SetBaseTimeoutMs(this->tcbContext->GetCurrentRtoMs()); this->reConfigTimer->Start(); break; } case RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS: case RTC::SCTP::ReconfigurationResponseParameter::Result::DENIED: case RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_WRONG_SSN: case RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_BAD_SEQUENCE_NUMBER: { MS_WARN_TAG( sctp, "reset stream error [reqSeqNbr:%" PRIu32 ", result:%s]", this->currentRequest->GetReqSeqNbr(), ReconfigurationResponseParameter::ResultToString( receivedReconfigurationResponseParameter->GetResult()) .c_str()); this->associationListener.OnAssociationStreamsResetFailed( this->currentRequest->GetStreamIds(), ReconfigurationResponseParameter::ResultToString( receivedReconfigurationResponseParameter->GetResult())); this->currentRequest = std::nullopt; this->retransmissionQueue->RollbackResetStreams(); break; } } } } void StreamResetHandler::OnReConfigTimer(uint64_t& baseTimeoutMs, bool& /*stop*/) { MS_TRACE(); if (this->currentRequest && this->currentRequest->HasBeenSent()) { // The request was deferred (received "In Progress"). This is not a // timeout, but just time to retry. if (this->currentRequest->IsDeferred()) { this->currentRequest->SetDeferred(false); } // There is an outstanding request, which timed out while waiting for a // response. else if (!this->tcbContext->IncrementTxErrorCounter("RECONFIG timeout")) { // Timed out. The connection will close after processing the timers. return; } } else { // There is no outstanding request, but there is a prepared one. This means // that the receiver has previously responded "in progress", which resulted // in retrying the request (but with a new `reqSeqNbr`) after a while. } auto packet = this->tcbContext->CreatePacket(); AddReConfigChunk(packet.get()); this->tcbContext->SendPacket(packet.get()); baseTimeoutMs = this->tcbContext->GetCurrentRtoMs(); } void StreamResetHandler::OnBackoffTimer( BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) { MS_TRACE(); const auto maxRestarts = backoffTimer->GetMaxRestarts(); MS_DEBUG_TAG( sctp, "%s timer has expired [expìrations:%zu/%s]", backoffTimer->GetLabel().c_str(), backoffTimer->GetExpirationCount(), maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite"); if (backoffTimer == this->reConfigTimer.get()) { OnReConfigTimer(baseTimeoutMs, stop); } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/association/TransmissionControlBlock.cpp ================================================ #define MS_CLASS "RTC::SCTP::TransmissionControlBlock" // TODO: SCTP: COMMENT #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/association/TransmissionControlBlock.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/SCTP/packet/chunks/CookieAckChunk.hpp" #include "RTC/SCTP/packet/chunks/CookieEchoChunk.hpp" #include "RTC/SCTP/packet/chunks/DataChunk.hpp" #include "RTC/SCTP/packet/chunks/IDataChunk.hpp" #include namespace RTC { namespace SCTP { /* Static. */ alignas(4) static thread_local uint8_t PacketFactoryBuffer[65536]; /* Instance methods. */ TransmissionControlBlock::TransmissionControlBlock( AssociationListenerInterface& associationListener, const SctpOptions& sctpOptions, SharedInterface* shared, SendQueueInterface& sendQueue, PacketSender& packetSender, uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities, size_t maxPacketLength, std::function isAssociationEstablished) : associationListener(associationListener), sctpOptions(sctpOptions), shared(shared), packetSender(packetSender), localVerificationTag(localVerificationTag), remoteVerificationTag(remoteVerificationTag), localInitialTsn(localInitialTsn), remoteInitialTsn(remoteInitialTsn), remoteAdvertisedReceiverWindowCredit(remoteAdvertisedReceiverWindowCredit), tieTag(tieTag), negotiatedCapabilities(negotiatedCapabilities), maxPacketLength(maxPacketLength), isAssociationEstablished(std::move(isAssociationEstablished)), t3RtxTimer(this->shared->CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = this, .label = "sctp-t3-rtx", .baseTimeoutMs = sctpOptions.initialRtoMs, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL, .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs, .maxRestarts = std::nullopt })), delayedAckTimer(this->shared->CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = this, .label = "sctp-delayed-ack", .baseTimeoutMs = sctpOptions.delayedAckMaxTimeoutMs, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL, .maxBackoffTimeoutMs = std::nullopt, .maxRestarts = 0 })), rto(sctpOptions), txErrorCounter(sctpOptions), dataTracker(this->delayedAckTimer.get(), remoteInitialTsn), reassemblyQueue( sctpOptions.maxReceiverWindowBufferSize, negotiatedCapabilities.messageInterleaving), retransmissionQueue( this, this->associationListener, localInitialTsn, remoteAdvertisedReceiverWindowCredit, sendQueue, this->t3RtxTimer.get(), sctpOptions, negotiatedCapabilities.partialReliability, negotiatedCapabilities.messageInterleaving), streamResetHandler( this->associationListener, this->shared, this, std::addressof(this->dataTracker), std::addressof(this->reassemblyQueue), std::addressof(this->retransmissionQueue)), heartbeatHandler(this->associationListener, sctpOptions, this->shared, this) { MS_TRACE(); sendQueue.EnableMessageInterleaving(this->negotiatedCapabilities.messageInterleaving); } TransmissionControlBlock::~TransmissionControlBlock() { MS_TRACE(); } void TransmissionControlBlock::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " local verification tag: %" PRIu32, this->localVerificationTag); MS_DUMP_CLEAN(indentation, " remote verification tag: %" PRIu32, this->remoteVerificationTag); MS_DUMP_CLEAN(indentation, " local initial tsn: %" PRIu32, this->localInitialTsn); MS_DUMP_CLEAN(indentation, " remote initial tsn: %" PRIu32, this->remoteInitialTsn); MS_DUMP_CLEAN( indentation, " remote advertised receiver window credit: %" PRIu32, this->remoteAdvertisedReceiverWindowCredit); MS_DUMP_CLEAN(indentation, " tie-tag: %" PRIu64, this->tieTag); this->negotiatedCapabilities.Dump(indentation + 1); this->rto.Dump(indentation + 1); this->txErrorCounter.Dump(indentation + 1); MS_DUMP_CLEAN(indentation, ""); } void TransmissionControlBlock::ObserveRttMs(uint64_t rttMs) { MS_TRACE(); #if MS_LOG_DEV_LEVEL == 3 const auto prevRtoMs = this->rto.GetRtoMs(); #endif this->rto.ObserveRttMs(rttMs); MS_DEBUG_DEV( "new rtt:%" PRIu64 ", previous rto:%" PRIu64 ", new rto:%" PRIu64 ", srtt:%" PRIu64, rttMs, prevRtoMs, this->rto.GetRtoMs(), this->rto.GetSrttMs()); this->t3RtxTimer->SetBaseTimeoutMs(this->rto.GetRtoMs()); const uint64_t delayedAckTimeoutMs = std::min( static_cast(this->rto.GetRtoMs() * 0.5), this->sctpOptions.delayedAckMaxTimeoutMs); this->delayedAckTimer->SetBaseTimeoutMs(delayedAckTimeoutMs); } std::unique_ptr TransmissionControlBlock::CreatePacket() const { MS_TRACE(); return CreatePacketWithVerificationTag(this->remoteVerificationTag); } std::unique_ptr TransmissionControlBlock::CreatePacketWithVerificationTag( uint32_t verificationTag) const { MS_TRACE(); auto packet = std::unique_ptr{ Packet::Factory(PacketFactoryBuffer, this->maxPacketLength) }; packet->SetSourcePort(this->sctpOptions.sourcePort); packet->SetDestinationPort(this->sctpOptions.destinationPort); packet->SetVerificationTag(verificationTag); return packet; } bool TransmissionControlBlock::SendPacket(Packet* packet) { MS_TRACE(); return this->packetSender.SendPacket( packet, /*writeChecksum*/ !this->negotiatedCapabilities.zeroChecksum); } void TransmissionControlBlock::SetRemoteStateCookie(std::vector remoteStateCookie) { MS_TRACE(); this->remoteStateCookie = std::move(remoteStateCookie); } void TransmissionControlBlock::ClearRemoteStateCookie() { MS_TRACE(); this->remoteStateCookie.reset(); } void TransmissionControlBlock::MaySendSackChunk() { MS_TRACE(); if (!this->dataTracker.ShouldSendAck(/*alsoIfDelayed*/ false)) { return; } const auto packet = CreatePacket(); this->dataTracker.AddSackSelectiveAck(packet.get(), this->reassemblyQueue.GetRemainingBytes()); SendPacket(packet.get()); } void TransmissionControlBlock::MayAddForwardTsnChunk(Packet* packet, uint64_t nowMs) { MS_TRACE(); if (nowMs >= this->limitForwardTsnUntilMs && this->retransmissionQueue.ShouldSendForwardTsn(nowMs)) { if (this->negotiatedCapabilities.messageInterleaving) { this->retransmissionQueue.AddIForwardTsn(packet); } else { this->retransmissionQueue.AddForwardTsn(packet); } // https://datatracker.ietf.org/doc/html/rfc3758 // // "IMPLEMENTATION NOTE: An implementation may wish to limit the number // of duplicate FORWARD TSN chunks it sends by ... waiting a full RTT // before sending a duplicate FORWARD TSN." // "Any delay applied to the sending of FORWARD TSN chunk SHOULD NOT // exceed 200ms and MUST NOT exceed 500ms". this->limitForwardTsnUntilMs = nowMs + std::min(uint64_t{ 200 }, this->rto.GetSrttMs()); } } void TransmissionControlBlock::MaySendFastRetransmit() { MS_TRACE(); if (!this->retransmissionQueue.HasDataToBeFastRetransmitted()) { return; } // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4 // // "Determine how many of the earliest (i.e., lowest TSN) DATA chunks // marked for retransmission will fit into a single packet, subject to // constraint of the path MTU of the destination transport address to // which the packet is being sent. Call this value K. Retransmit those // K DATA chunks in a single packet. When a Fast Retransmit is being // performed, the sender SHOULD ignore the value of cwnd and SHOULD NOT // delay retransmission for this single packet." const auto packet = CreatePacket(); auto result = this->retransmissionQueue.GetChunksForFastRetransmit(packet->GetAvailableLength()); for (auto& [tsn, data] : result) { if (this->negotiatedCapabilities.messageInterleaving) { auto* iDataChunk = packet->BuildChunkInPlace(); iDataChunk->SetTsn(tsn); iDataChunk->SetUserData(std::move(data)); iDataChunk->Consolidate(); } else { auto* dataChunk = packet->BuildChunkInPlace(); dataChunk->SetTsn(tsn); dataChunk->SetUserData(std::move(data)); dataChunk->Consolidate(); } } SendPacket(packet.get()); } void TransmissionControlBlock::SendBufferedPackets(uint64_t nowMs, bool addCookieAckChunk) { MS_TRACE(); for (size_t packetIdx{ 0 }; packetIdx < this->sctpOptions.maxBurst; ++packetIdx) { const auto packet = CreatePacket(); // Only add control Chunks to the first Packet that is sent, if sending // multiple Packets in one go (as allowed by the congestion window). if (packetIdx == 0) { if (addCookieAckChunk) { MS_DEBUG_DEV("adding COOKIE_ACK Chunk to the Packet"); const auto* cookieAckChunk = packet->BuildChunkInPlace(); cookieAckChunk->Consolidate(); } if (this->remoteStateCookie.has_value()) { // https://datatracker.ietf.org/doc/html/rfc9260#section-5.1 // // "The COOKIE ECHO chunk can be bundled with any pending outbound // DATA chunks, but it MUST be the first chunk in the packet..." if (packet->GetChunksCount() > 0) { MS_THROW_ERROR( "Packet must have no Chunks [addCookieAckChunk:%s]", addCookieAckChunk ? "true" : "no"); } auto* cookieEchoChunk = packet->BuildChunkInPlace(); cookieEchoChunk->SetCookie( remoteStateCookie->data(), static_cast(remoteStateCookie->size())); cookieEchoChunk->Consolidate(); } // https://datatracker.ietf.org/doc/html/rfc9260#section-6 // // "Before an endpoint transmits a DATA chunk, if any received DATA // chunks have not been acknowledged (e.g., due to delayed ack), the // sender should create a SACK and bundle it with the outbound DATA // chunk, as long as the size of the final SCTP packet does not exceed // the current MTU." if (this->dataTracker.ShouldSendAck(/*alsoIfDelayed*/ true)) { this->dataTracker.AddSackSelectiveAck( packet.get(), this->reassemblyQueue.GetRemainingBytes()); } const uint64_t nowMs = this->shared->GetTimeMs(); MayAddForwardTsnChunk(packet.get(), nowMs); if (this->streamResetHandler.ShouldSendStreamResetRequest()) { this->streamResetHandler.AddStreamResetRequest(packet.get()); } } auto chunksToSend = this->retransmissionQueue.GetChunksToSend(nowMs, packet->GetAvailableLength()); if (!chunksToSend.empty()) { // https://datatracker.ietf.org/doc/html/rfc9260#section-8.3 // // Sending DATA means that the path is not idle, restart heartbeat // timer. this->heartbeatHandler.RestartTimer(); } const bool immediateAck = GetCwnd() < (this->sctpOptions.immediateSackUnderCwndMtus * this->sctpOptions.mtu); for (auto& [tsn, data] : chunksToSend) { if (this->negotiatedCapabilities.messageInterleaving) { auto* iDataChunk = packet->BuildChunkInPlace(); iDataChunk->SetTsn(tsn); iDataChunk->SetI(immediateAck); iDataChunk->SetUserData(std::move(data)); iDataChunk->Consolidate(); } else { auto* dataChunk = packet->BuildChunkInPlace(); dataChunk->SetTsn(tsn); dataChunk->SetI(immediateAck); dataChunk->SetUserData(std::move(data)); dataChunk->Consolidate(); } } // https://datatracker.ietf.org/doc/html/rfc9653#section-5.2 // // "When an end point sends a packet containing a COOKIE ECHO chunk, it // MUST include a correct CRC32c checksum in the packet containing the // COOKIE ECHO chunk." if (!this->packetSender.SendPacket( packet.get(), /*writeChecksum*/ !negotiatedCapabilities.zeroChecksum || this->remoteStateCookie.has_value())) { break; } // https://datatracker.ietf.org/doc/html/rfc9260#section-5.1 // // "until the COOKIE ACK is returned the sender MUST NOT send any // other packets to the peer." if (this->remoteStateCookie.has_value()) { break; } } } void TransmissionControlBlock::OnT3RtxTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) { MS_TRACE(); // In the COOKIE_ECHO state, let the T1-COOKIE timer trigger // retransmissions, to avoid having two timers doing that. if (this->remoteStateCookie.has_value()) { MS_DEBUG_DEV("not retransmitting as T1-cookie is active"); } else { if (IncrementTxErrorCounter("t3-rtx expired")) { this->retransmissionQueue.HandleT3RtxTimerExpiry(); const uint64_t nowMs = this->shared->GetTimeMs(); SendBufferedPackets(nowMs); } } } void TransmissionControlBlock::OnDelayedAckTimer(uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) { MS_TRACE(); this->dataTracker.HandleDelayedAckTimerExpiry(); MaySendSackChunk(); } void TransmissionControlBlock::OnBackoffTimer( BackoffTimerHandleInterface* backoffTimer, uint64_t& baseTimeoutMs, bool& stop) { MS_TRACE(); const auto maxRestarts = backoffTimer->GetMaxRestarts(); MS_DEBUG_TAG( sctp, "%s timer has expired [expìrations:%zu/%s]", backoffTimer->GetLabel().c_str(), backoffTimer->GetExpirationCount(), maxRestarts ? std::to_string(maxRestarts.value()).c_str() : "Infinite"); if (backoffTimer == this->t3RtxTimer.get()) { OnT3RtxTimer(baseTimeoutMs, stop); } else if (backoffTimer == this->delayedAckTimer.get()) { OnDelayedAckTimer(baseTimeoutMs, stop); } } void TransmissionControlBlock::OnRetransmissionQueueNewRttMs(uint64_t newRttMs) { MS_TRACE(); ObserveRttMs(newRttMs); } void TransmissionControlBlock::OnRetransmissionQueueClearRetransmissionCounter() { MS_TRACE(); this->txErrorCounter.Clear(); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/Chunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::Chunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/Chunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp" #include "RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.hpp" #include "RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.hpp" #include "RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp" #include "RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp" #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp" #include "RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp" #include "RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp" #include "RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp" #include "RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp" #include "RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp" #include "RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.hpp" #include "RTC/SCTP/packet/parameters/StateCookieParameter.hpp" #include "RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp" #include "RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp" #include "RTC/SCTP/packet/parameters/UnknownParameter.hpp" #include "RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.hpp" #include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp" namespace RTC { namespace SCTP { /* Class variables. */ // clang-format off const std::unordered_map Chunk::ChunkType2String = { { Chunk::ChunkType::DATA, "DATA" }, { Chunk::ChunkType::INIT, "INIT" }, { Chunk::ChunkType::INIT_ACK, "INIT_ACK" }, { Chunk::ChunkType::SACK, "SACK" }, { Chunk::ChunkType::HEARTBEAT_REQUEST, "HEARTBEAT_REQUEST" }, { Chunk::ChunkType::HEARTBEAT_ACK, "HEARTBEAT_ACK" }, { Chunk::ChunkType::ABORT, "ABORT" }, { Chunk::ChunkType::SHUTDOWN, "SHUTDOWN" }, { Chunk::ChunkType::SHUTDOWN_ACK, "SHUTDOWN_ACK" }, { Chunk::ChunkType::OPERATION_ERROR, "OPERATION_ERROR" }, { Chunk::ChunkType::COOKIE_ECHO, "COOKIE_ECHO" }, { Chunk::ChunkType::COOKIE_ACK, "COOKIE_ACK" }, { Chunk::ChunkType::ECNE, "ECNE" }, { Chunk::ChunkType::CWR, "CWR" }, { Chunk::ChunkType::SHUTDOWN_COMPLETE, "SHUTDOWN_COMPLETE" }, { Chunk::ChunkType::FORWARD_TSN, "FORWARD_TSN" }, { Chunk::ChunkType::RE_CONFIG, "RE_CONFIG" }, { Chunk::ChunkType::I_DATA, "I_DATA" }, { Chunk::ChunkType::I_FORWARD_TSN, "I_FORWARD_TSN" }, }; // clang-format on /* Class methods. */ bool Chunk::IsChunk( const uint8_t* buffer, size_t bufferLength, Chunk::ChunkType& chunkType, uint16_t& chunkLength, uint8_t& padding) { MS_TRACE(); if (!TLV::IsTLV(buffer, bufferLength, chunkLength, padding)) { return false; } chunkType = static_cast(buffer[0]); return true; } const std::string& Chunk::ChunkTypeToString(ChunkType chunkType) { MS_TRACE(); static const std::string Unknown("UNKNOWN"); auto it = Chunk::ChunkType2String.find(chunkType); if (it == Chunk::ChunkType2String.end()) { return Unknown; } return it->second; } /* Instance methods. */ Chunk::Chunk(uint8_t* buffer, size_t bufferLength) : TLV(buffer, bufferLength) { MS_TRACE(); } Chunk::~Chunk() { MS_TRACE(); // NOTE: Here we cannot check CanHaveParameters() or CanHaveErrorCauses() // because this is the destructor of Chunk so the subclass has been // already destroyed (its destructor runs first). for (const auto* parameter : this->parameters) { delete parameter; } for (const auto* errorCause : this->errorCauses) { delete errorCause; } } void Chunk::Serialize(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); const auto* previousBuffer = GetBuffer(); // Invoke the parent method to copy the whole buffer. Serializable::Serialize(buffer, bufferLength); if (CanHaveParameters()) { for (auto* parameter : this->parameters) { const size_t offset = parameter->GetBuffer() - previousBuffer; parameter->SoftSerialize(buffer + offset); } } if (CanHaveErrorCauses()) { for (auto* errorCause : this->errorCauses) { const size_t offset = errorCause->GetBuffer() - previousBuffer; errorCause->SoftSerialize(buffer + offset); } } } void Chunk::AddParameter(const Parameter* parameter) { MS_TRACE(); AssertCanHaveParameters(); AssertDoesNotNeedConsolidation(); const size_t previousLength = GetLength(); // This will update the total length and Length field of the Chunk. // NOTE: It may throw. AddItem(parameter); // Let's append the Parameter at the end of existing Parameters. auto* clonedParameter = parameter->Clone(const_cast(GetBuffer()) + previousLength, parameter->GetLength()); // Add the Parameter to the list. this->parameters.push_back(clonedParameter); } void Chunk::AddErrorCause(const ErrorCause* errorCause) { MS_TRACE(); AssertCanHaveErrorCauses(); AssertDoesNotNeedConsolidation(); const size_t previousLength = GetLength(); // This will update the total length and Length field of the Chunk. // NOTE: It may throw. AddItem(errorCause); // Let's append the Error Cause at the end of existing Error Causes. auto* clonedErrorCause = errorCause->Clone( const_cast(GetBuffer()) + previousLength, errorCause->GetLength()); this->errorCauses.push_back(clonedErrorCause); } void Chunk::DumpCommon(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN( indentation, " type: %" PRIu8 " (%s) (unknown: %s)", static_cast(GetType()), Chunk::ChunkTypeToString(GetType()).c_str(), HasUnknownType() ? "yes" : "no"); MS_DUMP_CLEAN( indentation, " flags: " MS_UINT8_TO_BINARY_PATTERN, MS_UINT8_TO_BINARY(GetFlags())); TLV::DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " needs consolidation of parameters or error causes: %s", NeedsConsolidation() ? "yes" : "no"); } void Chunk::DumpParameters(int indentation) const { MS_TRACE(); if (CanHaveParameters()) { MS_DUMP_CLEAN(indentation, " parameters count: %zu", GetParametersCount()); for (const auto* parameter : this->parameters) { parameter->Dump(indentation + 1); } } } void Chunk::DumpErrorCauses(int indentation) const { MS_TRACE(); if (CanHaveErrorCauses()) { MS_DUMP_CLEAN(indentation, " error causes count: %zu", GetErrorCausesCount()); for (const auto* errorCause : this->errorCauses) { errorCause->Dump(indentation + 1); } } } void Chunk::SoftSerialize(const uint8_t* buffer) { MS_TRACE(); const auto* previousBuffer = GetBuffer(); SetBuffer(const_cast(buffer)); if (CanHaveParameters()) { for (auto* parameter : this->parameters) { const size_t offset = parameter->GetBuffer() - previousBuffer; parameter->SoftSerialize(buffer + offset); } } if (CanHaveErrorCauses()) { for (auto* errorCause : this->errorCauses) { const size_t offset = errorCause->GetBuffer() - previousBuffer; errorCause->SoftSerialize(buffer + offset); } } } void Chunk::SoftCloneInto(Chunk* chunk) const { MS_TRACE(); // Soft clone Parameters into the given Chunk. if (CanHaveParameters()) { for (auto* parameter : this->parameters) { const size_t offset = parameter->GetBuffer() - GetBuffer(); auto* softClonedParameter = parameter->SoftClone(chunk->GetBuffer() + offset); chunk->parameters.push_back(softClonedParameter); } } // Soft clone Error Causes into the given Chunk. if (CanHaveErrorCauses()) { for (auto* errorCause : this->errorCauses) { const size_t offset = errorCause->GetBuffer() - GetBuffer(); auto* softClonedErrorCause = errorCause->SoftClone(chunk->GetBuffer() + offset); chunk->errorCauses.push_back(softClonedErrorCause); } } // Need to manually set Serializable length. chunk->SetLength(GetLength()); } void Chunk::InitializeHeader(ChunkType chunkType, uint8_t flags, uint16_t lengthFieldValue) { MS_TRACE(); SetType(chunkType); SetFlags(flags); InitializeTLVHeader(lengthFieldValue); } bool Chunk::ParseParameters() { MS_TRACE(); AssertCanHaveParameters(); // Here we assume that the Chunk buffer has been validated and // GetLength() returns the fixed minimum length of the specific Chunk // subclass, so GetBuffer() + GetLength() points to the beginning of the // potential Parameters. And of course we assume that a Chunk cannot have // both Parameters and Error Causes. auto* ptr = const_cast(GetBuffer()) + GetLength(); // Here we assume that the Chunk has been validated so Length field is // reliable. We want to be ready for Length field to include or not the // possible padding of the last Parameter (as per RFC recommendation). In // fact, we rely on parameter->GetLength() while parsing the buffer so we // want to provide each Parameter::StrictParse() call with a 4-bytes // padded buffer length. const auto* end = GetBuffer() + Utils::Byte::PadTo4Bytes(GetLengthField()); while (ptr < end) { // The remaining length in the given length is the potential buffer // length of the Parameter. const size_t parameterMaxBufferLength = end - ptr; // Here we must anticipate the type of each Parameter to use its // appropriate parser. Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter( ptr, parameterMaxBufferLength, parameterType, parameterLength, padding)) { MS_WARN_TAG(sctp, "not an SCTP Parameter"); return false; } Parameter* parameter{ nullptr }; // NOLINT(misc-const-correctness) switch (parameterType) { case Parameter::ParameterType::HEARTBEAT_INFO: { parameter = HeartbeatInfoParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::IPV4_ADDRESS: { parameter = IPv4AddressParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::IPV6_ADDRESS: { parameter = IPv6AddressParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::STATE_COOKIE: { parameter = StateCookieParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::UNRECOGNIZED_PARAMETER: { parameter = UnrecognizedParameterParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::COOKIE_PRESERVATIVE: { parameter = CookiePreservativeParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES: { parameter = SupportedAddressTypesParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::FORWARD_TSN_SUPPORTED: { parameter = ForwardTsnSupportedParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::SUPPORTED_EXTENSIONS: { parameter = SupportedExtensionsParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE: { parameter = ZeroChecksumAcceptableParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST: { parameter = OutgoingSsnResetRequestParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST: { parameter = IncomingSsnResetRequestParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::SSN_TSN_RESET_REQUEST: { parameter = SsnTsnResetRequestParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::RECONFIGURATION_RESPONSE: { parameter = ReconfigurationResponseParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST: { parameter = AddOutgoingStreamsRequestParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } case Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST: { parameter = AddIncomingStreamsRequestParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); break; } default: { parameter = UnknownParameter::ParseStrict( ptr, parameterLength + padding, parameterLength, padding); } } if (!parameter) { return false; } this->parameters.push_back(parameter); ptr += parameter->GetLength(); } if (ptr != end) { auto expectedLength = end - GetBuffer(); auto computedLength = ptr - GetBuffer(); MS_WARN_TAG( sctp, "computed length (%zu bytes) doesn't match the expected length (%zu bytes)", computedLength, expectedLength); return false; } return true; } bool Chunk::ParseErrorCauses() { MS_TRACE(); AssertCanHaveErrorCauses(); // Here we assume that the Chunk buffer has been validated and GetLength() // returns the fixed minimum length of the specific Chunk subclass, so // GetBuffer() + GetLength() points to the beginning of the potential // Error Causes. And of course we assume that a Chunk cannot have both // Parameters and Error Causes. auto* ptr = const_cast(GetBuffer()) + GetLength(); // Here we assume that the Chunk has been validated so Length field is // reliable. We want to be ready for Length field to include or not the // possible padding of the last Error Cause (as per RFCrecommendation). // In fact, we rely on errorCause->GetLength() while parsing the buffer // so we want to provide each ErrorCause::StrictParse() call with a // 4-bytes padded buffer length. const auto* end = GetBuffer() + Utils::Byte::PadTo4Bytes(GetLengthField()); while (ptr < end) { // The remaining length in the given length is the potential buffer // length of the Error Cause. const size_t errorCauseMaxBufferLength = end - ptr; // Here we must anticipate the type of each Error Cause to use its // appropriate parser. ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(ptr, errorCauseMaxBufferLength, causeCode, causeLength, padding)) { MS_WARN_TAG(sctp, "not an SCTP Error Cause"); return false; } ErrorCause* errorCause{ nullptr }; // NOLINT(misc-const-correctness) switch (causeCode) { case ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER: { errorCause = InvalidStreamIdentifierErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER: { errorCause = MissingMandatoryParameterErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::STALE_COOKIE: { errorCause = StaleCookieErrorCause::ParseStrict(ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE: { errorCause = OutOfResourceErrorCause::ParseStrict(ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS: { errorCause = UnresolvableAddressErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE: { errorCause = UnrecognizedChunkTypeErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER: { errorCause = InvalidMandatoryParameterErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS: { errorCause = UnrecognizedParametersErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::NO_USER_DATA: { errorCause = NoUserDataErrorCause::ParseStrict(ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN: { errorCause = CookieReceivedWhileShuttingDownErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES: { errorCause = RestartOfAnAssociationWithNewAddressesErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT: { errorCause = UserInitiatedAbortErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } case ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION: { errorCause = ProtocolViolationErrorCause::ParseStrict( ptr, causeLength + padding, causeLength, padding); break; } default: { errorCause = UnknownErrorCause::ParseStrict(ptr, causeLength + padding, causeLength, padding); } } if (!errorCause) { return false; } this->errorCauses.push_back(errorCause); ptr += errorCause->GetLength(); } if (ptr != end) { auto expectedLength = end - GetBuffer(); auto computedLength = ptr - GetBuffer(); MS_WARN_TAG( sctp, "computed length (%zu bytes) doesn't match the expected length (%zu bytes)", computedLength, expectedLength); return false; } return true; } void Chunk::HandleInPlaceParameter(Parameter* parameter) { MS_TRACE(); this->needsConsolidation = true; // When the application completes the Parameter it must call // `parameter->Consolidate()` and that will trigger this event. parameter->SetConsolidatedListener( [this, parameter]() { try { // Fix buffer length assigned to the Parameter. // NOTE: It may throw. parameter->SetBufferLength(parameter->GetLength()); // This will update the total length and Length field of the Chunk. // NOTE: It may throw. AddItem(parameter); // Add the Parameter to the list. this->parameters.push_back(parameter); this->needsConsolidation = false; } catch (const MediaSoupError& error) { this->needsConsolidation = false; throw; } }); } void Chunk::HandleInPlaceErrorCause(ErrorCause* errorCause) { MS_TRACE(); this->needsConsolidation = true; // When the application completes the Error Cause it must call // `errorCause->Consolidate()` and that will trigger this event. errorCause->SetConsolidatedListener( [this, errorCause]() { try { // Fix buffer length assigned to the Error Cause. errorCause->SetBufferLength(errorCause->GetLength()); // This will update the total length and Length field of the Chunk. // NOTE: It may throw. AddItem(errorCause); // Add the Error Cause to the list. this->errorCauses.push_back(errorCause); this->needsConsolidation = false; } catch (const MediaSoupError& error) { this->needsConsolidation = false; throw; } }); } void Chunk::AssertCanHaveParameters() const { MS_TRACE(); if (!CanHaveParameters()) { MS_THROW_ERROR("this Chunk class cannot have Parameters"); } } void Chunk::AssertCanHaveErrorCauses() const { MS_TRACE(); if (!CanHaveErrorCauses()) { MS_THROW_ERROR("this Chunk class cannot have Error Causes"); } } void Chunk::AssertDoesNotNeedConsolidation() const { MS_TRACE(); if (this->needsConsolidation) { MS_THROW_ERROR("Chunk needs consolidation of some ongoing Parameter or Error Cause"); } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/ErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::ErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/ErrorCause.hpp" #include "Logger.hpp" #include "Utils.hpp" namespace RTC { namespace SCTP { /* Class variables. */ // clang-format off const std::unordered_map ErrorCause::ErrorCauseCode2String = { { ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, "INVALID_STREAM_IDENTIFIER" }, { ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER, "MISSING_MANDATORY_PARAMETER" }, { ErrorCause::ErrorCauseCode::STALE_COOKIE, "STALE_COOKIE" }, { ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, "OUT_OF_RESOURCE" }, { ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS, "UNRESOLVABLE_ADDRESS" }, { ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, "UNRECOGNIZED_CHUNK_TYPE" }, { ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER, "INVALID_MANDATORY_PARAMETER" }, { ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS, "UNRECOGNIZED_PARAMETERS" }, { ErrorCause::ErrorCauseCode::NO_USER_DATA, "NO_USER_DATA" }, { ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN, "COOKIE_RECEIVED_WHILE_SHUTTING_DOWN" }, { ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES, "RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES" }, { ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT, "USER_INITIATED_ABORT" }, { ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION, "PROTOCOL_VIOLATION" }, }; // clang-format on /* Class methods. */ bool ErrorCause::IsErrorCause( const uint8_t* buffer, size_t bufferLength, ErrorCause::ErrorCauseCode& causeCode, uint16_t& causeLength, uint8_t& padding) { MS_TRACE(); if (!TLV::IsTLV(buffer, bufferLength, causeLength, padding)) { return false; } causeCode = static_cast(Utils::Byte::Get2Bytes(buffer, 0)); return true; } const std::string& ErrorCause::ErrorCauseCodeToString(ErrorCauseCode causeCode) { MS_TRACE(); static const std::string Unknown("UNKNOWN"); auto it = ErrorCause::ErrorCauseCode2String.find(causeCode); if (it == ErrorCause::ErrorCauseCode2String.end()) { return Unknown; } return it->second; } /* Instance methods. */ ErrorCause::ErrorCause(uint8_t* buffer, size_t bufferLength) : TLV(buffer, bufferLength) { MS_TRACE(); } ErrorCause::~ErrorCause() { MS_TRACE(); } void ErrorCause::SoftCloneInto(ErrorCause* errorCause) const { MS_TRACE(); // Need to manually set Serializable length. errorCause->SetLength(GetLength()); } void ErrorCause::DumpCommon(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN( indentation, " code: %" PRIu16 " (%s) (unknown: %s)", static_cast(GetCode()), ErrorCause::ErrorCauseCodeToString(GetCode()).c_str(), HasUnknownCode() ? "yes" : "no"); TLV::DumpCommon(indentation); } void ErrorCause::SoftSerialize(const uint8_t* buffer) { MS_TRACE(); SetBuffer(const_cast(buffer)); } void ErrorCause::InitializeHeader(ErrorCauseCode causeCode, uint16_t lengthFieldValue) { MS_TRACE(); SetCode(causeCode); InitializeTLVHeader(lengthFieldValue); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/Packet.cpp ================================================ #define MS_CLASS "RTC::SCTP::Packet" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/Packet.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp" #include "RTC/SCTP/packet/chunks/CookieAckChunk.hpp" #include "RTC/SCTP/packet/chunks/CookieEchoChunk.hpp" #include "RTC/SCTP/packet/chunks/DataChunk.hpp" #include "RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp" #include "RTC/SCTP/packet/chunks/IDataChunk.hpp" #include "RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/InitAckChunk.hpp" #include "RTC/SCTP/packet/chunks/InitChunk.hpp" #include "RTC/SCTP/packet/chunks/OperationErrorChunk.hpp" #include "RTC/SCTP/packet/chunks/ReConfigChunk.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownChunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp" #include "RTC/SCTP/packet/chunks/UnknownChunk.hpp" namespace RTC { namespace SCTP { /* Class methods. */ bool Packet::IsSctp(const uint8_t* /*buffer*/, size_t bufferLength) { return ( bufferLength >= Packet::CommonHeaderLength && Utils::Byte::IsPaddedTo4Bytes(bufferLength)); } Packet* Packet::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (!Packet::IsSctp(buffer, bufferLength)) { MS_WARN_TAG(sctp, "not an SCTP Packet"); return nullptr; } auto* packet = new Packet(const_cast(buffer), bufferLength); // Pointer that initially points to the given data buffer and is later // incremented to point to other parts of the Packet. const auto* ptr = buffer; // Move to chunks. ptr = packet->GetChunksPointer(); while (ptr < buffer + bufferLength) { // The remaining length in the buffer is the potential buffer length // of the Chunk. const size_t chunkMaxBufferLength = bufferLength - (ptr - buffer); // Here we must anticipate the type of each Chunk to use its appropriate // parser. Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(ptr, chunkMaxBufferLength, chunkType, chunkLength, padding)) { MS_WARN_TAG(sctp, "not an SCTP Chunk"); delete packet; return nullptr; } Chunk* chunk{ nullptr }; // NOLINT(misc-const-correctness) MS_DEBUG_DEV("parsing SCTP Chunk [ptr:%zu, type:%" PRIu8 "]", ptr - buffer, chunkType); switch (chunkType) { case Chunk::ChunkType::DATA: { chunk = DataChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::INIT: { chunk = InitChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::INIT_ACK: { chunk = InitAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::SACK: { chunk = SackChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::HEARTBEAT_REQUEST: { chunk = HeartbeatRequestChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::HEARTBEAT_ACK: { chunk = HeartbeatAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::ABORT: { chunk = AbortAssociationChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::SHUTDOWN: { chunk = ShutdownChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::SHUTDOWN_ACK: { chunk = ShutdownAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::OPERATION_ERROR: { chunk = OperationErrorChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::COOKIE_ECHO: { chunk = CookieEchoChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::COOKIE_ACK: { chunk = CookieAckChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::SHUTDOWN_COMPLETE: { chunk = ShutdownCompleteChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::FORWARD_TSN: { chunk = ForwardTsnChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::RE_CONFIG: { chunk = ReConfigChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::I_DATA: { chunk = IDataChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } case Chunk::ChunkType::I_FORWARD_TSN: { chunk = IForwardTsnChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); break; } default: { chunk = UnknownChunk::ParseStrict(ptr, chunkLength + padding, chunkLength, padding); } } if (!chunk) { delete packet; return nullptr; } packet->chunks.push_back(chunk); ptr += chunk->GetLength(); } const size_t computedLength = ptr - buffer; // Ensure computed length matches the total given buffer length. if (computedLength != bufferLength) { MS_WARN_TAG( sctp, "computed length (%zu bytes) != buffer length (%zu bytes)", computedLength, bufferLength); delete packet; return nullptr; } // It's mandatory to call SetLength() once we are done and we know the // exact length of the Packet. packet->SetLength(computedLength); return packet; } Packet* Packet::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); const size_t computedLength = Packet::CommonHeaderLength; // No space for common header. if (bufferLength < computedLength) { MS_THROW_TYPE_ERROR("no space for common header"); } auto* packet = new Packet(buffer, bufferLength); // Must initialize extra fields in the header. packet->SetSourcePort(0u); packet->SetDestinationPort(0u); packet->SetVerificationTag(0u); packet->SetChecksum(0u); // No need to invoke SetLength() since constructor invoked it with // minimum Packet length. return packet; } /* Instance methods. */ Packet::Packet(uint8_t* buffer, size_t bufferLength) : Serializable(buffer, bufferLength) { MS_TRACE(); SetLength(Packet::CommonHeaderLength); } Packet::~Packet() { MS_TRACE(); for (const auto* chunk : this->chunks) { delete chunk; } } void Packet::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " length: %zu (buffer length: %zu)", GetLength(), GetBufferLength()); MS_DUMP_CLEAN(indentation, " source port: %" PRIu16, GetSourcePort()); MS_DUMP_CLEAN(indentation, " destination port: %" PRIu16, GetDestinationPort()); MS_DUMP_CLEAN(indentation, " verification tag: %" PRIu32, GetVerificationTag()); MS_DUMP_CLEAN(indentation, " checksum: %" PRIu32, GetChecksum()); MS_DUMP_CLEAN(indentation, " chunks count: %zu", GetChunksCount()); MS_DUMP_CLEAN( indentation, " needs consolidation of chunks: %s", NeedsConsolidation() ? "yes" : "no"); for (const auto* chunk : this->chunks) { chunk->Dump(indentation + 1); } MS_DUMP_CLEAN(indentation, ""); } void Packet::Serialize(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); const auto* previousBuffer = GetBuffer(); // Invoke the parent method to copy the whole buffer. Serializable::Serialize(buffer, bufferLength); for (auto* chunk : this->chunks) { const size_t offset = chunk->GetBuffer() - previousBuffer; chunk->SoftSerialize(buffer + offset); } } Packet* Packet::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedPacket = new Packet(buffer, bufferLength); Serializable::CloneInto(clonedPacket); // Soft clone Packet Chunks into the given cloned Packet. for (auto* chunk : this->chunks) { const size_t offset = chunk->GetBuffer() - GetBuffer(); auto* softClonedChunk = chunk->SoftClone(buffer + offset); clonedPacket->chunks.push_back(softClonedChunk); } return clonedPacket; } void Packet::SetSourcePort(uint16_t sourcePort) { MS_TRACE(); GetHeaderPointer()->sourcePort = htons(sourcePort); } void Packet::SetDestinationPort(uint16_t destinationPort) { MS_TRACE(); GetHeaderPointer()->destinationPort = htons(destinationPort); } void Packet::SetVerificationTag(uint32_t verificationTag) { MS_TRACE(); GetHeaderPointer()->verificationTag = htonl(verificationTag); } void Packet::SetChecksum(uint32_t checksum) { MS_TRACE(); GetHeaderPointer()->checksum = htonl(checksum); } void Packet::AddChunk(const Chunk* chunk) { MS_TRACE(); AssertDoesNotNeedConsolidation(); const size_t length = GetLength() + chunk->GetLength(); // Let's append the Chunk at the end of existing Chunks. auto* clonedChunk = chunk->Clone(const_cast(GetBuffer()) + GetLength(), chunk->GetLength()); // Update Serializable length. try { SetLength(length); } catch (const MediaSoupError& error) { delete clonedChunk; throw; } this->chunks.push_back(clonedChunk); } void Packet::WriteCRC32cChecksum() { MS_TRACE(); SetChecksum(0u); auto crc32c = Utils::Crypto::GetCRC32c(GetBuffer(), GetLength()); SetChecksum(crc32c); } bool Packet::ValidateCRC32cChecksum() const { MS_TRACE(); auto crc32c = GetChecksum(); // NOTE: Cannot use SetChecksum() because its a `const` method. GetHeaderPointer()->checksum = 0; auto computedCrc32c = Utils::Crypto::GetCRC32c(GetBuffer(), GetLength()); GetHeaderPointer()->checksum = htonl(crc32c); return computedCrc32c == crc32c; } void Packet::HandleInPlaceChunk(Chunk* chunk) { MS_TRACE(); this->needsConsolidation = true; // When the application completes the Chunk it must call // `chunk->Consolidate()` and that will trigger this event. chunk->SetConsolidatedListener( [this, chunk]() { try { if (chunk->NeedsConsolidation()) { MS_THROW_ERROR("ongoing Chunk needs consolidation"); } // Fix buffer length assigned to the Chunk. chunk->SetBufferLength(chunk->GetLength()); // Update Packet length. // NOTE: This will throw if there is no enough space in the Packet // buffer. SetLength(GetLength() + chunk->GetLength()); // Add the Chunk to the list. this->chunks.push_back(chunk); this->needsConsolidation = false; } catch (const MediaSoupError& error) { this->needsConsolidation = false; throw; } }); } void Packet::AssertDoesNotNeedConsolidation() const { MS_TRACE(); if (this->needsConsolidation) { MS_THROW_ERROR("Packet needs consolidation of some ongoing Chunk"); } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/Parameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::Parameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/Parameter.hpp" #include "Logger.hpp" #include "Utils.hpp" namespace RTC { namespace SCTP { /* Class variables. */ // clang-format off const std::unordered_map Parameter::ParameterType2String = { { Parameter::ParameterType::HEARTBEAT_INFO, "HEARTBEAT_INFO" }, { Parameter::ParameterType::IPV4_ADDRESS, "IPV4_ADDRESS" }, { Parameter::ParameterType::IPV6_ADDRESS, "IPV6_ADDRESS" }, { Parameter::ParameterType::STATE_COOKIE, "STATE_COOKIE" }, { Parameter::ParameterType::UNRECOGNIZED_PARAMETER, "UNRECOGNIZED_PARAMETER" }, { Parameter::ParameterType::COOKIE_PRESERVATIVE, "COOKIE_PRESERVATIVE" }, { Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, "SUPPORTED_ADDRESS_TYPES" }, { Parameter::ParameterType::FORWARD_TSN_SUPPORTED, "FORWARD_TSN_SUPPORTED" }, { Parameter::ParameterType::SUPPORTED_EXTENSIONS, "SUPPORTED_EXTENSIONS" }, { Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, "OUTGOING_SSN_RESET_REQUEST" }, { Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, "INCOMING_SSN_RESET_REQUEST" }, { Parameter::ParameterType::SSN_TSN_RESET_REQUEST, "SSN_TSN_RESET_REQUEST" }, { Parameter::ParameterType::RECONFIGURATION_RESPONSE, "RECONFIGURATION_RESPONSE" }, { Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST, "ADD_OUTGOING_STREAMS_REQUEST" }, { Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST, "ADD_INCOMING_STREAMS_REQUEST" }, { Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE, "ZERO_CHECKSUM_ACCEPTABLE" }, }; // clang-format on /* Class methods. */ bool Parameter::IsParameter( const uint8_t* buffer, size_t bufferLength, Parameter::ParameterType& parameterType, uint16_t& parameterLength, uint8_t& padding) { MS_TRACE(); if (!TLV::IsTLV(buffer, bufferLength, parameterLength, padding)) { return false; } parameterType = static_cast(Utils::Byte::Get2Bytes(buffer, 0)); return true; } const std::string& Parameter::ParameterTypeToString(ParameterType parameterType) { MS_TRACE(); static const std::string Unknown("UNKNOWN"); auto it = Parameter::ParameterType2String.find(parameterType); if (it == Parameter::ParameterType2String.end()) { return Unknown; } return it->second; } /* Instance methods. */ Parameter::Parameter(uint8_t* buffer, size_t bufferLength) : TLV(buffer, bufferLength) { MS_TRACE(); } Parameter::~Parameter() { MS_TRACE(); } void Parameter::SoftCloneInto(Parameter* parameter) const { MS_TRACE(); // Need to manually set Serializable length. parameter->SetLength(GetLength()); } void Parameter::DumpCommon(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN( indentation, " type: %" PRIu16 " (%s) (unknown: %s)", static_cast(GetType()), Parameter::ParameterTypeToString(GetType()).c_str(), HasUnknownType() ? "yes" : "no"); TLV::DumpCommon(indentation); } void Parameter::SoftSerialize(const uint8_t* buffer) { MS_TRACE(); SetBuffer(const_cast(buffer)); } void Parameter::InitializeHeader(ParameterType parameterType, uint16_t lengthFieldValue) { MS_TRACE(); SetType(parameterType); InitializeTLVHeader(lengthFieldValue); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/TLV.cpp ================================================ #define MS_CLASS "RTC::SCTP::TLV" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/TLV.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::memmove() #include // std::numeric_limits namespace RTC { namespace SCTP { /* Class methods. */ bool TLV::IsTLV(const uint8_t* buffer, size_t bufferLength, uint16_t& itemLength, uint8_t& padding) { MS_TRACE(); if (bufferLength < TLV::TLVHeaderLength) { MS_WARN_TAG(sctp, "no space for Header [bufferLength:%zu]", bufferLength); return false; } itemLength = Utils::Byte::Get2Bytes(buffer, 2); if (itemLength < TLV::TLVHeaderLength) { MS_WARN_TAG( sctp, "Length field must have value greater or equal than %zu", TLV::TLVHeaderLength); return false; } // Item total length must be multiple of 4 bytes and must include padding // bytes despite item Length field does not include padding. // NOTE: We must cast to size_t, otherwise a maximum item Length value of // 65535 would generate a padded length of 0 bytes! const size_t paddedItemLength = Utils::Byte::PadTo4Bytes(size_t{ itemLength }); if (bufferLength < paddedItemLength) { MS_WARN_TAG( sctp, "no space for 4-byte padded announced Length [paddedItemLength:%zu, bufferLength:%zu]", paddedItemLength, bufferLength); return false; } padding = paddedItemLength - itemLength; return true; } /* Instance methods. */ TLV::TLV(uint8_t* buffer, size_t bufferLength) : Serializable(buffer, bufferLength) { MS_TRACE(); } TLV::~TLV() { MS_TRACE(); } void TLV::DumpCommon(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN( indentation, " length field: %" PRIu16 " (padding: %zu, buffer length: %zu)", GetLengthField(), GetLength() - GetLengthField(), GetBufferLength()); } void TLV::InitializeTLVHeader(uint16_t lengthFieldValue) { MS_TRACE(); SetLengthField(lengthFieldValue); } void TLV::SetLengthField(size_t lengthField) { MS_TRACE(); if (lengthField > std::numeric_limits::max()) { MS_THROW_TYPE_ERROR("lengthField (%zu bytes) cannot be greater than 65535", lengthField); } Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 2, lengthField); } void TLV::SetVariableLengthValue(const uint8_t* value, size_t valueLength) { MS_TRACE(); MS_ASSERT(value != nullptr || valueLength == 0, "value cannot be nullptr if valueLength is > 0"); // NOTE: This can throw. SetVariableLengthValueLength(valueLength); if (value) { std::memmove(GetVariableLengthValuePointer(), value, valueLength); } } void TLV::SetVariableLengthValueLength(size_t valueLength) { MS_TRACE(); auto previousLength = GetLength(); auto previousLengthField = GetLengthField(); auto previousValueLength = GetVariableLengthValueLength(); auto newNotPaddedLength = size_t{ previousLengthField } - size_t{ previousValueLength } + valueLength; auto newPaddedLength = Utils::Byte::PadTo4Bytes(newNotPaddedLength); try { // Let's call SetLength() on parent with the new computed length. // NOTE: If there is no space in the buffer for it, it will throw. SetLength(newPaddedLength); // Update Length field. // NOTE: This will throw if computed value is too big. SetLengthField(newNotPaddedLength); } catch (const MediaSoupError& error) { // Rollback. SetLength(previousLength); SetLengthField(previousLengthField); throw; } // Fill padding bytes with zero. FillPadding(newPaddedLength - newNotPaddedLength); } void TLV::AddItem(const TLV* item) { MS_TRACE(); auto previousLength = GetLength(); auto previousLengthField = GetLengthField(); try { // Update length. // NOTE: This will throw if there is no enough space in the buffer. SetLength(previousLength + item->GetLength()); // Update Length field. // NOTE: This will throw if computed Length field value is too big. SetLengthField(previousLength + item->GetLengthField()); } catch (const MediaSoupError& error) { // Rollback. SetLength(previousLength); SetLengthField(previousLengthField); throw; } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/UserData.cpp ================================================ #define MS_CLASS "RTC::SCTP::UserData" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/UserData.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { UserData::UserData( uint16_t streamId, uint16_t ssn, uint32_t mid, uint32_t fsn, uint32_t ppid, std::vector payload, bool isBeginning, bool isEnd, bool isUnordered) : streamId(streamId), ssn(ssn), mid(mid), fsn(fsn), ppid(ppid), payload(std::move(payload)), isBeginning(isBeginning), isEnd(isEnd), isUnordered(isUnordered) { MS_TRACE(); } UserData::~UserData() { MS_TRACE(); } void UserData::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " stream id: %" PRIu16, GetStreamId()); MS_DUMP_CLEAN(indentation, " ssn: %" PRIu16, GetStreamSequenceNumber()); MS_DUMP_CLEAN(indentation, " mid: %" PRIu32, GetMessageId()); MS_DUMP_CLEAN(indentation, " fsn: %" PRIu32, GetFragmentSequenceNumber()); MS_DUMP_CLEAN(indentation, " ppid: %" PRIu32, GetPayloadProtocolId()); MS_DUMP_CLEAN(indentation, " payload length: %zu", GetPayloadLength()); MS_DUMP_CLEAN(indentation, " is beginning: %s", IsBeginning() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, " is end: %s", IsEnd() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, " is unordered: %s", IsUnordered() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/AbortAssociationChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::AbortAssociationChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ AbortAssociationChunk* AbortAssociationChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::ABORT) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return AbortAssociationChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } AbortAssociationChunk* AbortAssociationChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Chunk::ChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new AbortAssociationChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::ABORT, 0, Chunk::ChunkHeaderLength); // No need to invoke SetLength() since constructor invoked it with // minimum AbortAssociationChunk length. return chunk; } AbortAssociationChunk* AbortAssociationChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); auto* chunk = new AbortAssociationChunk(const_cast(buffer), bufferLength); // Parse Error Causes. if (!chunk->ParseErrorCauses()) { MS_WARN_DEV("failed to parse Error Causes"); delete chunk; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ AbortAssociationChunk::AbortAssociationChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } AbortAssociationChunk::~AbortAssociationChunk() { MS_TRACE(); } void AbortAssociationChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " flag T: %" PRIu8, GetT()); DumpErrorCauses(indentation); MS_DUMP_CLEAN(indentation, ""); } AbortAssociationChunk* AbortAssociationChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new AbortAssociationChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void AbortAssociationChunk::SetT(bool flag) { MS_TRACE(); SetBit0(flag); } AbortAssociationChunk* AbortAssociationChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new AbortAssociationChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/CookieAckChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::CookieAckChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/CookieAckChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ CookieAckChunk* CookieAckChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::COOKIE_ACK) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return CookieAckChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } CookieAckChunk* CookieAckChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Chunk::ChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new CookieAckChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::COOKIE_ACK, 0, Chunk::ChunkHeaderLength); // No need to invoke SetLength() since constructor invoked it with // CookieAckChunk fixed length. return chunk; } CookieAckChunk* CookieAckChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t /*padding*/) { MS_TRACE(); if (chunkLength != Chunk::ChunkHeaderLength) { MS_WARN_TAG(sctp, "CookieAckChunk Length field must be %zu", Chunk::ChunkHeaderLength); return nullptr; } auto* chunk = new CookieAckChunk(const_cast(buffer), bufferLength); return chunk; } /* Instance methods. */ CookieAckChunk::CookieAckChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } CookieAckChunk::~CookieAckChunk() { MS_TRACE(); } void CookieAckChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, ""); } CookieAckChunk* CookieAckChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new CookieAckChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } CookieAckChunk* CookieAckChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new CookieAckChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/CookieEchoChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::CookieEchoChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/CookieEchoChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ CookieEchoChunk* CookieEchoChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::COOKIE_ECHO) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return CookieEchoChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } CookieEchoChunk* CookieEchoChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Chunk::ChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new CookieEchoChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::COOKIE_ECHO, 0, Chunk::ChunkHeaderLength); // No need to invoke SetLength() since constructor invoked it with // minimum CookieEchoChunk length. return chunk; } CookieEchoChunk* CookieEchoChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); auto* chunk = new CookieEchoChunk(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ CookieEchoChunk::CookieEchoChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } CookieEchoChunk::~CookieEchoChunk() { MS_TRACE(); } void CookieEchoChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " cookie length: %" PRIu16 " (has cookie: %s)", GetCookieLength(), HasCookie() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } CookieEchoChunk* CookieEchoChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new CookieEchoChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void CookieEchoChunk::SetCookie(const uint8_t* cookie, uint16_t cookieLength) { MS_TRACE(); SetVariableLengthValue(cookie, cookieLength); } CookieEchoChunk* CookieEchoChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new CookieEchoChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/DataChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::DataChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/DataChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /* Class methods. */ DataChunk* DataChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::DATA) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return DataChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } DataChunk* DataChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < DataChunk::DataChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new DataChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::DATA, 0, DataChunk::DataChunkHeaderLength); // Must also initialize extra fields in the header. chunk->SetTsn(0); chunk->SetStreamId(0); chunk->SetStreamSequenceNumber(0); chunk->SetPayloadProtocolId(0); // No need to invoke SetLength() since constructor invoked it with // minimum DataChunk length. return chunk; } DataChunk* DataChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); if (chunkLength < DataChunk::DataChunkHeaderLength) { MS_WARN_TAG( sctp, "DataChunk Length field must be equal or greater than %zu", DataChunk::DataChunkHeaderLength); return nullptr; } auto* chunk = new DataChunk(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ DataChunk::DataChunk(uint8_t* buffer, size_t bufferLength) : AnyDataChunk(buffer, bufferLength) { MS_TRACE(); SetLength(DataChunk::DataChunkHeaderLength); } DataChunk::~DataChunk() { MS_TRACE(); } void DataChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " flag I: %" PRIu8, GetI()); MS_DUMP_CLEAN(indentation, " flag U: %" PRIu8, GetU()); MS_DUMP_CLEAN(indentation, " flag B: %" PRIu8, GetB()); MS_DUMP_CLEAN(indentation, " flag E: %" PRIu8, GetE()); MS_DUMP_CLEAN(indentation, " tsn: %" PRIu32, GetTsn()); MS_DUMP_CLEAN(indentation, " stream id: %" PRIu16, GetStreamId()); MS_DUMP_CLEAN(indentation, " stream sequence number: %" PRIu16, GetStreamSequenceNumber()); MS_DUMP_CLEAN(indentation, " payload protocol id (PPID): %" PRIu32, GetPayloadProtocolId()); MS_DUMP_CLEAN( indentation, " user data length: %" PRIu16 " (has user data: %s)", GetUserDataPayloadLength(), HasUserDataPayload() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } DataChunk* DataChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new DataChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void DataChunk::SetI(bool flag) { MS_TRACE(); SetBit3(flag); } void DataChunk::SetU(bool flag) { MS_TRACE(); SetBit2(flag); } void DataChunk::SetB(bool flag) { MS_TRACE(); SetBit1(flag); } void DataChunk::SetE(bool flag) { MS_TRACE(); SetBit0(flag); } void DataChunk::SetTsn(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } void DataChunk::SetStreamId(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 8, value); } void DataChunk::SetStreamSequenceNumber(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 10, value); } void DataChunk::SetPayloadProtocolId(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 12, value); } void DataChunk::SetUserDataPayload(const uint8_t* userDataPayload, uint16_t userDataPayloadLength) { MS_TRACE(); SetVariableLengthValue(userDataPayload, userDataPayloadLength); } void DataChunk::SetUserData(UserData userData) { MS_TRACE(); SetStreamId(userData.GetStreamId()); SetStreamSequenceNumber(userData.GetStreamSequenceNumber()); SetPayloadProtocolId(userData.GetPayloadProtocolId()); SetB(userData.IsBeginning()); SetE(userData.IsEnd()); SetU(userData.IsUnordered()); const auto payload = std::move(userData).ReleasePayload(); SetUserDataPayload(payload.data(), payload.size()); } DataChunk* DataChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new DataChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/ForwardTsnChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::ForwardTsnChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /* Class methods. */ ForwardTsnChunk* ForwardTsnChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::FORWARD_TSN) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return ForwardTsnChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } ForwardTsnChunk* ForwardTsnChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ForwardTsnChunk::ForwardTsnChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new ForwardTsnChunk(buffer, bufferLength); chunk->InitializeHeader( Chunk::ChunkType::FORWARD_TSN, 0, ForwardTsnChunk::ForwardTsnChunkHeaderLength); // Must also initialize extra fields in the header. chunk->SetNewCumulativeTsn(0); // No need to invoke SetLength() since constructor invoked it with // minimum ForwardTsnChunk length. return chunk; } ForwardTsnChunk* ForwardTsnChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); if (chunkLength < ForwardTsnChunk::ForwardTsnChunkHeaderLength) { MS_WARN_TAG( sctp, "ForwardTsnChunk Length field must be equal or greater than %zu", ForwardTsnChunk::ForwardTsnChunkHeaderLength); return nullptr; } // Here we must validate that length is multiple of 4. if (chunkLength % 4 != 0) { MS_WARN_TAG(sctp, "wrong length (not multiple of 4)"); return nullptr; } auto* chunk = new ForwardTsnChunk(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ ForwardTsnChunk::ForwardTsnChunk(uint8_t* buffer, size_t bufferLength) : AnyForwardTsnChunk(buffer, bufferLength) { MS_TRACE(); SetLength(ForwardTsnChunk::ForwardTsnChunkHeaderLength); } ForwardTsnChunk::~ForwardTsnChunk() { MS_TRACE(); } void ForwardTsnChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " new cumulative tsn: %" PRIu32, GetNewCumulativeTsn()); MS_DUMP_CLEAN(indentation, " number of skipped streams: %" PRIu16, GetNumberOfSkippedStreams()); MS_DUMP_CLEAN(indentation, " skipped streams:"); for (const auto& skippedStream : GetSkippedStreams()) { MS_DUMP_CLEAN( indentation, " - stream id: %" PRIu16 ", ssn:%" PRIu16, skippedStream.streamId, skippedStream.ssn); } MS_DUMP_CLEAN(indentation, ""); } ForwardTsnChunk* ForwardTsnChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new ForwardTsnChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void ForwardTsnChunk::SetNewCumulativeTsn(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } std::vector ForwardTsnChunk::GetSkippedStreams() const { MS_TRACE(); std::vector skippedStreams; const uint16_t numSkippedStreams = GetNumberOfSkippedStreams(); skippedStreams.reserve(numSkippedStreams); for (uint16_t idx{ 0 }; idx < numSkippedStreams; ++idx) { skippedStreams.emplace_back(GetSkippedStreamIdAt(idx), GetStreamSequenceAt(idx)); } return skippedStreams; } void ForwardTsnChunk::AddSkippedStream(const AnyForwardTsnChunk::SkippedStream& skippedStream) { MS_TRACE(); auto previousVariableLengthValueLength = GetVariableLengthValueLength(); // NOTE: This may throw. SetVariableLengthValueLength(previousVariableLengthValueLength + 4); // Add the new stream and stream sequence. Utils::Byte::Set2Bytes( GetVariableLengthValuePointer(), previousVariableLengthValueLength, skippedStream.streamId); Utils::Byte::Set2Bytes( GetVariableLengthValuePointer(), previousVariableLengthValueLength + 2, skippedStream.ssn); } ForwardTsnChunk* ForwardTsnChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new ForwardTsnChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/HeartbeatAckChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::HeartbeatAckChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ HeartbeatAckChunk* HeartbeatAckChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::HEARTBEAT_ACK) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return HeartbeatAckChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } HeartbeatAckChunk* HeartbeatAckChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Chunk::ChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new HeartbeatAckChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::HEARTBEAT_ACK, 0, Chunk::ChunkHeaderLength); // No need to invoke SetLength() since constructor invoked it with // minimum HeartbeatAckChunk length. return chunk; } HeartbeatAckChunk* HeartbeatAckChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); auto* chunk = new HeartbeatAckChunk(const_cast(buffer), bufferLength); // Parse Parameters. if (!chunk->ParseParameters()) { MS_WARN_DEV("failed to parse Parameters"); delete chunk; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ HeartbeatAckChunk::HeartbeatAckChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } HeartbeatAckChunk::~HeartbeatAckChunk() { MS_TRACE(); } void HeartbeatAckChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); DumpParameters(indentation); MS_DUMP_CLEAN(indentation, ""); } HeartbeatAckChunk* HeartbeatAckChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new HeartbeatAckChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } HeartbeatAckChunk* HeartbeatAckChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new HeartbeatAckChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/HeartbeatRequestChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::HeartbeatRequestChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ HeartbeatRequestChunk* HeartbeatRequestChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::HEARTBEAT_REQUEST) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return HeartbeatRequestChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } HeartbeatRequestChunk* HeartbeatRequestChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Chunk::ChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new HeartbeatRequestChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::HEARTBEAT_REQUEST, 0, Chunk::ChunkHeaderLength); // No need to invoke SetLength() since constructor invoked it with // minimum HeartbeatRequestChunk length. return chunk; } HeartbeatRequestChunk* HeartbeatRequestChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); auto* chunk = new HeartbeatRequestChunk(const_cast(buffer), bufferLength); // Parse Parameters. if (!chunk->ParseParameters()) { MS_WARN_DEV("failed to parse Parameters"); delete chunk; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ HeartbeatRequestChunk::HeartbeatRequestChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } HeartbeatRequestChunk::~HeartbeatRequestChunk() { MS_TRACE(); } void HeartbeatRequestChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); DumpParameters(indentation); MS_DUMP_CLEAN(indentation, ""); } HeartbeatRequestChunk* HeartbeatRequestChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new HeartbeatRequestChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } HeartbeatRequestChunk* HeartbeatRequestChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new HeartbeatRequestChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/IDataChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::IDataChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/IDataChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /* Class methods. */ IDataChunk* IDataChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::I_DATA) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return IDataChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } IDataChunk* IDataChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < IDataChunk::IDataChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new IDataChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::I_DATA, 0, IDataChunk::IDataChunkHeaderLength); // Must also initialize extra fields in the header. chunk->SetTsn(0); chunk->SetStreamId(0); chunk->SetReserved(); chunk->SetMessageId(0); // NOTE: BitB is not set so we must set FSN to 0 rather than setting PPID. chunk->SetFragmentSequenceNumber(0); // No need to invoke SetLength() since constructor invoked it with // minimum IDataChunk length. return chunk; } IDataChunk* IDataChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); if (chunkLength < IDataChunk::IDataChunkHeaderLength) { MS_WARN_TAG( sctp, "IDataChunk Length field must be equal or greater than %zu", IDataChunk::IDataChunkHeaderLength); return nullptr; } auto* chunk = new IDataChunk(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ IDataChunk::IDataChunk(uint8_t* buffer, size_t bufferLength) : AnyDataChunk(buffer, bufferLength) { MS_TRACE(); SetLength(IDataChunk::IDataChunkHeaderLength); } IDataChunk::~IDataChunk() { MS_TRACE(); } void IDataChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " flag I: %" PRIu8, GetI()); MS_DUMP_CLEAN(indentation, " flag U: %" PRIu8, GetU()); MS_DUMP_CLEAN(indentation, " flag B: %" PRIu8, GetB()); MS_DUMP_CLEAN(indentation, " flag E: %" PRIu8, GetE()); MS_DUMP_CLEAN(indentation, " tsn: %" PRIu32, GetTsn()); MS_DUMP_CLEAN(indentation, " stream id: %" PRIu16, GetStreamId()); MS_DUMP_CLEAN(indentation, " message id: %" PRIu32, GetMessageId()); if (GetB()) { MS_DUMP_CLEAN(indentation, " payload protocol id (PPID): %" PRIu32, GetPayloadProtocolId()); } else { MS_DUMP_CLEAN( indentation, " fragment sequence number (FSN): %" PRIu32, GetFragmentSequenceNumber()); } MS_DUMP_CLEAN( indentation, " user data length: %" PRIu16 " (has user data: %s)", GetUserDataPayloadLength(), HasUserDataPayload() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } IDataChunk* IDataChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new IDataChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void IDataChunk::SetI(bool flag) { MS_TRACE(); SetBit3(flag); } void IDataChunk::SetU(bool flag) { MS_TRACE(); SetBit2(flag); } void IDataChunk::SetB(bool flag) { MS_TRACE(); SetBit1(flag); } void IDataChunk::SetE(bool flag) { MS_TRACE(); SetBit0(flag); } void IDataChunk::SetTsn(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } void IDataChunk::SetStreamId(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 8, value); } void IDataChunk::SetMessageId(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 12, value); } void IDataChunk::SetPayloadProtocolId(uint32_t value) { MS_TRACE(); if (!GetB()) { MS_THROW_ERROR("cannot set payload protocol id (PPID) if bit B is not set"); } Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 16, value); } void IDataChunk::SetFragmentSequenceNumber(uint32_t value) { MS_TRACE(); if (GetB()) { MS_THROW_ERROR("cannot set payload protocol id (PPID) if bit B is set"); } Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 16, value); } void IDataChunk::SetUserDataPayload(const uint8_t* userDataPayload, uint16_t userDataPayloadLength) { MS_TRACE(); SetVariableLengthValue(userDataPayload, userDataPayloadLength); } void IDataChunk::SetUserData(UserData userData) { MS_TRACE(); SetStreamId(userData.GetStreamId()); SetMessageId(userData.GetMessageId()); SetB(userData.IsBeginning()); SetE(userData.IsEnd()); SetU(userData.IsUnordered()); if (GetB()) { SetPayloadProtocolId(userData.GetPayloadProtocolId()); } else { SetFragmentSequenceNumber(userData.GetFragmentSequenceNumber()); } const auto payload = std::move(userData).ReleasePayload(); SetUserDataPayload(payload.data(), payload.size()); } IDataChunk* IDataChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new IDataChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } void IDataChunk::SetReserved() { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 10, 0); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/IForwardTsnChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::IForwardTsnChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /* Class methods. */ IForwardTsnChunk* IForwardTsnChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::I_FORWARD_TSN) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return IForwardTsnChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } IForwardTsnChunk* IForwardTsnChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < IForwardTsnChunk::IForwardTsnChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new IForwardTsnChunk(buffer, bufferLength); chunk->InitializeHeader( Chunk::ChunkType::I_FORWARD_TSN, 0, IForwardTsnChunk::IForwardTsnChunkHeaderLength); // Must also initialize extra fields in the header. chunk->SetNewCumulativeTsn(0); // No need to invoke SetLength() since constructor invoked it with // minimum IForwardTsnChunk length. return chunk; } IForwardTsnChunk* IForwardTsnChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); if (chunkLength < IForwardTsnChunk::IForwardTsnChunkHeaderLength) { MS_WARN_TAG( sctp, "IForwardTsnChunk Length field must be equal or greater than %zu", IForwardTsnChunk::IForwardTsnChunkHeaderLength); return nullptr; } // Here we must validate that length is multiple of 8. if (chunkLength % 8 != 0) { MS_WARN_TAG(sctp, "wrong length (not multiple of 4)"); return nullptr; } auto* chunk = new IForwardTsnChunk(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ IForwardTsnChunk::IForwardTsnChunk(uint8_t* buffer, size_t bufferLength) : AnyForwardTsnChunk(buffer, bufferLength) { MS_TRACE(); SetLength(IForwardTsnChunk::IForwardTsnChunkHeaderLength); } IForwardTsnChunk::~IForwardTsnChunk() { MS_TRACE(); } void IForwardTsnChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " new cumulative tsn: %" PRIu32, GetNewCumulativeTsn()); MS_DUMP_CLEAN(indentation, " number of skipped streams: %" PRIu16, GetNumberOfSkippedStreams()); MS_DUMP_CLEAN(indentation, " skipped streams:"); for (auto& skippedStream : GetSkippedStreams()) { MS_DUMP_CLEAN( indentation, " - unordered:%s, stream id: %" PRIu16 ", mid:%" PRIu32, skippedStream.unordered ? "yes" : "no", skippedStream.streamId, skippedStream.mid); } MS_DUMP_CLEAN(indentation, ""); } IForwardTsnChunk* IForwardTsnChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new IForwardTsnChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void IForwardTsnChunk::SetNewCumulativeTsn(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } std::vector IForwardTsnChunk::GetSkippedStreams() const { MS_TRACE(); std::vector skippedStreams; const uint16_t numSkippedStreams = GetNumberOfSkippedStreams(); skippedStreams.reserve(numSkippedStreams); for (uint16_t idx{ 0 }; idx < numSkippedStreams; ++idx) { skippedStreams.emplace_back( GetUFlagAt(idx), GetSkippedStreamIdAt(idx), GetMessageIdentifierAt(idx)); } return skippedStreams; } void IForwardTsnChunk::AddSkippedStream(const AnyForwardTsnChunk::SkippedStream& skippedStream) { MS_TRACE(); auto previousVariableLengthValueLength = GetVariableLengthValueLength(); // NOTE: This may throw. SetVariableLengthValueLength(previousVariableLengthValueLength + 8); // Add the new stream, flag U and message identifier. Utils::Byte::Set2Bytes( GetVariableLengthValuePointer(), previousVariableLengthValueLength, skippedStream.streamId); Utils::Byte::Set2Bytes( GetVariableLengthValuePointer(), previousVariableLengthValueLength + 2, skippedStream.unordered); Utils::Byte::Set4Bytes( GetVariableLengthValuePointer(), previousVariableLengthValueLength + 4, skippedStream.mid); } IForwardTsnChunk* IForwardTsnChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new IForwardTsnChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/InitAckChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::InitAckChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/InitAckChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /* Class methods. */ InitAckChunk* InitAckChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::INIT_ACK) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return InitAckChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } InitAckChunk* InitAckChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < InitAckChunk::InitAckChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new InitAckChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::INIT_ACK, 0, InitAckChunk::InitAckChunkHeaderLength); // Must also initialize extra fields in the header. chunk->SetInitiateTag(0); chunk->SetAdvertisedReceiverWindowCredit(0); chunk->SetNumberOfOutboundStreams(0); chunk->SetNumberOfInboundStreams(0); chunk->SetInitialTsn(0); // No need to invoke SetLength() since constructor invoked it with // minimum InitAckChunk length. return chunk; } InitAckChunk* InitAckChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); if (chunkLength < InitAckChunk::InitAckChunkHeaderLength) { MS_WARN_TAG( sctp, "InitAckChunk Length field must be equal or greater than %zu", InitAckChunk::InitAckChunkHeaderLength); return nullptr; } auto* chunk = new InitAckChunk(const_cast(buffer), bufferLength); // Parse Parameters. if (!chunk->ParseParameters()) { MS_WARN_DEV("failed to parse Parameters"); delete chunk; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ InitAckChunk::InitAckChunk(uint8_t* buffer, size_t bufferLength) : AnyInitChunk(buffer, bufferLength) { MS_TRACE(); SetLength(InitAckChunk::InitAckChunkHeaderLength); } InitAckChunk::~InitAckChunk() { MS_TRACE(); } void InitAckChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " initiate tag: %" PRIu32, GetInitiateTag()); MS_DUMP_CLEAN( indentation, " advertised receiver window credit: %" PRIu32, GetAdvertisedReceiverWindowCredit()); MS_DUMP_CLEAN( indentation, " number of outbound streams: %" PRIu16, GetNumberOfOutboundStreams()); MS_DUMP_CLEAN(indentation, " number of inbound streams: %" PRIu16, GetNumberOfInboundStreams()); MS_DUMP_CLEAN(indentation, " initial tsn: %" PRIu32, GetInitialTsn()); DumpParameters(indentation); MS_DUMP_CLEAN(indentation, ""); } InitAckChunk* InitAckChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new InitAckChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void InitAckChunk::SetInitiateTag(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } void InitAckChunk::SetAdvertisedReceiverWindowCredit(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 8, value); } void InitAckChunk::SetNumberOfOutboundStreams(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 12, value); } void InitAckChunk::SetNumberOfInboundStreams(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 14, value); } void InitAckChunk::SetInitialTsn(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 16, value); } InitAckChunk* InitAckChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new InitAckChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/InitChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::InitChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/InitChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/SCTP/packet/Chunk.hpp" namespace RTC { namespace SCTP { /* Class methods. */ InitChunk* InitChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::INIT) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return InitChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } InitChunk* InitChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < InitChunk::InitChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new InitChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::INIT, 0, InitChunk::InitChunkHeaderLength); // Must also initialize extra fields in the header. chunk->SetInitiateTag(0); chunk->SetAdvertisedReceiverWindowCredit(0); chunk->SetNumberOfOutboundStreams(0); chunk->SetNumberOfInboundStreams(0); chunk->SetInitialTsn(0); // No need to invoke SetLength() since constructor invoked it with // minimum InitChunk length. return chunk; } InitChunk* InitChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); if (chunkLength < InitChunk::InitChunkHeaderLength) { MS_WARN_TAG( sctp, "InitChunk Length field must be equal or greater than %zu", InitChunk::InitChunkHeaderLength); return nullptr; } auto* chunk = new InitChunk(const_cast(buffer), bufferLength); // Parse Parameters. if (!chunk->ParseParameters()) { MS_WARN_DEV("failed to parse Parameters"); delete chunk; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ InitChunk::InitChunk(uint8_t* buffer, size_t bufferLength) : AnyInitChunk(buffer, bufferLength) { MS_TRACE(); SetLength(InitChunk::InitChunkHeaderLength); } InitChunk::~InitChunk() { MS_TRACE(); } void InitChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " initiate tag: %" PRIu32, GetInitiateTag()); MS_DUMP_CLEAN( indentation, " advertised receiver window credit: %" PRIu32, GetAdvertisedReceiverWindowCredit()); MS_DUMP_CLEAN( indentation, " number of outbound streams: %" PRIu16, GetNumberOfOutboundStreams()); MS_DUMP_CLEAN(indentation, " number of inbound streams: %" PRIu16, GetNumberOfInboundStreams()); MS_DUMP_CLEAN(indentation, " initial tsn: %" PRIu32, GetInitialTsn()); DumpParameters(indentation); MS_DUMP_CLEAN(indentation, ""); } InitChunk* InitChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new InitChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void InitChunk::SetInitiateTag(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } void InitChunk::SetAdvertisedReceiverWindowCredit(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 8, value); } void InitChunk::SetNumberOfOutboundStreams(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 12, value); } void InitChunk::SetNumberOfInboundStreams(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 14, value); } void InitChunk::SetInitialTsn(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 16, value); } InitChunk* InitChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new InitChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/OperationErrorChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::OperationErrorChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/OperationErrorChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ OperationErrorChunk* OperationErrorChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::OPERATION_ERROR) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return OperationErrorChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } OperationErrorChunk* OperationErrorChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Chunk::ChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new OperationErrorChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::OPERATION_ERROR, 0, Chunk::ChunkHeaderLength); // No need to invoke SetLength() since constructor invoked it with // minimum OperationErrorChunk length. return chunk; } OperationErrorChunk* OperationErrorChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); auto* chunk = new OperationErrorChunk(const_cast(buffer), bufferLength); // Parse Error Causes. if (!chunk->ParseErrorCauses()) { MS_WARN_DEV("failed to parse Error Causes"); delete chunk; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ OperationErrorChunk::OperationErrorChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } OperationErrorChunk::~OperationErrorChunk() { MS_TRACE(); } void OperationErrorChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); DumpErrorCauses(indentation); MS_DUMP_CLEAN(indentation, ""); } OperationErrorChunk* OperationErrorChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new OperationErrorChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } OperationErrorChunk* OperationErrorChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new OperationErrorChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/ReConfigChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::ReConfigChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/ReConfigChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ ReConfigChunk* ReConfigChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::RE_CONFIG) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return ReConfigChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } ReConfigChunk* ReConfigChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Chunk::ChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new ReConfigChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::RE_CONFIG, 0, Chunk::ChunkHeaderLength); // No need to invoke SetLength() since constructor invoked it with // minimum ReConfigChunk length. return chunk; } ReConfigChunk* ReConfigChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); auto* chunk = new ReConfigChunk(const_cast(buffer), bufferLength); // Parse Parameters. if (!chunk->ParseParameters()) { MS_WARN_DEV("failed to parse Parameters"); delete chunk; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ ReConfigChunk::ReConfigChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } ReConfigChunk::~ReConfigChunk() { MS_TRACE(); } void ReConfigChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); DumpParameters(indentation); MS_DUMP_CLEAN(indentation, ""); } ReConfigChunk* ReConfigChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new ReConfigChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } ReConfigChunk* ReConfigChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new ReConfigChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/SackChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::SackChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::memmove() #include namespace RTC { namespace SCTP { /* Class methods. */ SackChunk* SackChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::SACK) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return SackChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } SackChunk* SackChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < SackChunk::SackChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new SackChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::SACK, 0, SackChunk::SackChunkHeaderLength); // Must also initialize extra fields in the header. chunk->SetCumulativeTsnAck(0); chunk->SetAdvertisedReceiverWindowCredit(0); chunk->SetNumberOfGapAckBlocks(0); chunk->SetNumberOfDuplicateTsns(0); // No need to invoke SetLength() since constructor invoked it with // minimum SackChunk length. return chunk; } SackChunk* SackChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); if (chunkLength < SackChunk::SackChunkHeaderLength) { MS_WARN_TAG( sctp, "SackChunk Length field must be equal or greater than %zu", SackChunk::SackChunkHeaderLength); return nullptr; } auto* chunk = new SackChunk(const_cast(buffer), bufferLength); // In this Chunk we must validate that some fields have correct values. if ( (chunk->GetNumberOfGapAckBlocks() * 4) + (chunk->GetNumberOfDuplicateTsns() * 4) != chunkLength - SackChunk::SackChunkHeaderLength) { MS_WARN_TAG( sctp, "wrong values in Number of Gap Ack Blocks and/or Number of Duplicate TSNs fields"); delete chunk; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ SackChunk::SackChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(SackChunk::SackChunkHeaderLength); } SackChunk::~SackChunk() { MS_TRACE(); } void SackChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " cumulative tsn ack: %" PRIu32, GetCumulativeTsnAck()); MS_DUMP_CLEAN( indentation, " advertised receiver window credit: %" PRIu32, GetAdvertisedReceiverWindowCredit()); MS_DUMP_CLEAN(indentation, " validated gap ack blocks:"); for (const auto& gapAckBlock : GetValidatedGapAckBlocks()) { MS_DUMP_CLEAN( indentation, " - start: %" PRIu16 ", end:%" PRIu16, gapAckBlock.start, gapAckBlock.end); } MS_DUMP_CLEAN(indentation, " duplicate tsns:"); for (const uint32_t duplicateTsn : GetDuplicateTsns()) { MS_DUMP_CLEAN(indentation, " - tsn: %" PRIu32, duplicateTsn); } MS_DUMP_CLEAN(indentation, ""); } SackChunk* SackChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new SackChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void SackChunk::SetCumulativeTsnAck(uint32_t tsn) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, tsn); } void SackChunk::SetAdvertisedReceiverWindowCredit(uint32_t aRwnd) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 8, aRwnd); } std::vector SackChunk::GetGapAckBlocks() const { MS_TRACE(); const uint16_t numberOfGapAckBlocks = GetNumberOfGapAckBlocks(); std::vector gapAckBlocks; gapAckBlocks.reserve(numberOfGapAckBlocks); for (uint16_t idx{ 0 }; idx < numberOfGapAckBlocks; ++idx) { gapAckBlocks.emplace_back(GetAckBlockStartAt(idx), GetAckBlockEndAt(idx)); } return gapAckBlocks; } std::vector SackChunk::GetValidatedGapAckBlocks() const { MS_TRACE(); const uint16_t numberOfGapAckBlocks = GetNumberOfGapAckBlocks(); std::vector gapAckBlocks; gapAckBlocks.reserve(numberOfGapAckBlocks); if (ValidateGapAckBlocks()) { for (uint16_t idx{ 0 }; idx < numberOfGapAckBlocks; ++idx) { gapAckBlocks.emplace_back(GetAckBlockStartAt(idx), GetAckBlockEndAt(idx)); } return gapAckBlocks; } // First: Only keep blocks that are sane. for (uint16_t idx{ 0 }; idx < numberOfGapAckBlocks; ++idx) { const uint16_t start = GetAckBlockStartAt(idx); const uint16_t end = GetAckBlockEndAt(idx); if (end > start) { gapAckBlocks.emplace_back(start, end); } } // Not more than at most one remaining? Exit early. if (gapAckBlocks.size() == 1) { return gapAckBlocks; } // Sort the intervals by their start value, to aid in the merging below. std::ranges::sort(gapAckBlocks, {}, &SackChunk::GapAckBlock::start); // Merge overlapping ranges. size_t writeIdx{ 0 }; for (size_t readIdx{ 1 }; readIdx < gapAckBlocks.size(); ++readIdx) { if (gapAckBlocks[writeIdx].end + 1 >= gapAckBlocks[readIdx].start) { gapAckBlocks[writeIdx].end = std::max(gapAckBlocks[writeIdx].end, gapAckBlocks[readIdx].end); } else { ++writeIdx; gapAckBlocks[writeIdx] = gapAckBlocks[readIdx]; } } gapAckBlocks.resize(writeIdx + 1); return gapAckBlocks; } std::vector SackChunk::GetDuplicateTsns() const { MS_TRACE(); const uint32_t numberOfDuplicateTsns = GetNumberOfDuplicateTsns(); std::vector duplicateTsns; duplicateTsns.reserve(numberOfDuplicateTsns); for (uint32_t idx{ 0 }; idx < numberOfDuplicateTsns; ++idx) { duplicateTsns.emplace_back(GetDuplicateTsnAt(idx)); } return duplicateTsns; } void SackChunk::AddAckBlock(uint16_t start, uint16_t end) { MS_TRACE(); // NOTE: This may throw. SetVariableLengthValueLength(GetVariableLengthValueLength() + 4); // Must move duplicate TSNs down. std::memmove( GetDuplicateTsnsPointer() + 4, GetDuplicateTsnsPointer(), GetNumberOfDuplicateTsns() * 4); // Add the new ack block. Utils::Byte::Set2Bytes(GetAckBlocksPointer(), GetNumberOfGapAckBlocks() * 4, start); Utils::Byte::Set2Bytes(GetAckBlocksPointer(), (GetNumberOfGapAckBlocks() * 4) + 2, end); // Update the counter field. // NOTE: Do this after moving bytes. SetNumberOfGapAckBlocks(GetNumberOfGapAckBlocks() + 1); } void SackChunk::AddDuplicateTsn(uint32_t tsn) { MS_TRACE(); // NOTE: This may throw. SetVariableLengthValueLength(GetVariableLengthValueLength() + 4); // Add the new duplicate TSN. Utils::Byte::Set4Bytes(GetDuplicateTsnsPointer(), GetNumberOfDuplicateTsns() * 4, tsn); // Update the counter field. // NOTE: Do this after moving bytes. SetNumberOfDuplicateTsns(GetNumberOfDuplicateTsns() + 1); } SackChunk* SackChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new SackChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } void SackChunk::SetNumberOfGapAckBlocks(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 12, value); } void SackChunk::SetNumberOfDuplicateTsns(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 14, value); } bool SackChunk::ValidateGapAckBlocks() const { MS_TRACE(); const uint16_t numberOfGapAckBlocks = GetNumberOfGapAckBlocks(); if (numberOfGapAckBlocks == 0) { return true; } // Ensure that gap-ack-blocks are sorted, has an "end" that is not before // "start" and are non-overlapping and non-adjacent. uint16_t prevEnd{ 0 }; for (uint16_t idx{ 0 }; idx < numberOfGapAckBlocks; ++idx) { const uint16_t start = GetAckBlockStartAt(idx); const uint16_t end = GetAckBlockEndAt(idx); if (end < start) { return false; } else if (start <= (prevEnd + 1)) { return false; } prevEnd = end; } return true; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/ShutdownAckChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::ShutdownAckChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ ShutdownAckChunk* ShutdownAckChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::SHUTDOWN_ACK) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return ShutdownAckChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } ShutdownAckChunk* ShutdownAckChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Chunk::ChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new ShutdownAckChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::SHUTDOWN_ACK, 0, Chunk::ChunkHeaderLength); // No need to invoke SetLength() since constructor invoked it with // ShutdownAckChunk fixed length. return chunk; } ShutdownAckChunk* ShutdownAckChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t /*padding*/) { MS_TRACE(); if (chunkLength != Chunk::ChunkHeaderLength) { MS_WARN_TAG(sctp, "ShutdownAckChunk Length field must be %zu", Chunk::ChunkHeaderLength); return nullptr; } auto* chunk = new ShutdownAckChunk(const_cast(buffer), bufferLength); return chunk; } /* Instance methods. */ ShutdownAckChunk::ShutdownAckChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } ShutdownAckChunk::~ShutdownAckChunk() { MS_TRACE(); } void ShutdownAckChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, ""); } ShutdownAckChunk* ShutdownAckChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new ShutdownAckChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } ShutdownAckChunk* ShutdownAckChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new ShutdownAckChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/ShutdownChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::ShutdownChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/ShutdownChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ ShutdownChunk* ShutdownChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::SHUTDOWN) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return ShutdownChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } ShutdownChunk* ShutdownChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ShutdownChunk::ShutdownChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new ShutdownChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::SHUTDOWN, 0, ShutdownChunk::ShutdownChunkHeaderLength); // Must also initialize extra fields in the header. chunk->SetCumulativeTsnAck(0); // No need to invoke SetLength() since constructor invoked it with // ShutdownChunk fixed length. return chunk; } ShutdownChunk* ShutdownChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t /*padding*/) { MS_TRACE(); if (chunkLength != ShutdownChunk::ShutdownChunkHeaderLength) { MS_WARN_TAG( sctp, "ShutdownChunk Length field must be %zu", ShutdownChunk::ShutdownChunkHeaderLength); return nullptr; } auto* chunk = new ShutdownChunk(const_cast(buffer), bufferLength); return chunk; } /* Instance methods. */ ShutdownChunk::ShutdownChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(ShutdownChunk::ShutdownChunkHeaderLength); } ShutdownChunk::~ShutdownChunk() { MS_TRACE(); } void ShutdownChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " cumulative tsn ack : %" PRIu32, GetCumulativeTsnAck()); MS_DUMP_CLEAN(indentation, ""); } ShutdownChunk* ShutdownChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new ShutdownChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void ShutdownChunk::SetCumulativeTsnAck(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } ShutdownChunk* ShutdownChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new ShutdownChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/ShutdownCompleteChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::ShutdownCompleteChunk" #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ ShutdownCompleteChunk* ShutdownCompleteChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } if (chunkType != Chunk::ChunkType::SHUTDOWN_COMPLETE) { MS_WARN_DEV("invalid Chunk type"); return nullptr; } return ShutdownCompleteChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } ShutdownCompleteChunk* ShutdownCompleteChunk::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Chunk::ChunkHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* chunk = new ShutdownCompleteChunk(buffer, bufferLength); chunk->InitializeHeader(Chunk::ChunkType::SHUTDOWN_COMPLETE, 0, Chunk::ChunkHeaderLength); // No need to invoke SetLength() since constructor invoked it with // ShutdownCompleteChunk fixed length. return chunk; } ShutdownCompleteChunk* ShutdownCompleteChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t /*padding*/) { MS_TRACE(); if (chunkLength != Chunk::ChunkHeaderLength) { MS_WARN_TAG(sctp, "ShutdownCompleteChunk Length field must be %zu", Chunk::ChunkHeaderLength); return nullptr; } auto* chunk = new ShutdownCompleteChunk(const_cast(buffer), bufferLength); return chunk; } /* Instance methods. */ ShutdownCompleteChunk::ShutdownCompleteChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } ShutdownCompleteChunk::~ShutdownCompleteChunk() { MS_TRACE(); } void ShutdownCompleteChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " flag T: %" PRIu8, GetT()); MS_DUMP_CLEAN(indentation, ""); } ShutdownCompleteChunk* ShutdownCompleteChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new ShutdownCompleteChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } void ShutdownCompleteChunk::SetT(bool flag) { MS_TRACE(); SetBit0(flag); } ShutdownCompleteChunk* ShutdownCompleteChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new ShutdownCompleteChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/chunks/UnknownChunk.cpp ================================================ #define MS_CLASS "RTC::SCTP::UnknownChunk" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/chunks/UnknownChunk.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { /* Class methods. */ UnknownChunk* UnknownChunk::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Chunk::ChunkType chunkType; uint16_t chunkLength; uint8_t padding; if (!Chunk::IsChunk(buffer, bufferLength, chunkType, chunkLength, padding)) { return nullptr; } return UnknownChunk::ParseStrict(buffer, bufferLength, chunkLength, padding); } UnknownChunk* UnknownChunk::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t chunkLength, uint8_t padding) { MS_TRACE(); auto* chunk = new UnknownChunk(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. chunk->SetLength(chunkLength + padding); return chunk; } /* Instance methods. */ UnknownChunk::UnknownChunk(uint8_t* buffer, size_t bufferLength) : Chunk(buffer, bufferLength) { MS_TRACE(); SetLength(Chunk::ChunkHeaderLength); } UnknownChunk::~UnknownChunk() { MS_TRACE(); } void UnknownChunk::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " unknown value length: %" PRIu16 " (has unknown value: %s)", GetUnknownValueLength(), HasUnknownValue() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } UnknownChunk* UnknownChunk::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedChunk = new UnknownChunk(buffer, bufferLength); CloneInto(clonedChunk); SoftCloneInto(clonedChunk); return clonedChunk; } UnknownChunk* UnknownChunk::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedChunk = new UnknownChunk(const_cast(buffer), GetLength()); SoftCloneInto(softClonedChunk); return softClonedChunk; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::CookieReceivedWhileShuttingDownErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ CookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return CookieReceivedWhileShuttingDownErrorCause::ParseStrict( buffer, bufferLength, causeLength, padding); } CookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ErrorCause::ErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new CookieReceivedWhileShuttingDownErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN, ErrorCause::ErrorCauseHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } CookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/) { MS_TRACE(); if (causeLength != ErrorCause::ErrorCauseHeaderLength) { MS_WARN_TAG( sctp, "CookieReceivedWhileShuttingDownErrorCause Length field must be %zu", ErrorCause::ErrorCauseHeaderLength); return nullptr; } auto* errorCause = new CookieReceivedWhileShuttingDownErrorCause(const_cast(buffer), bufferLength); return errorCause; } /* Instance methods. */ CookieReceivedWhileShuttingDownErrorCause::CookieReceivedWhileShuttingDownErrorCause( uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } CookieReceivedWhileShuttingDownErrorCause::~CookieReceivedWhileShuttingDownErrorCause() { MS_TRACE(); } void CookieReceivedWhileShuttingDownErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, ""); } CookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new CookieReceivedWhileShuttingDownErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } CookieReceivedWhileShuttingDownErrorCause* CookieReceivedWhileShuttingDownErrorCause::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new CookieReceivedWhileShuttingDownErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::InvalidMandatoryParameterErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ InvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return InvalidMandatoryParameterErrorCause::ParseStrict( buffer, bufferLength, causeLength, padding); } InvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ErrorCause::ErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new InvalidMandatoryParameterErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER, ErrorCause::ErrorCauseHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } InvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/) { MS_TRACE(); if (causeLength != ErrorCause::ErrorCauseHeaderLength) { MS_WARN_TAG( sctp, "InvalidMandatoryParameterErrorCause Length field must be %zu", ErrorCause::ErrorCauseHeaderLength); return nullptr; } auto* errorCause = new InvalidMandatoryParameterErrorCause(const_cast(buffer), bufferLength); return errorCause; } /* Instance methods. */ InvalidMandatoryParameterErrorCause::InvalidMandatoryParameterErrorCause( uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } InvalidMandatoryParameterErrorCause::~InvalidMandatoryParameterErrorCause() { MS_TRACE(); } void InvalidMandatoryParameterErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, ""); } InvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new InvalidMandatoryParameterErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } InvalidMandatoryParameterErrorCause* InvalidMandatoryParameterErrorCause::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new InvalidMandatoryParameterErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::InvalidStreamIdentifierErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include namespace RTC { namespace SCTP { /* Class methods. */ InvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return InvalidStreamIdentifierErrorCause::ParseStrict( buffer, bufferLength, causeLength, padding); } InvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new InvalidStreamIdentifierErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength); // Initialize header extra fields. errorCause->SetStreamIdentifier(0); errorCause->SetReserved(); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } InvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/) { MS_TRACE(); if (causeLength != InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength) { MS_WARN_TAG( sctp, "InvalidStreamIdentifierErrorCause Length field must be %zu", InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength); return nullptr; } auto* errorCause = new InvalidStreamIdentifierErrorCause(const_cast(buffer), bufferLength); return errorCause; } /* Instance methods. */ InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCause( uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(InvalidStreamIdentifierErrorCause::InvalidStreamIdentifierErrorCauseHeaderLength); } InvalidStreamIdentifierErrorCause::~InvalidStreamIdentifierErrorCause() { MS_TRACE(); } void InvalidStreamIdentifierErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " stream identifier: %" PRIu16, GetStreamIdentifier()); MS_DUMP_CLEAN(indentation, ""); } InvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new InvalidStreamIdentifierErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void InvalidStreamIdentifierErrorCause::SetStreamIdentifier(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 4, value); } InvalidStreamIdentifierErrorCause* InvalidStreamIdentifierErrorCause::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new InvalidStreamIdentifierErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } const std::string InvalidStreamIdentifierErrorCause::ContentToString() const { MS_TRACE(); return "stream:" + std::to_string(GetStreamIdentifier()); } void InvalidStreamIdentifierErrorCause::SetReserved() { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 6, 0); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::MissingMandatoryParameterErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::ostringstream #include namespace RTC { namespace SCTP { /* Class methods. */ MissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return MissingMandatoryParameterErrorCause::ParseStrict( buffer, bufferLength, causeLength, padding); } MissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new MissingMandatoryParameterErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER, MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength); // Initialize extra fields. errorCause->SetNumberOfMissingParameters(0); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } MissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding) { MS_TRACE(); if (causeLength < MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength) { MS_WARN_TAG( sctp, "MissingMandatoryParameterErrorCause Length field must be equal or greater than %zu", MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength); return nullptr; } auto* errorCause = new MissingMandatoryParameterErrorCause(const_cast(buffer), bufferLength); // In this Chunk we must validate that some fields have correct values. if ( (errorCause->GetNumberOfMissingParameters() * 2) != causeLength - MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength) { MS_WARN_TAG(sctp, "wrong values in Number of Missing Parameters field"); delete errorCause; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. errorCause->SetLength(causeLength + padding); return errorCause; } /* Instance methods. */ MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCause( uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(MissingMandatoryParameterErrorCause::MissingMandatoryParameterErrorCauseHeaderLength); } MissingMandatoryParameterErrorCause::~MissingMandatoryParameterErrorCause() { MS_TRACE(); } void MissingMandatoryParameterErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " number of missing parameters: %" PRIu32, GetNumberOfMissingParameters()); for (uint32_t idx{ 0 }; idx < GetNumberOfMissingParameters(); ++idx) { const auto parameterType = GetMissingParameterTypeAt(idx); MS_DUMP_CLEAN( indentation, " - idx: %" PRIu32 ", parameter type: %s (%" PRIu16 ")", idx, Parameter::ParameterTypeToString(parameterType).c_str(), static_cast(parameterType)); } MS_DUMP_CLEAN(indentation, ""); } MissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new MissingMandatoryParameterErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void MissingMandatoryParameterErrorCause::AddMissingParameterType( Parameter::ParameterType parameterType) { MS_TRACE(); // NOTE: This may throw. SetVariableLengthValueLength(GetVariableLengthValueLength() + 2); // Add the new missing mandatory parameter type. Utils::Byte::Set2Bytes( GetVariableLengthValuePointer(), GetNumberOfMissingParameters() * 2, static_cast(parameterType)); // Update the counter field. SetNumberOfMissingParameters(GetNumberOfMissingParameters() + 1); } MissingMandatoryParameterErrorCause* MissingMandatoryParameterErrorCause::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new MissingMandatoryParameterErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } const std::string MissingMandatoryParameterErrorCause::ContentToString() const { MS_TRACE(); std::ostringstream missingParameterTypesOss; bool firstParameterType = true; for (uint32_t idx{ 0 }; idx < GetNumberOfMissingParameters(); ++idx) { const auto parameterType = GetMissingParameterTypeAt(idx); missingParameterTypesOss << (firstParameterType ? "" : ", ") << RTC::SCTP::Parameter::ParameterTypeToString(parameterType).c_str() << " (" << std::to_string(static_cast(parameterType)) << ")"; firstParameterType = false; } return "types:[" + missingParameterTypesOss.str() + "]"; } void MissingMandatoryParameterErrorCause::SetNumberOfMissingParameters(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::NoUserDataErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include namespace RTC { namespace SCTP { /* Class methods. */ NoUserDataErrorCause* NoUserDataErrorCause::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::NO_USER_DATA) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return NoUserDataErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding); } NoUserDataErrorCause* NoUserDataErrorCause::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new NoUserDataErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::NO_USER_DATA, NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength); // Initialize value. errorCause->SetTsn(0); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } NoUserDataErrorCause* NoUserDataErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/) { MS_TRACE(); if (causeLength != NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength) { MS_WARN_TAG( sctp, "NoUserDataErrorCause Length field must be %zu", NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength); return nullptr; } auto* errorCause = new NoUserDataErrorCause(const_cast(buffer), bufferLength); return errorCause; } /* Instance methods. */ NoUserDataErrorCause::NoUserDataErrorCause(uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(NoUserDataErrorCause::NoUserDataErrorCauseHeaderLength); } NoUserDataErrorCause::~NoUserDataErrorCause() { MS_TRACE(); } void NoUserDataErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " tsn: %" PRIu32, GetTsn()); MS_DUMP_CLEAN(indentation, ""); } NoUserDataErrorCause* NoUserDataErrorCause::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new NoUserDataErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void NoUserDataErrorCause::SetTsn(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } NoUserDataErrorCause* NoUserDataErrorCause::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new NoUserDataErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } const std::string NoUserDataErrorCause::ContentToString() const { MS_TRACE(); return "tsn:\"" + std::to_string(GetTsn()) + "\""; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::OutOfResourceErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ OutOfResourceErrorCause* OutOfResourceErrorCause::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return OutOfResourceErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding); } OutOfResourceErrorCause* OutOfResourceErrorCause::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ErrorCause::ErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new OutOfResourceErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, ErrorCause::ErrorCauseHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } OutOfResourceErrorCause* OutOfResourceErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/) { MS_TRACE(); if (causeLength != ErrorCause::ErrorCauseHeaderLength) { MS_WARN_TAG( sctp, "OutOfResourceErrorCause Length field must be %zu", ErrorCause::ErrorCauseHeaderLength); return nullptr; } auto* errorCause = new OutOfResourceErrorCause(const_cast(buffer), bufferLength); return errorCause; } /* Instance methods. */ OutOfResourceErrorCause::OutOfResourceErrorCause(uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } OutOfResourceErrorCause::~OutOfResourceErrorCause() { MS_TRACE(); } void OutOfResourceErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, ""); } OutOfResourceErrorCause* OutOfResourceErrorCause::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new OutOfResourceErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } OutOfResourceErrorCause* OutOfResourceErrorCause::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new OutOfResourceErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::ProtocolViolationErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ ProtocolViolationErrorCause* ProtocolViolationErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return ProtocolViolationErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding); } ProtocolViolationErrorCause* ProtocolViolationErrorCause::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ErrorCause::ErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new ProtocolViolationErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION, ErrorCause::ErrorCauseHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } ProtocolViolationErrorCause* ProtocolViolationErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding) { MS_TRACE(); auto* errorCause = new ProtocolViolationErrorCause(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. errorCause->SetLength(causeLength + padding); return errorCause; } /* Instance methods. */ ProtocolViolationErrorCause::ProtocolViolationErrorCause(uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } ProtocolViolationErrorCause::~ProtocolViolationErrorCause() { MS_TRACE(); } void ProtocolViolationErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " additional information length: %" PRIu16 " (has additional information: %s)", GetAdditionalInformationLength(), HasAdditionalInformation() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } ProtocolViolationErrorCause* ProtocolViolationErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new ProtocolViolationErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void ProtocolViolationErrorCause::SetAdditionalInformation(const uint8_t* info, uint16_t infoLength) { MS_TRACE(); SetVariableLengthValue(info, infoLength); } void ProtocolViolationErrorCause::SetAdditionalInformation(const std::string& info) { MS_TRACE(); SetVariableLengthValue(reinterpret_cast(info.c_str()), info.size()); } ProtocolViolationErrorCause* ProtocolViolationErrorCause::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new ProtocolViolationErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } const std::string ProtocolViolationErrorCause::ContentToString() const { MS_TRACE(); if (HasAdditionalInformation()) { return "info::\"" + std::string( reinterpret_cast(GetAdditionalInformation()), GetAdditionalInformationLength()) + "\""; } else { return ""; } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ RestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return RestartOfAnAssociationWithNewAddressesErrorCause::ParseStrict( buffer, bufferLength, causeLength, padding); } RestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ErrorCause::ErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new RestartOfAnAssociationWithNewAddressesErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES, ErrorCause::ErrorCauseHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } RestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding) { MS_TRACE(); auto* errorCause = new RestartOfAnAssociationWithNewAddressesErrorCause( const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. errorCause->SetLength(causeLength + padding); return errorCause; } /* Instance methods. */ RestartOfAnAssociationWithNewAddressesErrorCause::RestartOfAnAssociationWithNewAddressesErrorCause( uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } RestartOfAnAssociationWithNewAddressesErrorCause::~RestartOfAnAssociationWithNewAddressesErrorCause() { MS_TRACE(); } void RestartOfAnAssociationWithNewAddressesErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " new address tlvs length: %" PRIu16 " (has new address tlvs: %s)", GetNewAddressTlvsLength(), HasNewAddressTlvs() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } RestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new RestartOfAnAssociationWithNewAddressesErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void RestartOfAnAssociationWithNewAddressesErrorCause::SetNewAddressTlvs( const uint8_t* tlvs, uint16_t tlvsLength) { MS_TRACE(); SetVariableLengthValue(tlvs, tlvsLength); } RestartOfAnAssociationWithNewAddressesErrorCause* RestartOfAnAssociationWithNewAddressesErrorCause::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new RestartOfAnAssociationWithNewAddressesErrorCause( const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::StaleCookieErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include namespace RTC { namespace SCTP { /* Class methods. */ StaleCookieErrorCause* StaleCookieErrorCause::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::STALE_COOKIE) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return StaleCookieErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding); } StaleCookieErrorCause* StaleCookieErrorCause::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new StaleCookieErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::STALE_COOKIE, StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength); // Initialize value. errorCause->SetMeasureOfStaleness(0); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } StaleCookieErrorCause* StaleCookieErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t /*padding*/) { MS_TRACE(); if (causeLength != StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength) { MS_WARN_TAG( sctp, "StaleCookieErrorCause Length field must be %zu", StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength); return nullptr; } auto* errorCause = new StaleCookieErrorCause(const_cast(buffer), bufferLength); return errorCause; } /* Instance methods. */ StaleCookieErrorCause::StaleCookieErrorCause(uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(StaleCookieErrorCause::StaleCookieErrorCauseHeaderLength); } StaleCookieErrorCause::~StaleCookieErrorCause() { MS_TRACE(); } void StaleCookieErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " measure of staleness: %" PRIu32, GetMeasureOfStaleness()); MS_DUMP_CLEAN(indentation, ""); } StaleCookieErrorCause* StaleCookieErrorCause::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new StaleCookieErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void StaleCookieErrorCause::SetMeasureOfStaleness(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } StaleCookieErrorCause* StaleCookieErrorCause::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new StaleCookieErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } const std::string StaleCookieErrorCause::ContentToString() const { MS_TRACE(); return "staleness:" + std::to_string(GetMeasureOfStaleness()); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/UnknownErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::UnknownErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { /* Class methods. */ UnknownErrorCause* UnknownErrorCause::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } return UnknownErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding); } UnknownErrorCause* UnknownErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding) { MS_TRACE(); auto* errorCause = new UnknownErrorCause(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. errorCause->SetLength(causeLength + padding); return errorCause; } /* Instance methods. */ UnknownErrorCause::UnknownErrorCause(uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } UnknownErrorCause::~UnknownErrorCause() { MS_TRACE(); } void UnknownErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, ""); } UnknownErrorCause* UnknownErrorCause::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new UnknownErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } UnknownErrorCause* UnknownErrorCause::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new UnknownErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::UnrecognizedChunkTypeErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ UnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return UnrecognizedChunkTypeErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding); } UnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ErrorCause::ErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new UnrecognizedChunkTypeErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, ErrorCause::ErrorCauseHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } UnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding) { MS_TRACE(); auto* errorCause = new UnrecognizedChunkTypeErrorCause(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. errorCause->SetLength(causeLength + padding); return errorCause; } /* Instance methods. */ UnrecognizedChunkTypeErrorCause::UnrecognizedChunkTypeErrorCause(uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } UnrecognizedChunkTypeErrorCause::~UnrecognizedChunkTypeErrorCause() { MS_TRACE(); } void UnrecognizedChunkTypeErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " unrecognized chunk length: %" PRIu16 " (has unrecognized chunk: %s)", GetUnrecognizedChunkLength(), HasUnrecognizedChunk() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } UnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new UnrecognizedChunkTypeErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void UnrecognizedChunkTypeErrorCause::SetUnrecognizedChunk(const uint8_t* chunk, uint16_t chunkLength) { MS_TRACE(); SetVariableLengthValue(chunk, chunkLength); } UnrecognizedChunkTypeErrorCause* UnrecognizedChunkTypeErrorCause::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new UnrecognizedChunkTypeErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::UnrecognizedParametersErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ UnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return UnrecognizedParametersErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding); } UnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ErrorCause::ErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new UnrecognizedParametersErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS, ErrorCause::ErrorCauseHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } UnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding) { MS_TRACE(); auto* errorCause = new UnrecognizedParametersErrorCause(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. errorCause->SetLength(causeLength + padding); return errorCause; } /* Instance methods. */ UnrecognizedParametersErrorCause::UnrecognizedParametersErrorCause(uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } UnrecognizedParametersErrorCause::~UnrecognizedParametersErrorCause() { MS_TRACE(); } void UnrecognizedParametersErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " unrecognized parameters length: %" PRIu16 " (has unrecognized parameters: %s)", GetUnrecognizedParametersLength(), HasUnrecognizedParameters() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } UnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new UnrecognizedParametersErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void UnrecognizedParametersErrorCause::SetUnrecognizedParameters( const uint8_t* parameters, uint16_t parametersLength) { MS_TRACE(); SetVariableLengthValue(parameters, parametersLength); } UnrecognizedParametersErrorCause* UnrecognizedParametersErrorCause::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new UnrecognizedParametersErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::UnresolvableAddressErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ UnresolvableAddressErrorCause* UnresolvableAddressErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return UnresolvableAddressErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding); } UnresolvableAddressErrorCause* UnresolvableAddressErrorCause::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ErrorCause::ErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new UnresolvableAddressErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS, ErrorCause::ErrorCauseHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } UnresolvableAddressErrorCause* UnresolvableAddressErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding) { MS_TRACE(); auto* errorCause = new UnresolvableAddressErrorCause(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. errorCause->SetLength(causeLength + padding); return errorCause; } /* Instance methods. */ UnresolvableAddressErrorCause::UnresolvableAddressErrorCause(uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } UnresolvableAddressErrorCause::~UnresolvableAddressErrorCause() { MS_TRACE(); } void UnresolvableAddressErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " unresolvable address length: %" PRIu16 " (has unresolvable address: %s)", GetUnresolvableAddressLength(), HasUnresolvableAddress() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } UnresolvableAddressErrorCause* UnresolvableAddressErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new UnresolvableAddressErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void UnresolvableAddressErrorCause::SetUnresolvableAddress( const uint8_t* address, uint16_t addressLength) { MS_TRACE(); SetVariableLengthValue(address, addressLength); } UnresolvableAddressErrorCause* UnresolvableAddressErrorCause::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new UnresolvableAddressErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.cpp ================================================ #define MS_CLASS "RTC::SCTP::UserInitiatedAbortErrorCause" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ UserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); ErrorCause::ErrorCauseCode causeCode; uint16_t causeLength; uint8_t padding; if (!ErrorCause::IsErrorCause(buffer, bufferLength, causeCode, causeLength, padding)) { return nullptr; } if (causeCode != ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT) { MS_WARN_DEV("invalid Error Cause code"); return nullptr; } return UserInitiatedAbortErrorCause::ParseStrict(buffer, bufferLength, causeLength, padding); } UserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ErrorCause::ErrorCauseHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* errorCause = new UserInitiatedAbortErrorCause(buffer, bufferLength); errorCause->InitializeHeader( ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT, ErrorCause::ErrorCauseHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return errorCause; } UserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t causeLength, uint8_t padding) { MS_TRACE(); auto* errorCause = new UserInitiatedAbortErrorCause(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. errorCause->SetLength(causeLength + padding); return errorCause; } /* Instance methods. */ UserInitiatedAbortErrorCause::UserInitiatedAbortErrorCause(uint8_t* buffer, size_t bufferLength) : ErrorCause(buffer, bufferLength) { MS_TRACE(); SetLength(ErrorCause::ErrorCauseHeaderLength); } UserInitiatedAbortErrorCause::~UserInitiatedAbortErrorCause() { MS_TRACE(); } void UserInitiatedAbortErrorCause::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); if (HasUpperLayerAbortReason()) { const auto reason = GetUpperLayerAbortReason(); MS_DUMP_CLEAN(indentation, " has upper layer abort reason: yes"); MS_DUMP_CLEAN( indentation, " upper layer abort reason: \"%.*s\"", static_cast(reason.size()), reason.data()); } else { MS_DUMP_CLEAN(indentation, " has upper layer abort reason: no"); } MS_DUMP_CLEAN(indentation, ""); } UserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedErrorCause = new UserInitiatedAbortErrorCause(buffer, bufferLength); CloneInto(clonedErrorCause); return clonedErrorCause; } void UserInitiatedAbortErrorCause::SetUpperLayerAbortReason(const std::string_view& reason) { MS_TRACE(); SetVariableLengthValue( reinterpret_cast(reason.data()), static_cast(reason.size())); } UserInitiatedAbortErrorCause* UserInitiatedAbortErrorCause::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedErrorCause = new UserInitiatedAbortErrorCause(const_cast(buffer), GetLength()); SoftCloneInto(softClonedErrorCause); return softClonedErrorCause; } const std::string UserInitiatedAbortErrorCause::ContentToString() const { MS_TRACE(); if (HasUpperLayerAbortReason()) { return "reason::\"" + std::string(GetUpperLayerAbortReason()) + "\""; } else { return ""; } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::AddIncomingStreamsRequestParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ AddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return AddIncomingStreamsRequestParameter::ParseStrict( buffer, bufferLength, parameterLength, padding); } AddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new AddIncomingStreamsRequestParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST, AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength); // Initialize header extra fields to zero. parameter->SetReconfigurationRequestSequenceNumber(0); parameter->SetNumberOfNewStreams(0); parameter->SetReserved(); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } AddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/) { MS_TRACE(); if (parameterLength != AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength) { MS_WARN_TAG( sctp, "AddIncomingStreamsRequestParameter Length field must be %zu", AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength); return nullptr; } auto* parameter = new AddIncomingStreamsRequestParameter(const_cast(buffer), bufferLength); return parameter; } /* Instance methods. */ AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameter( uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(AddIncomingStreamsRequestParameter::AddIncomingStreamsRequestParameterHeaderLength); } AddIncomingStreamsRequestParameter::~AddIncomingStreamsRequestParameter() { MS_TRACE(); } void AddIncomingStreamsRequestParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " re-configuration request sequence number: %" PRIu32, GetReconfigurationRequestSequenceNumber()); MS_DUMP_CLEAN(indentation, " number of new streams: %" PRIu16, GetNumberOfNewStreams()); MS_DUMP_CLEAN(indentation, ""); } AddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new AddIncomingStreamsRequestParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void AddIncomingStreamsRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } void AddIncomingStreamsRequestParameter::SetNumberOfNewStreams(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 8, value); } AddIncomingStreamsRequestParameter* AddIncomingStreamsRequestParameter::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new AddIncomingStreamsRequestParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } void AddIncomingStreamsRequestParameter::SetReserved() { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 10, 0); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::AddOutgoingStreamsRequestParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ AddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return AddOutgoingStreamsRequestParameter::ParseStrict( buffer, bufferLength, parameterLength, padding); } AddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new AddOutgoingStreamsRequestParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST, AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength); // Initialize header extra fields to zero. parameter->SetReconfigurationRequestSequenceNumber(0); parameter->SetNumberOfNewStreams(0); parameter->SetReserved(); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } AddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/) { MS_TRACE(); if (parameterLength != AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength) { MS_WARN_TAG( sctp, "AddOutgoingStreamsRequestParameter Length field must be %zu", AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength); return nullptr; } auto* parameter = new AddOutgoingStreamsRequestParameter(const_cast(buffer), bufferLength); return parameter; } /* Instance methods. */ AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameter( uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(AddOutgoingStreamsRequestParameter::AddOutgoingStreamsRequestParameterHeaderLength); } AddOutgoingStreamsRequestParameter::~AddOutgoingStreamsRequestParameter() { MS_TRACE(); } void AddOutgoingStreamsRequestParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " re-configuration request sequence number: %" PRIu32, GetReconfigurationRequestSequenceNumber()); MS_DUMP_CLEAN(indentation, " number of new streams: %" PRIu16, GetNumberOfNewStreams()); MS_DUMP_CLEAN(indentation, ""); } AddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new AddOutgoingStreamsRequestParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void AddOutgoingStreamsRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } void AddOutgoingStreamsRequestParameter::SetNumberOfNewStreams(uint16_t value) { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 8, value); } AddOutgoingStreamsRequestParameter* AddOutgoingStreamsRequestParameter::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new AddOutgoingStreamsRequestParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } void AddOutgoingStreamsRequestParameter::SetReserved() { MS_TRACE(); Utils::Byte::Set2Bytes(const_cast(GetBuffer()), 10, 0); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/CookiePreservativeParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::CookiePreservativeParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ CookiePreservativeParameter* CookiePreservativeParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::COOKIE_PRESERVATIVE) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return CookiePreservativeParameter::ParseStrict(buffer, bufferLength, parameterLength, padding); } CookiePreservativeParameter* CookiePreservativeParameter::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < CookiePreservativeParameter::CookiePreservativeParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new CookiePreservativeParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::COOKIE_PRESERVATIVE, CookiePreservativeParameter::CookiePreservativeParameterHeaderLength); // Must also initialize the value. parameter->SetLifeSpanIncrement(0); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } CookiePreservativeParameter* CookiePreservativeParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/) { MS_TRACE(); if (parameterLength != CookiePreservativeParameter::CookiePreservativeParameterHeaderLength) { MS_WARN_TAG( sctp, "CookiePreservativeParameter Length field must be %zu", CookiePreservativeParameter::CookiePreservativeParameterHeaderLength); return nullptr; } auto* parameter = new CookiePreservativeParameter(const_cast(buffer), bufferLength); return parameter; } /* Instance methods. */ CookiePreservativeParameter::CookiePreservativeParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(CookiePreservativeParameter::CookiePreservativeParameterHeaderLength); } CookiePreservativeParameter::~CookiePreservativeParameter() { MS_TRACE(); } void CookiePreservativeParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " suggested cookie life-span increment: %" PRIu32, GetLifeSpanIncrement()); MS_DUMP_CLEAN(indentation, ""); } CookiePreservativeParameter* CookiePreservativeParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new CookiePreservativeParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void CookiePreservativeParameter::SetLifeSpanIncrement(uint32_t increment) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, increment); } CookiePreservativeParameter* CookiePreservativeParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new CookiePreservativeParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::ForwardTsnSupportedParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ ForwardTsnSupportedParameter* ForwardTsnSupportedParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::FORWARD_TSN_SUPPORTED) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return ForwardTsnSupportedParameter::ParseStrict(buffer, bufferLength, parameterLength, padding); } ForwardTsnSupportedParameter* ForwardTsnSupportedParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Parameter::ParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new ForwardTsnSupportedParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::FORWARD_TSN_SUPPORTED, Parameter::ParameterHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } ForwardTsnSupportedParameter* ForwardTsnSupportedParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/) { MS_TRACE(); if (parameterLength != Parameter::ParameterHeaderLength) { MS_WARN_TAG( sctp, "ForwardTsnSupportedParameter Length field must be %zu", Parameter::ParameterHeaderLength); return nullptr; } auto* parameter = new ForwardTsnSupportedParameter(const_cast(buffer), bufferLength); return parameter; } /* Instance methods. */ ForwardTsnSupportedParameter::ForwardTsnSupportedParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(Parameter::ParameterHeaderLength); } ForwardTsnSupportedParameter::~ForwardTsnSupportedParameter() { MS_TRACE(); } void ForwardTsnSupportedParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, ""); } ForwardTsnSupportedParameter* ForwardTsnSupportedParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new ForwardTsnSupportedParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } ForwardTsnSupportedParameter* ForwardTsnSupportedParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new ForwardTsnSupportedParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/HeartbeatInfoParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::HeartbeatInfoParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ HeartbeatInfoParameter* HeartbeatInfoParameter::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::HEARTBEAT_INFO) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return HeartbeatInfoParameter::ParseStrict(buffer, bufferLength, parameterLength, padding); } HeartbeatInfoParameter* HeartbeatInfoParameter::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Parameter::ParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new HeartbeatInfoParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::HEARTBEAT_INFO, Parameter::ParameterHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } HeartbeatInfoParameter* HeartbeatInfoParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding) { MS_TRACE(); auto* parameter = new HeartbeatInfoParameter(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. parameter->SetLength(parameterLength + padding); return parameter; } /* Instance methods. */ HeartbeatInfoParameter::HeartbeatInfoParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(Parameter::ParameterHeaderLength); } HeartbeatInfoParameter::~HeartbeatInfoParameter() { MS_TRACE(); } void HeartbeatInfoParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " info length: %" PRIu16 " (has info: %s)", GetInfoLength(), HasInfo() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } HeartbeatInfoParameter* HeartbeatInfoParameter::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new HeartbeatInfoParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void HeartbeatInfoParameter::SetInfo(const uint8_t* info, uint16_t infoLength) { MS_TRACE(); SetVariableLengthValue(info, infoLength); } HeartbeatInfoParameter* HeartbeatInfoParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new HeartbeatInfoParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/IPv4AddressParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::IPv4AddressParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include #include // std::memset(), std::memmove() namespace RTC { namespace SCTP { /* Class methods. */ IPv4AddressParameter* IPv4AddressParameter::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::IPV4_ADDRESS) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return IPv4AddressParameter::ParseStrict(buffer, bufferLength, parameterLength, padding); } IPv4AddressParameter* IPv4AddressParameter::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < IPv4AddressParameter::IPv4AddressParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new IPv4AddressParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::IPV4_ADDRESS, IPv4AddressParameter::IPv4AddressParameterHeaderLength); // Must also initialize the IPv4 field to zero. parameter->ResetIPv4Address(); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } IPv4AddressParameter* IPv4AddressParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/) { MS_TRACE(); if (parameterLength != IPv4AddressParameter::IPv4AddressParameterHeaderLength) { MS_WARN_TAG( sctp, "IPv4AddressParameter Length field must be %zu", IPv4AddressParameter::IPv4AddressParameterHeaderLength); return nullptr; } auto* parameter = new IPv4AddressParameter(const_cast(buffer), bufferLength); return parameter; } /* Instance methods. */ IPv4AddressParameter::IPv4AddressParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(IPv4AddressParameter::IPv4AddressParameterHeaderLength); } IPv4AddressParameter::~IPv4AddressParameter() { MS_TRACE(); } void IPv4AddressParameter::Dump(int indentation) const { MS_TRACE(); char ipStr[INET_ADDRSTRLEN] = { 0 }; uv_inet_ntop(AF_INET, GetIPv4Address(), ipStr, sizeof(ipStr)); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " ipv4 address: %s", ipStr); MS_DUMP_CLEAN(indentation, ""); } IPv4AddressParameter* IPv4AddressParameter::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new IPv4AddressParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void IPv4AddressParameter::SetIPv4Address(const uint8_t* ip) { MS_TRACE(); std::memmove(const_cast(GetBuffer()) + 4, ip, 4); } IPv4AddressParameter* IPv4AddressParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new IPv4AddressParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } void IPv4AddressParameter::ResetIPv4Address() { MS_TRACE(); std::memset(const_cast(GetBuffer()) + 4, 0x00, 4); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/IPv6AddressParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::IPv6AddressParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include #include // std::memset(), std::memmove() namespace RTC { namespace SCTP { /* Class methods. */ IPv6AddressParameter* IPv6AddressParameter::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::IPV6_ADDRESS) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return IPv6AddressParameter::ParseStrict(buffer, bufferLength, parameterLength, padding); } IPv6AddressParameter* IPv6AddressParameter::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < IPv6AddressParameter::IPv6AddressParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new IPv6AddressParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::IPV6_ADDRESS, IPv6AddressParameter::IPv6AddressParameterHeaderLength); // Must also initialize the IPv6 field to zero. parameter->ResetIPv6Address(); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } IPv6AddressParameter* IPv6AddressParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/) { MS_TRACE(); if (parameterLength != IPv6AddressParameter::IPv6AddressParameterHeaderLength) { MS_WARN_TAG( sctp, "IPv6AddressParameter Length field must be %zu", IPv6AddressParameter::IPv6AddressParameterHeaderLength); return nullptr; } auto* parameter = new IPv6AddressParameter(const_cast(buffer), bufferLength); return parameter; } /* Instance methods. */ IPv6AddressParameter::IPv6AddressParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(IPv6AddressParameter::IPv6AddressParameterHeaderLength); } IPv6AddressParameter::~IPv6AddressParameter() { MS_TRACE(); } void IPv6AddressParameter::Dump(int indentation) const { MS_TRACE(); char ipStr[INET6_ADDRSTRLEN] = { 0 }; uv_inet_ntop(AF_INET6, GetIPv6Address(), ipStr, sizeof(ipStr)); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " ipv6 address: %s", ipStr); MS_DUMP_CLEAN(indentation, ""); } IPv6AddressParameter* IPv6AddressParameter::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new IPv6AddressParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void IPv6AddressParameter::SetIPv6Address(const uint8_t* ip) { MS_TRACE(); std::memmove(const_cast(GetBuffer()) + 4, ip, 16); } IPv6AddressParameter* IPv6AddressParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new IPv6AddressParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } void IPv6AddressParameter::ResetIPv6Address() { MS_TRACE(); std::memset(const_cast(GetBuffer()) + 4, 0x00, 16); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::IncomingSsnResetRequestParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ IncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return IncomingSsnResetRequestParameter::ParseStrict( buffer, bufferLength, parameterLength, padding); } IncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new IncomingSsnResetRequestParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength); // Must also initialize extra fields in the header. parameter->SetReconfigurationRequestSequenceNumber(0); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } IncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding) { MS_TRACE(); if (parameterLength < IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength) { MS_WARN_TAG( sctp, "IncomingSsnResetRequestParameter Length field must be equal or greater than %zu", IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength); return nullptr; } auto* parameter = new IncomingSsnResetRequestParameter(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. parameter->SetLength(parameterLength + padding); return parameter; } /* Instance methods. */ IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(IncomingSsnResetRequestParameter::IncomingSsnResetRequestParameterHeaderLength); } IncomingSsnResetRequestParameter::~IncomingSsnResetRequestParameter() { MS_TRACE(); } void IncomingSsnResetRequestParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " re-configuration request sequence number: %" PRIu32, GetReconfigurationRequestSequenceNumber()); MS_DUMP_CLEAN(indentation, " stream ids:"); for (const uint16_t streamId : GetStreamIds()) { MS_DUMP_CLEAN(indentation, " - stream id: %" PRIu16, streamId); } MS_DUMP_CLEAN(indentation, ""); } IncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new IncomingSsnResetRequestParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void IncomingSsnResetRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } std::vector IncomingSsnResetRequestParameter::GetStreamIds() const { MS_TRACE(); const uint16_t numberOfStreams = GetNumberOfStreams(); std::vector streamIds; streamIds.reserve(numberOfStreams); for (uint16_t idx{ 0 }; idx < numberOfStreams; ++idx) { streamIds.emplace_back(GetStreamAt(idx)); } return streamIds; } void IncomingSsnResetRequestParameter::AddStreamId(uint16_t streamId) { MS_TRACE(); auto previousVariableLengthValueLength = GetVariableLengthValueLength(); // NOTE: This may throw. SetVariableLengthValueLength(previousVariableLengthValueLength + 2); // Add the new stream. Utils::Byte::Set2Bytes( GetVariableLengthValuePointer(), previousVariableLengthValueLength, streamId); } IncomingSsnResetRequestParameter* IncomingSsnResetRequestParameter::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new IncomingSsnResetRequestParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::OutgoingSsnResetRequestParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ OutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return OutgoingSsnResetRequestParameter::ParseStrict( buffer, bufferLength, parameterLength, padding); } OutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new OutgoingSsnResetRequestParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength); // Must also initialize extra fields in the header. parameter->SetReconfigurationRequestSequenceNumber(0); parameter->SetReconfigurationResponseSequenceNumber(0); parameter->SetSenderLastAssignedTsn(0); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } OutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding) { MS_TRACE(); if (parameterLength < OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength) { MS_WARN_TAG( sctp, "OutgoingSsnResetRequestParameter Length field must be equal or greater than %zu", OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength); return nullptr; } auto* parameter = new OutgoingSsnResetRequestParameter(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. parameter->SetLength(parameterLength + padding); return parameter; } /* Instance methods. */ OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(OutgoingSsnResetRequestParameter::OutgoingSsnResetRequestParameterHeaderLength); } OutgoingSsnResetRequestParameter::~OutgoingSsnResetRequestParameter() { MS_TRACE(); } void OutgoingSsnResetRequestParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " re-configuration request sequence number: %" PRIu32, GetReconfigurationRequestSequenceNumber()); MS_DUMP_CLEAN( indentation, " re-configuration response sequence number: %" PRIu32, GetReconfigurationResponseSequenceNumber()); MS_DUMP_CLEAN(indentation, " sender last assigned tsn: %" PRIu32, GetSenderLastAssignedTsn()); MS_DUMP_CLEAN(indentation, " stream ids:"); for (const uint16_t streamId : GetStreamIds()) { MS_DUMP_CLEAN(indentation, " - stream id: %" PRIu16, streamId); } MS_DUMP_CLEAN(indentation, ""); } OutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new OutgoingSsnResetRequestParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void OutgoingSsnResetRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } void OutgoingSsnResetRequestParameter::SetReconfigurationResponseSequenceNumber(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 8, value); } void OutgoingSsnResetRequestParameter::SetSenderLastAssignedTsn(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 12, value); } std::vector OutgoingSsnResetRequestParameter::GetStreamIds() const { MS_TRACE(); const uint16_t numberOfStreams = GetNumberOfStreams(); std::vector streamIds; streamIds.reserve(numberOfStreams); for (uint16_t idx{ 0 }; idx < numberOfStreams; ++idx) { streamIds.emplace_back(GetStreamAt(idx)); } return streamIds; } void OutgoingSsnResetRequestParameter::AddStreamId(uint16_t streamId) { MS_TRACE(); auto previousVariableLengthValueLength = GetVariableLengthValueLength(); // NOTE: This may throw. SetVariableLengthValueLength(previousVariableLengthValueLength + 2); // Add the new stream. Utils::Byte::Set2Bytes( GetVariableLengthValuePointer(), previousVariableLengthValueLength, streamId); } OutgoingSsnResetRequestParameter* OutgoingSsnResetRequestParameter::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new OutgoingSsnResetRequestParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::ReconfigurationResponseParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class variables. */ // clang-format off const std::unordered_map ReconfigurationResponseParameter::Result2String = { { ReconfigurationResponseParameter::Result::SUCCESS_NOTHING_TO_DO, "SUCCESS_NOTHING_TO_DO" }, { ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED, "SUCCESS_PERFORMED" }, { ReconfigurationResponseParameter::Result::DENIED, "DENIED" }, { ReconfigurationResponseParameter::Result::ERROR_WRONG_SSN, "ERROR_WRONG_SSN" }, { ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS, "ERROR_REQUEST_ALREADY_IN_PROGRESS" }, { ReconfigurationResponseParameter::Result::ERROR_BAD_SEQUENCE_NUMBER, "ERROR_BAD_SEQUENCE_NUMBER" }, { ReconfigurationResponseParameter::Result::IN_PROGRESS, "IN_PROGRESS" }, }; // clang-format on /* Class methods. */ ReconfigurationResponseParameter* ReconfigurationResponseParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::RECONFIGURATION_RESPONSE) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return ReconfigurationResponseParameter::ParseStrict( buffer, bufferLength, parameterLength, padding); } ReconfigurationResponseParameter* ReconfigurationResponseParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new ReconfigurationResponseParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::RECONFIGURATION_RESPONSE, ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength); // Must also initialize extra fields in the header. parameter->SetReconfigurationResponseSequenceNumber(0); parameter->SetResult(static_cast(0)); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } const std::string& ReconfigurationResponseParameter::ResultToString( ReconfigurationResponseParameter::Result result) { MS_TRACE(); static const std::string Unknown("UNKNOWN"); auto it = ReconfigurationResponseParameter::Result2String.find(result); if (it == ReconfigurationResponseParameter::Result2String.end()) { return Unknown; } return it->second; } ReconfigurationResponseParameter* ReconfigurationResponseParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding) { MS_TRACE(); if ( parameterLength != ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength && parameterLength != ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLengthWithOptionalFields) { MS_WARN_TAG( sctp, "ReconfigurationResponseParameter Length field must be %zu or %zu", ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength, ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLengthWithOptionalFields); return nullptr; } auto* parameter = new ReconfigurationResponseParameter(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. parameter->SetLength(parameterLength + padding); return parameter; } /* Instance methods. */ ReconfigurationResponseParameter::ReconfigurationResponseParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength); } ReconfigurationResponseParameter::~ReconfigurationResponseParameter() { MS_TRACE(); } void ReconfigurationResponseParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " re-configuration response sequence number: %" PRIu32, GetReconfigurationResponseSequenceNumber()); MS_DUMP_CLEAN( indentation, " result: %" PRIu32 " (%s)", static_cast(GetResult()), ReconfigurationResponseParameter::ResultToString(GetResult()).c_str()); MS_DUMP_CLEAN(indentation, " has next tsns: %s", HasNextTsns() ? "yes" : "no"); if (HasNextTsns()) { MS_DUMP_CLEAN(indentation, " sender next tsn: %" PRIu32, GetSenderNextTsn()); MS_DUMP_CLEAN(indentation, " receiver next tsn: %" PRIu32, GetReceiverNextTsn()); } MS_DUMP_CLEAN(indentation, ""); } ReconfigurationResponseParameter* ReconfigurationResponseParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new ReconfigurationResponseParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void ReconfigurationResponseParameter::SetReconfigurationResponseSequenceNumber(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } void ReconfigurationResponseParameter::SetResult(Result result) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 8, static_cast(result)); } void ReconfigurationResponseParameter::SetNextTsns(uint32_t senderNextTsn, uint32_t receiverNextTsn) { MS_TRACE(); if (!HasNextTsns()) { // This may throw. SetVariableLengthValueLength( ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLengthWithOptionalFields - ReconfigurationResponseParameter::ReconfigurationResponseParameterHeaderLength); } Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 12, senderNextTsn); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 16, receiverNextTsn); } ReconfigurationResponseParameter* ReconfigurationResponseParameter::SoftClone( const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new ReconfigurationResponseParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::SsnTsnResetRequestParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ SsnTsnResetRequestParameter* SsnTsnResetRequestParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::SSN_TSN_RESET_REQUEST) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return SsnTsnResetRequestParameter::ParseStrict(buffer, bufferLength, parameterLength, padding); } SsnTsnResetRequestParameter* SsnTsnResetRequestParameter::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new SsnTsnResetRequestParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::SSN_TSN_RESET_REQUEST, SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength); // Initialize header extra fields to zero. parameter->SetReconfigurationRequestSequenceNumber(0); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } SsnTsnResetRequestParameter* SsnTsnResetRequestParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/) { MS_TRACE(); if (parameterLength != SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength) { MS_WARN_TAG( sctp, "SsnTsnResetRequestParameter Length field must be %zu", SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength); return nullptr; } auto* parameter = new SsnTsnResetRequestParameter(const_cast(buffer), bufferLength); return parameter; } /* Instance methods. */ SsnTsnResetRequestParameter::SsnTsnResetRequestParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(SsnTsnResetRequestParameter::SsnTsnResetRequestParameterHeaderLength); } SsnTsnResetRequestParameter::~SsnTsnResetRequestParameter() { MS_TRACE(); } void SsnTsnResetRequestParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " re-configuration request sequence number: %" PRIu32, GetReconfigurationRequestSequenceNumber()); MS_DUMP_CLEAN(indentation, ""); } SsnTsnResetRequestParameter* SsnTsnResetRequestParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new SsnTsnResetRequestParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void SsnTsnResetRequestParameter::SetReconfigurationRequestSequenceNumber(uint32_t value) { MS_TRACE(); Utils::Byte::Set4Bytes(const_cast(GetBuffer()), 4, value); } SsnTsnResetRequestParameter* SsnTsnResetRequestParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new SsnTsnResetRequestParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/StateCookieParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::StateCookieParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/StateCookieParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/SCTP/association/StateCookie.hpp" namespace RTC { namespace SCTP { /* Class methods. */ StateCookieParameter* StateCookieParameter::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::STATE_COOKIE) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return StateCookieParameter::ParseStrict(buffer, bufferLength, parameterLength, padding); } StateCookieParameter* StateCookieParameter::Factory(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Parameter::ParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new StateCookieParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::STATE_COOKIE, Parameter::ParameterHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } StateCookieParameter* StateCookieParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding) { MS_TRACE(); auto* parameter = new StateCookieParameter(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. parameter->SetLength(parameterLength + padding); return parameter; } /* Instance methods. */ StateCookieParameter::StateCookieParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(Parameter::ParameterHeaderLength); } StateCookieParameter::~StateCookieParameter() { MS_TRACE(); } void StateCookieParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " cookie length: %" PRIu16 " (has cookie: %s)", GetCookieLength(), HasCookie() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } StateCookieParameter* StateCookieParameter::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new StateCookieParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void StateCookieParameter::SetCookie(const uint8_t* cookie, uint16_t cookieLength) { MS_TRACE(); SetVariableLengthValue(cookie, cookieLength); } void StateCookieParameter::WriteStateCookieInPlace( uint32_t localVerificationTag, uint32_t remoteVerificationTag, uint32_t localInitialTsn, uint32_t remoteInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, uint64_t tieTag, const NegotiatedCapabilities& negotiatedCapabilities) { MS_TRACE(); // The buffer in which the StateCookie will be written starts at the // position of the Cookie field in the StateCookieParameter. auto* buffer = GetVariableLengthValuePointer(); // The available buffer length is the total buffer length of the // StateCookieParameter minus its fixed header length (no matter there // was a Cookie already in the Parameter since we are overriding it // anyway). const size_t bufferLength = GetBufferLength() - Parameter::ParameterHeaderLength; StateCookie::Write( buffer, bufferLength, localVerificationTag, remoteVerificationTag, localInitialTsn, remoteInitialTsn, remoteAdvertisedReceiverWindowCredit, tieTag, negotiatedCapabilities); SetVariableLengthValueLength(StateCookie::StateCookieLength); } StateCookieParameter* StateCookieParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new StateCookieParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::SupportedAddressTypesParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ SupportedAddressTypesParameter* SupportedAddressTypesParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return SupportedAddressTypesParameter::ParseStrict( buffer, bufferLength, parameterLength, padding); } SupportedAddressTypesParameter* SupportedAddressTypesParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Parameter::ParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new SupportedAddressTypesParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, Parameter::ParameterHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } SupportedAddressTypesParameter* SupportedAddressTypesParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding) { MS_TRACE(); auto* parameter = new SupportedAddressTypesParameter(const_cast(buffer), bufferLength); // Here we must validate that Length field is even. if (parameter->GetLengthField() % 2 != 0) { MS_WARN_TAG(sctp, "wrong Length value (not even)"); delete parameter; return nullptr; } // Must always invoke SetLength() after constructing a Serializable with // not fixed length. parameter->SetLength(parameterLength + padding); return parameter; } /* Instance methods. */ SupportedAddressTypesParameter::SupportedAddressTypesParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(Parameter::ParameterHeaderLength); } SupportedAddressTypesParameter::~SupportedAddressTypesParameter() { MS_TRACE(); } void SupportedAddressTypesParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " number of address types: %" PRIu16, GetNumberOfAddressTypes()); for (uint32_t idx{ 0 }; idx < GetNumberOfAddressTypes(); ++idx) { MS_DUMP_CLEAN( indentation, " - idx: %" PRIu16 ", address type: %" PRIu16, idx, GetAddressTypeAt(idx)); } MS_DUMP_CLEAN(indentation, ""); } SupportedAddressTypesParameter* SupportedAddressTypesParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new SupportedAddressTypesParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void SupportedAddressTypesParameter::AddAddressType(uint16_t addressType) { MS_TRACE(); // We must save previous count since SetVariableLengthValueLength() will // make GetNumberOfAddressTypes() return a different value. auto previousNumberOfAddressTypes = GetNumberOfAddressTypes(); // NOTE: This may throw. SetVariableLengthValueLength(GetVariableLengthValueLength() + 2); // Add the new missing mandatory parameter type. Utils::Byte::Set2Bytes( GetVariableLengthValuePointer(), previousNumberOfAddressTypes * 2, addressType); } SupportedAddressTypesParameter* SupportedAddressTypesParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new SupportedAddressTypesParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/SupportedExtensionsParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::SupportedExtensionsParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ SupportedExtensionsParameter* SupportedExtensionsParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::SUPPORTED_EXTENSIONS) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return SupportedExtensionsParameter::ParseStrict(buffer, bufferLength, parameterLength, padding); } SupportedExtensionsParameter* SupportedExtensionsParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Parameter::ParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new SupportedExtensionsParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::SUPPORTED_EXTENSIONS, Parameter::ParameterHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } SupportedExtensionsParameter* SupportedExtensionsParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding) { MS_TRACE(); auto* parameter = new SupportedExtensionsParameter(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. parameter->SetLength(parameterLength + padding); return parameter; } /* Instance methods. */ SupportedExtensionsParameter::SupportedExtensionsParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(Parameter::ParameterHeaderLength); } SupportedExtensionsParameter::~SupportedExtensionsParameter() { MS_TRACE(); } void SupportedExtensionsParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, " number of chunk types: %" PRIu16, GetNumberOfChunkTypes()); for (uint32_t idx{ 0 }; idx < GetNumberOfChunkTypes(); ++idx) { MS_DUMP_CLEAN( indentation, " - idx: %" PRIu16 ", chunk type: %" PRIu8 " (%s)", idx, static_cast(GetChunkTypeAt(idx)), Chunk::ChunkTypeToString(GetChunkTypeAt(idx)).c_str()); } MS_DUMP_CLEAN(indentation, ""); } SupportedExtensionsParameter* SupportedExtensionsParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new SupportedExtensionsParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void SupportedExtensionsParameter::AddChunkType(Chunk::ChunkType chunkType) { MS_TRACE(); // We must save previous count since SetVariableLengthValueLength() will // make GetNumberOfChunkTypes() return a different value. auto previousNumberOfChunkTypes = GetNumberOfChunkTypes(); // NOTE: This may throw. SetVariableLengthValueLength(GetVariableLengthValueLength() + 1); // Add the new missing mandatory parameter type. Utils::Byte::Set1Byte( GetVariableLengthValuePointer(), previousNumberOfChunkTypes, static_cast(chunkType)); } SupportedExtensionsParameter* SupportedExtensionsParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new SupportedExtensionsParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/UnknownParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::UnknownParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/UnknownParameter.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { /* Class methods. */ UnknownParameter* UnknownParameter::Parse(const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } return UnknownParameter::ParseStrict(buffer, bufferLength, parameterLength, padding); } UnknownParameter* UnknownParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding) { MS_TRACE(); auto* parameter = new UnknownParameter(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. parameter->SetLength(parameterLength + padding); return parameter; } /* Instance methods. */ UnknownParameter::UnknownParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(Parameter::ParameterHeaderLength); } UnknownParameter::~UnknownParameter() { MS_TRACE(); } void UnknownParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN(indentation, ""); } UnknownParameter* UnknownParameter::Clone(uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new UnknownParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } UnknownParameter* UnknownParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new UnknownParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::UnrecognizedParameterParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class methods. */ UnrecognizedParameterParameter* UnrecognizedParameterParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::UNRECOGNIZED_PARAMETER) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return UnrecognizedParameterParameter::ParseStrict( buffer, bufferLength, parameterLength, padding); } UnrecognizedParameterParameter* UnrecognizedParameterParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < Parameter::ParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new UnrecognizedParameterParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::UNRECOGNIZED_PARAMETER, Parameter::ParameterHeaderLength); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } UnrecognizedParameterParameter* UnrecognizedParameterParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t padding) { MS_TRACE(); auto* parameter = new UnrecognizedParameterParameter(const_cast(buffer), bufferLength); // Must always invoke SetLength() after constructing a Serializable with // not fixed length. parameter->SetLength(parameterLength + padding); return parameter; } /* Instance methods. */ UnrecognizedParameterParameter::UnrecognizedParameterParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(Parameter::ParameterHeaderLength); } UnrecognizedParameterParameter::~UnrecognizedParameterParameter() { MS_TRACE(); } void UnrecognizedParameterParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " unrecognized parameter length: %" PRIu16 " (has unrecognized parameter: %s)", GetUnrecognizedParameterLength(), HasUnrecognizedParameter() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } UnrecognizedParameterParameter* UnrecognizedParameterParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new UnrecognizedParameterParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void UnrecognizedParameterParameter::SetUnrecognizedParameter( const uint8_t* parameter, uint16_t parameterLength) { MS_TRACE(); SetVariableLengthValue(parameter, parameterLength); } UnrecognizedParameterParameter* UnrecognizedParameterParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new UnrecognizedParameterParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.cpp ================================================ #define MS_CLASS "RTC::SCTP::ZeroChecksumAcceptableParameter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" namespace RTC { namespace SCTP { /* Class variables. */ // clang-format off const std::unordered_map ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod2String = { { ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE, "NONE" }, { ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS, "SCTP_OVER_DTLS" }, }; // clang-format on /* Class methods. */ ZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::Parse( const uint8_t* buffer, size_t bufferLength) { MS_TRACE(); Parameter::ParameterType parameterType; uint16_t parameterLength; uint8_t padding; if (!Parameter::IsParameter(buffer, bufferLength, parameterType, parameterLength, padding)) { return nullptr; } if (parameterType != Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE) { MS_WARN_DEV("invalid Parameter type"); return nullptr; } return ZeroChecksumAcceptableParameter::ParseStrict( buffer, bufferLength, parameterLength, padding); } ZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::Factory( uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength) { MS_THROW_TYPE_ERROR("buffer too small"); } auto* parameter = new ZeroChecksumAcceptableParameter(buffer, bufferLength); parameter->InitializeHeader( Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE, ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength); // Initialize Alternate Error Detection Method (EDMID) to zero (none). parameter->SetAlternateErrorDetectionMethod(AlternateErrorDetectionMethod::NONE); // No need to invoke SetLength() since parent constructor invoked it. return parameter; } ZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::ParseStrict( const uint8_t* buffer, size_t bufferLength, uint16_t parameterLength, uint8_t /*padding*/) { MS_TRACE(); if (parameterLength != ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength) { MS_WARN_TAG( sctp, "ZeroChecksumAcceptableParameter Length field must be %zu", ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength); return nullptr; } auto* parameter = new ZeroChecksumAcceptableParameter(const_cast(buffer), bufferLength); return parameter; } const std::string& ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethodToString( AlternateErrorDetectionMethod alternateErrorDetectionMethod) { MS_TRACE(); static const std::string Unknown("UNKNOWN"); auto it = ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod2String.find( alternateErrorDetectionMethod); if (it == ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod2String.end()) { return Unknown; } return it->second; } /* Instance methods. */ ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameter(uint8_t* buffer, size_t bufferLength) : Parameter(buffer, bufferLength) { MS_TRACE(); SetLength(ZeroChecksumAcceptableParameter::ZeroChecksumAcceptableParameterHeaderLength); } ZeroChecksumAcceptableParameter::~ZeroChecksumAcceptableParameter() { MS_TRACE(); } void ZeroChecksumAcceptableParameter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); DumpCommon(indentation); MS_DUMP_CLEAN( indentation, " alternate error detection method: %" PRIu32 " (%s)", static_cast(GetAlternateErrorDetectionMethod()), ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethodToString( GetAlternateErrorDetectionMethod()) .c_str()); MS_DUMP_CLEAN(indentation, ""); } ZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::Clone( uint8_t* buffer, size_t bufferLength) const { MS_TRACE(); auto* clonedParameter = new ZeroChecksumAcceptableParameter(buffer, bufferLength); CloneInto(clonedParameter); return clonedParameter; } void ZeroChecksumAcceptableParameter::SetAlternateErrorDetectionMethod( AlternateErrorDetectionMethod alternateErrorDetectionMethod) { MS_TRACE(); Utils::Byte::Set4Bytes( const_cast(GetBuffer()), 4, static_cast(alternateErrorDetectionMethod)); } ZeroChecksumAcceptableParameter* ZeroChecksumAcceptableParameter::SoftClone(const uint8_t* buffer) const { MS_TRACE(); auto* softClonedParameter = new ZeroChecksumAcceptableParameter(const_cast(buffer), GetLength()); SoftCloneInto(softClonedParameter); return softClonedParameter; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/public/AssociationMetrics.cpp ================================================ #define MS_CLASS "RTC::SCTP::AssociationMetrics" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/public/AssociationMetrics.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { /* Instance methods. */ void AssociationMetrics::Dump(int indentation) const { MS_TRACE(); auto peerImplementationStringView = Types::SctpImplementationToString(this->peerImplementation); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " tx packets count: %" PRIu64, this->txPacketsCount); MS_DUMP_CLEAN(indentation, " tx messages count: %" PRIu64, this->txMessagesCount); MS_DUMP_CLEAN(indentation, " rx packets count: %" PRIu64, this->rxPacketsCount); MS_DUMP_CLEAN(indentation, " rx messages count: %" PRIu64, this->rxMessagesCount); MS_DUMP_CLEAN(indentation, " rtx packets count: %" PRIu64, this->rtxPacketsCount); MS_DUMP_CLEAN(indentation, " rtx bytes count: %" PRIu64, this->rtxBytesCount); MS_DUMP_CLEAN(indentation, " current congestion window (bytes): %zu", this->cwndBytes); MS_DUMP_CLEAN(indentation, " smoothed round trip time (ms): %" PRIu64, this->srttMs); MS_DUMP_CLEAN(indentation, " unacked data count: %zu", this->unackDataCount); MS_DUMP_CLEAN( indentation, " peer's last announced receiver window size: %" PRIu32, this->peerRwndBytes); MS_DUMP_CLEAN( indentation, " peer implementation: %.*s", static_cast(peerImplementationStringView.size()), peerImplementationStringView.data()); MS_DUMP_CLEAN( indentation, " uses partial reliability: %s", this->usesPartialReliability ? "yes" : "no"); MS_DUMP_CLEAN( indentation, " uses message interleaving: %s", this->usesMessageInterleaving ? "yes" : "no"); MS_DUMP_CLEAN(indentation, " uses re-config: %s", this->usesReConfig ? "yes" : "no"); MS_DUMP_CLEAN(indentation, " uses zero checksum: %s", this->usesZeroChecksum ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/public/Message.cpp ================================================ #define MS_CLASS "RTC::SCTP::Message" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/public/Message.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { Message::Message(uint16_t streamId, uint32_t ppid, std::vector payload) : streamId(streamId), ppid(ppid), payload(std::move(payload)) { MS_TRACE(); } Message::~Message() { MS_TRACE(); } void Message::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " stream id: %" PRIu16, GetStreamId()); MS_DUMP_CLEAN(indentation, " ppid: %" PRIu32, GetPayloadProtocolId()); MS_DUMP_CLEAN(indentation, " payload length: %zu", GetPayloadLength()); MS_DUMP_CLEAN(indentation, ""); } void Message::SetStreamId(uint16_t streamId) { MS_TRACE(); this->streamId = streamId; ; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/rx/DataTracker.cpp ================================================ #define MS_CLASS "RTC::SCTP::DataTracker" // TODO: SCTP: COMMENT #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/rx/DataTracker.hpp" #include "Logger.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include namespace RTC { namespace SCTP { DataTracker::DataTracker(BackoffTimerHandleInterface* delayedAckTimer, uint32_t remoteInitialTsn) : delayedAckTimer(delayedAckTimer), lastCumulativeAckedTsn(this->tsnUnwrapper.Unwrap(remoteInitialTsn - 1)) { MS_TRACE(); } bool DataTracker::IsTsnValid(uint32_t tsn) const { MS_TRACE(); // Note that this method doesn't return `false` for old DATA/I-DATA chunks, // as those are actually valid, and receiving those may affect the // generated SACK response (by setting "duplicate TSNs"). const Types::UnwrappedTsn unwrappedTsn = this->tsnUnwrapper.PeekUnwrap(tsn); const uint32_t difference = Types::UnwrappedTsn::Difference(unwrappedTsn, this->lastCumulativeAckedTsn); if (difference > DataTracker::MaxAcceptedOutstandingFragments) { return false; } return true; } bool DataTracker::Observe(uint32_t tsn, bool immediateAck) { MS_TRACE(); const Types::UnwrappedTsn unwrappedTsn = this->tsnUnwrapper.Unwrap(tsn); // `IsTsnValid()` must be called prior to calling this method. MS_ASSERT( Types::UnwrappedTsn::Difference(unwrappedTsn, this->lastCumulativeAckedTsn) <= DataTracker::MaxAcceptedOutstandingFragments, "Types::UnwrappedTsn::Difference(unwrappedTsn, this->lastCumulativeAckedTsn) > DataTracker::MaxAcceptedOutstandingFragments"); bool isDuplicate = false; // Old chunk already seen before? if (unwrappedTsn <= this->lastCumulativeAckedTsn) { if (this->duplicateTsns.size() < DataTracker::MaxDuplicateTsnReported) { this->duplicateTsns.insert(unwrappedTsn.Wrap()); } // https://datatracker.ietf.org/doc/html/rfc9260#section-6.2 // // "When a packet arrives with duplicate DATA chunk(s) and with no new // DATA chunk(s), the endpoint MUST immediately send a SACK with no // delay. If a packet arrives with duplicate DATA chunk(s) bundled with // new DATA chunks, the endpoint MAY immediately send a SACK." UpdateAckState(AckState::IMMEDIATE, "duplicate data"); isDuplicate = true; } else { if (unwrappedTsn == this->lastCumulativeAckedTsn.GetNextValue()) { this->lastCumulativeAckedTsn = unwrappedTsn; // The cumulative acked `tsn` may be moved even further, if a gap was // filled. if ( !this->additionalTsnBlocks.IsEmpty() && this->additionalTsnBlocks.Front().firstTsn == this->lastCumulativeAckedTsn.GetNextValue()) { this->lastCumulativeAckedTsn = this->additionalTsnBlocks.Front().lastTsn; this->additionalTsnBlocks.PopFront(); } } else { const bool inserted = this->additionalTsnBlocks.Add(unwrappedTsn); if (!inserted) { // Already seen before. if (this->duplicateTsns.size() < DataTracker::MaxDuplicateTsnReported) { this->duplicateTsns.insert(unwrappedTsn.Wrap()); } // https://datatracker.ietf.org/doc/html/rfc9260#section-6.2 // // "When a packet arrives with duplicate DATA chunk(s) and with no // new DATA chunk(s), the endpoint MUST immediately send a SACK with // no delay. If a packet arrives with duplicate DATA chunk(s) // bundled with new DATA chunks, the endpoint MAY immediately send a // SACK." // // No need to do this. SACKs are sent immediately on packet loss below. isDuplicate = true; } } } // https://tools.ietf.org/html/rfc9260#section-6.7 // // "Upon the reception of a new DATA chunk, an endpoint shall examine the // continuity of the TSNs received. If the endpoint detects a gap in the // received DATA chunk sequence, it SHOULD send a SACK with Gap Ack Blocks // immediately. The data receiver continues sending a SACK after receipt // of each SCTP packet that doesn't fill the gap." if (!this->additionalTsnBlocks.IsEmpty()) { UpdateAckState(AckState::IMMEDIATE, "packet loss"); } // https://tools.ietf.org/html/rfc7053#section-5.2 // // "Upon receipt of an SCTP packet containing a DATA chunk with the I bit // set, the receiver SHOULD NOT delay the sending of the corresponding // SACK chunk, i.e., the receiver SHOULD immediately respond with the // corresponding SACK chunk." if (immediateAck) { UpdateAckState(AckState::IMMEDIATE, "immediate-ack bit set"); } if (!this->packetSeen) { // https://tools.ietf.org/html/rfc9260#section-5.1 // // "After the reception of the first DATA chunk in an association the // endpoint MUST immediately respond with a SACK to acknowledge the DATA // chunk." this->packetSeen = true; UpdateAckState(AckState::IMMEDIATE, "first data chunk"); } // https://tools.ietf.org/html/rfc9260#section-6.2 // // "Specifically, an acknowledgement SHOULD be generated for at least // every second packet (not every second DATA chunk) received, and SHOULD // be generated within 200 ms of the arrival of any unacknowledged DATA // chunk." if (this->ackState == AckState::IDLE) { UpdateAckState(AckState::BECOMING_DELAYED, "received data when idle"); } else if (this->ackState == AckState::DELAYED) { UpdateAckState(AckState::IMMEDIATE, "received data when already delayed"); } return !isDuplicate; } void DataTracker::ObservePacketEnd() { MS_TRACE(); if (this->ackState == AckState::BECOMING_DELAYED) { UpdateAckState(AckState::DELAYED, "packet end"); } } bool DataTracker::HandleForwardTsn(uint32_t newCumulativeTsn) { // Forward-TSN is sent to make the receiver (this association) "forget" // about partly received (or not received at all) data, up until // `newCumulativeTsn`. const Types::UnwrappedTsn unwrappedTsn = this->tsnUnwrapper.Unwrap(newCumulativeTsn); const Types::UnwrappedTsn prevLastCumAckTsn = this->lastCumulativeAckedTsn; // Old chunk already seen before? if (unwrappedTsn <= this->lastCumulativeAckedTsn) { // https://tools.ietf.org/html/rfc3758#section-3.6 // // "Note, if the "New Cumulative TSN" value carried in the arrived // FORWARD-TSN chunk is found to be behind or at the current cumulative // TSN point, the data receiver MUST treat this FORWARD TSN as out-of-date // and MUST NOT update its Cumulative TSN. The receiver SHOULD send a // SACK to its peer (the sender of the FORWARD TSN) since such a duplicate // may indicate the previous SACK was lost in the network." UpdateAckState(AckState::IMMEDIATE, "Forward TSN new cumulative tsn was behind"); return false; } // https://tools.ietf.org/html/rfc3758#section-3.6 // // "When a FORWARD TSN chunk arrives, the data receiver MUST first update // its cumulative TSN point to the value carried in the FORWARD TSN chunk, // and then MUST further advance its cumulative TSN point locally if // possible" // The `newCumulativeTsn` will become the current // `this->lastCumulativeAckedTsn`, and if there have been prior "gaps" // that are now overlapping with the new value, remove them. this->lastCumulativeAckedTsn = unwrappedTsn; this->additionalTsnBlocks.EraseTo(unwrappedTsn); // See if the `this->lastCumulativeAckedTsn` can be moved even further. if ( !this->additionalTsnBlocks.IsEmpty() && this->additionalTsnBlocks.Front().firstTsn == this->lastCumulativeAckedTsn.GetNextValue()) { this->lastCumulativeAckedTsn = this->additionalTsnBlocks.Front().lastTsn; this->additionalTsnBlocks.PopFront(); } MS_DEBUG_DEV( "Forward TSN [prevLastCumAckTsn:%" PRIu32 ", newCumulativeTsn:%" PRIu32 ", lastCumulativeAckedTsn:%" PRIu32, prevLastCumAckTsn.Wrap(), newCumulativeTsn, this->lastCumulativeAckedTsn.Wrap()); // https://tools.ietf.org/html/rfc3758#section-3.6 // // "Any time a FORWARD TSN chunk arrives, for the purposes of sending a // SACK, the receiver MUST follow the same rules as if a DATA chunk had // been received (i.e., follow the delayed sack rules specified in ...". if (this->ackState == AckState::IDLE) { UpdateAckState(AckState::BECOMING_DELAYED, "received forward TSN when idle"); } else if (this->ackState == AckState::DELAYED) { UpdateAckState(AckState::IMMEDIATE, "received forward TSN when already delayed"); } return true; } bool DataTracker::ShouldSendAck(bool alsoIfDelayed) { MS_TRACE(); if ( this->ackState == AckState::IMMEDIATE || (alsoIfDelayed && (this->ackState == AckState::BECOMING_DELAYED || this->ackState == AckState::DELAYED))) { UpdateAckState(AckState::IDLE, "should send SACK"); return true; } return false; } void DataTracker::ForceImmediateSack() { MS_TRACE(); UpdateAckState(AckState::IMMEDIATE, "force immediate SACK"); } bool DataTracker::WillIncreaseCumAckTsn(uint32_t tsn) const { MS_TRACE(); const Types::UnwrappedTsn unwrappedTsn = this->tsnUnwrapper.PeekUnwrap(tsn); return unwrappedTsn == this->lastCumulativeAckedTsn.GetNextValue(); } void DataTracker::AddSackSelectiveAck(Packet* packet, size_t aRwnd) { MS_TRACE(); // Note that in SCTP, the receiver side is allowed to discard received data // and signal that to the sender, but only chunks that have previously been // reported in the gap-ack-blocks. However, this implementation will never // do that. So this SACK produced is more like a NR-SACK as explained in // https://ieeexplore.ieee.org/document/4697037 and which there is an RFC // draft at https://tools.ietf.org/html/draft-tuexen-tsvwg-sctp-multipath-17. auto* sackChunk = packet->BuildChunkInPlace(); sackChunk->SetCumulativeTsnAck(this->lastCumulativeAckedTsn.Wrap()); sackChunk->SetAdvertisedReceiverWindowCredit(aRwnd); const auto& tsnBlocks = this->additionalTsnBlocks.GetBlocks(); for (size_t i{ 0 }; i < tsnBlocks.size() && i < DataTracker::MaxGapAckBlocksReported; ++i) { const auto startDiff = Types::UnwrappedTsn::Difference(tsnBlocks[i].firstTsn, this->lastCumulativeAckedTsn); const auto endDiff = Types::UnwrappedTsn::Difference(tsnBlocks[i].lastTsn, this->lastCumulativeAckedTsn); sackChunk->AddAckBlock(startDiff, endDiff); } std::set duplicateTsns; this->duplicateTsns.swap(duplicateTsns); for (const uint32_t tsn : duplicateTsns) { sackChunk->AddDuplicateTsn(tsn); } sackChunk->Consolidate(); } void DataTracker::HandleDelayedAckTimerExpiry() { MS_TRACE(); UpdateAckState(AckState::IMMEDIATE, "delayed ack timer expired"); } void DataTracker::UpdateAckState(AckState newAckState, std::string_view reason) { MS_TRACE(); if (newAckState != this->ackState) { const auto& previousAckStateStrView = DataTracker::AckStateToString(this->ackState); const auto& newAckStateStrView = DataTracker::AckStateToString(newAckState); #if MS_LOG_DEV_LEVEL == 3 MS_DEBUG_DEV( "ack state changed from %.*s to %.*s due to %.*s", static_cast(previousAckStateStrView.size()), previousAckStateStrView.data(), static_cast(newAckStateStrView.size()), newAckStateStrView.data(), static_cast(reason.size()), reason.data()); #endif if (this->ackState == AckState::DELAYED) { this->delayedAckTimer->Stop(); } else if (newAckState == AckState::DELAYED) { this->delayedAckTimer->Start(); } this->ackState = newAckState; } } bool DataTracker::AdditionalTsnBlocks::Add(Types::UnwrappedTsn tsn) { MS_TRACE(); // Find any block to expand. It will look for any block that includes (also // when expanded) the provided `tsn`. It will return the block that is greater // than, or equal to `tsn`. const auto it = std::ranges::lower_bound( this->blocks, tsn, std::less{}, [](const TsnRange& e) { return e.lastTsn.GetNextValue(); }); // No matching block found. There is no greater than, or equal block, // which means that this TSN is greater than any block. It can then be // inserted at the end. if (it == this->blocks.end()) { this->blocks.emplace_back(tsn, tsn); return true; } // `tsn` is already in this block. if (tsn >= it->firstTsn && tsn <= it->lastTsn) { return false; } // This block can be expanded to the right, or merged with the next. if (it->lastTsn.GetNextValue() == tsn) { const auto nextIt = it + 1; if (nextIt != this->blocks.end() && tsn.GetNextValue() == nextIt->firstTsn) { // Expanding it would make it adjacent to next block, merge those. it->lastTsn = nextIt->lastTsn; this->blocks.erase(nextIt); return true; } // Expand to the right. it->lastTsn = tsn; return true; } // This block can be expanded to the left. Merging to the left would've // been covered by the above "merge to the right". Both blocks (expand a // right-most block to the left and expand a left-most block to the right) // would match, but the left-most would be returned by std::lower_bound. if (it->firstTsn == tsn.GetNextValue()) { MS_ASSERT( it == this->blocks.begin() || (it - 1)->lastTsn.GetNextValue() != tsn, "got wrong iterator"); it->firstTsn = tsn; return true; } // Need to create a new block in the middle. this->blocks.emplace(it, tsn, tsn); return true; } void DataTracker::AdditionalTsnBlocks::EraseTo(Types::UnwrappedTsn tsn) { MS_TRACE(); // Find the block that is greater than or equals `tsn`. const auto it = std::ranges::lower_bound( this->blocks, tsn, std::less{}, &TsnRange::lastTsn); // The block that is found is greater or equal (or possibly ::end(), when // no block is greater or equal). All blocks before this block can be // safely removed. The TSN might be within this block, so possibly truncate // it. const bool tsnIsWithinBlock = it != this->blocks.end() && tsn >= it->firstTsn; this->blocks.erase(this->blocks.begin(), it); if (tsnIsWithinBlock) { this->blocks.front().firstTsn = tsn.GetNextValue(); } } void DataTracker::AdditionalTsnBlocks::PopFront() { MS_TRACE(); MS_ASSERT(!this->blocks.empty(), "this->blocks is empty"); this->blocks.erase(this->blocks.begin()); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/rx/InterleavedReassemblyStreams.cpp ================================================ #define MS_CLASS "RTC::SCTP::InterleavedReassemblyStreams" // TODO: SCTP: COMMENT #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/rx/InterleavedReassemblyStreams.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { InterleavedReassemblyStreams::InterleavedReassemblyStreams( ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage) : onAssembledMessage(std::move(onAssembledMessage)) { MS_TRACE(); } int32_t InterleavedReassemblyStreams::AddData(Types::UnwrappedTsn tsn, UserData data) { MS_TRACE(); return GetOrCreateStream(FullStreamId(data.IsUnordered(), data.GetStreamId())) .AddData(tsn, std::move(data)); } size_t InterleavedReassemblyStreams::HandleForwardTsn( Types::UnwrappedTsn /*newCumulativeTsn*/, std::span skippedStreams) { MS_TRACE(); size_t bytesRemoved = 0; for (const auto& skippedStream : skippedStreams) { bytesRemoved += GetOrCreateStream(FullStreamId(skippedStream.unordered, skippedStream.streamId)) .EraseTo(skippedStream.mid); } return bytesRemoved; } void InterleavedReassemblyStreams::ResetStreams(std::span streamIds) { MS_TRACE(); if (streamIds.empty()) { for (auto& [fullStreamId, stream] : this->streams) { MS_DEBUG_DEV("resetting implicit stream [streamId:%" PRIu16 "]", fullStreamId.streamId); stream.Reset(); } } else { for (const auto streamId : streamIds) { MS_DEBUG_DEV("resetting explicit stream [streamId:%" PRIu16 "]", streamId); GetOrCreateStream(FullStreamId(/*unordered*/ true, streamId)).Reset(); GetOrCreateStream(FullStreamId(/*unordered*/ false, streamId)).Reset(); } } } InterleavedReassemblyStreams::Stream& InterleavedReassemblyStreams::GetOrCreateStream( const FullStreamId& streamId) { MS_TRACE(); auto it = this->streams.find(streamId); if (it == this->streams.end()) { it = this->streams .emplace( std::piecewise_construct, std::forward_as_tuple(streamId), std::forward_as_tuple(streamId, this)) .first; } auto& stream = it->second; return stream; } int32_t InterleavedReassemblyStreams::Stream::AddData(Types::UnwrappedTsn tsn, UserData data) { MS_TRACE(); MS_ASSERT( data.IsUnordered() == this->fullStreamId.unordered, "data.IsUnordered() != this->streamId.unordered"); MS_ASSERT( data.GetStreamId() == this->fullStreamId.streamId, "data.GetStreamId() != this->fullStreamId.streamId"); auto queuedBytes = static_cast(data.GetPayloadLength()); const Types::UnwrappedMid mid = this->midUnwrapper.Unwrap(data.GetMessageId()); const uint32_t fsn = data.GetFragmentSequenceNumber(); // Avoid inserting it into any map if it can be delivered directly. if (this->fullStreamId.unordered && data.IsBeginning() && data.IsEnd()) { AssembleMessage(tsn, std::move(data)); return 0; } else if (!this->fullStreamId.unordered && mid == this->nextMid && data.IsBeginning() && data.IsEnd()) { AssembleMessage(tsn, std::move(data)); this->nextMid.Increment(); // This might unblock assembling more messages. return -TryToAssembleMessages(); } // Slow path. const auto [unused, inserted] = this->chunksByMid[mid].emplace(fsn, std::make_pair(tsn, std::move(data))); if (!inserted) { return 0; } if (this->fullStreamId.unordered) { queuedBytes -= TryToAssembleMessage(mid); } else { if (mid == this->nextMid) { queuedBytes -= TryToAssembleMessages(); } } return queuedBytes; } size_t InterleavedReassemblyStreams::Stream::EraseTo(uint32_t mid) { MS_TRACE(); const Types::UnwrappedMid unwrappedMid = this->midUnwrapper.Unwrap(mid); size_t removedBytes = 0; auto it = this->chunksByMid.begin(); while (it != this->chunksByMid.end() && it->first <= unwrappedMid) { removedBytes += std::accumulate( it->second.begin(), it->second.end(), 0, [](size_t acc, const auto& i) { const auto& data = i.second.second; return acc + data.GetPayloadLength(); }); it = this->chunksByMid.erase(it); } if (!this->fullStreamId.unordered) { // For ordered streams, erasing a message might suddenly unblock that // queue and allow it to deliver any following received messages. if (unwrappedMid >= this->nextMid) { this->nextMid = unwrappedMid.GetNextValue(); } removedBytes += TryToAssembleMessages(); } return removedBytes; } size_t InterleavedReassemblyStreams::Stream::TryToAssembleMessage(Types::UnwrappedMid mid) { MS_TRACE(); const auto it = this->chunksByMid.find(mid); if (it == this->chunksByMid.end()) { MS_DEBUG_DEV("trying to assemble message [mid:%" PRIu32 "]", mid.Wrap()); return 0; } ChunkMap& chunks = it->second; if (!chunks.begin()->second.second.IsBeginning() || !chunks.rbegin()->second.second.IsEnd()) { MS_DEBUG_DEV( "cannot assemble message, missing beggining or end [mid:%" PRIu32 "]", mid.Wrap()); return 0; } // NOTE: This is uint32_t - uint32_t casted to uint64_t. Not very nice but // it works. const int64_t fnsDiff = chunks.rbegin()->first - chunks.begin()->first; if (fnsDiff != (static_cast(chunks.size()) - 1)) { MS_DEBUG_DEV( "cannot assemble message, not all chunks exist [has:%zu, expected:%" PRIi64 "]", chunks.size(), fnsDiff + 1); return 0; } const size_t removedBytes = AssembleMessage(chunks); MS_DEBUG_DEV("message assembled [mid:%" PRIu32 ", removedBytes:%zu]", mid.Wrap(), removedBytes); this->chunksByMid.erase(mid); return removedBytes; } size_t InterleavedReassemblyStreams::Stream::TryToAssembleMessages() { MS_TRACE(); size_t removedBytes = 0; for (;;) { const size_t removedBytesThisIt = TryToAssembleMessage(this->nextMid); if (removedBytesThisIt == 0) { break; } removedBytes += removedBytesThisIt; this->nextMid.Increment(); } return removedBytes; } size_t InterleavedReassemblyStreams::Stream::AssembleMessage(Types::UnwrappedTsn tsn, UserData data) { MS_TRACE(); const size_t payloadLength = data.GetPayloadLength(); const auto streamId = data.GetStreamId(); const auto pip = data.GetPayloadProtocolId(); const Types::UnwrappedTsn tsns[1] = { tsn }; Message message( streamId, pip, // NOTE: clang-tidy doesn't understand that this is fine. // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) std::move(data).ReleasePayload()); this->parent.onAssembledMessage(tsns, std::move(message)); return payloadLength; } size_t InterleavedReassemblyStreams::Stream::AssembleMessage(ChunkMap& tsnChunks) { MS_TRACE(); const size_t count = tsnChunks.size(); // Fast path - zero-copy. if (count == 1) { return AssembleMessage( tsnChunks.begin()->second.first, std::move(tsnChunks.begin()->second.second)); } // Slow path - will need to concatenate the payload. std::vector tsns; std::vector payload; const size_t payloadLength = std::accumulate( tsnChunks.begin(), tsnChunks.end(), 0, [](size_t acc, const auto& i) { const auto& data = i.second.second; return acc + data.GetPayloadLength(); }); payload.reserve(payloadLength); tsns.reserve(count); for (auto& item : tsnChunks) { const Types::UnwrappedTsn tsn = item.second.first; UserData& data = item.second.second; tsns.push_back(tsn); payload.insert(payload.end(), data.GetPayload().begin(), data.GetPayload().end()); } const UserData& data = tsnChunks.begin()->second.second; Message message(data.GetStreamId(), data.GetPayloadProtocolId(), std::move(payload)); this->parent.onAssembledMessage(tsns, std::move(message)); return payloadLength; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/rx/ReassemblyQueue.cpp ================================================ #define MS_CLASS "RTC::SCTP::ReassemblyQueue" // TODO: SCTP: COMMENT #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/rx/ReassemblyQueue.hpp" #include "Logger.hpp" #include "RTC/SCTP/rx/InterleavedReassemblyStreams.hpp" #include "RTC/SCTP/rx/TraditionalReassemblyStreams.hpp" #include namespace RTC { namespace SCTP { ReassemblyQueue::ReassemblyQueue(size_t maxLengthBytes, bool useMessageInterleaving) : maxLengthBytes(maxLengthBytes), watermarkBytes(this->maxLengthBytes * ReassemblyQueue::HighWatermarkLimit), reassemblyStreams(CreateReassemblyStreams( [this](std::span tsns, Message message) { AddReassembledMessage(tsns, std::move(message)); }, useMessageInterleaving)) { MS_TRACE(); } ReassemblyQueue::~ReassemblyQueue() { MS_TRACE(); } void ReassemblyQueue::AddData(uint32_t tsn, UserData data) { MS_TRACE(); MS_DEBUG_DEV( "added data [tsn:%" PRIu32 ", streamId:%" PRIu16 ", mid:%" PRIu32 ", fsn:%" PRIu32 ", type:%s]", tsn, data.GetStreamId(), data.GetMessageId(), data.GetFragmentSequenceNumber(), (data.IsBeginning() && data.IsEnd() ? "complete" : data.IsBeginning() ? "first" : data.IsEnd() ? "last" : "middle")); const Types::UnwrappedTsn unwrappedTsn = this->tsnUnwrapper.Unwrap(tsn); // If a stream reset has been received with a "sender's last assigned tsn" // in the future, the association is in "deferred reset processing" mode // and must buffer chunks until it's exited. if ( this->deferredResetStreams.has_value() && unwrappedTsn > this->deferredResetStreams->senderLastAssignedTsn && this->deferredResetStreams->streamIds.contains(data.GetStreamId())) { MS_DEBUG_DEV( "deferrink chunk [tsn:%" PRIu32 ", streamId:%" PRIu16 "] until tsn %" PRIu32, tsn, data.GetStreamId(), this->deferredResetStreams->senderLastAssignedTsn.Wrap()); // https://tools.ietf.org/html/rfc6525#section-5.2.2 // // "In this mode, any data arriving with a TSN larger than the Sender's // Last Assigned TSN for the affected stream(s) MUST be queued locally // and held until the cumulative acknowledgment point reaches the // Sender's Last Assigned TSN." this->queuedBytes += data.GetPayloadLength(); // NOTE: We use C++20 so we don't support `std::move_only_function` and // hence we need to move UserData to a shared pointer. Otherwise it will // fail to compile because `std::function` doesn't accept move-only // callables and `UserData` has its copy constructor deleted, so the // resulting lambda is not copyable and `std::function` will reject it. auto sharedData = std::make_shared(std::move(data)); this->deferredResetStreams->deferredActions.emplace_back( [this, tsn, sharedData]() mutable { this->queuedBytes -= sharedData->GetPayloadLength(); AddData(tsn, std::move(*sharedData)); }); // TODO: Once we upgrade to C++23, replace the above with: // this->deferredResetStreams->deferredActions.emplace_back( // [this, tsn, data = std::move(data)]() mutable // { // this->queuedBytes -= data.GetPayloadLength(); // AddData(tsn, std::move(data)); // }); } else { this->queuedBytes += this->reassemblyStreams->AddData(unwrappedTsn, std::move(data)); } // https://datatracker.ietf.org/doc/html/rfc9260#section-6.9 // // "If the data receiver runs out of buffer space while still waiting for // more fragments to complete the reassembly of the message, it SHOULD // dispatch part of its inbound message through a partial delivery API // (see Section 11), freeing some of its receive buffer space so that the // rest of the message can be received." // TODO: dcsctp: Support EOR flag and partial delivery? AssertIsConsistent(); } std::optional ReassemblyQueue::GetNextMessage() { MS_TRACE(); if (this->reassembledMessages.empty()) { return std::nullopt; } Message message = std::move(this->reassembledMessages.front()); this->reassembledMessages.pop_front(); this->queuedBytes -= message.GetPayloadLength(); return message; } void ReassemblyQueue::HandleForwardTsn( uint32_t newCumulativeTsn, std::span skippedStreams) { MS_TRACE(); const Types::UnwrappedTsn tsn = this->tsnUnwrapper.Unwrap(newCumulativeTsn); if (this->deferredResetStreams.has_value() && tsn > this->deferredResetStreams->senderLastAssignedTsn) { MS_DEBUG_DEV("forward TSN to %" PRIu32 ", deferring", tsn.Wrap()); this->deferredResetStreams->deferredActions.emplace_back( [this, newCumulativeTsn, skippedStreams2 = std::vector( skippedStreams.begin(), skippedStreams.end())] { HandleForwardTsn(newCumulativeTsn, skippedStreams2); }); AssertIsConsistent(); return; } MS_DEBUG_DEV("forward TSN to %" PRIu32 ", performing", tsn.Wrap()); this->queuedBytes -= this->reassemblyStreams->HandleForwardTsn(tsn, skippedStreams); AssertIsConsistent(); } void ReassemblyQueue::ResetStreamsAndLeaveDeferredReset(std::span streamIds) { MS_TRACE(); #if MS_LOG_DEV_LEVEL == 3 std::string streamIdList; for (const auto streamId : streamIds) { if (!streamIdList.empty()) { streamIdList += ','; } streamIdList += std::to_string(streamId); } MS_DEBUG_DEV("resetting streams [streamIds:%s]", streamIdList.c_str()); #endif // https://tools.ietf.org/html/rfc6525#section-5.2.2 // // "streams MUST be reset to 0 as the next expected SSN." this->reassemblyStreams->ResetStreams(streamIds); if (this->deferredResetStreams.has_value()) { MS_DEBUG_DEV( "leaving deferred reset processing, feeding back %zu actions", this->deferredResetStreams->deferredActions.size()); // https://tools.ietf.org/html/rfc6525#section-5.2.2 // // "Any queued TSNs (queued at step E2) MUST now be released and // processed normally." auto deferredActions = std::move(this->deferredResetStreams->deferredActions); this->deferredResetStreams = std::nullopt; for (auto& action : deferredActions) { action(); } } AssertIsConsistent(); } void ReassemblyQueue::EnterDeferredReset( uint32_t senderLastAssignedTsn, std::span streamIds) { MS_TRACE(); if (!this->deferredResetStreams.has_value()) { return; } MS_DEBUG_DEV( "entering deferred reset [senderLastAssignedTsn:%" PRIu32 "]", senderLastAssignedTsn); this->deferredResetStreams = std::make_optional( this->tsnUnwrapper.Unwrap(senderLastAssignedTsn), std::set(streamIds.begin(), streamIds.end())); AssertIsConsistent(); } std::unique_ptr ReassemblyQueue::CreateReassemblyStreams( ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage, bool useMessageInterleaving) { MS_TRACE(); if (useMessageInterleaving) { return std::make_unique(std::move(onAssembledMessage)); } else { return std::make_unique(std::move(onAssembledMessage)); } } void ReassemblyQueue::AddReassembledMessage(std::span tsns, Message message) { MS_TRACE(); #if MS_LOG_DEV_LEVEL == 3 std::string tsnList; for (const auto tsn : tsns) { if (!tsnList.empty()) { tsnList += ','; } tsnList += std::to_string(tsn.Wrap()); } MS_DEBUG_DEV( "resetting streams [ppid:%" PRIu32 ", payloadLength:%zu, tsns:%s]", message.GetPayloadProtocolId(), message.GetPayloadLength(), tsnList.c_str()); #endif this->queuedBytes += message.GetPayloadLength(); this->reassembledMessages.emplace_back(std::move(message)); } void ReassemblyQueue::AssertIsConsistent() const { MS_TRACE(); // Allow this->queuedBytes to be larger than this->maxLengthBytes, as it's // not actively enforced in this class. But in case it wraps around // (becomes negative, but as it's unsigned, that would wrap to very big), // this would trigger. MS_ASSERT( this->queuedBytes <= 2 * this->maxLengthBytes, "this->queuedBytes > 2 * this->maxLengthBytes"); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/rx/TraditionalReassemblyStreams.cpp ================================================ #define MS_CLASS "RTC::SCTP::TraditionalReassemblyStreams" // TODO: SCTP: COMMENT #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/rx/TraditionalReassemblyStreams.hpp" #include "Logger.hpp" namespace RTC { namespace SCTP { TraditionalReassemblyStreams::TraditionalReassemblyStreams( ReassemblyStreamsInterface::OnAssembledMessage onAssembledMessage) : onAssembledMessage(std::move(onAssembledMessage)) { MS_TRACE(); } int32_t TraditionalReassemblyStreams::AddData(Types::UnwrappedTsn tsn, UserData data) { MS_TRACE(); if (data.IsUnordered()) { const auto it = this->unorderedStreams.try_emplace(data.GetStreamId(), this).first; auto& stream = it->second; return stream.AddData(tsn, std::move(data)); } const auto it = this->orderedStreams.try_emplace(data.GetStreamId(), this).first; auto& stream = it->second; return stream.AddData(tsn, std::move(data)); } size_t TraditionalReassemblyStreams::HandleForwardTsn( Types::UnwrappedTsn newCumulativeTsn, std::span skippedStreams) { MS_TRACE(); size_t removedBytes = 0; // The `skippedStreams` only cover ordered messages - need to iterate all // unordered streams manually to remove those chunks. for (auto& [unused, stream] : this->unorderedStreams) { removedBytes += stream.EraseTo(newCumulativeTsn); } for (const auto& skippedStream : skippedStreams) { const auto it = this->orderedStreams.try_emplace(skippedStream.streamId, this).first; auto& stream = it->second; removedBytes += stream.EraseTo(skippedStream.ssn); } return removedBytes; } void TraditionalReassemblyStreams::ResetStreams(std::span streamIds) { MS_TRACE(); if (streamIds.empty()) { for (auto& [streamId, stream] : this->orderedStreams) { MS_DEBUG_DEV("resetting implicit stream [streamId:%" PRIu16 "]", streamId); stream.Reset(); } } else { for (const auto streamId : streamIds) { const auto it = this->orderedStreams.find(streamId); if (it != this->orderedStreams.end()) { MS_DEBUG_DEV("resetting explicit stream [streamId:%" PRIu16 "]", streamId); auto& stream = it->second; stream.Reset(); } } } } size_t TraditionalReassemblyStreams::StreamBase::AssembleMessage( const ChunkMap::iterator start, const ChunkMap::iterator end) { MS_TRACE(); const size_t count = std::distance(start, end); // Fast path - zero-copy if (count == 1) { return AssembleMessage(start->first, std::move(start->second)); } // Slow path - will need to concatenate the payload. std::vector tsns; std::vector payload; const size_t payloadLength = std::accumulate( start, end, 0, [](size_t acc, const auto& i) { const auto& data = i.second; return acc + data.GetPayloadLength(); }); tsns.reserve(count); payload.reserve(payloadLength); for (auto it = start; it != end; ++it) { const auto tsn = it->first; auto& data = it->second; tsns.push_back(tsn); payload.insert(payload.end(), data.GetPayload().begin(), data.GetPayload().end()); } const auto& startData = start->second; Message message(startData.GetStreamId(), startData.GetPayloadProtocolId(), std::move(payload)); this->parent.onAssembledMessage(tsns, std::move(message)); return payloadLength; } size_t TraditionalReassemblyStreams::StreamBase::AssembleMessage( Types::UnwrappedTsn tsn, UserData data) { MS_TRACE(); // Fast path - zero-copy. const size_t payloadLength = data.GetPayloadLength(); const auto streamId = data.GetStreamId(); const auto pip = data.GetPayloadProtocolId(); const Types::UnwrappedTsn tsns[1] = { tsn }; Message message( streamId, pip, // NOTE: clang-tidy doesn't understand that this is fine. // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) std::move(data).ReleasePayload()); this->parent.onAssembledMessage(tsns, std::move(message)); return payloadLength; } int32_t TraditionalReassemblyStreams::OrderedStream::AddData(Types::UnwrappedTsn tsn, UserData data) { MS_TRACE(); const auto queuedBytes = static_cast(data.GetPayloadLength()); const Types::UnwrappedSsn ssn = this->ssnUnwrapper.Unwrap(data.GetStreamSequenceNumber()); if (ssn == this->nextSsn) { return queuedBytes - TryToAssembleMessagesFastpath(ssn, tsn, std::move(data)); } const auto [it, inserted] = this->chunksBySsn[ssn].emplace(tsn, std::move(data)); if (!inserted) { return 0; } return queuedBytes; } size_t TraditionalReassemblyStreams::OrderedStream::EraseTo(uint16_t ssn) { MS_TRACE(); Types::UnwrappedSsn unwrappedSsn = this->ssnUnwrapper.Unwrap(ssn); const auto endIt = this->chunksBySsn.upper_bound(unwrappedSsn); size_t removedBytes = std::accumulate( this->chunksBySsn.begin(), endIt, 0, [](size_t acc1, const auto& i1) { return acc1 + std::accumulate( i1.second.begin(), i1.second.end(), 0, [](size_t acc2, const auto& i2) { const auto& data = i2.second; return acc2 + data.GetPayloadLength(); }); }); this->chunksBySsn.erase(this->chunksBySsn.begin(), endIt); if (unwrappedSsn >= this->nextSsn) { unwrappedSsn.Increment(); this->nextSsn = unwrappedSsn; } removedBytes += TryToAssembleMessages(); return removedBytes; } size_t TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessage() { MS_TRACE(); if (this->chunksBySsn.empty() || this->chunksBySsn.begin()->first != this->nextSsn) { return 0; } ChunkMap& chunks = this->chunksBySsn.begin()->second; if (!chunks.begin()->second.IsBeginning() || !chunks.rbegin()->second.IsEnd()) { return 0; } const uint32_t tsnDiff = Types::UnwrappedTsn::Difference(chunks.rbegin()->first, chunks.begin()->first); if (tsnDiff != chunks.size() - 1) { return 0; } const size_t assembledBytes = AssembleMessage(chunks.begin(), chunks.end()); this->chunksBySsn.erase(this->chunksBySsn.begin()); this->nextSsn.Increment(); return assembledBytes; } size_t TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessages() { MS_TRACE(); size_t assembledBytes = 0; for (;;) { const size_t assembledBytesThisIter = TryToAssembleMessage(); if (assembledBytesThisIter == 0) { break; } assembledBytes += assembledBytesThisIter; } return assembledBytes; } size_t TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessagesFastpath( Types::UnwrappedSsn ssn, Types::UnwrappedTsn tsn, UserData data) { MS_TRACE(); MS_ASSERT(ssn == this->nextSsn, "ssn != this->nextSsn"); size_t assembledBytes = 0; if (data.IsBeginning() && data.IsEnd()) { assembledBytes += AssembleMessage(tsn, std::move(data)); this->nextSsn.Increment(); } else { const size_t queuedBytes = data.GetPayloadLength(); auto [it, inserted] = this->chunksBySsn[ssn].emplace(tsn, std::move(data)); // Not actually assembled, but deduplicated meaning queued size doesn't // include this message. if (!inserted) { return queuedBytes; } } return assembledBytes + TryToAssembleMessages(); } int32_t TraditionalReassemblyStreams::UnorderedStream::AddData(Types::UnwrappedTsn tsn, UserData data) { MS_TRACE(); // Fastpath for already assembled chunks. if (data.IsBeginning() && data.IsEnd()) { AssembleMessage(tsn, std::move(data)); return 0; } auto queuedBytes = static_cast(data.GetPayloadLength()); const auto [it, inserted] = this->chunks.emplace(tsn, std::move(data)); if (!inserted) { return 0; } queuedBytes -= TryToAssembleMessage(it); return queuedBytes; } size_t TraditionalReassemblyStreams::UnorderedStream::EraseTo(Types::UnwrappedTsn tsn) { MS_TRACE(); const auto endIt = this->chunks.upper_bound(tsn); const size_t removedBytes = std::accumulate( this->chunks.begin(), endIt, 0, [](size_t acc, const auto& i) { const auto& data = i.second; return acc + data.GetPayloadLength(); }); this->chunks.erase(this->chunks.begin(), endIt); return removedBytes; } size_t TraditionalReassemblyStreams::UnorderedStream::TryToAssembleMessage(ChunkMap::iterator it) { MS_TRACE(); // TODO: dcsctp: This method is O(N) with the number of fragments in a // message, which can be inefficient for very large values of N. This // could be optimized by e.g. only trying to assemble a message once _any_ // beginning and _any_ end has been found. const std::optional start = FindBeginning(it); if (!start.has_value()) { return 0; } const std::optional end = FindEnd(it); if (!end.has_value()) { return 0; } const size_t bytesAssembled = AssembleMessage(*start, *end); this->chunks.erase(*start, *end); return bytesAssembled; } std::optional::iterator> TraditionalReassemblyStreams:: UnorderedStream::FindBeginning(std::map::iterator it) { MS_TRACE(); Types::UnwrappedTsn prevTsn = it->first; for (;;) { if (it->second.IsBeginning()) { return it; } if (it == this->chunks.begin()) { return std::nullopt; } it--; if (it->first.GetNextValue() != prevTsn) { return std::nullopt; } prevTsn = it->first; } } std::optional::iterator> TraditionalReassemblyStreams:: UnorderedStream::FindEnd(std::map::iterator it) { MS_TRACE(); Types::UnwrappedTsn prevTsn = it->first; for (;;) { if (it->second.IsEnd()) { return ++it; } it++; if (it == this->chunks.end()) { return std::nullopt; } if (it->first != prevTsn.GetNextValue()) { return std::nullopt; } prevTsn = it->first; } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/tx/OutstandingData.cpp ================================================ #define MS_CLASS "RTC::SCTP::OutstandingData" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/tx/OutstandingData.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include namespace RTC { namespace SCTP { /* Static. */ // The number of times a packet must be NACKed before it's retransmitted. // // @see https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4 constexpr uint8_t NumberOfNacksForRetransmission{ 3 }; /* Instance methods. */ OutstandingData::Item::Item( uint32_t outgoingMessageId, UserData data, uint64_t timeSentMs, uint16_t maxRetransmissions, uint64_t expiresAtMs, std::optional lifecycleId) : outgoingMessageId(outgoingMessageId), data(std::move(data)), timeSentMs(timeSentMs), maxRetransmissions(maxRetransmissions), expiresAtMs(expiresAtMs), lifecycleId(lifecycleId) { MS_TRACE(); } void OutstandingData::Item::Ack() { MS_TRACE(); if (this->lifecycle != Lifecycle::ABANDONED) { this->lifecycle = Lifecycle::ACTIVE; } this->ackState = AckState::ACKED; } OutstandingData::Item::NackAction OutstandingData::Item::Nack(bool retransmitNow) { MS_TRACE(); this->ackState = AckState::NACKED; ++this->nackCount; if (!ShouldBeRetransmitted() && !IsAbandoned() && (retransmitNow || this->nackCount >= NumberOfNacksForRetransmission)) { // Nacked enough times, it's considered lost. if (this->numRetransmissions < this->maxRetransmissions) { this->lifecycle = Lifecycle::TO_BE_RETRANSMITTED; return NackAction::RETRANSMIT; } Abandon(); return NackAction::ABANDON; } return NackAction::NOTHING; } void OutstandingData::Item::MarkAsRetransmitted() { MS_TRACE(); this->lifecycle = Lifecycle::ACTIVE; this->ackState = AckState::UNACKED; this->nackCount = 0; ++this->numRetransmissions; } void OutstandingData::Item::Abandon() { MS_TRACE(); MS_ASSERT( this->expiresAtMs != Types::ExpiresAtMsInfinite || this->maxRetransmissions != Types::MaxRetransmitsNoLimit, "item should not have infinite expiration time or its retransmission times shouldn't be the maximum"); this->lifecycle = Lifecycle::ABANDONED; } OutstandingData::OutstandingData( size_t dataChunkHeaderLength, Types::UnwrappedTsn lastCumulativeTsnAck, std::function discardFromSendQueue) : dataChunkHeaderLength(dataChunkHeaderLength), lastCumulativeTsnAck(lastCumulativeTsnAck), discardFromSendQueue(std::move(discardFromSendQueue)) { MS_TRACE(); } OutstandingData::AckInfo OutstandingData::HandleSack( Types::UnwrappedTsn cumulativeTsnAck, std::span gapAckBlocks, bool isInFastRecovery) { MS_TRACE(); const bool cumulativeTsnAckAdvanced = cumulativeTsnAck > this->lastCumulativeTsnAck; OutstandingData::AckInfo ackInfo(cumulativeTsnAck); // Erase all items up to cumulativeTsnAck. RemoveAcked(cumulativeTsnAck, ackInfo); // ACK packets reported in the gap ack blocks. AckGapBlocks(cumulativeTsnAck, gapAckBlocks, ackInfo); // NACK and possibly mark for retransmit Chunks that weren't acked. NackBetweenAckBlocks( cumulativeTsnAck, gapAckBlocks, isInFastRecovery, cumulativeTsnAckAdvanced, ackInfo); AssertIsConsistent(); return ackInfo; } std::vector> OutstandingData::GetChunksToBeFastRetransmitted( size_t maxLength) { MS_TRACE(); std::vector> result = ExtractChunksThatCanFit(this->toBeFastRetransmitted, maxLength); // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4 // // "Those TSNs marked for retransmission due to the Fast-Retransmit // algorithm that did not fit in the sent datagram carrying K other TSNs // are also marked as ineligible for a subsequent Fast Retransmit. // However, as they are marked for retransmission they will be // retransmitted later on as soon as cwnd allows." if (!this->toBeFastRetransmitted.empty()) { this->toBeRetransmitted.insert( this->toBeFastRetransmitted.begin(), this->toBeFastRetransmitted.end()); this->toBeFastRetransmitted.clear(); } AssertIsConsistent(); return result; } std::vector> OutstandingData::GetChunksToBeRetransmitted( size_t maxLength) { MS_TRACE(); // Chunks scheduled for fast retransmission must be sent first. MS_ASSERT(this->toBeFastRetransmitted.empty(), "this->toBeFastRetransmitted is not empty"); return ExtractChunksThatCanFit(this->toBeRetransmitted, maxLength); } void OutstandingData::ExpireOutstandingChunks(uint64_t nowMs) { MS_TRACE(); std::vector tsnsToExpire; Types::UnwrappedTsn tsn = this->lastCumulativeTsnAck; for (const Item& item : this->outstandingData) { tsn.Increment(); // Chunks that are nacked can be expired. Care should be taken not to // expire unacked (in-flight) Chunks as they might have been received, // but the SACK is either delayed or in-flight and may be received // later. if (item.IsAbandoned()) { // Already abandoned. } else if (item.IsNacked() && item.HasExpired(nowMs)) { tsnsToExpire.push_back(tsn); } else { // A non-expired Chunk. No need to iterate any further. break; } } for (const Types::UnwrappedTsn tsnToExpire : tsnsToExpire) { // The item is retrieved by TSN, as AbandonAllFor() may have modified // `this->outstandingData` and invalidated iterators from the first // loop. const Item& item = GetItem(tsnToExpire); MS_WARN_TAG( sctp, "marking nacked Chunk %" PRIu32 " and message %" PRIu32 " as expired", tsnToExpire.Wrap(), item.GetData().GetMessageId()); AbandonAllFor(item); } AssertIsConsistent(); } Types::UnwrappedTsn OutstandingData::GetHighestOutstandingTsn() const { MS_TRACE(); return Types::UnwrappedTsn::AddTo(this->lastCumulativeTsnAck, this->outstandingData.size()); } std::optional OutstandingData::Insert( uint32_t outgoingMessageId, const UserData& data, uint64_t timeSentMs, uint16_t maxRetransmissions, uint64_t expiresAtMs, std::optional lifecycleId) { MS_TRACE(); // All Chunks are always padded to be even divisible by 4. const size_t chunkLength = GetSerializedChunkLength(data); this->unackedPayloadBytes += data.GetPayloadLength(); this->unackedPacketBytes += chunkLength; ++this->unackedItems; const Types::UnwrappedTsn tsn = GetNextTsn(); const Item& item = this->outstandingData.emplace_back( outgoingMessageId, data.Clone(), timeSentMs, maxRetransmissions, expiresAtMs, lifecycleId); if (item.HasExpired(timeSentMs)) { // No need to send it, it was expired when it was in the send queue. MS_WARN_TAG( sctp, "marking freshly produced Chunk %" PRIu32 " and message %" PRIu32 " as expired", tsn.Wrap(), item.GetData().GetMessageId()); AbandonAllFor(item); AssertIsConsistent(); return std::nullopt; } AssertIsConsistent(); return tsn; } void OutstandingData::NackAll() { MS_TRACE(); Types::UnwrappedTsn tsn = this->lastCumulativeTsnAck; // A two-pass algorithm is needed, as NackItem will invalidate iterators. std::vector tsnsToNack; for (const Item& item : this->outstandingData) { tsn.Increment(); if (!item.IsAcked()) { tsnsToNack.push_back(tsn); } } for (const Types::UnwrappedTsn tsnToNack : tsnsToNack) { NackItem( tsnToNack, /*retransmitNow*/ true, /*doFastRetransmit*/ false); } AssertIsConsistent(); } const ForwardTsnChunk* OutstandingData::AddForwardTsn(Packet* packet) const { MS_TRACE(); std::map skippedPerOrderedStream; Types::UnwrappedTsn newCumulativeAck = this->lastCumulativeTsnAck; Types::UnwrappedTsn tsn = this->lastCumulativeTsnAck; for (const Item& item : this->outstandingData) { tsn.Increment(); if ( this->streamResetBreakpointTsns.contains(tsn) || (tsn != newCumulativeAck.GetNextValue()) || !item.IsAbandoned()) { break; } newCumulativeAck = tsn; if ( !item.GetData().IsUnordered() && item.GetData().GetStreamSequenceNumber() > skippedPerOrderedStream[item.GetData().GetStreamId()]) { skippedPerOrderedStream[item.GetData().GetStreamId()] = item.GetData().GetStreamSequenceNumber(); } } auto* forwardTsnChunk = packet->BuildChunkInPlace(); forwardTsnChunk->SetNewCumulativeTsn(newCumulativeAck.Wrap()); for (const auto& [streamId, ssn] : skippedPerOrderedStream) { forwardTsnChunk->AddSkippedStream(AnyForwardTsnChunk::SkippedStream{ streamId, ssn }); } forwardTsnChunk->Consolidate(); return forwardTsnChunk; } const IForwardTsnChunk* OutstandingData::AddIForwardTsn(Packet* packet) const { MS_TRACE(); std::map, uint32_t /*mid*/> skippedPerStream; Types::UnwrappedTsn newCumulativeAck = this->lastCumulativeTsnAck; Types::UnwrappedTsn tsn = this->lastCumulativeTsnAck; for (const Item& item : this->outstandingData) { tsn.Increment(); if ( this->streamResetBreakpointTsns.contains(tsn) || (tsn != newCumulativeAck.GetNextValue()) || !item.IsAbandoned()) { break; } newCumulativeAck = tsn; const std::pair stream = std::make_pair(item.GetData().IsUnordered(), item.GetData().GetStreamId()); skippedPerStream[stream] = std::max(item.GetData().GetMessageId(), skippedPerStream[stream]); } auto* iForwardTsnChunk = packet->BuildChunkInPlace(); iForwardTsnChunk->SetNewCumulativeTsn(newCumulativeAck.Wrap()); for (const auto& [stream, mid] : skippedPerStream) { const uint16_t streamId = stream.second; const bool unordered = stream.first; iForwardTsnChunk->AddSkippedStream( AnyForwardTsnChunk::SkippedStream{ unordered, streamId, mid }); } iForwardTsnChunk->Consolidate(); return iForwardTsnChunk; } std::optional OutstandingData::MeasureRtt(uint64_t nowMs, Types::UnwrappedTsn tsn) const { MS_TRACE(); if (tsn > this->lastCumulativeTsnAck && tsn < GetNextTsn()) { const Item& item = GetItem(tsn); if (!item.HasBeenRetransmitted()) { // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.1 // // "Karn's algorithm: RTT measurements MUST NOT be made using packets // that were retransmitted (and thus for which it is ambiguous // whether the reply was for the first instance of the Chunk or for a // later instance)" return nowMs - item.GetTimeSentMs(); } } return std::nullopt; } bool OutstandingData::ShouldSendForwardTsn() const { MS_TRACE(); if (!this->outstandingData.empty()) { return this->outstandingData.front().IsAbandoned(); } else { return false; } } void OutstandingData::BeginResetStreams() { MS_TRACE(); this->streamResetBreakpointTsns.insert(GetNextTsn()); } #ifdef MS_TEST std::vector< std::pair> OutstandingData::GetChunkStatesForTesting() const { MS_TRACE(); std::vector> states; states.emplace_back(this->lastCumulativeTsnAck.Wrap(), State::ACKED); Types::UnwrappedTsn tsn = this->lastCumulativeTsnAck; for (const Item& item : this->outstandingData) { tsn.Increment(); State state; if (item.IsAbandoned()) { state = State::ABANDONED; } else if (item.ShouldBeRetransmitted()) { state = State::TO_BE_RETRANSMITTED; } else if (item.IsAcked()) { state = State::ACKED; } else if (item.IsNacked()) { state = State::NACKED; } else if (item.IsOutstanding()) { state = State::IN_FLIGHT; } else { MS_THROW_ERROR("should not end here"); } states.emplace_back(tsn.Wrap(), state); } return states; } #endif size_t OutstandingData::GetSerializedChunkLength(const UserData& data) const { MS_TRACE(); return Utils::Byte::PadTo4Bytes(this->dataChunkHeaderLength + data.GetPayloadLength()); } OutstandingData::Item& OutstandingData::GetItem(Types::UnwrappedTsn tsn) { MS_TRACE(); MS_ASSERT( tsn > this->lastCumulativeTsnAck, "tsn must be higher than this->lastCumulativeTsnAck"); MS_ASSERT(tsn < GetNextTsn(), "tsn must be higher than GetNextTsn()"); const size_t index = Types::UnwrappedTsn::Difference(tsn, this->lastCumulativeTsnAck) - 1; MS_ASSERT(index >= 0, "index must be equal or higher than 0"); MS_ASSERT( index < this->outstandingData.size(), "index must be lower than this->outstandingData.size()"); return this->outstandingData[index]; } const OutstandingData::Item& OutstandingData::GetItem(Types::UnwrappedTsn tsn) const { MS_TRACE(); MS_ASSERT( tsn > this->lastCumulativeTsnAck, "tsn must be higher than this->lastCumulativeTsnAck"); MS_ASSERT(tsn < GetNextTsn(), "tsn must be higher than GetNextTsn()"); const size_t index = Types::UnwrappedTsn::Difference(tsn, this->lastCumulativeTsnAck) - 1; MS_ASSERT(index >= 0, "index must be equal or higher than 0"); MS_ASSERT( index < this->outstandingData.size(), "index must be lower than this->outstandingData.size()"); return this->outstandingData[index]; } void OutstandingData::RemoveAcked(Types::UnwrappedTsn cumulativeTsnAck, AckInfo& ackInfo) { MS_TRACE(); while (!this->outstandingData.empty() && this->lastCumulativeTsnAck < cumulativeTsnAck) { const Types::UnwrappedTsn tsn = this->lastCumulativeTsnAck.GetNextValue(); Item& item = this->outstandingData.front(); AckChunk(ackInfo, tsn, item); if (item.GetLifecycleId().has_value()) { MS_ASSERT(item.GetData().IsEnd(), "item.GetData().IsEnd() must be true"); if (item.IsAbandoned()) { ackInfo.abandonedLifecycleIds.push_back(item.GetLifecycleId().value()); } else { ackInfo.ackedLifecycleIds.push_back(item.GetLifecycleId().value()); } } this->outstandingData.pop_front(); this->lastCumulativeTsnAck.Increment(); } this->streamResetBreakpointTsns.erase( this->streamResetBreakpointTsns.begin(), this->streamResetBreakpointTsns.upper_bound(cumulativeTsnAck.GetNextValue())); } void OutstandingData::AckGapBlocks( Types::UnwrappedTsn cumulativeTsnAck, std::span gapAckBlocks, AckInfo& ackInfo) { MS_TRACE(); // Mark all non-gaps as ACKED (but they can't be removed) as (from RFC) // "SCTP considers the information carried in the Gap Ack Blocks in the // SACK Chunk as advisory". Note that when NR-SACK is supported, this can // be handled differently. for (const auto& block : gapAckBlocks) { const Types::UnwrappedTsn start = Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.start); const Types::UnwrappedTsn end = Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.end); for (Types::UnwrappedTsn tsn = start; tsn <= end; tsn = tsn.GetNextValue()) { if (tsn > this->lastCumulativeTsnAck && tsn < GetNextTsn()) { Item& item = GetItem(tsn); AckChunk(ackInfo, tsn, item); } } } } void OutstandingData::NackBetweenAckBlocks( Types::UnwrappedTsn cumulativeTsnAck, std::span gapAckBlocks, bool isInFastRecovery, bool cumulativeTsnAckedAdvanced, AckInfo& ackInfo) { MS_TRACE(); // Mark everything between the blocks as NACKED/TO_BE_RETRANSMITTED. // // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4 // // "Mark the DATA chunk(s) with three miss indications for retransmission." // "For each incoming SACK, miss indications are incremented only for // missing TSNs prior to the highest TSN newly acknowledged in the SACK." // // What this means is that only when there is a increasing stream of data // received and there are new packets seen (since last time), packets that // are in-flight and between gaps should be nacked. This means that SCTP // relies on the T3-RTX-timer to re-send packets otherwise. Types::UnwrappedTsn maxTsnToNack = ackInfo.highestTsnAcked; if (isInFastRecovery && cumulativeTsnAckedAdvanced) { // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4 // // "If an endpoint is in Fast Recovery and a SACK arrives that advances // the Cumulative TSN Ack Point, the miss indications are incremented // for all TSNs reported missing in the SACK." maxTsnToNack = Types::UnwrappedTsn::AddTo( cumulativeTsnAck, gapAckBlocks.empty() ? 0 : gapAckBlocks.rbegin()->end); } Types::UnwrappedTsn prevBlockLastAcked = cumulativeTsnAck; for (const auto& block : gapAckBlocks) { const Types::UnwrappedTsn curBlockFirstAcked = Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.start); for (Types::UnwrappedTsn tsn = prevBlockLastAcked.GetNextValue(); tsn < curBlockFirstAcked && tsn <= maxTsnToNack && tsn < GetNextTsn(); tsn = tsn.GetNextValue()) { ackInfo.hasPacketLoss |= NackItem( tsn, /*retransmitNow*/ false, /*doFastRetransmit*/ !isInFastRecovery); } prevBlockLastAcked = Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.end); } // Note that packets are not NACKED which are above the highest // gap-ack-block (or above the cumulative ack TSN if no gap-ack-blocks) // as only packets up until the `highestTsnAcked` (see above) should be // considered when NACKing. } void OutstandingData::AckChunk(AckInfo& ackInfo, Types::UnwrappedTsn tsn, Item& item) { MS_TRACE(); if (!item.IsAcked()) { const size_t serializedLength = GetSerializedChunkLength(item.GetData()); ackInfo.bytesAcked += serializedLength; if (item.IsOutstanding()) { this->unackedPayloadBytes -= item.GetData().GetPayloadLength(); this->unackedPacketBytes -= serializedLength; --this->unackedItems; } if (item.ShouldBeRetransmitted()) { MS_ASSERT( !this->toBeFastRetransmitted.contains(tsn), "tsn should not be present in this->toBeFastRetransmitted"); this->toBeRetransmitted.erase(tsn); } item.Ack(); ackInfo.highestTsnAcked = std::max(ackInfo.highestTsnAcked, tsn); } } bool OutstandingData::NackItem(Types::UnwrappedTsn tsn, bool retransmitNow, bool doFastRetransmit) { MS_TRACE(); Item& item = GetItem(tsn); // Ignore NACKs for chunks that have already been acknowledged. if (item.IsAcked()) { return false; } const bool wasOutstanding = item.IsOutstanding(); const Item::NackAction action = item.Nack(retransmitNow); if (wasOutstanding && !item.IsOutstanding()) { this->unackedPayloadBytes -= item.GetData().GetPayloadLength(); this->unackedPacketBytes -= GetSerializedChunkLength(item.GetData()); --this->unackedItems; } switch (action) { case Item::NackAction::NOTHING: { return false; } case Item::NackAction::RETRANSMIT: { if (doFastRetransmit) { this->toBeFastRetransmitted.insert(tsn); } else { this->toBeRetransmitted.insert(tsn); } MS_DEBUG_TAG(sctp, "tsn %" PRIu32 " marked for retransmission", tsn.Wrap()); break; } case Item::NackAction::ABANDON: { MS_DEBUG_TAG(sctp, "tsn %" PRIu32 " nacked, resulted in abandoning", tsn.Wrap()); AbandonAllFor(item); break; } } return true; } void OutstandingData::AbandonAllFor(const OutstandingData::Item& item) { MS_TRACE(); // Erase all remaining chunks from the producer, if any. if (this->discardFromSendQueue(item.GetData().GetStreamId(), item.GetOutgoingMessageId())) { // There were remaining chunks to be produced for this message. Since the // receiver may have already received all chunks (up till now) for this // message, we can't just FORWARD-TSN to the last fragment in this // (abandoned) message and start sending a new message, as the receiver will // then see a new message before the end of the previous one was seen (or // skipped over). So create a new fragment, representing the end, that the // received will never see as it is abandoned immediately and used as cum // TSN in the sent FORWARD-TSN. UserData messageEnd( item.GetData().GetStreamId(), item.GetData().GetStreamSequenceNumber(), item.GetData().GetMessageId(), item.GetData().GetFragmentSequenceNumber(), item.GetData().GetPayloadProtocolId(), std::vector(), /*isBeginning*/ false, /*isEnd*/ true, /*isUnordered*/ item.GetData().IsUnordered()); const Types::UnwrappedTsn tsn = GetNextTsn(); Item& addedItem = this->outstandingData.emplace_back( item.GetOutgoingMessageId(), std::move(messageEnd), /*timeSentMs*/ 0, /*maxRetransmissions*/ 0, /*expiresAtMs*/ Types::ExpiresAtMsInfinite, /*lifecycleId*/ std::nullopt); // The added Chunk shouldn't be included in `this->unackedPacketBytes`, // so set it as acked. addedItem.Ack(); MS_DEBUG_TAG(sctp, "adding unsent end placeholder for message at TSN %" PRIu32, tsn.Wrap()); } Types::UnwrappedTsn tsn = this->lastCumulativeTsnAck; for (Item& other : this->outstandingData) { tsn.Increment(); if ( !other.IsAbandoned() && other.GetData().GetStreamId() == item.GetData().GetStreamId() && other.GetOutgoingMessageId() == item.GetOutgoingMessageId()) { MS_WARN_TAG(sctp, "marking Chunk %" PRIu32 " as abandoned", tsn.Wrap()); if (other.ShouldBeRetransmitted()) { this->toBeFastRetransmitted.erase(tsn); this->toBeRetransmitted.erase(tsn); } const bool wasOutstanding = other.IsOutstanding(); other.Abandon(); if (wasOutstanding) { this->unackedPayloadBytes -= other.GetData().GetPayloadLength(); this->unackedPacketBytes -= GetSerializedChunkLength(other.GetData()); --this->unackedItems; } } } } std::vector> OutstandingData::ExtractChunksThatCanFit( std::set& chunks, size_t maxLength) { MS_TRACE(); std::vector> result; for (auto it = chunks.begin(); it != chunks.end();) { const Types::UnwrappedTsn tsn = *it; Item& item = GetItem(tsn); MS_ASSERT(item.ShouldBeRetransmitted(), "item should be retransmitted"); MS_ASSERT(!item.IsOutstanding(), "item should not be outstanding"); MS_ASSERT(!item.IsAbandoned(), "item should not be abandoned"); MS_ASSERT(!item.IsAcked(), "item should not be acked"); const size_t serializedLength = GetSerializedChunkLength(item.GetData()); if (serializedLength <= maxLength) { item.MarkAsRetransmitted(); result.emplace_back(tsn.Wrap(), item.GetData().Clone()); maxLength -= serializedLength; this->unackedPayloadBytes += item.GetData().GetPayloadLength(); this->unackedPacketBytes += serializedLength; ++this->unackedItems; it = chunks.erase(it); } else { ++it; } // No point in continuing if the packet is full. if (maxLength <= this->dataChunkHeaderLength) { break; } } return result; } void OutstandingData::AssertIsConsistent() const { MS_TRACE(); size_t actualUnackedPayloadBytes{ 0 }; size_t actualUnackedPacketBytes{ 0 }; size_t actualUnackedItems{ 0 }; std::set combinedToBeRetransmitted; combinedToBeRetransmitted.insert(this->toBeRetransmitted.begin(), this->toBeRetransmitted.end()); combinedToBeRetransmitted.insert( this->toBeFastRetransmitted.begin(), this->toBeFastRetransmitted.end()); std::set actualCombinedToBeRetransmitted; Types::UnwrappedTsn tsn = this->lastCumulativeTsnAck; for (const Item& item : this->outstandingData) { tsn.Increment(); if (item.IsOutstanding()) { actualUnackedPayloadBytes += item.GetData().GetPayloadLength(); actualUnackedPacketBytes += GetSerializedChunkLength(item.GetData()); ++actualUnackedItems; } if (item.ShouldBeRetransmitted()) { actualCombinedToBeRetransmitted.insert(tsn); } } MS_ASSERT( actualUnackedPayloadBytes == this->unackedPayloadBytes, "actualUnackedPayloadBytes != this->unackedPayloadBytes"); MS_ASSERT( actualUnackedPacketBytes == this->unackedPacketBytes, "actualUnackedPacketBytes != this->unackedPacketBytes"); MS_ASSERT(actualUnackedItems == this->unackedItems, "actualUnackedItems != this->unackedItems"); MS_ASSERT( actualCombinedToBeRetransmitted == combinedToBeRetransmitted, "actualCombinedToBeRetransmitted != combinedToBeRetransmitted"); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/tx/RetransmissionErrorCounter.cpp ================================================ #define MS_CLASS "RTC::SCTP::RetransmissionErrorCounter" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/tx/RetransmissionErrorCounter.hpp" #include "Logger.hpp" #include namespace RTC { namespace SCTP { /* Instance methods. */ RetransmissionErrorCounter::RetransmissionErrorCounter(const SctpOptions& sctpOptions) : limit(sctpOptions.maxRetransmissions) { MS_TRACE(); } void RetransmissionErrorCounter::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN( indentation, " limit: %s", this->limit ? std::to_string(this->limit.value()).c_str() : "Infinite"); MS_DUMP_CLEAN(indentation, " counter: %zu", this->counter); MS_DUMP_CLEAN(indentation, " exhausted: %s", IsExhausted() ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } RetransmissionErrorCounter::~RetransmissionErrorCounter() { MS_TRACE(); } bool RetransmissionErrorCounter::Increment(std::string_view reason) { MS_TRACE(); this->counter++; if (IsExhausted()) { MS_WARN_TAG( sctp, "too many retransmissions [counter:%zu, limit:%s]: %.*s", this->counter, this->limit ? std::to_string(this->limit.value()).c_str() : "Infinite", static_cast(reason.size()), reason.data()); return false; } MS_DEBUG_DEV( "%.*s [counter:%zu, limit:%s]", static_cast(reason.size()), reason.data(), this->counter, this->limit ? std::to_string(*this->limit).c_str() : "Infinite"); return true; } void RetransmissionErrorCounter::Clear() { MS_TRACE(); if (this->counter > 0) { MS_DEBUG_DEV("recovered from counter %zu", this->counter); this->counter = 0; } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/tx/RetransmissionQueue.cpp ================================================ #define MS_CLASS "RTC::SCTP::RetransmissionQueue" // TODO: SCTP: Comment. #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/tx/RetransmissionQueue.hpp" #include "Logger.hpp" #include "Utils.hpp" #include "RTC/SCTP/packet/chunks/DataChunk.hpp" #include "RTC/SCTP/packet/chunks/IDataChunk.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include // std::min() #include // std::accumulate() #include namespace RTC { namespace SCTP { /* Instance methods. */ RetransmissionQueue::RetransmissionQueue( Listener* listener, AssociationListenerInterface& associationListener, uint32_t localInitialTsn, uint32_t remoteAdvertisedReceiverWindowCredit, SendQueueInterface& sendQueue, BackoffTimerHandleInterface* t3RtxTimer, const SctpOptions& sctpOptions, // NOTE: I don't like default argument values in dcsctp (true and false), // let's be explicit. bool supportsPartialReliability, bool useMessageInterleaving) : listener(listener), associationListener(associationListener), sctpOptions(sctpOptions), supportsPartialReliability(supportsPartialReliability), dataChunkHeaderLength( useMessageInterleaving ? IDataChunk::IDataChunkHeaderLength : DataChunk::DataChunkHeaderLength), t3RtxTimer(t3RtxTimer), cwnd(sctpOptions.initialCwndMtus * sctpOptions.mtu), rwnd(remoteAdvertisedReceiverWindowCredit), // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.1 // // "The initial value of ssthresh MAY be arbitrarily high (for example, // implementations MAY use the size of the receiver advertised window)." ssthresh(this->rwnd), sendQueue(sendQueue), outstandingData( this->dataChunkHeaderLength, this->tsnUnwrapper.Unwrap(localInitialTsn - 1), [this](uint16_t streamId, uint32_t outgoingMessageId) { return this->sendQueue.Discard(streamId, outgoingMessageId); }) { MS_TRACE(); } RetransmissionQueue::~RetransmissionQueue() { MS_TRACE(); } bool RetransmissionQueue::HandleReceivedSackChunk(uint64_t nowMs, const SackChunk* receivedSackChunk) { MS_TRACE(); if (!IsSackChunkValid(receivedSackChunk)) { return false; } const Types::UnwrappedTsn oldLastCumulativeTsnAck = this->outstandingData.GetLastCumulativeTsnAck(); const size_t oldUnackedPacketBytes = this->outstandingData.GetUnackedPacketBytes(); #if MS_LOG_DEV_LEVEL == 3 const size_t oldRwnd = this->rwnd; #endif const Types::UnwrappedTsn cumulativeTsnAck = this->tsnUnwrapper.Unwrap(receivedSackChunk->GetCumulativeTsnAck()); if (receivedSackChunk->GetValidatedGapAckBlocks().empty()) { UpdateRttMs(nowMs, cumulativeTsnAck); } // Exit fast recovery before continuing processing, in case it needs to go // into fast recovery again due to new reported packet loss. MayExitFastRecovery(cumulativeTsnAck); const OutstandingData::AckInfo ackInfo = this->outstandingData.HandleSack( cumulativeTsnAck, receivedSackChunk->GetValidatedGapAckBlocks(), IsInFastRecovery()); // Add lifecycle events for delivered messages. for (const uint64_t lifecycleId : ackInfo.ackedLifecycleIds) { MS_DEBUG_TAG( sctp, "triggering OnAssociationLifecycleMessageDelivered() [lifecycleId:%" PRIu64 "]", lifecycleId); this->associationListener.OnAssociationLifecycleMessageDelivered(lifecycleId); this->associationListener.OnAssociationLifecycleMessageEnd(lifecycleId); } // Add lifecycle events for abandoned messages. for (const uint64_t lifecycleId : ackInfo.abandonedLifecycleIds) { MS_DEBUG_TAG( sctp, "triggering OnLifecycleMessageExpired() [lifecycleId:%" PRIu64 ", maybeDelivered:true]", lifecycleId); this->associationListener.OnAssociationLifecycleMessageExpired( lifecycleId, /*maybeDelivered*/ true); this->associationListener.OnAssociationLifecycleMessageEnd(lifecycleId); } // Update of this->outstandingData is now done. Congestion control remains. UpdateReceiverWindow(receivedSackChunk->GetAdvertisedReceiverWindowCredit()); MS_DEBUG_DEV( "received SACK [cumulativeTsnAck:%" PRIu32 ", oldLastCumulativeTsnAck:%" PRIu32 ", unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu, rwnd:%zu, oldRwnd:%zu]", cumulativeTsnAck.Wrap(), oldLastCumulativeTsnAck.Wrap(), this->outstandingData.GetUnackedPacketBytes(), oldUnackedPacketBytes, this->rwnd, oldRwnd); if (cumulativeTsnAck > oldLastCumulativeTsnAck) { // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.2 // // "Whenever a SACK is received that acknowledges the DATA chunk with // the earliest outstanding TSN for that address, restart the T3-rtx // timer for that address with its current RTO (if there is still // outstanding data on that address)." this->t3RtxTimer->Stop(); HandleIncreasedCumulativeTsnAck(oldUnackedPacketBytes, ackInfo.bytesAcked); } if (ackInfo.hasPacketLoss) { HandlePacketLoss(ackInfo.highestTsnAcked); } // https://datatracker.ietf.org/doc/html/rfc9260#section-8.2 // // "When an outstanding TSN is acknowledged [...] the endpoint shall clear // the error counter ...". if (ackInfo.bytesAcked > 0) { this->listener->OnRetransmissionQueueClearRetransmissionCounter(); } StartT3RtxTimerIfOutstandingData(); return true; } void RetransmissionQueue::HandleT3RtxTimerExpiry() { MS_TRACE(); const size_t oldCwnd = this->cwnd; const size_t oldUnackedPacketBytes = GetUnackedPacketBytes(); // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3 // // "For the destination address for which the timer expires, adjust // its ssthresh with rules defined in Section 7.2.3 and set the cwnd // <- MTU." this->ssthresh = std::max(this->cwnd / 2, 4 * this->sctpOptions.mtu); this->cwnd = 1 * this->sctpOptions.mtu; // Errata: https://datatracker.ietf.org/doc/html/rfc8540#section-3.11 this->partialBytesAcked = 0; // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3 // // "For the destination address for which the timer expires, set RTO // <- RTO * 2 ("back off the timer"). The maximum value discussed in // rule C7 above (RTO.max) may be used to provide an upper bound to this // doubling operation." // Already done by the BackoffTimerHandle implementation. // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3 // // "Determine how many of the earliest (i.e., lowest TSN) outstanding // DATA chunks for the address for which the T3-rtx has expired will fit // into a single Packet" // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3 // // "Note: Any DATA chunks that were sent to the address for which the // T3-rtx timer expired but did not fit in one MTU (rule E3 above) should // be marked for retransmission and sent as soon as cwnd allows (normally, // when a SACK arrives)." this->outstandingData.NackAll(); // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.3 // // "Start the retransmission timer T3-rtx on the destination address to // which the retransmission is sent, if rule R1 above indicates to do so." // Already done by the BackoffTimerHandle implementation. MS_DEBUG_TAG( sctp, "%s timer has expired [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu]", this->t3RtxTimer->GetLabel().c_str(), this->cwnd, oldCwnd, this->ssthresh, GetUnackedPacketBytes(), oldUnackedPacketBytes); } std::vector> RetransmissionQueue::GetChunksForFastRetransmit( size_t maxLength) { MS_TRACE(); MS_ASSERT( this->outstandingData.HasDataToBeFastRetransmitted(), "no data to be fast-retransmitted"); MS_ASSERT( Utils::Byte::IsPaddedTo4Bytes(maxLength), "given maxLength %zu is not divisible by 4", maxLength); std::vector> toBeSent; #if MS_LOG_DEV_LEVEL == 3 const size_t oldUnackedPacketBytes = GetUnackedPacketBytes(); #endif toBeSent = this->outstandingData.GetChunksToBeFastRetransmitted(maxLength); MS_ASSERT(!toBeSent.empty(), "toBeSent cannot be empty"); // https://datatracker.ietf.org/doc/html/rfc9260#section-7.2.4 // // "4) Restart the T3-rtx timer only if ... the endpoint is retransmitting // the first outstanding DATA chunk sent to that address." if (toBeSent[0].first == this->outstandingData.GetLastCumulativeTsnAck().GetNextValue().Wrap()) { MS_DEBUG_DEV("first outstanding data to be retransmitted, restarting T3-rtx timer"); this->t3RtxTimer->Stop(); } // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.2 // // "Every time a DATA chunk is sent to any address (including a // retransmission), if the T3-rtx timer of that address is not running, // start it running so that it will expire after the RTO of that address." if (!this->t3RtxTimer->IsRunning()) { this->t3RtxTimer->Start(); } const size_t bytesRetransmitted = std::accumulate( toBeSent.begin(), toBeSent.end(), size_t{ 0 }, [&](size_t r, const std::pair& data) { return r + GetSerializedChunkLength(data.second); }); ++this->rtxPacketsCount; this->rtxBytesCount += bytesRetransmitted; #if MS_LOG_DEV_LEVEL == 3 std::string tsnList; for (const auto& [tsn, data] : toBeSent) { if (!tsnList.empty()) { tsnList += ','; } tsnList += std::to_string(tsn); } MS_DEBUG_DEV( "fast-retransmitting TSN %s (%zu bytes) [unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu]", tsnList.c_str(), bytesRetransmitted, GetUnackedPacketBytes(), oldUnackedPacketBytes); #endif return toBeSent; } std::vector> RetransmissionQueue::GetChunksToSend( uint64_t nowMs, size_t maxLength) { MS_TRACE(); MS_ASSERT( Utils::Byte::IsPaddedTo4Bytes(maxLength), "given maxLength %zu is not divisible by 4", maxLength); std::vector> toBeSent; const size_t oldUnackedPacketBytes = GetUnackedPacketBytes(); #if MS_LOG_DEV_LEVEL == 3 const size_t oldRwnd = this->rwnd; #endif // Calculate the bandwidth budget (how many bytes that is allowed to be // sent). const size_t maxPacketBytesAllowedByCwnd = oldUnackedPacketBytes >= this->cwnd ? 0 : this->cwnd - oldUnackedPacketBytes; size_t maxPacketBytesAllowedByRwnd = Utils::Byte::PadTo4Bytes(GetRwnd() + this->dataChunkHeaderLength); // https://datatracker.ietf.org/doc/html/rfc9260#section-6.1 // // "However, regardless of the value of rwnd (including if it is 0), the // data sender can always have one DATA chunk in flight to the receiver if // allowed by cwnd (see rule B, below)." if (this->outstandingData.GetUnackedItems() == 0) { maxPacketBytesAllowedByRwnd = this->sctpOptions.mtu; } size_t maxBytes = Utils::Byte::PadDownTo4Bytes( std::min({ maxPacketBytesAllowedByCwnd, maxPacketBytesAllowedByRwnd, maxLength })); toBeSent = this->outstandingData.GetChunksToBeRetransmitted(maxBytes); const size_t bytesRetransmitted = std::accumulate( toBeSent.begin(), toBeSent.end(), size_t{ 0 }, [&](size_t r, const std::pair& data) { return r + GetSerializedChunkLength(data.second); }); maxBytes -= bytesRetransmitted; if (!toBeSent.empty()) { ++this->rtxPacketsCount; this->rtxBytesCount += bytesRetransmitted; } while (maxBytes > this->dataChunkHeaderLength) { MS_ASSERT( Utils::Byte::IsPaddedTo4Bytes(maxBytes), "computed maxBytes %zu during the loop is not divisible by 4", maxBytes); std::optional dataToSend = this->sendQueue.Produce(nowMs, maxBytes - this->dataChunkHeaderLength); if (!dataToSend.has_value()) { break; } const size_t chunkSize = GetSerializedChunkLength(dataToSend->data); maxBytes -= chunkSize; this->rwnd -= dataToSend->data.GetPayloadLength(); const std::optional tsn = this->outstandingData.Insert( dataToSend->outgoingMessageId, dataToSend->data, nowMs, this->supportsPartialReliability ? dataToSend->maxRetransmissions : Types::MaxRetransmitsNoLimit, this->supportsPartialReliability ? dataToSend->expiresAtMs : Types::ExpiresAtMsInfinite, dataToSend->lifecycleId); if (tsn.has_value()) { if (dataToSend->lifecycleId.has_value()) { MS_ASSERT(dataToSend->data.IsEnd(), "data.IsEnd() should return true"); this->associationListener.OnAssociationLifecycleMessageFullySent( dataToSend->lifecycleId.value()); } toBeSent.emplace_back(tsn->Wrap(), std::move(dataToSend->data)); } } // https://tools.ietf.org/html/rfc9260#section-6.3.2 // // "Every time a DATA chunk is sent to any address (including a // retransmission), if the T3-rtx timer of that address is not running, // start it running so that it will expire after the RTO of that address." if (!toBeSent.empty()) { if (!this->t3RtxTimer->IsRunning()) { this->t3RtxTimer->Start(); } #if MS_LOG_DEV_LEVEL == 3 std::string tsnList; for (const auto& [tsn, data] : toBeSent) { if (!tsnList.empty()) { tsnList += ','; } tsnList += std::to_string(tsn); } const size_t bytesRetransmitted = std::accumulate( toBeSent.begin(), toBeSent.end(), size_t{ 0 }, [&](size_t r, const std::pair& d) { return r + GetSerializedChunkLength(d.second); }); MS_DEBUG_DEV( "sending TSN %s (%zu bytes) [unackedPacketBytes:%zu, oldUnackedPacketBytes:%zu, cwnd:%zu, rwnd:%zu, oldRwnd:%zu]", tsnList.c_str(), bytesRetransmitted, GetUnackedPacketBytes(), oldUnackedPacketBytes, cwnd, rwnd, oldRwnd); #endif } return toBeSent; } bool RetransmissionQueue::ShouldSendForwardTsn(uint64_t nowMs) { MS_TRACE(); if (!this->supportsPartialReliability) { return false; } this->outstandingData.ExpireOutstandingChunks(nowMs); return this->outstandingData.ShouldSendForwardTsn(); } void RetransmissionQueue::PrepareResetStream(uint16_t streamId) { MS_TRACE(); // TODO: As per TODO comment in same method in dcsctp: // // These calls are now only affecting the send queue. The packet buffer // can also change behavior - for example draining the chunk producer and // eagerly assign TSNs so that an "Outgoing SSN Reset Request" can be sent // quickly, with a known sender last assigned TSN. this->sendQueue.PrepareResetStream(streamId); } bool RetransmissionQueue::HasStreamsReadyToBeReset() const { MS_TRACE(); return this->sendQueue.HasStreamsReadyToBeReset(); } std::vector RetransmissionQueue::BeginResetStreams() { MS_TRACE(); this->outstandingData.BeginResetStreams(); return this->sendQueue.GetStreamsReadyToBeReset(); } void RetransmissionQueue::CommitResetStreams() { MS_TRACE(); this->sendQueue.CommitResetStreams(); } void RetransmissionQueue::RollbackResetStreams() { MS_TRACE(); this->sendQueue.RollbackResetStreams(); } size_t RetransmissionQueue::GetSerializedChunkLength(const UserData& data) const { MS_TRACE(); return Utils::Byte::PadTo4Bytes(this->dataChunkHeaderLength + data.GetPayloadLength()); } bool RetransmissionQueue::IsSackChunkValid(const SackChunk* sackChunk) const { MS_TRACE(); // https://tools.ietf.org/html/rfc9260#section-6.2.1 // // "If Cumulative TSN Ack is less than the Cumulative TSN Ack Point, then // drop the SACK. Since Cumulative TSN Ack is monotonically increasing, a // SACK whose Cumulative TSN Ack is less than the Cumulative TSN Ack Point // indicates an out-of- order SACK." // // @remarks // - Important not to drop SACKs with identical TSN to that previously // received, as the gap-ack-blocks or dup tsn fields may have changed. const Types::UnwrappedTsn cumulativeTsnAck = this->tsnUnwrapper.PeekUnwrap(sackChunk->GetCumulativeTsnAck()); if (cumulativeTsnAck < this->outstandingData.GetLastCumulativeTsnAck()) { // https://tools.ietf.org/html/rfc9260#section-6.2.1 // // "If Cumulative TSN Ack is less than the Cumulative TSN Ack Point, // then drop the SACK. Since Cumulative TSN Ack is monotonically // increasing, a SACK whose Cumulative TSN Ack is less than the // Cumulative TSN Ack Point indicates an out-of- order SACK." return false; } else if (cumulativeTsnAck > this->outstandingData.GetHighestOutstandingTsn()) { return false; } return std::ranges::all_of( sackChunk->GetGapAckBlocks(), [&](const auto& block) { return Types::UnwrappedTsn::AddTo(cumulativeTsnAck, block.end) <= this->outstandingData.GetHighestOutstandingTsn(); }); } void RetransmissionQueue::UpdateRttMs(uint64_t nowMs, Types::UnwrappedTsn cumulativeTsnAck) { MS_TRACE(); // RTT updating is flawed in SCTP, as explained in e.g. Pedersen J, Griwodz C, // Halvorsen P (2006) Considerations of SCTP retransmission delays for thin // streams. // Due to delayed acknowledgement, the SACK may be sent much later which // increases the calculated RTT. // // TODO: As per TODO comment in same method in dcsctp: // // Consider occasionally sending DATA chunks with I-bit set and use only // those packets for measurement. const auto rttMs = this->outstandingData.MeasureRtt(nowMs, cumulativeTsnAck); if (rttMs.has_value()) { this->listener->OnRetransmissionQueueNewRttMs(rttMs.value()); } } void RetransmissionQueue::MayExitFastRecovery(Types::UnwrappedTsn cumulativeTsnAck) { MS_TRACE(); // https://tools.ietf.org/html/rfc9260#section-7.2.4 // // "When a SACK acknowledges all TSNs up to and including this [fast // recovery] exit point, Fast Recovery is exited." if (this->fastRecoveryExitTsn.has_value() && cumulativeTsnAck >= this->fastRecoveryExitTsn.value()) { MS_DEBUG_DEV( "exit point %" PRIu32 " reached, exiting fast recovery", this->fastRecoveryExitTsn.value().Wrap()); this->fastRecoveryExitTsn = std::nullopt; } } void RetransmissionQueue::StopT3RtxTimerOnIncreasedCumulativeTsnAck( Types::UnwrappedTsn /*cumulativeTsnAck*/) { MS_TRACE(); // TODO: This method is NOT implemented in dcsctp! // // @see https://issues.webrtc.org/issues/505751236 } void RetransmissionQueue::HandleIncreasedCumulativeTsnAck( size_t unackedPacketBytes, size_t totalBytesAcked) { MS_TRACE(); // Allow some margin for classifying as fully utilized, due to e.g. that // too small packets (less than kMinimumFragmentedPayload) are not sent + // overhead. const bool isFullyUtilized = unackedPacketBytes + this->sctpOptions.mtu >= this->cwnd; #if MS_LOG_DEV_LEVEL == 3 const size_t oldCwnd = this->cwnd; #endif if (GetCongestionAlgorithmPhase() == CongestionAlgorithmPhase::SLOW_START) { if (isFullyUtilized && !IsInFastRecovery()) { // https://tools.ietf.org/html/rfc9260#section-7.2.1 // // "Only when these three conditions are met can the cwnd be // increased; otherwise, the cwnd MUST not be increased. If these // conditions are met, then cwnd MUST be increased by, at most, the // lesser of 1) the total size of the previously outstanding DATA // chunk(s) acknowledged, and 2) the destination's path MTU." this->cwnd += std::min(totalBytesAcked, this->sctpOptions.mtu); MS_DEBUG_DEV("SS increase [cwnd:%zu, oldCwnd:%zu]", this->cwnd, oldCwnd); } } else if (GetCongestionAlgorithmPhase() == CongestionAlgorithmPhase::CONGESTION_AVOIDANCE) { // https://tools.ietf.org/html/rfc9260#section-7.2.2 // // "Whenever cwnd is greater than ssthresh, upon each SACK arrival // that advances the Cumulative TSN Ack Point, increase // partial_bytes_acked by the total number of bytes of all new chunks // acknowledged in that SACK including chunks acknowledged by the new // Cumulative TSN Ack and by Gap Ack Blocks." #if MS_LOG_DEV_LEVEL == 3 const size_t oldPba = this->partialBytesAcked; #endif this->partialBytesAcked += totalBytesAcked; if (this->partialBytesAcked >= this->cwnd && isFullyUtilized) { // https://tools.ietf.org/html/rfc9260#section-7.2.2 // // "When partial_bytes_acked is equal to or greater than cwnd and // before the arrival of the SACK the sender had cwnd or more bytes of // data outstanding (i.e., before arrival of the SACK, flightsize was // greater than or equal to cwnd), increase cwnd by MTU, and reset // partial_bytes_acked to (partial_bytes_acked - cwnd)." // Errata: https://datatracker.ietf.org/doc/html/rfc8540#section-3.12 this->partialBytesAcked -= this->cwnd; this->cwnd += this->sctpOptions.mtu; MS_DEBUG_DEV( "CA increase [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, pba:%zu, oldPba:%zu]", this->cwnd, oldCwnd, this->ssthresh, this->partialBytesAcked, oldPba); } else { MS_DEBUG_DEV( "CA unchanged [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, pba:%zu, oldPba:%zu]", this->cwnd, oldCwnd, this->ssthresh, this->partialBytesAcked, oldPba); } } } void RetransmissionQueue::HandlePacketLoss(Types::UnwrappedTsn /*highestTsnAcked*/) { MS_TRACE(); // https://tools.ietf.org/html/rfc9260#section-7.2.4 // // "If not in Fast Recovery, adjust the ssthresh and cwnd of the // destination address(es) to which the missing DATA chunks were last // sent, according to the formula described in Section 7.2.3." if (!IsInFastRecovery()) { #if MS_LOG_DEV_LEVEL == 3 const size_t oldCwnd = this->cwnd; const size_t oldPba = this->partialBytesAcked; #endif this->ssthresh = std::max(this->cwnd / 2, this->sctpOptions.minCwndMtus * this->sctpOptions.mtu); this->cwnd = this->ssthresh; this->partialBytesAcked = 0; MS_DEBUG_DEV( "packet loss detected (not fast recovery) [cwnd:%zu, oldCwnd:%zu, ssthresh:%zu, pba:%zu, oldPba:%zu]", this->cwnd, oldCwnd, this->ssthresh, this->partialBytesAcked, oldPba); // https://tools.ietf.org/html/rfc9260#section-7.2.4 // // "If not in Fast Recovery, enter Fast Recovery and mark the highest // outstanding TSN as the Fast Recovery exit point." this->fastRecoveryExitTsn = this->outstandingData.GetHighestOutstandingTsn(); MS_DEBUG_DEV( "fast recovery initiated with exit point %" PRIu32, this->fastRecoveryExitTsn.value().Wrap()); } // https://tools.ietf.org/html/rfc9260#section-7.2.4 // // "While in Fast Recovery, the ssthresh and cwnd SHOULD NOT change for // any destinations due to a subsequent Fast Recovery event (i.e., one // SHOULD NOT reduce the cwnd further due to a subsequent Fast // Retransmit)." else { MS_DEBUG_DEV("packet loss detected (fast recovery), no changes"); } } void RetransmissionQueue::UpdateReceiverWindow(uint32_t aRwnd) { MS_TRACE(); this->rwnd = this->outstandingData.GetUnackedPayloadBytes() >= aRwnd ? 0 : aRwnd - this->outstandingData.GetUnackedPayloadBytes(); } void RetransmissionQueue::StartT3RtxTimerIfOutstandingData() { MS_TRACE(); // Note: Can't use `GetUnackedPacketBytes()` as that one doesn't count // chunks to be retransmitted. // https://tools.ietf.org/html/rfc9260#section-6.3.2 // "Whenever all outstanding data sent to an address have been // acknowledged, turn off the T3-rtx timer of that address. if (this->outstandingData.IsEmpty()) { // Note: Already stopped in `StopT3RtxTimerOnIncreasedCumulativeTsnAck()`." // // TODO: As said above, `StopT3RtxTimerOnIncreasedCumulativeTsnAck()` // is NOT implemented in dcsctp and of course it's never called from // anywhere. } else { // https://tools.ietf.org/html/rfc9260#section-6.3.2 // // "Whenever a SACK is received that acknowledges the DATA chunk with // the earliest outstanding TSN for that address, restart the T3-rtx // timer for that address with its current RTO (if there is still // outstanding data on that address)." // "Whenever a SACK is received missing a TSN that was previously // acknowledged via a Gap Ack Block, start the T3-rtx for the // destination address to which the DATA chunk was originally // transmitted if it is not already running." if (!this->t3RtxTimer->IsRunning()) { this->t3RtxTimer->Start(); } } } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/tx/RetransmissionTimeout.cpp ================================================ #define MS_CLASS "RTC::SCTP::RetransmissionTimeout" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/tx/RetransmissionTimeout.hpp" #include "Logger.hpp" #include // std::abs(), std::max(), std::round() namespace RTC { namespace SCTP { /* Static. */ // https://datatracker.ietf.org/doc/html/rfc9260#section-16 static constexpr double RtoAlpha{ 1.0 / 8.0 }; static constexpr double RtoBeta{ 1.0 / 4.0 }; // A factor that the `minRttVarianceMs` configuration option will be divided // by (before later multiplied with K, which is 4 according to RFC6298). When // this value was introduced, it was unintentionally divided by 8 since that // code worked with scaled numbers (to avoid floating point math). That // behavior is kept as downstream users have measured good values for their // use-cases. static constexpr double HeuristicVarianceAdjustment{ 8.0 }; /* Instance methods. */ RetransmissionTimeout::RetransmissionTimeout(const SctpOptions& sctpOptions) : minRtoMs(sctpOptions.minRtoMs), maxRtoMs(sctpOptions.maxRtoMs), maxRttMs(sctpOptions.maxRttMs), minRttVarianceMs(sctpOptions.minRttVarianceMs / HeuristicVarianceAdjustment), srttMs(sctpOptions.initialRtoMs), rtoMs(sctpOptions.initialRtoMs), firstMeasurement(true) { MS_TRACE(); } RetransmissionTimeout::~RetransmissionTimeout() { MS_TRACE(); } void RetransmissionTimeout::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " min rto (ms): %" PRIu64, this->minRtoMs); MS_DUMP_CLEAN(indentation, " max rto (ms): %" PRIu64, this->maxRtoMs); MS_DUMP_CLEAN(indentation, " max rtt (ms): %" PRIu64, this->maxRttMs); MS_DUMP_CLEAN(indentation, " min rtt variance (ms): %" PRIu64, this->minRttVarianceMs); MS_DUMP_CLEAN(indentation, " rto (ms): %" PRIu64, GetRtoMs()); MS_DUMP_CLEAN(indentation, " srtt (ms): %" PRIu64, GetSrttMs()); MS_DUMP_CLEAN(indentation, ""); } void RetransmissionTimeout::ObserveRttMs(uint64_t rttMs) { MS_TRACE(); // Unrealistic values will be skipped. If a wrongly measured (or otherwise // corrupt) value was processed, it could change the state in a way that // would take a very long time to recover. if (rttMs == 0 || rttMs > this->maxRttMs) { MS_WARN_DEV(sctp, "skipping given unrealistic rttMs value %" PRIu64, rttMs); return; } // https://datatracker.ietf.org/doc/html/rfc9260#section-6.3.1 if (this->firstMeasurement) { this->srttMs = rttMs; this->rttVarMs = rttMs / 2.0; this->firstMeasurement = false; } else { const double rttDiffMs = std::abs(this->srttMs - static_cast(rttMs)); this->rttVarMs = ((1.0 - RtoBeta) * this->rttVarMs) + (RtoBeta * rttDiffMs); this->srttMs = ((1.0 - RtoAlpha) * this->srttMs) + (RtoAlpha * rttMs); } this->rttVarMs = std::max(this->rttVarMs, static_cast(this->minRttVarianceMs)); this->rtoMs = this->srttMs + (4.0 * this->rttVarMs); this->rtoMs = std::round( std::clamp( this->rtoMs, static_cast(this->minRtoMs), static_cast(this->maxRtoMs))); MS_DEBUG_DEV("new computed RTO: %" PRIu64 " ms", this->rtoMs); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/tx/RoundRobinSendQueue.cpp ================================================ #define MS_CLASS "RTC::SCTP::RoundRobinSendQueue" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/tx/RoundRobinSendQueue.hpp" #include "Logger.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include #include #include #include // std::forward_as_tuple() namespace RTC { namespace SCTP { /* Instance methods. */ RoundRobinSendQueue::RoundRobinSendQueue( AssociationListenerInterface& associationListener, size_t mtu, uint16_t defaultPriority, size_t totalBufferedAmountLowThreshold) : associationListener(associationListener), defaultPriority(defaultPriority), scheduler(mtu), totalBufferedAmountThresholdWatcher( [this]() { this->associationListener.OnAssociationTotalBufferedAmountLow(); }) { MS_TRACE(); this->totalBufferedAmountThresholdWatcher.SetLowThreshold(totalBufferedAmountLowThreshold); } RoundRobinSendQueue::~RoundRobinSendQueue() { MS_TRACE(); } void RoundRobinSendQueue::AddMessage( uint64_t nowMs, Message message, const SendMessageOptions& sendMessageOptions) { MS_TRACE(); MS_ASSERT(message.GetPayloadLength(), "message payload cannot be empty"); // Any limited lifetime should start counting from now - when the message // has been added to the queue. // `expiresAtMs` is the time when it expires. Which is slightly larger // than the message's lifetime, as the message is alive during its entire // lifetime (which may be zero). const MessageAttributes attributes = { .isUnordered = sendMessageOptions.unordered, .maxRetransmissions = sendMessageOptions.maxRetransmissions.has_value() ? sendMessageOptions.maxRetransmissions.value() : Types::MaxRetransmitsNoLimit, .expiresAtMs = sendMessageOptions.lifetimeMs.has_value() ? nowMs + sendMessageOptions.lifetimeMs.value() + 1 : Types::ExpiresAtMsInfinite, .lifecycleId = sendMessageOptions.lifecycleId, }; const uint16_t streamId = message.GetStreamId(); GetOrCreateStreamInfo(streamId).AddMessage(std::move(message), attributes); AssertIsConsistent(); } uint16_t RoundRobinSendQueue::GetStreamPriority(uint16_t streamId) const { MS_TRACE(); const auto it = this->streams.find(streamId); if (it == this->streams.end()) { return this->defaultPriority; } return it->second.GetPriority(); } void RoundRobinSendQueue::SetStreamPriority(uint16_t streamId, uint16_t priority) { MS_TRACE(); OutgoingStream& stream = GetOrCreateStreamInfo(streamId); stream.SetPriority(priority); AssertIsConsistent(); } std::optional RoundRobinSendQueue::Produce( uint64_t nowMs, size_t maxLength) { MS_TRACE(); return this->scheduler.Produce(nowMs, maxLength); } bool RoundRobinSendQueue::Discard(uint16_t streamId, uint32_t outgoingMessageId) { MS_TRACE(); const bool hasDiscarded = GetOrCreateStreamInfo(streamId).Discard(outgoingMessageId); AssertIsConsistent(); return hasDiscarded; } void RoundRobinSendQueue::PrepareResetStream(uint16_t streamId) { MS_TRACE(); GetOrCreateStreamInfo(streamId).Pause(); AssertIsConsistent(); } bool RoundRobinSendQueue::HasStreamsReadyToBeReset() const { MS_TRACE(); return std::ranges::any_of( this->streams, [](const auto& entry) { const auto& stream = entry.second; return stream.IsReadyToBeReset(); }); } std::vector RoundRobinSendQueue::GetStreamsReadyToBeReset() { MS_TRACE(); MS_ASSERT( std::ranges::count_if( this->streams, [](const auto& s) { const auto& stream = s.second; return stream.IsResetting(); }) == 0, "none of the streams can be resetting"); std::vector readyStreams; for (auto& [streamId, stream] : this->streams) { if (stream.IsReadyToBeReset()) { stream.SetAsResetting(); readyStreams.push_back(streamId); } } return readyStreams; } void RoundRobinSendQueue::CommitResetStreams() { MS_TRACE(); MS_ASSERT( std::ranges::count_if( this->streams, [](const auto& s) { const auto& stream = s.second; return stream.IsResetting(); }) > 0, "at least one stream must be resetting"); for (auto& [unused, stream] : this->streams) { if (stream.IsResetting()) { stream.Reset(); } } AssertIsConsistent(); } void RoundRobinSendQueue::RollbackResetStreams() { MS_TRACE(); MS_ASSERT( std::ranges::count_if( this->streams, [](const auto& s) { const auto& stream = s.second; return stream.IsResetting(); }) > 0, "at least one stream must be resetting"); for (auto& [unused, stream] : this->streams) { if (stream.IsResetting()) { stream.Resume(); } } AssertIsConsistent(); } void RoundRobinSendQueue::Reset() { MS_TRACE(); // Recalculate buffered amount, as partially sent messages may have been // put fully back in the queue. for (auto& [unused, stream] : this->streams) { stream.Reset(); } this->scheduler.ForceReschedule(); } size_t RoundRobinSendQueue::GetStreamBufferedAmount(uint16_t streamId) const { MS_TRACE(); const auto it = this->streams.find(streamId); if (it == this->streams.end()) { return 0; } const auto& stream = it->second; return stream.GetBufferedAmount().GetValue(); } size_t RoundRobinSendQueue::GetStreamBufferedAmountLowThreshold(uint16_t streamId) const { MS_TRACE(); const auto it = this->streams.find(streamId); if (it == this->streams.end()) { return 0; } const auto& stream = it->second; return stream.GetBufferedAmount().GetLowThreshold(); } void RoundRobinSendQueue::SetStreamBufferedAmountLowThreshold(uint16_t streamId, size_t bytes) { MS_TRACE(); GetOrCreateStreamInfo(streamId).GetBufferedAmount().SetLowThreshold(bytes); } RoundRobinSendQueue::OutgoingStream& RoundRobinSendQueue::GetOrCreateStreamInfo(uint16_t streamId) { MS_TRACE(); const auto it = this->streams.find(streamId); if (it != this->streams.end()) { auto& stream = it->second; return stream; } const auto [it2, inserted] = this->streams.emplace( std::piecewise_construct, std::forward_as_tuple(streamId), std::forward_as_tuple( this, std::addressof(this->scheduler), streamId, this->defaultPriority, [this, streamId]() { this->associationListener.OnAssociationStreamBufferedAmountLow(streamId); })); return it2->second; } void RoundRobinSendQueue::AssertIsConsistent() const { MS_TRACE(); std::set expectedActiveStreamIds; const std::set actualActiveStreamIds = this->scheduler.GetActiveStreamsForTesting(); size_t totalBufferedAmount{ 0 }; for (const auto& [streamId, stream] : this->streams) { totalBufferedAmount += stream.GetBufferedAmount().GetValue(); if (stream.GetBytesToSendInNextMessage() > 0) { expectedActiveStreamIds.emplace(streamId); } } if (expectedActiveStreamIds != actualActiveStreamIds) { const auto join = [](const auto& set) { std::string out; size_t i{ 0 }; for (const auto& v : set) { if (i++ != 0) { out += ","; } out += std::to_string(v); } return out; }; MS_ASSERT( expectedActiveStreamIds == actualActiveStreamIds, "active streams mismatch [actual=[%s], expected=[%s]]", join(actualActiveStreamIds).c_str(), join(expectedActiveStreamIds).c_str()); } MS_ASSERT( totalBufferedAmount == this->totalBufferedAmountThresholdWatcher.GetValue(), "total buffered amount mismatch [actual=[%zu], expected=[%zu]]", totalBufferedAmount, this->totalBufferedAmountThresholdWatcher.GetValue()); } void RoundRobinSendQueue::ThresholdWatcher::Decrease(size_t bytes) { MS_TRACE(); MS_ASSERT( bytes <= this->value, "bytes (%zu) must be smaller or equal than this->value (%zu)", bytes, this->value); const size_t oldValue = this->value; this->value -= bytes; if (oldValue > this->lowThreshold && this->value <= this->lowThreshold) { this->onThresholdReached(); } } void RoundRobinSendQueue::ThresholdWatcher::SetLowThreshold(size_t lowThreshold) { MS_TRACE(); // Betting on https://github.com/w3c/webrtc-pc/issues/2654 being accepted. if (this->lowThreshold < this->value && lowThreshold >= this->value) { this->onThresholdReached(); } this->lowThreshold = lowThreshold; } void RoundRobinSendQueue::OutgoingStream::AddMessage(Message message, MessageAttributes attributes) { MS_TRACE(); const bool wasActive = GetBytesToSendInNextMessage() > 0; this->bufferedAmountThresholdWatcher.Increase(message.GetPayloadLength()); this->parent.totalBufferedAmountThresholdWatcher.Increase(message.GetPayloadLength()); const uint32_t outgoingMessageId = this->parent.currentOutgoingMessageId; this->parent.currentOutgoingMessageId = this->parent.currentOutgoingMessageId + 1; this->items.emplace_back(outgoingMessageId, std::move(message), attributes); if (!wasActive) { this->schedulerStream->MayMakeActive(); } AssertIsConsistent(); } std::optional RoundRobinSendQueue::OutgoingStream::Produce( uint64_t nowMs, size_t maxLength) { MS_TRACE(); MS_ASSERT( this->pauseState != PauseState::PAUSED && this->pauseState != PauseState::RESETTING, "pause state must not be PAUSED or RESETTING"); while (!this->items.empty()) { Item& item = this->items.front(); Message& message = item.message; // Allocate Message ID and SSN when the first fragment is sent. if (!item.mid.has_value()) { // This entire message has already expired. Try the next one. if (item.attributes.expiresAtMs != Types::ExpiresAtMsInfinite && item.attributes.expiresAtMs <= nowMs) { HandleMessageExpired(item); this->items.pop_front(); continue; } uint32_t& mid = item.attributes.isUnordered ? this->nextUnorderedMid : this->nextOrderedMid; item.mid = mid; mid += 1; } if (!item.attributes.isUnordered && !item.ssn.has_value()) { item.ssn = this->nextSsn; this->nextSsn += 1; } // Grab the next `maxLength` fragment from this message and calculate // flags. const std::span messagePayload = message.GetPayload(); const std::span chunkPayload = messagePayload.subspan( item.remainingOffset, std::min(messagePayload.size() - item.remainingOffset, maxLength)); const bool isBeginning = chunkPayload.data() == messagePayload.data(); const bool isEnd = (chunkPayload.data() + chunkPayload.size()) == (messagePayload.data() + messagePayload.size()); const uint16_t streamId = message.GetStreamId(); const uint32_t ppid = message.GetPayloadProtocolId(); // Zero-copy the payload if the message fits in a single chunk. std::vector payload = isBeginning && isEnd ? std::move(message).ReleasePayload() : std::vector(chunkPayload.begin(), chunkPayload.end()); const uint32_t fsn = item.currentFsn; item.currentFsn += 1; this->bufferedAmountThresholdWatcher.Decrease(payload.size()); this->parent.totalBufferedAmountThresholdWatcher.Decrease(payload.size()); SendQueueInterface::DataToSend dataToSend( item.outgoingMessageId, UserData( streamId, item.ssn.value_or(0), *item.mid, fsn, ppid, std::move(payload), isBeginning, isEnd, item.attributes.isUnordered)); dataToSend.maxRetransmissions = item.attributes.maxRetransmissions; dataToSend.expiresAtMs = item.attributes.expiresAtMs; dataToSend.lifecycleId = isEnd ? item.attributes.lifecycleId : std::nullopt; if (isEnd) { // The entire message has been sent, and its last data copied to // `dataToSend`, so it can safely be discarded. this->items.pop_front(); if (this->pauseState == PauseState::PENDING) { MS_DEBUG_DEV( "pause state on stream %" PRIu16 " is moving from PENDING to PAUSED", streamId); this->pauseState = PauseState::PAUSED; } } else { item.remainingOffset += chunkPayload.size(); item.remainingLength -= chunkPayload.size(); MS_ASSERT( item.remainingOffset + item.remainingLength == item.message.GetPayloadLength(), "item.remainingOffset + item.remainingLength != item.message.GetPayloadLength()"); MS_ASSERT(item.remainingLength > 0, "item.remainingLength <= 0"); } AssertIsConsistent(); return dataToSend; } AssertIsConsistent(); return std::nullopt; } size_t RoundRobinSendQueue::OutgoingStream::GetBytesToSendInNextMessage() const { MS_TRACE(); if (this->pauseState == PauseState::PAUSED || this->pauseState == PauseState::RESETTING) { // The stream has paused (and there is no partially sent message). return 0; } if (this->items.empty()) { return 0; } return this->items.front().remainingLength; } bool RoundRobinSendQueue::OutgoingStream::Discard(uint32_t outgoingMessageId) { MS_TRACE(); bool result = false; if (!this->items.empty()) { Item& item = this->items.front(); if (item.outgoingMessageId == outgoingMessageId) { HandleMessageExpired(item); this->items.pop_front(); // Only partially sent messages are discarded, so if a message was // discarded, then it was the currently sent message. this->schedulerStream->ForceReschedule(); if (this->pauseState == PauseState::PENDING) { this->pauseState = PauseState::PAUSED; this->schedulerStream->MakeInactive(); } else if (GetBytesToSendInNextMessage() == 0) { this->schedulerStream->MakeInactive(); } // As the item still existed, it had unsent data. result = true; } } AssertIsConsistent(); return result; } void RoundRobinSendQueue::OutgoingStream::Pause() { MS_TRACE(); if (this->pauseState != PauseState::NOT_PAUSED) { // Already in progress. return; } const bool hadPendingItems = !this->items.empty(); // https://datatracker.ietf.org/doc/html/rfc8831#section-6.7 // // "Closing of a data channel MUST be signaled by resetting the corresponding // outgoing streams [RFC6525]. This means that if one side decides to close // the data channel, it resets the corresponding outgoing stream." // ... "[RFC6525] also guarantees that all the messages are delivered (or // abandoned) before the stream is reset." // A stream is paused when it's about to be reset. In this implementation, // it will throw away all non-partially send messages - they will be // abandoned as noted above. This is subject to change. It will however // not discard any partially sent messages - only whole messages. Partially // delivered messages (at the time of receiving a Stream Reset command) will // always deliver all the fragments before actually resetting the stream. for (auto it = this->items.begin(); it != this->items.end();) { if (it->remainingOffset == 0) { auto& item = *it; HandleMessageExpired(item); it = this->items.erase(it); } else { ++it; } } this->pauseState = (this->items.empty() || this->items.front().remainingOffset == 0) ? PauseState::PAUSED : PauseState::PENDING; if (hadPendingItems && this->pauseState == PauseState::PAUSED) { MS_DEBUG_DEV("stream %" PRIu16 " was previously active, but is now paused", GetStreamId()); this->schedulerStream->MakeInactive(); } AssertIsConsistent(); } void RoundRobinSendQueue::OutgoingStream::Resume() { MS_TRACE(); MS_ASSERT(this->pauseState == PauseState::RESETTING, "pause state must be RESETTING"); this->pauseState = PauseState::NOT_PAUSED; this->schedulerStream->MayMakeActive(); AssertIsConsistent(); } void RoundRobinSendQueue::OutgoingStream::SetAsResetting() { MS_TRACE(); MS_ASSERT(this->pauseState == PauseState::PAUSED, "pause state must be PAUSED"); this->pauseState = PauseState::RESETTING; } void RoundRobinSendQueue::OutgoingStream::Reset() { MS_TRACE(); // This can be called both when an outgoing stream reset has been responded // to, or when the entire SendQueue is reset due to detecting the peer having // restarted. The stream may be in any state at this time. const PauseState oldPauseState = this->pauseState; this->pauseState = PauseState::NOT_PAUSED; this->nextOrderedMid = 0; this->nextUnorderedMid = 0; this->nextSsn = 0; if (!this->items.empty()) { // If this message has been partially sent, reset it so that it will be // re-sent. auto& item = this->items.front(); this->bufferedAmountThresholdWatcher.Increase( item.message.GetPayloadLength() - item.remainingLength); this->parent.totalBufferedAmountThresholdWatcher.Increase( item.message.GetPayloadLength() - item.remainingLength); item.remainingOffset = 0; item.remainingLength = item.message.GetPayloadLength(); item.mid = std::nullopt; item.ssn = std::nullopt; item.currentFsn = 0; if (oldPauseState == PauseState::PAUSED || oldPauseState == PauseState::RESETTING) { this->schedulerStream->MayMakeActive(); } } AssertIsConsistent(); } bool RoundRobinSendQueue::OutgoingStream::HasPartiallySentMessage() const { MS_TRACE(); if (this->items.empty()) { return false; } return this->items.front().mid.has_value(); } void RoundRobinSendQueue::OutgoingStream::HandleMessageExpired( RoundRobinSendQueue::OutgoingStream::Item& item) { MS_TRACE(); this->bufferedAmountThresholdWatcher.Decrease(item.remainingLength); this->parent.totalBufferedAmountThresholdWatcher.Decrease(item.remainingLength); if (item.attributes.lifecycleId.has_value()) { MS_DEBUG_DEV( "triggering OnAssociationLifecycleMessageExpired(%" PRIu64 "), /*maybeDelivered*/ false)", item.attributes.lifecycleId); this->parent.associationListener.OnAssociationLifecycleMessageExpired( item.attributes.lifecycleId.value(), /*maybeDelivered*/ false); this->parent.associationListener.OnAssociationLifecycleMessageEnd( item.attributes.lifecycleId.value()); } } void RoundRobinSendQueue::OutgoingStream::AssertIsConsistent() const { MS_TRACE(); size_t bytes{ 0 }; for (const auto& item : this->items) { bytes += item.remainingLength; } MS_ASSERT( bytes == this->bufferedAmountThresholdWatcher.GetValue(), "bytes != this->bufferedAmountThresholdWatcher.GetValue()"); } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SCTP/tx/StreamScheduler.cpp ================================================ #define MS_CLASS "RTC::SCTP::StreamScheduler" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SCTP/tx/StreamScheduler.hpp" #include "Logger.hpp" #include namespace RTC { namespace SCTP { /* Instance methods. */ std::optional StreamScheduler::Produce(uint64_t nowMs, size_t maxLength) { MS_TRACE(); // For non-interleaved streams, avoid rescheduling while still sending a // message as it needs to be sent in full. For interleaved messaging, // reschedule for every I-DATA chunk sent. const bool rescheduling = this->enableMessageInterleaving || !this->currentlySendingAMessage; MS_DEBUG_DEV("producing data, rescheduling"); MS_ASSERT( rescheduling || this->currentStream, "it must be rescheduling or there should be current stream"); std::optional dataToSend; while (!dataToSend.has_value() && !this->activeStreams.empty()) { if (rescheduling) { const auto it = this->activeStreams.begin(); this->currentStream = *it; MS_DEBUG_DEV("rescheduling to stream %" PRIu16, this->currentStream->GetStreamId()); this->activeStreams.erase(it); this->currentStream->ForceMarkInactive(); } else { MS_DEBUG_DEV("producing from previous stream %" PRIu16, this->currentStream->GetStreamId()); MS_ASSERT( std::ranges::any_of( this->activeStreams, [this](const auto* stream) { return stream == this->currentStream; }), "current stream should be in active streams"); } dataToSend = this->currentStream->Produce(nowMs, maxLength); } if (!dataToSend.has_value()) { MS_DEBUG_DEV("there is no stream with data, cannot produce any data"); AssertIsConsistent(); return std::nullopt; } MS_ASSERT( dataToSend->data.GetStreamId() == this->currentStream->GetStreamId(), "no matching stream id"); MS_DEBUG_DEV( "producing data [type:%s, beginning:%s, end:%s, streamId:%" PRIu16 ", ppid:%" PRIu32 ", length:%zu]", dataToSend->data.IsUnordered() ? "unordered" : "ordered", dataToSend->data.IsBeginning() ? "yes" : "no", dataToSend->data.IsEnd() ? "yes" : "no", dataToSend->data.GetStreamId(), dataToSend->data.GetPayloadProtocolId(), dataToSend->data.GetPayloadLength()); this->currentlySendingAMessage = !dataToSend->data.IsEnd(); this->virtualTime = this->currentStream->GetCurrentTime(); // One side-effect of rescheduling is that the new stream will not be // present in `this->activeStreams`. const size_t bytesToSendNext = this->currentStream->GetBytesToSendInNextMessage(); if (rescheduling && bytesToSendNext > 0) { this->currentStream->MakeActive(bytesToSendNext); } else if (!rescheduling && bytesToSendNext == 0) { this->currentStream->MakeInactive(); } AssertIsConsistent(); return dataToSend; } std::set StreamScheduler::GetActiveStreamsForTesting() const { MS_TRACE(); std::set streamIds; for (const auto& stream : this->activeStreams) { streamIds.insert(stream->GetStreamId()); } return streamIds; } void StreamScheduler::AssertIsConsistent() const { MS_TRACE(); for (const Stream* stream : this->activeStreams) { if (stream->GetNextFinishTime() == 0) { MS_ASSERT( stream->GetNextFinishTime() > 0, "stream %" PRIu16 " is active but has no next-finish-time", stream->GetStreamId()); } } } void StreamScheduler::Stream::SetPriority(uint16_t priority) { MS_TRACE(); this->priority = priority; this->inverseWeight = 1.0 / std::max(static_cast(priority), 1e-6); } void StreamScheduler::Stream::MayMakeActive() { MS_TRACE(); MS_ASSERT(this->nextFinishTime == 0, "next-finish-time must be 0"); const size_t bytesToSendNext = GetBytesToSendInNextMessage(); if (bytesToSendNext == 0) { return; } MakeActive(bytesToSendNext); } void StreamScheduler::Stream::MakeActive(size_t bytesToSendNext) { MS_TRACE(); this->currentVirtualTime = this->parent.virtualTime; MS_ASSERT(bytesToSendNext > 0, "bytesToSendNext must be higher than 0"); const double nextFinishTime = CalculateFinishTime(std::min(bytesToSendNext, this->parent.maxPayloadBytes)); MS_ASSERT(nextFinishTime > 0, "nextFinishTime must be higher than 0"); MS_DEBUG_DEV("making stream %" PRIu16 " active, expiring at %f", GetStreamId(), nextFinishTime); MS_ASSERT(this->nextFinishTime == 0, "this->nextFinishTime must be 0"); this->nextFinishTime = nextFinishTime; MS_ASSERT( !std::ranges::any_of( this->parent.activeStreams, [this](const auto* stream) { return stream == this; }), "this stream must not be in active streams"); this->parent.activeStreams.emplace(this); } void StreamScheduler::Stream::ForceMarkInactive() { MS_TRACE(); MS_DEBUG_DEV("making stream %" PRIu16 " inactive", GetStreamId()); MS_ASSERT(this->nextFinishTime != 0, "this->nextFinishTime must be different than 0"); this->nextFinishTime = 0; } void StreamScheduler::Stream::MakeInactive() { MS_TRACE(); ForceMarkInactive(); std::erase_if( this->parent.activeStreams, [this](const auto* stream) { return stream == this; }); } double StreamScheduler::Stream::CalculateFinishTime(size_t bytesToSendNext) const { MS_TRACE(); if (this->parent.enableMessageInterleaving) { // Perform weighted fair queuing scheduling. return this->currentVirtualTime + (static_cast(bytesToSendNext) * this->inverseWeight); } // Perform round-robin scheduling by letting the stream have its next // virtual finish time in the future. It doesn't matter how far into the // future, just any positive number so that any other stream that has the // same virtual finish time as this stream gets to produce their data // before revisiting this stream. return this->currentVirtualTime + 1; } std::optional StreamScheduler::Stream::Produce( uint64_t nowMs, size_t maxLength) { MS_TRACE(); std::optional dataToSend = this->producer.Produce(nowMs, maxLength); if (dataToSend.has_value()) { const double newCurrentVirtualTime = CalculateFinishTime(dataToSend->data.GetPayloadLength()); MS_DEBUG_DEV( "virtual time changed [from:%f, to:%f]", this->currentVirtualTime, newCurrentVirtualTime); this->currentVirtualTime = newCurrentVirtualTime; } return dataToSend; } } // namespace SCTP } // namespace RTC ================================================ FILE: worker/src/RTC/SctpAssociation.cpp ================================================ #define MS_CLASS "RTC::SctpAssociation" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SctpAssociation.hpp" #include "DepUsrSCTP.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::snprintf() #include // std::malloc(), std::free() #include // std::memset(), std::memcpy() #include // Free send buffer threshold (in bytes) upon which send_cb will be executed. static const uint32_t SendBufferThreshold{ 256u }; /* SCTP events to which we are subscribing. */ // clang-format off const uint16_t EventTypes[] = { SCTP_ADAPTATION_INDICATION, SCTP_ASSOC_CHANGE, SCTP_ASSOC_RESET_EVENT, SCTP_REMOTE_ERROR, SCTP_SHUTDOWN_EVENT, SCTP_SEND_FAILED_EVENT, SCTP_STREAM_RESET_EVENT, SCTP_STREAM_CHANGE_EVENT }; // clang-format on /* Static methods for usrsctp callbacks. */ inline static int onRecvSctpData( struct socket* /*sock*/, union sctp_sockstore /*addr*/, void* data, size_t len, struct sctp_rcvinfo rcv, int flags, void* ulpInfo) { auto* sctpAssociation = DepUsrSCTP::RetrieveSctpAssociation(reinterpret_cast(ulpInfo)); if (!sctpAssociation) { MS_WARN_TAG(sctp, "no SctpAssociation found"); std::free(data); return 0; } if (flags & MSG_NOTIFICATION) { sctpAssociation->OnUsrSctpReceiveSctpNotification( static_cast(data), len); } else { const uint16_t streamId = rcv.rcv_sid; const uint32_t ppid = ntohl(rcv.rcv_ppid); const uint16_t ssn = rcv.rcv_ssn; MS_DEBUG_TAG( sctp, "data chunk received [length:%zu, streamId:%" PRIu16 ", SSN:%" PRIu16 ", TSN:%" PRIu32 ", PPID:%" PRIu32 ", context:%" PRIu32 ", flags:%d]", len, rcv.rcv_sid, rcv.rcv_ssn, rcv.rcv_tsn, ntohl(rcv.rcv_ppid), rcv.rcv_context, flags); sctpAssociation->OnUsrSctpReceiveSctpData( streamId, ssn, ppid, flags, static_cast(data), len); } std::free(data); return 1; } inline static int onSendSctpData(struct socket* /*sock*/, uint32_t freeBuffer, void* ulpInfo) { auto* sctpAssociation = DepUsrSCTP::RetrieveSctpAssociation(reinterpret_cast(ulpInfo)); if (!sctpAssociation) { MS_WARN_TAG(sctp, "no SctpAssociation found"); return 0; } sctpAssociation->OnUsrSctpSentData(freeBuffer); return 1; } namespace RTC { /* Static. */ static constexpr size_t SctpMtu{ 1200 }; static constexpr uint16_t MaxSctpStreams{ 65535 }; /* Instance methods. */ SctpAssociation::SctpAssociation( Listener* listener, uint16_t os, uint16_t mis, size_t maxSctpMessageSize, size_t sctpSendBufferSize, bool isDataChannel) : id(DepUsrSCTP::GetNextSctpAssociationId()), listener(listener), os(os), mis(mis), maxSctpMessageSize(maxSctpMessageSize), sctpSendBufferSize(std::max(sctpSendBufferSize, maxSctpMessageSize)), isDataChannel(isDataChannel) { MS_TRACE(); // Register ourselves in usrsctp. // NOTE: This must be done before calling usrsctp_bind(). usrsctp_register_address(reinterpret_cast(this->id)); int ret; // NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer) this->socket = usrsctp_socket( AF_CONN, SOCK_STREAM, IPPROTO_SCTP, onRecvSctpData, onSendSctpData, SendBufferThreshold, reinterpret_cast(this->id)); if (!this->socket) { usrsctp_deregister_address(reinterpret_cast(this->id)); MS_THROW_ERROR("usrsctp_socket() failed: %s", std::strerror(errno)); } usrsctp_set_ulpinfo(this->socket, reinterpret_cast(this->id)); // Make the socket non-blocking. ret = usrsctp_set_non_blocking(this->socket, 1); if (ret < 0) { usrsctp_deregister_address(reinterpret_cast(this->id)); MS_THROW_ERROR("usrsctp_set_non_blocking() failed: %s", std::strerror(errno)); } // Set SO_LINGER. // This ensures that the usrsctp close call deletes the association. This // prevents usrsctp from calling the global send callback with references to // this class as the address. struct linger lingerOpt{}; // NOLINT(cppcoreguidelines-pro-type-member-init) lingerOpt.l_onoff = 1; lingerOpt.l_linger = 0; ret = usrsctp_setsockopt(this->socket, SOL_SOCKET, SO_LINGER, &lingerOpt, sizeof(lingerOpt)); if (ret < 0) { usrsctp_deregister_address(reinterpret_cast(this->id)); MS_THROW_ERROR("usrsctp_setsockopt(SO_LINGER) failed: %s", std::strerror(errno)); } // Set SCTP_ENABLE_STREAM_RESET. struct sctp_assoc_value av{}; // NOLINT(cppcoreguidelines-pro-type-member-init) av.assoc_value = SCTP_ENABLE_RESET_STREAM_REQ | SCTP_ENABLE_RESET_ASSOC_REQ | SCTP_ENABLE_CHANGE_ASSOC_REQ; ret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_ENABLE_STREAM_RESET, &av, sizeof(av)); if (ret < 0) { usrsctp_deregister_address(reinterpret_cast(this->id)); MS_THROW_ERROR("usrsctp_setsockopt(SCTP_ENABLE_STREAM_RESET) failed: %s", std::strerror(errno)); } // Set SCTP_NODELAY. const uint32_t noDelay = 1; ret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_NODELAY, &noDelay, sizeof(noDelay)); if (ret < 0) { usrsctp_deregister_address(reinterpret_cast(this->id)); MS_THROW_ERROR("usrsctp_setsockopt(SCTP_NODELAY) failed: %s", std::strerror(errno)); } // Enable events. struct sctp_event event{}; // NOLINT(cppcoreguidelines-pro-type-member-init) std::memset(&event, 0, sizeof(event)); event.se_on = 1; for (size_t i{ 0 }; i < sizeof(EventTypes) / sizeof(uint16_t); ++i) { event.se_type = EventTypes[i]; ret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_EVENT, &event, sizeof(event)); if (ret < 0) { usrsctp_deregister_address(reinterpret_cast(this->id)); MS_THROW_ERROR("usrsctp_setsockopt(SCTP_EVENT) failed: %s", std::strerror(errno)); } } // Init message. struct sctp_initmsg initmsg{}; // NOLINT(cppcoreguidelines-pro-type-member-init) std::memset(&initmsg, 0, sizeof(initmsg)); initmsg.sinit_num_ostreams = this->os; initmsg.sinit_max_instreams = this->mis; ret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg)); if (ret < 0) { usrsctp_deregister_address(reinterpret_cast(this->id)); MS_THROW_ERROR("usrsctp_setsockopt(SCTP_INITMSG) failed: %s", std::strerror(errno)); } // Server side. struct sockaddr_conn sconn{}; // NOLINT(cppcoreguidelines-pro-type-member-init) std::memset(&sconn, 0, sizeof(sconn)); sconn.sconn_family = AF_CONN; sconn.sconn_port = htons(5000); sconn.sconn_addr = reinterpret_cast(this->id); #ifdef HAVE_SCONN_LEN sconn.sconn_len = sizeof(sconn); #endif ret = usrsctp_bind(this->socket, reinterpret_cast(&sconn), sizeof(sconn)); if (ret < 0) { usrsctp_deregister_address(reinterpret_cast(this->id)); MS_THROW_ERROR("usrsctp_bind() failed: %s", std::strerror(errno)); } auto bufferSize = static_cast(sctpSendBufferSize); if (usrsctp_setsockopt(this->socket, SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(int)) < 0) { usrsctp_deregister_address(reinterpret_cast(this->id)); MS_THROW_ERROR("usrsctp_setsockopt(SO_SNDBUF) failed: %s", std::strerror(errno)); } // Register the SctpAssociation into the global map. DepUsrSCTP::RegisterSctpAssociation(this); } SctpAssociation::~SctpAssociation() { MS_TRACE(); usrsctp_set_ulpinfo(this->socket, nullptr); usrsctp_close(this->socket); // Deregister ourselves from usrsctp. usrsctp_deregister_address(reinterpret_cast(this->id)); // Register the SctpAssociation from the global map. DepUsrSCTP::DeregisterSctpAssociation(this); delete[] this->messageBuffer; } void SctpAssociation::TransportConnected() { MS_TRACE(); this->transportConnected = true; MayConnect(); } void SctpAssociation::TransportDisconnected() { MS_TRACE(); this->transportConnected = false; } flatbuffers::Offset SctpAssociation::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::SctpParameters::CreateSctpParameters( builder, // Add port (always 5000). 5000, // Add OS. this->os, // Add MIS. this->mis, // Add maxMessageSize. this->maxSctpMessageSize, // Add sendBufferSize. this->sctpSendBufferSize, // Add sctpBufferedAmountLowThreshold. this->sctpBufferedAmount, // Add isDataChannel. this->isDataChannel); } void SctpAssociation::ProcessSctpData(const uint8_t* data, size_t len) { MS_TRACE(); this->sctpDataReceived = true; MayConnect(); #if MS_LOG_DEV_LEVEL == 3 // NOTE: Only uncomment this during local debugging if needed. // MS_DUMP_DATA(data, len); #endif usrsctp_conninput(reinterpret_cast(this->id), data, len, 0); } void SctpAssociation::SendSctpMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb) { MS_TRACE(); // This must be controlled by the DataConsumer. MS_ASSERT( len <= this->maxSctpMessageSize, "given message exceeds max allowed message size [message size:%zu, max message size:%zu]", len, this->maxSctpMessageSize); const auto& parameters = dataConsumer->GetSctpStreamParameters(); // Fill sctp_sendv_spa. struct sctp_sendv_spa spa{}; // NOLINT(cppcoreguidelines-pro-type-member-init) std::memset(&spa, 0, sizeof(spa)); spa.sendv_flags = SCTP_SEND_SNDINFO_VALID; spa.sendv_sndinfo.snd_sid = parameters.streamId; spa.sendv_sndinfo.snd_ppid = htonl(ppid); spa.sendv_sndinfo.snd_flags = SCTP_EOR; // If ordered it must be reliable. if (parameters.ordered) { spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_NONE; spa.sendv_prinfo.pr_value = 0; } // Configure reliability: https://tools.ietf.org/html/rfc3758 else { spa.sendv_flags |= SCTP_SEND_PRINFO_VALID; spa.sendv_sndinfo.snd_flags |= SCTP_UNORDERED; if (parameters.maxPacketLifeTime != 0) { spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_TTL; spa.sendv_prinfo.pr_value = parameters.maxPacketLifeTime; } else if (parameters.maxRetransmits != 0) { spa.sendv_prinfo.pr_policy = SCTP_PR_SCTP_RTX; spa.sendv_prinfo.pr_value = parameters.maxRetransmits; } } this->sctpBufferedAmount += len; // Notify the listener about the buffered amount increase regardless // usrsctp_sendv result. // In case of failure the correct value will be later provided by usrsctp // via onSendSctpData. this->listener->OnSctpAssociationBufferedAmount(this, this->sctpBufferedAmount); const ssize_t ret = usrsctp_sendv( this->socket, msg, len, nullptr, 0, &spa, static_cast(sizeof(spa)), SCTP_SENDV_SPA, 0); if (ret < 0) { const bool sctpSendBufferFull = errno == EWOULDBLOCK || errno == EAGAIN; // SCTP send buffer being full is legit, not an error. if (sctpSendBufferFull) { MS_WARN_DEV( "error sending SCTP message [sid:%" PRIu16 ", ppid:%" PRIu32 ", message size:%zu]: %s", parameters.streamId, ppid, len, std::strerror(errno)); } else { MS_WARN_TAG( sctp, "error sending SCTP message [sid:%" PRIu16 ", ppid:%" PRIu32 ", message size:%zu]: %s", parameters.streamId, ppid, len, std::strerror(errno)); } if (cb) { (*cb)(false, sctpSendBufferFull); delete cb; } if (sctpSendBufferFull) { dataConsumer->SctpAssociationSendBufferFull(); } } else if (cb) { (*cb)(true, false); delete cb; } } void SctpAssociation::HandleDataProducer(RTC::DataProducer* /*dataProducer*/) { MS_TRACE(); this->firstStreamCreated = true; MayConnect(); } void SctpAssociation::HandleDataConsumer(RTC::DataConsumer* dataConsumer) { MS_TRACE(); this->firstStreamCreated = true; MayConnect(); auto streamId = dataConsumer->GetSctpStreamParameters().streamId; // We need more OS. if (streamId > this->os - 1) { AddOutgoingStreams(/*force*/ false); } } void SctpAssociation::DataProducerClosed(RTC::DataProducer* dataProducer) { MS_TRACE(); auto streamId = dataProducer->GetSctpStreamParameters().streamId; // Send SCTP_RESET_STREAMS to the remote. // https://tools.ietf.org/html/rfc8831#section-6.7 if (this->isDataChannel) { ResetSctpStream(streamId, StreamDirection::OUTGOING); } else { ResetSctpStream(streamId, StreamDirection::INCOMING); } } void SctpAssociation::DataConsumerClosed(RTC::DataConsumer* dataConsumer) { MS_TRACE(); auto streamId = dataConsumer->GetSctpStreamParameters().streamId; // Send SCTP_RESET_STREAMS to the remote. ResetSctpStream(streamId, StreamDirection::OUTGOING); } void SctpAssociation::MayConnect() { MS_TRACE(); // Just run the SCTP stack if our state is 'new'. // Notice that once MayConnect() is called (and the code below is executed), // SCTP state will no longer be "NEW". if (this->state != SctpState::NEW) { MS_DEBUG_DEV("SCTP state is not NEW, ignoring"); return; } // If the transport is not connected, don't do anything. else if (!this->transportConnected) { MS_DEBUG_DEV("transport is not connected, ignoring"); return; } // If there are no SCTP streams yet and no SCTP data has been yet received // from the remote peer, don't do anything. // This is because the peer may never create a DataChannel so we shouldn't // try to connect SCTP (SCTP INIT chunk, etc) since it will timeout and // trigger "SCTP failed". else if (!this->firstStreamCreated && !this->sctpDataReceived) { MS_DEBUG_DEV( "no SCTP stream has been created yet and no SCTP data has been received yet, ignoring"); return; } MS_DEBUG_TAG(sctp, "connecting SCTP"); try { int ret; struct sockaddr_conn rconn{}; // NOLINT(cppcoreguidelines-pro-type-member-init) std::memset(&rconn, 0, sizeof(rconn)); rconn.sconn_family = AF_CONN; rconn.sconn_port = htons(5000); rconn.sconn_addr = reinterpret_cast(this->id); #ifdef HAVE_SCONN_LEN rconn.sconn_len = sizeof(rconn); #endif ret = usrsctp_connect(this->socket, reinterpret_cast(&rconn), sizeof(rconn)); if (ret < 0 && errno != EINPROGRESS) { MS_THROW_ERROR("usrsctp_connect() failed: %s", std::strerror(errno)); } // Disable MTU discovery. sctp_paddrparams peerAddrParams{}; // NOLINT(cppcoreguidelines-pro-type-member-init) std::memset(&peerAddrParams, 0, sizeof(peerAddrParams)); std::memcpy(&peerAddrParams.spp_address, &rconn, sizeof(rconn)); peerAddrParams.spp_flags = SPP_PMTUD_DISABLE; // The MTU value provided specifies the space available for chunks in the // packet, so let's subtract the SCTP header size. peerAddrParams.spp_pathmtu = SctpMtu - sizeof(struct sctp_common_header); ret = usrsctp_setsockopt( this->socket, IPPROTO_SCTP, SCTP_PEER_ADDR_PARAMS, &peerAddrParams, sizeof(peerAddrParams)); if (ret < 0) { MS_THROW_ERROR("usrsctp_setsockopt(SCTP_PEER_ADDR_PARAMS) failed: %s", std::strerror(errno)); } // Announce connecting state. MS_DEBUG_DEV("SCTP state switched to CONNECTING (in MayConnect())"); this->state = SctpState::CONNECTING; this->listener->OnSctpAssociationConnecting(this); } catch (const MediaSoupError& /*error*/) { MS_DEBUG_DEV("SCTP state switched to FAILED (in MayConnect())"); this->state = SctpState::FAILED; this->listener->OnSctpAssociationFailed(this); } } void SctpAssociation::ResetSctpStream(uint16_t streamId, StreamDirection direction) const { MS_TRACE(); // Do nothing if an outgoing stream that could not be allocated by us. if (direction == StreamDirection::OUTGOING && streamId > this->os - 1) { return; } int ret; struct sctp_assoc_value av{}; // NOLINT(cppcoreguidelines-pro-type-member-init) socklen_t len = sizeof(av); ret = usrsctp_getsockopt(this->socket, IPPROTO_SCTP, SCTP_RECONFIG_SUPPORTED, &av, &len); if (ret == 0) { if (av.assoc_value != 1) { MS_DEBUG_TAG(sctp, "stream reconfiguration not negotiated"); return; } } else { MS_WARN_TAG( sctp, "could not retrieve whether stream reconfiguration has been negotiated: %s\n", std::strerror(errno)); return; } // As per spec: https://tools.ietf.org/html/rfc6525#section-4.1 len = sizeof(sctp_assoc_t) + ((2 + 1) * sizeof(uint16_t)); auto* srs = static_cast(std::malloc(len)); switch (direction) { case StreamDirection::INCOMING: srs->srs_flags = SCTP_STREAM_RESET_INCOMING; break; case StreamDirection::OUTGOING: srs->srs_flags = SCTP_STREAM_RESET_OUTGOING; break; } srs->srs_number_streams = 1; srs->srs_stream_list[0] = streamId; // No need for htonl(). ret = usrsctp_setsockopt(this->socket, IPPROTO_SCTP, SCTP_RESET_STREAMS, srs, len); if (ret == 0) { MS_DEBUG_TAG(sctp, "SCTP_RESET_STREAMS sent [streamId:%" PRIu16 "]", streamId); } else { MS_WARN_TAG(sctp, "usrsctp_setsockopt(SCTP_RESET_STREAMS) failed: %s", std::strerror(errno)); } std::free(srs); } void SctpAssociation::AddOutgoingStreams(bool force) { MS_TRACE(); uint16_t additionalOs{ 0 }; if (MaxSctpStreams - this->os >= 32) { additionalOs = 32; } else { additionalOs = MaxSctpStreams - this->os; } if (additionalOs == 0) { MS_WARN_TAG(sctp, "cannot add more outgoing streams [OS:%" PRIu16 "]", this->os); return; } auto nextDesiredOs = this->os + additionalOs; // Already in progress, ignore (unless forced). if (!force && nextDesiredOs == this->desiredOs) { return; } // Update desired value. this->desiredOs = nextDesiredOs; // If not connected, defer it. if (this->state != SctpState::CONNECTED) { MS_DEBUG_TAG(sctp, "SCTP not connected, deferring OS increase"); return; } struct sctp_add_streams sas{}; // NOLINT(cppcoreguidelines-pro-type-member-init) std::memset(&sas, 0, sizeof(sas)); sas.sas_instrms = 0; sas.sas_outstrms = additionalOs; MS_DEBUG_TAG(sctp, "adding %" PRIu16 " outgoing streams", additionalOs); const int ret = usrsctp_setsockopt( this->socket, IPPROTO_SCTP, SCTP_ADD_STREAMS, &sas, static_cast(sizeof(sas))); if (ret < 0) { MS_WARN_TAG(sctp, "usrsctp_setsockopt(SCTP_ADD_STREAMS) failed: %s", std::strerror(errno)); } } void SctpAssociation::OnUsrSctpSendSctpData(void* buffer, size_t len) { MS_TRACE(); const uint8_t* data = static_cast(buffer); #if MS_LOG_DEV_LEVEL == 3 // NOTE: Only uncomment this during local debugging if needed. // MS_DUMP_DATA(data, len); #endif this->listener->OnSctpAssociationSendData(this, data, len); } void SctpAssociation::OnUsrSctpReceiveSctpData( uint16_t streamId, uint16_t ssn, uint32_t ppid, int flags, const uint8_t* data, size_t len) { // Ignore WebRTC DataChannel Control DATA chunks. if (ppid == 50) { MS_WARN_TAG(sctp, "ignoring SCTP data with ppid:50 (WebRTC DataChannel Control)"); return; } if (this->messageBufferLen != 0 && ssn != this->lastSsnReceived) { MS_WARN_TAG( sctp, "message chunk received with different SSN while buffer not empty, buffer discarded [ssn:%" PRIu16 ", last ssn received:%" PRIu16 "]", ssn, this->lastSsnReceived); this->messageBufferLen = 0; } // Update last SSN received. this->lastSsnReceived = ssn; auto eor = static_cast(flags & MSG_EOR); if (this->messageBufferLen + len > this->maxSctpMessageSize) { MS_WARN_TAG( sctp, "ongoing received message exceeds max allowed message size [message size:%zu, max message size:%zu, eor:%u]", this->messageBufferLen + len, this->maxSctpMessageSize, eor ? 1 : 0); this->lastSsnReceived = 0; return; } // If end of message and there is no buffered data, notify it directly. if (eor && this->messageBufferLen == 0) { MS_DEBUG_DEV("directly notifying listener [eor:1, buffer len:0]"); this->listener->OnSctpAssociationMessageReceived(this, streamId, data, len, ppid); } // If end of message and there is buffered data, append data and notify buffer. else if (eor && this->messageBufferLen != 0) { std::memcpy(this->messageBuffer + this->messageBufferLen, data, len); this->messageBufferLen += len; MS_DEBUG_DEV("notifying listener [eor:1, buffer len:%zu]", this->messageBufferLen); this->listener->OnSctpAssociationMessageReceived( this, streamId, this->messageBuffer, this->messageBufferLen, ppid); this->messageBufferLen = 0; } // If non end of message, append data to the buffer. else if (!eor) { // Allocate the buffer if not already done. if (!this->messageBuffer) { this->messageBuffer = new uint8_t[this->maxSctpMessageSize]; } std::memcpy(this->messageBuffer + this->messageBufferLen, data, len); this->messageBufferLen += len; MS_DEBUG_DEV("data buffered [eor:0, buffer len:%zu]", this->messageBufferLen); } } void SctpAssociation::OnUsrSctpReceiveSctpNotification(union sctp_notification* notification, size_t len) { if (notification->sn_header.sn_length != (uint32_t)len) { return; } switch (notification->sn_header.sn_type) { case SCTP_ADAPTATION_INDICATION: { MS_DEBUG_TAG( sctp, "SCTP adaptation indication [%x]", notification->sn_adaptation_event.sai_adaptation_ind); break; } case SCTP_ASSOC_CHANGE: { switch (notification->sn_assoc_change.sac_state) { case SCTP_COMM_UP: { MS_DEBUG_TAG( sctp, "SCTP association connected, streams [out:%" PRIu16 ", in:%" PRIu16 "]", notification->sn_assoc_change.sac_outbound_streams, notification->sn_assoc_change.sac_inbound_streams); // Update our OS. this->os = notification->sn_assoc_change.sac_outbound_streams; // Increase if requested before connected. if (this->desiredOs > this->os) { AddOutgoingStreams(/*force*/ true); } if (this->state != SctpState::CONNECTED) { MS_DEBUG_DEV("SCTP state switched to CONNECTED (in SCTP_ASSOC_CHANGE)"); this->state = SctpState::CONNECTED; this->listener->OnSctpAssociationConnected(this); } break; } case SCTP_COMM_LOST: { if (notification->sn_header.sn_length > 0) { static const size_t BufferSize{ 1024 }; static thread_local char buffer[BufferSize]; const uint32_t len = notification->sn_assoc_change.sac_length - sizeof(struct sctp_assoc_change); for (uint32_t i{ 0 }; i < len; ++i) { std::snprintf( buffer, BufferSize, " 0x%02x", notification->sn_assoc_change.sac_info[i]); } MS_DEBUG_TAG(sctp, "SCTP communication lost [info:%s]", buffer); } else { MS_DEBUG_TAG(sctp, "SCTP communication lost"); } if (this->state != SctpState::CLOSED) { MS_DEBUG_DEV("SCTP state switched to CLOSED (in SCTP_COMM_LOST)"); this->state = SctpState::CLOSED; this->listener->OnSctpAssociationClosed(this); } break; } case SCTP_RESTART: { MS_DEBUG_TAG( sctp, "SCTP remote association restarted, streams [out:%" PRIu16 ", int:%" PRIu16 "]", notification->sn_assoc_change.sac_outbound_streams, notification->sn_assoc_change.sac_inbound_streams); // Update our OS. this->os = notification->sn_assoc_change.sac_outbound_streams; // Increase if requested before connected. if (this->desiredOs > this->os) { AddOutgoingStreams(/*force*/ true); } if (this->state != SctpState::CONNECTED) { MS_DEBUG_DEV("SCTP state switched to CONNECTED (in SCTP_RESTART)"); this->state = SctpState::CONNECTED; this->listener->OnSctpAssociationConnected(this); } break; } case SCTP_SHUTDOWN_COMP: { MS_DEBUG_TAG(sctp, "SCTP association gracefully closed"); if (this->state != SctpState::CLOSED) { MS_DEBUG_DEV("SCTP state switched to CLOSED (in SCTP_SHUTDOWN_COMP)"); this->state = SctpState::CLOSED; this->listener->OnSctpAssociationClosed(this); } break; } case SCTP_CANT_STR_ASSOC: { if (notification->sn_header.sn_length > 0) { static const size_t BufferSize{ 1024 }; static thread_local char buffer[BufferSize]; const uint32_t len = notification->sn_assoc_change.sac_length - sizeof(struct sctp_assoc_change); for (uint32_t i{ 0 }; i < len; ++i) { std::snprintf( buffer, BufferSize, " 0x%02x", notification->sn_assoc_change.sac_info[i]); } MS_WARN_TAG(sctp, "SCTP setup failed: %s", buffer); } if (this->state != SctpState::FAILED) { MS_DEBUG_DEV("SCTP state switched to FAILED (in SCTP_CANT_STR_ASSOC)"); this->state = SctpState::FAILED; this->listener->OnSctpAssociationFailed(this); } break; } default:; } break; } // https://tools.ietf.org/html/rfc6525#section-6.1.2. case SCTP_ASSOC_RESET_EVENT: { MS_DEBUG_TAG(sctp, "SCTP association reset event received"); break; } // An Operation Error is not considered fatal in and of itself, but may be // used with an ABORT chunk to report a fatal condition. case SCTP_REMOTE_ERROR: { static const size_t BufferSize{ 1024 }; static thread_local char buffer[BufferSize]; const uint32_t len = notification->sn_remote_error.sre_length - sizeof(struct sctp_remote_error); for (uint32_t i{ 0 }; i < len; i++) { std::snprintf(buffer, BufferSize, "0x%02x", notification->sn_remote_error.sre_data[i]); } MS_WARN_TAG( sctp, "remote SCTP association error [type:0x%04x, data:%s]", notification->sn_remote_error.sre_error, buffer); break; } // When a peer sends a SHUTDOWN, SCTP delivers this notification to // inform the application that it should cease sending data. case SCTP_SHUTDOWN_EVENT: { MS_DEBUG_TAG(sctp, "remote SCTP association shutdown"); if (this->state != SctpState::CLOSED) { MS_DEBUG_DEV("SCTP state switched to CLOSED (in SCTP_SHUTDOWN_EVENT)"); this->state = SctpState::CLOSED; this->listener->OnSctpAssociationClosed(this); } break; } case SCTP_SEND_FAILED_EVENT: { static const size_t BufferSize{ 1024 }; static thread_local char buffer[BufferSize]; const uint32_t len = notification->sn_send_failed_event.ssfe_length - sizeof(struct sctp_send_failed_event); for (uint32_t i{ 0 }; i < len; ++i) { std::snprintf(buffer, BufferSize, "0x%02x", notification->sn_send_failed_event.ssfe_data[i]); } MS_WARN_TAG( sctp, "SCTP message sent failure [streamId:%" PRIu16 ", ppid:%" PRIu32 ", sent:%s, error:0x%08x, info:%s]", notification->sn_send_failed_event.ssfe_info.snd_sid, ntohl(notification->sn_send_failed_event.ssfe_info.snd_ppid), (notification->sn_send_failed_event.ssfe_flags & SCTP_DATA_SENT) ? "yes" : "no", notification->sn_send_failed_event.ssfe_error, buffer); break; } case SCTP_STREAM_RESET_EVENT: { bool incoming{ false }; bool outgoing{ false }; const uint16_t numStreams = (notification->sn_strreset_event.strreset_length - sizeof(struct sctp_stream_reset_event)) / sizeof(uint16_t); if (notification->sn_strreset_event.strreset_flags & SCTP_STREAM_RESET_INCOMING_SSN) { incoming = true; } if (notification->sn_strreset_event.strreset_flags & SCTP_STREAM_RESET_OUTGOING_SSN) { outgoing = true; } if (MS_HAS_DEBUG_TAG(sctp)) { std::string streamIds; for (uint16_t i{ 0 }; i < numStreams; ++i) { auto streamId = notification->sn_strreset_event.strreset_stream_list[i]; // Don't log more than 5 stream ids. if (i > 4) { streamIds.append("..."); break; } if (i > 0) { streamIds.append(","); } streamIds.append(std::to_string(streamId)); } MS_DEBUG_TAG( sctp, "SCTP stream reset event [flags:%x, i|o:%s|%s, num streams:%" PRIu16 ", stream ids:%s]", notification->sn_strreset_event.strreset_flags, incoming ? "true" : "false", outgoing ? "true" : "false", numStreams, streamIds.c_str()); } // Special case for WebRTC DataChannels in which we must also reset our // outgoing SCTP stream. if (incoming && !outgoing && this->isDataChannel) { for (uint16_t i{ 0 }; i < numStreams; ++i) { auto streamId = notification->sn_strreset_event.strreset_stream_list[i]; ResetSctpStream(streamId, StreamDirection::OUTGOING); } } break; } case SCTP_STREAM_CHANGE_EVENT: { if (notification->sn_strchange_event.strchange_flags == 0) { MS_DEBUG_TAG( sctp, "SCTP stream changed, streams [out:%" PRIu16 ", in:%" PRIu16 ", flags:%x]", notification->sn_strchange_event.strchange_outstrms, notification->sn_strchange_event.strchange_instrms, notification->sn_strchange_event.strchange_flags); } else if (notification->sn_strchange_event.strchange_flags & SCTP_STREAM_RESET_DENIED) { MS_WARN_TAG( sctp, "SCTP stream change denied, streams [out:%" PRIu16 ", in:%" PRIu16 ", flags:%x]", notification->sn_strchange_event.strchange_outstrms, notification->sn_strchange_event.strchange_instrms, notification->sn_strchange_event.strchange_flags); break; } else if (notification->sn_strchange_event.strchange_flags & SCTP_STREAM_RESET_FAILED) { MS_WARN_TAG( sctp, "SCTP stream change failed, streams [out:%" PRIu16 ", in:%" PRIu16 ", flags:%x]", notification->sn_strchange_event.strchange_outstrms, notification->sn_strchange_event.strchange_instrms, notification->sn_strchange_event.strchange_flags); break; } // Update OS. this->os = notification->sn_strchange_event.strchange_outstrms; break; } default: { MS_WARN_TAG( sctp, "unhandled SCTP event received [type:%" PRIu16 "]", notification->sn_header.sn_type); } } } void SctpAssociation::OnUsrSctpSentData(uint32_t freeBuffer) { auto previousSctpBufferedAmount = this->sctpBufferedAmount; this->sctpBufferedAmount = this->sctpSendBufferSize - freeBuffer; if (this->sctpBufferedAmount != previousSctpBufferedAmount) { this->listener->OnSctpAssociationBufferedAmount(this, this->sctpBufferedAmount); } } } // namespace RTC ================================================ FILE: worker/src/RTC/SctpDictionaries/SctpStreamParameters.cpp ================================================ #define MS_CLASS "RTC::SctpStreamParameters" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/SctpDictionaries.hpp" namespace RTC { /* Instance methods. */ SctpStreamParameters::SctpStreamParameters(const FBS::SctpParameters::SctpStreamParameters* data) { MS_TRACE(); this->streamId = data->streamId(); if (this->streamId > 65534) { MS_THROW_TYPE_ERROR("streamId must not be greater than 65534"); } // ordered is optional. bool orderedGiven = false; if (auto ordered = data->ordered(); ordered.has_value()) { orderedGiven = true; this->ordered = ordered.value(); } // maxPacketLifeTime is optional. if (auto maxPacketLifeTime = data->maxPacketLifeTime(); maxPacketLifeTime.has_value()) { this->maxPacketLifeTime = maxPacketLifeTime.value(); } // maxRetransmits is optional. if (auto maxRetransmits = data->maxRetransmits(); maxRetransmits.has_value()) { this->maxRetransmits = maxRetransmits.value(); } if (this->maxPacketLifeTime && this->maxRetransmits) { MS_THROW_TYPE_ERROR("cannot provide both maxPacketLifeTime and maxRetransmits"); } if (orderedGiven && this->ordered && (this->maxPacketLifeTime || this->maxRetransmits)) { MS_THROW_TYPE_ERROR("cannot be ordered with maxPacketLifeTime or maxRetransmits"); } else if (!orderedGiven && (this->maxPacketLifeTime || this->maxRetransmits)) { this->ordered = false; } } flatbuffers::Offset SctpStreamParameters::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); return FBS::SctpParameters::CreateSctpStreamParameters( builder, this->streamId, this->ordered, this->maxPacketLifeTime ? flatbuffers::Optional(this->maxPacketLifeTime) : flatbuffers::nullopt, this->maxRetransmits ? flatbuffers::Optional(this->maxRetransmits) : flatbuffers::nullopt); } } // namespace RTC ================================================ FILE: worker/src/RTC/SctpListener.cpp ================================================ #define MS_CLASS "RTC::SctpListener" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SctpListener.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "RTC/DataProducer.hpp" namespace RTC { /* Instance methods. */ flatbuffers::Offset SctpListener::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add streamIdTable. std::vector> streamIdTable; for (const auto& kv : this->streamIdTable) { auto streamId = kv.first; auto* dataProducer = kv.second; streamIdTable.emplace_back( FBS::Common::CreateUint16StringDirect(builder, streamId, dataProducer->id.c_str())); } return FBS::Transport::CreateSctpListenerDirect(builder, &streamIdTable); } void SctpListener::AddDataProducer(RTC::DataProducer* dataProducer) { MS_TRACE(); const auto& sctpParameters = dataProducer->GetSctpStreamParameters(); // Add entries into the streamIdTable. auto streamId = sctpParameters.streamId; if (this->streamIdTable.find(streamId) == this->streamIdTable.end()) { this->streamIdTable[streamId] = dataProducer; } else { MS_THROW_ERROR("streamId already exists in SCTP listener [streamId:%" PRIu16 "]", streamId); } } void SctpListener::RemoveDataProducer(RTC::DataProducer* dataProducer) { MS_TRACE(); // Remove from the listener table all entries pointing to the DataProducer. for (auto it = this->streamIdTable.begin(); it != this->streamIdTable.end();) { if (it->second == dataProducer) { it = this->streamIdTable.erase(it); } else { ++it; } } } RTC::DataProducer* SctpListener::GetDataProducer(uint16_t streamId) { MS_TRACE(); auto it = this->streamIdTable.find(streamId); if (it != this->streamIdTable.end()) { auto* dataProducer = it->second; return dataProducer; } return nullptr; } } // namespace RTC ================================================ FILE: worker/src/RTC/SenderBandwidthEstimator.cpp ================================================ #define MS_CLASS "RTC::SenderBandwidthEstimator" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SenderBandwidthEstimator.hpp" #include "Logger.hpp" namespace RTC { /* Static. */ // static constexpr uint64_t AvailableBitrateEventInterval{ 2000u }; // In ms. static constexpr uint16_t MaxSentInfoAge{ 2000u }; // TODO: Let's see. static constexpr float DefaultRtt{ 100 }; /* Instance methods. */ SenderBandwidthEstimator::SenderBandwidthEstimator( RTC::SenderBandwidthEstimator::Listener* listener, SharedInterface* shared, uint32_t initialAvailableBitrate) : listener(listener), shared(shared), initialAvailableBitrate(initialAvailableBitrate), rtt(DefaultRtt), sendTransmission(1000u), sendTransmissionTrend(0.15f) { MS_TRACE(); } SenderBandwidthEstimator::~SenderBandwidthEstimator() { MS_TRACE(); } void SenderBandwidthEstimator::TransportConnected() { MS_TRACE(); this->availableBitrate = this->initialAvailableBitrate; this->lastAvailableBitrateEventAtMs = this->shared->GetTimeMs(); } void SenderBandwidthEstimator::TransportDisconnected() { MS_TRACE(); this->availableBitrate = 0u; this->sentInfos.clear(); this->cummulativeResult.Reset(); } void SenderBandwidthEstimator::RtpPacketSent(const SentInfo& sentInfo) { MS_TRACE(); auto nowMs = sentInfo.sentAtMs; // Remove old sent infos. auto it = this->sentInfos.lower_bound(sentInfo.wideSeq - MaxSentInfoAge + 1); this->sentInfos.erase(this->sentInfos.begin(), it); // Insert the sent info into the map. this->sentInfos[sentInfo.wideSeq] = sentInfo; // Fill the send transmission counter. this->sendTransmission.Update(sentInfo.size, nowMs); // Update the send transmission trend. auto sendBitrate = this->sendTransmission.GetRate(nowMs); this->sendTransmissionTrend.Update(sendBitrate, nowMs); // TODO: Remove. // MS_DEBUG_DEV( // "[wideSeq:%" PRIu16 ", size:%zu, isProbation:%s, bitrate:%" PRIu32 "]", // sentInfo.wideSeq, // sentInfo.size, // sentInfo.isProbation ? "true" : "false", // sendBitrate); } void SenderBandwidthEstimator::ReceiveRtcpTransportFeedback( const RTC::RTCP::FeedbackRtpTransportPacket* feedback) { MS_TRACE(); auto nowMs = this->shared->GetTimeMs(); const uint64_t elapsedMs = nowMs - this->cummulativeResult.GetStartedAtMs(); // Drop ongoing cummulative result if too old. if (elapsedMs > 1000u) { this->cummulativeResult.Reset(); } for (auto& result : feedback->GetPacketResults()) { if (!result.received) { continue; } const uint16_t wideSeq = result.sequenceNumber; auto it = this->sentInfos.find(wideSeq); if (it == this->sentInfos.end()) { MS_WARN_DEV("received packet not present in sent infos [wideSeq:%" PRIu16 "]", wideSeq); continue; } auto& sentInfo = it->second; if (!sentInfo.isProbation) { this->cummulativeResult.AddPacket( sentInfo.size, static_cast(sentInfo.sentAtMs), result.receivedAtMs); } else { this->probationCummulativeResult.AddPacket( sentInfo.size, static_cast(sentInfo.sentAtMs), result.receivedAtMs); } } // Handle probation packets separately. if (this->probationCummulativeResult.GetNumPackets() >= 2u) { // MS_DEBUG_DEV( // "probation [packets:%zu, size:%zu] " // "[send bps:%" PRIu32 ", recv bps:%" PRIu32 "] ", // this->probationCummulativeResult.GetNumPackets(), // this->probationCummulativeResult.GetTotalSize(), // this->probationCummulativeResult.GetSendBitrate(), // this->probationCummulativeResult.GetReceiveBitrate()); EstimateAvailableBitrate(this->probationCummulativeResult); // Reset probation cummulative result. this->probationCummulativeResult.Reset(); } else { // Reset probation cummulative result. this->probationCummulativeResult.Reset(); } // Handle real and probation packets all together. if (elapsedMs >= 100u && this->cummulativeResult.GetNumPackets() >= 20u) { // auto sendBitrate = this->sendTransmission.GetRate(nowMs); // auto sendBitrateTrend = this->sendTransmissionTrend.GetValue(); // MS_DEBUG_DEV( // "real+prob [packets:%zu, size:%zu] " // "[send bps:%" PRIu32 ", recv bps:%" PRIu32 // "] " // "[real bps:%" PRIu32 ", trend bps:%" PRIu32 "]", // this->cummulativeResult.GetNumPackets(), // this->cummulativeResult.GetTotalSize(), // this->cummulativeResult.GetSendBitrate(), // this->cummulativeResult.GetReceiveBitrate(), // sendBitrate, // sendBitrateTrend); EstimateAvailableBitrate(this->cummulativeResult); // Reset cummulative result. this->cummulativeResult.Reset(); } } void SenderBandwidthEstimator::EstimateAvailableBitrate(CummulativeResult& cummulativeResult) { MS_TRACE(); auto previousAvailableBitrate = this->availableBitrate; const double ratio = static_cast(cummulativeResult.GetReceiveBitrate()) / static_cast(cummulativeResult.GetSendBitrate()); auto bitrate = std::min(cummulativeResult.GetReceiveBitrate(), cummulativeResult.GetSendBitrate()); if (0.75f <= ratio && ratio <= 1.25f) { if (bitrate > this->availableBitrate) { this->availableBitrate = bitrate; MS_DEBUG_DEV("BWE UP [ratio:%f, availableBitrate:%" PRIu32 "]", ratio, this->availableBitrate); } } else { if (bitrate < this->availableBitrate) { this->availableBitrate = bitrate; MS_DEBUG_DEV( "BWE DOWN [ratio:%f, availableBitrate:%" PRIu32 "]", ratio, this->availableBitrate); } } // TODO: No, should wait for AvailableBitrateEventInterval and so on. this->listener->OnSenderBandwidthEstimatorAvailableBitrate( this, this->availableBitrate, previousAvailableBitrate); } void SenderBandwidthEstimator::UpdateRtt(float rtt) { MS_TRACE(); this->rtt = rtt; } uint32_t SenderBandwidthEstimator::GetAvailableBitrate() const { MS_TRACE(); return this->availableBitrate; } void SenderBandwidthEstimator::RescheduleNextAvailableBitrateEvent() { MS_TRACE(); this->lastAvailableBitrateEventAtMs = this->shared->GetTimeMs(); } void SenderBandwidthEstimator::CummulativeResult::AddPacket( size_t size, int64_t sentAtMs, int64_t receivedAtMs) { MS_TRACE(); if (this->numPackets == 0u) { this->firstPacketSentAtMs = sentAtMs; this->firstPacketReceivedAtMs = receivedAtMs; this->lastPacketSentAtMs = sentAtMs; this->lastPacketReceivedAtMs = receivedAtMs; } else { this->firstPacketSentAtMs = std::min(sentAtMs, this->firstPacketSentAtMs); this->firstPacketReceivedAtMs = std::min(receivedAtMs, this->firstPacketReceivedAtMs); this->lastPacketSentAtMs = std::max(sentAtMs, this->lastPacketSentAtMs); this->lastPacketReceivedAtMs = std::max(receivedAtMs, this->lastPacketReceivedAtMs); } this->numPackets++; this->totalSize += size; } void SenderBandwidthEstimator::CummulativeResult::Reset() { MS_TRACE(); this->numPackets = 0u; this->totalSize = 0u; this->firstPacketSentAtMs = 0u; this->lastPacketSentAtMs = 0u; this->firstPacketReceivedAtMs = 0u; this->lastPacketReceivedAtMs = 0u; } } // namespace RTC ================================================ FILE: worker/src/RTC/SeqManager.cpp ================================================ #define MS_CLASS "RTC::SeqManager" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SeqManager.hpp" #include "Logger.hpp" #include "Utils.hpp" #include namespace RTC { template bool SeqManager::SeqLowerThan::operator()(T lhs, T rhs) const { return Utils::Number::IsLowerThan(lhs, rhs); } template bool SeqManager::SeqHigherThan::operator()(T lhs, T rhs) const { return Utils::Number::IsHigherThan(lhs, rhs); } template bool SeqManager::IsSeqHigherThan(T lhs, T rhs) { return Utils::Number::IsHigherThan(lhs, rhs); } template bool SeqManager::IsSeqLowerThan(T lhs, T rhs) { return Utils::Number::IsLowerThan(lhs, rhs); } template SeqManager::SeqManager(T initialOutput) : initialOutput(initialOutput) { MS_TRACE(); } template void SeqManager::Sync(T input) { MS_TRACE(); // Update base. this->base = (this->maxOutput - input) & SeqManager::MaxValue; // Update maxInput. this->maxInput = input; // Clear dropped set. this->dropped.clear(); } template void SeqManager::Drop(T input) { MS_TRACE(); // Mark as dropped if 'input' is higher than anyone already processed. if (SeqManager::IsSeqHigherThan(input, this->maxInput)) { this->maxInput = input; this->maxDropped = input; // Insert input in the last position. // Explicitly insert at the end, which is more performant. this->dropped.insert(this->dropped.end(), input); ClearDropped(); } // Mark as dropped if no input was forwarded after the last dropped one and // 'input' is higher than the last forwarded input 'this->maxForwarded'. // Allows for properly accounting for out of order drops until an input is forwarded. else if (this->maxInput == this->maxDropped && SeqManager::IsSeqHigherThan(input, this->maxForwarded)) { this->dropped.insert(input); ClearDropped(); } } template bool SeqManager::Input(T input, T& output) { MS_TRACE(); auto base = this->base; // No dropped inputs to consider. if (this->dropped.empty()) { goto done; } // Dropped inputs present, cleanup and update base. else { // Set 'maxInput' here if needed before calling ClearDropped(). if (this->started && SeqManager::IsSeqHigherThan(input, this->maxInput)) { this->maxInput = input; this->maxForwarded = input; } ClearDropped(); base = this->base; } // No dropped inputs to consider after cleanup. if (this->dropped.empty()) { goto done; } // This input was dropped. else if (this->dropped.find(input) != this->dropped.end()) { MS_DEBUG_DEV("trying to send a dropped input"); return false; } // There are dropped inputs, calculate 'base' for this input. else { auto droppedCount = this->dropped.size(); // Get the first dropped input which is higher than or equal 'input'. auto it = this->dropped.lower_bound(input); droppedCount -= std::distance(it, this->dropped.end()); base = (this->base - droppedCount) & SeqManager::MaxValue; } done: output = (input + base) & SeqManager::MaxValue; if (!this->started) { this->started = true; this->maxInput = input; this->maxForwarded = input; this->maxOutput = output; } else { // New input is higher than the maximum seen. if (SeqManager::IsSeqHigherThan(input, this->maxInput)) { this->maxInput = input; this->maxForwarded = input; } // New output is higher than the maximum seen. if (SeqManager::IsSeqHigherThan(output, this->maxOutput)) { this->maxOutput = output; } } output = (output + this->initialOutput) & SeqManager::MaxValue; return true; } template T SeqManager::GetMaxInput() const { return this->maxInput; } template T SeqManager::GetMaxOutput() const { return this->maxOutput; } /* * Delete droped inputs greater than maxInput, which belong to a previous * cycle. */ template void SeqManager::ClearDropped() { MS_TRACE(); // Cleanup dropped values. if (this->dropped.empty()) { return; } const size_t previousDroppedSize = this->dropped.size(); for (auto it = this->dropped.begin(); it != this->dropped.end();) { auto value = *it; if (SeqManager::IsSeqHigherThan(value, this->maxInput)) { it = this->dropped.erase(it); } else { break; } } // Adapt base. this->base = (this->base - (previousDroppedSize - this->dropped.size())) & SeqManager::MaxValue; } // Explicit instantiation to have all SeqManager definitions in this file. template class SeqManager; // For codecs. template class SeqManager; // For testing. template class SeqManager; // For RTP sequence numbers. template class SeqManager; // For PictureID (15 bits). } // namespace RTC ================================================ FILE: worker/src/RTC/Serializable.cpp ================================================ #define MS_CLASS "RTC::Serializable" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/Serializable.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::memmove(), std::memset() namespace RTC { Serializable::Serializable(const uint8_t* buffer, size_t bufferLength) : buffer(const_cast(buffer)), bufferLength(bufferLength) { MS_TRACE(); } Serializable::~Serializable() { MS_TRACE(); if (this->bufferReleasedListener) { (*this->bufferReleasedListener)(this, this->buffer); } } void Serializable::Serialize(uint8_t* buffer, size_t bufferLength) { MS_TRACE(); if (bufferLength < this->length) { MS_THROW_TYPE_ERROR( "bufferLength (%zu bytes) is lower than current length (%zu bytes)", bufferLength, this->length); } std::memmove(buffer, this->buffer, this->length); if (buffer != this->buffer && this->bufferReleasedListener) { (*this->bufferReleasedListener)(this, this->buffer); } this->buffer = buffer; this->bufferLength = bufferLength; } void Serializable::SetBufferReleasedListener(Serializable::BufferReleasedListener* listener) { MS_TRACE(); this->bufferReleasedListener = listener; } void Serializable::Consolidate() const { MS_TRACE(); if (!this->consolidatedListener) { MS_THROW_ERROR("consolidated listener not set"); } this->consolidatedListener(); } void Serializable::SetBuffer(uint8_t* buffer) { MS_TRACE(); if (buffer == this->buffer) { return; } if (this->bufferReleasedListener) { (*this->bufferReleasedListener)(this, this->buffer); } this->buffer = buffer; } void Serializable::SetBufferLength(size_t bufferLength) { MS_TRACE(); if (bufferLength < this->length) { MS_THROW_TYPE_ERROR( "buffer length (%zu bytes) is lower than current length (%zu bytes)", bufferLength, this->length); } if (bufferLength == 0) { MS_THROW_TYPE_ERROR("bufferLength cannot be 0"); } this->bufferLength = bufferLength; } void Serializable::SetLength(size_t length) { MS_TRACE(); if (length > this->bufferLength) { MS_THROW_TYPE_ERROR( "length (%zu bytes) is larger than internal buffer maximum length (%zu bytes)", length, this->bufferLength); } if (length == 0) { MS_THROW_TYPE_ERROR("length cannot be 0"); } this->length = length; } void Serializable::CloneInto(Serializable* serializable) const { MS_TRACE(); if (serializable->GetBufferLength() < this->length) { const auto bufferLength = serializable->GetBufferLength(); delete serializable; MS_THROW_TYPE_ERROR( "bufferLength (%zu bytes) is lower than current length (%zu bytes)", bufferLength, this->length); } std::memmove(const_cast(serializable->GetBuffer()), this->buffer, this->length); // Need to manually set Serializable length. serializable->SetLength(this->length); } void Serializable::FillPadding(uint8_t padding) { MS_TRACE(); if (padding > this->length) { MS_THROW_TYPE_ERROR( "padding (%" PRIu8 " bytes) cannot be greater than length (%zu bytes)", padding, this->length); } std::memset(this->buffer + this->length - padding, 0x00, padding); } void Serializable::SetConsolidatedListener(ConsolidatedListener&& listener) { MS_TRACE(); this->consolidatedListener = std::move(listener); } } // namespace RTC ================================================ FILE: worker/src/RTC/SimpleConsumer.cpp ================================================ #include "FBS/consumer.h" #define MS_CLASS "RTC::SimpleConsumer" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "RTC/RTP/Codecs/Tools.hpp" #include "RTC/SimpleConsumer.hpp" #ifdef MS_RTC_LOGGER_RTP #include "RTC/RtcLogger.hpp" #endif #include // std::numeric_limits namespace RTC { /* Static. */ static constexpr size_t TargetLayerRetransmissionBufferSize{ 15u }; /* Instance methods. */ SimpleConsumer::SimpleConsumer( SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, const FBS::Transport::ConsumeRequest* data) : RTC::Consumer::Consumer(shared, id, producerId, listener, data, RTC::RtpParameters::Type::SIMPLE) { MS_TRACE(); // Ensure there is a single encoding. if (this->consumableRtpEncodings.size() != 1u) { MS_THROW_TYPE_ERROR("invalid consumableRtpEncodings with size != 1"); } auto& encoding = this->rtpParameters.encodings[0]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); this->keyFrameSupported = RTC::RTP::Codecs::Tools::CanBeKeyFrame(mediaCodec->mimeType); // Create RtpStreamSend instance for sending a single stream to the remote. CreateRtpStream(); // Let's chosee an initial output seq number between 1000 and 32768 to avoid // libsrtp bug: // https://github.com/versatica/mediasoup/issues/1437 const uint16_t initialOutputSeq = Utils::Crypto::GetRandomUInt(1000u, std::numeric_limits::max() / 2); this->rtpSeqManager = RTC::SeqManager(initialOutputSeq); // Create the encoding context for Opus. if ( mediaCodec->mimeType.type == RTC::RtpCodecMimeType::Type::AUDIO && (mediaCodec->mimeType.subtype == RTC::RtpCodecMimeType::Subtype::OPUS || mediaCodec->mimeType.subtype == RTC::RtpCodecMimeType::Subtype::MULTIOPUS)) { RTC::RTP::Codecs::EncodingContext::Params params; this->encodingContext.reset( RTC::RTP::Codecs::Tools::GetEncodingContext(mediaCodec->mimeType, params)); // ignoreDtx is set to false by default. this->encodingContext->SetIgnoreDtx(data->ignoreDtx()); } // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ nullptr); } SimpleConsumer::~SimpleConsumer() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); delete this->rtpStream; this->targetLayerRetransmissionBuffer.clear(); } flatbuffers::Offset SimpleConsumer::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Call the parent method. auto base = RTC::Consumer::FillBuffer(builder); // Add rtpStream. std::vector> rtpStreams; rtpStreams.emplace_back(this->rtpStream->FillBuffer(builder)); auto dump = FBS::Consumer::CreateConsumerDumpDirect(builder, base, &rtpStreams); return FBS::Consumer::CreateDumpResponse(builder, dump); } flatbuffers::Offset SimpleConsumer::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); std::vector> rtpStreams; // Add stats of our send stream. rtpStreams.emplace_back(this->rtpStream->FillBufferStats(builder)); // Add stats of our recv stream. if (this->producerRtpStream) { rtpStreams.emplace_back(this->producerRtpStream->FillBufferStats(builder)); } return FBS::Consumer::CreateGetStatsResponseDirect(builder, &rtpStreams); } flatbuffers::Offset SimpleConsumer::FillBufferScore( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); MS_ASSERT(this->producerRtpStreamScores, "producerRtpStreamScores not set"); uint8_t producerScore{ 0 }; if (this->producerRtpStream) { producerScore = this->producerRtpStream->GetScore(); } return FBS::Consumer::CreateConsumerScoreDirect( builder, this->rtpStream->GetScore(), producerScore, this->producerRtpStreamScores); } void SimpleConsumer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::CONSUMER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Consumer_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::CONSUMER_REQUEST_KEY_FRAME: { if (IsActive()) { RequestKeyFrame(); } request->Accept(); break; } case Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS: { // Accept with empty preferred layers object. auto responseOffset = FBS::Consumer::CreateSetPreferredLayersResponse(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset); break; } default: { // Pass it to the parent class. RTC::Consumer::HandleRequest(request); } } } void SimpleConsumer::ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/) { MS_TRACE(); this->producerRtpStream = rtpStream; } void SimpleConsumer::ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/) { MS_TRACE(); this->producerRtpStream = rtpStream; // Emit the score event. EmitScore(); } void SimpleConsumer::ProducerRtpStreamScore( RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) { MS_TRACE(); // Emit the score event. EmitScore(); } void SimpleConsumer::ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/) { MS_TRACE(); // Do nothing. } uint8_t SimpleConsumer::GetBitratePriority() const { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); // Audio SimpleConsumer does not play the BWE game. if (this->kind != RTC::Media::Kind::VIDEO) { return 0u; } if (!IsActive()) { return 0u; } return this->priority; } uint32_t SimpleConsumer::IncreaseLayer(uint32_t bitrate, bool /*considerLoss*/) { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(this->kind == RTC::Media::Kind::VIDEO, "should be video"); MS_ASSERT(IsActive(), "should be active"); // If this is not the first time this method is called within the same iteration, // return 0 since a video SimpleConsumer does not keep state about this. if (this->managingBitrate) { return 0u; } this->managingBitrate = true; // Video SimpleConsumer does not really play the BWE game when. However, let's // be honest and try to be nice. auto nowMs = this->shared->GetTimeMs(); auto desiredBitrate = this->producerRtpStream->GetBitrate(nowMs); if (desiredBitrate < bitrate) { return desiredBitrate; } else { return bitrate; } } void SimpleConsumer::ApplyLayers() { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(this->kind == RTC::Media::Kind::VIDEO, "should be video"); MS_ASSERT(IsActive(), "should be active"); this->managingBitrate = false; // SimpleConsumer does not play the BWE game (even if video kind). } uint32_t SimpleConsumer::GetDesiredBitrate() const { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); // Audio SimpleConsumer does not play the BWE game. if (this->kind != RTC::Media::Kind::VIDEO) { return 0u; } if (!IsActive()) { return 0u; } auto nowMs = this->shared->GetTimeMs(); auto desiredBitrate = this->producerRtpStream->GetBitrate(nowMs); // If consumer.rtpParameters.encodings[0].maxBitrate was given and it's // greater than computed one, then use it. auto maxBitrate = this->rtpParameters.encodings[0].maxBitrate; desiredBitrate = std::max(maxBitrate, desiredBitrate); return desiredBitrate; } // NOLINTNEXTLINE(misc-no-recursion) void SimpleConsumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) { MS_TRACE(); #ifdef MS_RTC_LOGGER_RTP packet->logger.consumerId = this->id; #endif if (!IsActive()) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } // If we need to sync, support key frames and this is not a key frame, ignore // the packet. if (this->syncRequired && this->keyFrameSupported && !packet->IsKeyFrame()) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); #endif // NOTE: No need to drop the packet in the RTP sequence manager since here // we are blocking all packets but the key frame that would trigger sync // below. // Store the packet for the scenario in which this packet is part of the // key frame and it arrived before the first packet of the key frame. StorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket); return; } auto payloadType = packet->GetPayloadType(); // NOTE: This may happen if this Consumer supports just some codecs of those // in the corresponding Producer. if (!this->supportedCodecPayloadTypes[payloadType]) { MS_WARN_DEV("payload type not supported [payloadType:%" PRIu8 "]", payloadType); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } // Packets with only padding are not forwarded. if (packet->GetPayloadLength() == 0) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } bool marker; // Process the payload if needed. Drop packet if necessary. if (this->encodingContext && !packet->ProcessPayload(this->encodingContext.get(), marker)) { MS_DEBUG_DEV( "discarding packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } // Whether this is the first packet after re-sync. const bool isSyncPacket = this->syncRequired; // Whether packets stored in the target layer retransmission buffer must be // sent once this packet is sent. bool sendPacketsInTargetLayerRetransmissionBuffer{ false }; // Sync sequence number and timestamp if required. if (isSyncPacket) { if (packet->IsKeyFrame()) { MS_DEBUG_TAG( rtp, "sync key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); sendPacketsInTargetLayerRetransmissionBuffer = true; } this->rtpSeqManager.Sync(packet->GetSequenceNumber() - 1); this->syncRequired = false; } // Update RTP seq number and timestamp. uint16_t seq; this->rtpSeqManager.Input(packet->GetSequenceNumber(), seq); // Save original packet fields. auto origSsrc = packet->GetSsrc(); auto origSeq = packet->GetSequenceNumber(); // Rewrite packet. packet->SetSsrc(this->rtpParameters.encodings[0].ssrc); packet->SetSequenceNumber(seq); #ifdef MS_RTC_LOGGER_RTP packet->logger.sendRtpTimestamp = packet->GetTimestamp(); packet->logger.sendSeqNumber = seq; #endif if (isSyncPacket) { MS_DEBUG_TAG( rtp, "sending sync packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] from original [seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp(), origSeq); } const RTC::RTP::RtpStreamSend::ReceivePacketResult result = this->rtpStream->ReceivePacket(packet, sharedPacket); if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { // Send the packet. this->listener->OnConsumerSendRtpPacket(this, packet); // May emit 'trace' event. EmitTraceEventRtpAndKeyFrameTypes(packet); } else { MS_WARN_TAG( rtp, "failed to send packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] from original [seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp(), origSeq); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED); #endif } // Restore packet fields. packet->SetSsrc(origSsrc); packet->SetSequenceNumber(origSeq); // If sharedPacket doesn't have a packet inside and it has been stored we // need to clone the packet into it. if (!sharedPacket.HasPacket() && result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED) { sharedPacket.Assign(packet); } // If sent packet was the first packet of a key frame, let's send buffered // packets belonging to the same key frame that arrived earlier due to // packet misorder. if (sendPacketsInTargetLayerRetransmissionBuffer) { // NOTE: Only send buffered packets if the first packet containing the key // frame was sent. if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { for (auto& kv : this->targetLayerRetransmissionBuffer) { auto& bufferedSharedPacket = kv.second; auto* bufferedPacket = bufferedSharedPacket.GetPacket(); if (bufferedPacket->GetSequenceNumber() > origSeq) { MS_DEBUG_DEV( "sending packet buffered in the target layer retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] after sending first packet of the key frame [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", bufferedPacket->GetSsrc(), bufferedPacket->GetSequenceNumber(), bufferedPacket->GetTimestamp(), packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); SendRtpPacket(bufferedPacket, bufferedSharedPacket); // Be sure that the target layer retransmission buffer has not been // emptied as a result of sending this packet. If so, exit the loop. if (this->targetLayerRetransmissionBuffer.empty()) { MS_DEBUG_DEV( "target layer retransmission buffer emptied while iterating it, exiting the loop"); break; } } } } this->targetLayerRetransmissionBuffer.clear(); } } bool SimpleConsumer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) { MS_TRACE(); if (static_cast((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval) { return true; } auto* senderReport = this->rtpStream->GetRtcpSenderReport(nowMs); if (!senderReport) { return true; } // Build SDES chunk for this sender. auto* sdesChunk = this->rtpStream->GetRtcpSdesChunk(); auto* delaySinceLastRrSsrcInfo = this->rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs); // RTCP Compound packet buffer cannot hold the data. if (!packet->Add(senderReport, sdesChunk, delaySinceLastRrSsrcInfo)) { return false; } this->lastRtcpSentTime = nowMs; return true; } void SimpleConsumer::NeedWorstRemoteFractionLost( uint32_t /*mappedSsrc*/, uint8_t& worstRemoteFractionLost) { MS_TRACE(); if (!IsActive()) { return; } auto fractionLost = this->rtpStream->GetFractionLost(); // If our fraction lost is worse than the given one, update it. worstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost); } void SimpleConsumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) { MS_TRACE(); if (!IsActive()) { return; } // May emit 'trace' event. EmitTraceEventNackType(); this->rtpStream->ReceiveNack(nackPacket); } void SimpleConsumer::ReceiveKeyFrameRequest( RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) { MS_TRACE(); switch (messageType) { case RTC::RTCP::FeedbackPs::MessageType::PLI: { EmitTraceEventPliType(ssrc); break; } case RTC::RTCP::FeedbackPs::MessageType::FIR: { EmitTraceEventFirType(ssrc); break; } default:; } this->rtpStream->ReceiveKeyFrameRequest(messageType); if (IsActive()) { RequestKeyFrame(); } } void SimpleConsumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) { MS_TRACE(); this->rtpStream->ReceiveRtcpReceiverReport(report); } void SimpleConsumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) { MS_TRACE(); this->rtpStream->ReceiveRtcpXrReceiverReferenceTime(report); } uint32_t SimpleConsumer::GetTransmissionRate(uint64_t nowMs) { MS_TRACE(); if (!IsActive()) { return 0u; } return this->rtpStream->GetBitrate(nowMs); } float SimpleConsumer::GetRtt() const { MS_TRACE(); return this->rtpStream->GetRtt(); } void SimpleConsumer::UserOnTransportConnected() { MS_TRACE(); this->syncRequired = true; if (IsActive()) { RequestKeyFrame(); } } void SimpleConsumer::UserOnTransportDisconnected() { MS_TRACE(); this->rtpStream->Pause(); this->targetLayerRetransmissionBuffer.clear(); } void SimpleConsumer::UserOnPaused() { MS_TRACE(); this->rtpStream->Pause(); this->targetLayerRetransmissionBuffer.clear(); if (this->externallyManagedBitrate && this->kind == RTC::Media::Kind::VIDEO) { this->listener->OnConsumerNeedZeroBitrate(this); } } void SimpleConsumer::UserOnResumed() { MS_TRACE(); this->syncRequired = true; if (IsActive()) { RequestKeyFrame(); } } void SimpleConsumer::CreateRtpStream() { MS_TRACE(); auto& encoding = this->rtpParameters.encodings[0]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); MS_DEBUG_TAG( rtp, "[ssrc:%" PRIu32 ", payloadType:%" PRIu8 "]", encoding.ssrc, mediaCodec->payloadType); // Set stream params. RTC::RTP::RtpStream::Params params; params.ssrc = encoding.ssrc; params.payloadType = mediaCodec->payloadType; params.mimeType = mediaCodec->mimeType; params.clockRate = mediaCodec->clockRate; params.cname = this->rtpParameters.rtcp.cname; // Check in band FEC in codec parameters. if (mediaCodec->parameters.HasInteger("useinbandfec") && mediaCodec->parameters.GetInteger("useinbandfec") == 1) { MS_DEBUG_TAG(rtp, "in band FEC enabled"); params.useInBandFec = true; } // Check DTX in codec parameters. if (mediaCodec->parameters.HasInteger("usedtx") && mediaCodec->parameters.GetInteger("usedtx") == 1) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } // Check DTX in the encoding. if (encoding.dtx) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } for (const auto& fb : mediaCodec->rtcpFeedback) { if (!params.useNack && fb.type == "nack" && fb.parameter.empty()) { MS_DEBUG_2TAGS(rtp, rtcp, "NACK supported"); params.useNack = true; } else if (!params.usePli && fb.type == "nack" && fb.parameter == "pli") { MS_DEBUG_2TAGS(rtp, rtcp, "PLI supported"); params.usePli = true; } else if (!params.useFir && fb.type == "ccm" && fb.parameter == "fir") { MS_DEBUG_2TAGS(rtp, rtcp, "FIR supported"); params.useFir = true; } } this->rtpStream = new RTC::RTP::RtpStreamSend(this, this->shared, params, this->rtpParameters.mid); this->rtpStreams.push_back(this->rtpStream); // If the Consumer is paused, tell the RtpStreamSend. if (IsPaused() || IsProducerPaused()) { this->rtpStream->Pause(); } const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); if (rtxCodec && encoding.hasRtx) { this->rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc); } } void SimpleConsumer::RequestKeyFrame() { MS_TRACE(); if (this->kind != RTC::Media::Kind::VIDEO) { return; } auto mappedSsrc = this->consumableRtpEncodings[0].ssrc; this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); } void SimpleConsumer::StorePacketInTargetLayerRetransmissionBuffer( RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) { MS_TRACE(); MS_DEBUG_DEV( "storing packet in target layer retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); // Store original packet into the buffer. Only clone once and only if // necessary. if (!sharedPacket.HasPacket()) { sharedPacket.Assign(packet); } // Assert that, if sharedPacket was already filled, both packet and // sharedPacket are the very same RTP packet. else { sharedPacket.AssertSamePacket(packet); } this->targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket; if (this->targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize) { this->targetLayerRetransmissionBuffer.erase(this->targetLayerRetransmissionBuffer.begin()); } } void SimpleConsumer::EmitScore() const { MS_TRACE(); auto scoreOffset = FillBufferScore(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto notificationOffset = FBS::Consumer::CreateScoreNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), scoreOffset); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_SCORE, FBS::Notification::Body::Consumer_ScoreNotification, notificationOffset); } void SimpleConsumer::OnRtpStreamScore( RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) { MS_TRACE(); // Emit the score event. EmitScore(); } void SimpleConsumer::OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet) { MS_TRACE(); this->listener->OnConsumerRetransmitRtpPacket(this, packet); // May emit 'trace' event. EmitTraceEventRtpAndKeyFrameTypes(packet, this->rtpStream->HasRtx()); } } // namespace RTC ================================================ FILE: worker/src/RTC/SimulcastConsumer.cpp ================================================ #define MS_CLASS "RTC::SimulcastConsumer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SimulcastConsumer.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "RTC/RTP/Codecs/Tools.hpp" #ifdef MS_RTC_LOGGER_RTP #include "RTC/RtcLogger.hpp" #endif #include // std::numeric_limits namespace RTC { /* Static. */ static constexpr uint64_t StreamMinActiveMs{ 2000u }; static constexpr uint64_t BweDowngradeConservativeMs{ 10000u }; static constexpr uint64_t BweDowngradeMinActiveMs{ 8000u }; static constexpr uint16_t MaxSequenceNumberGap{ 100u }; static constexpr size_t TargetLayerRetransmissionBufferSize{ 30u }; /* Instance methods. */ SimulcastConsumer::SimulcastConsumer( SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, const FBS::Transport::ConsumeRequest* data) : RTC::Consumer::Consumer( shared, id, producerId, listener, data, RTC::RtpParameters::Type::SIMULCAST) { MS_TRACE(); // We allow a single encoding in simulcast (so we can enable temporal layers // with a single simulcast stream). // NOTE: No need to check this->consumableRtpEncodings.size() > 0 here since // it's already done in Consumer constructor. auto& encoding = this->rtpParameters.encodings[0]; // Ensure there are as many spatial layers as encodings. if (encoding.spatialLayers != this->consumableRtpEncodings.size()) { MS_THROW_TYPE_ERROR("encoding.spatialLayers does not match number of consumableRtpEncodings"); } // Fill mapMappedSsrcSpatialLayer. for (size_t idx{ 0u }; idx < this->consumableRtpEncodings.size(); ++idx) { auto& encoding = this->consumableRtpEncodings[idx]; this->mapMappedSsrcSpatialLayer[encoding.ssrc] = static_cast(idx); } // Set preferredLayers (if given). if (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeRequest::VT_PREFERREDLAYERS)) { const auto* preferredLayers = data->preferredLayers(); this->preferredLayers.spatial = preferredLayers->spatialLayer(); if (this->preferredLayers.spatial > encoding.spatialLayers - 1) { this->preferredLayers.spatial = static_cast(encoding.spatialLayers - 1); } if (auto preferredTemporalLayer = preferredLayers->temporalLayer(); preferredTemporalLayer.has_value()) { this->preferredLayers.temporal = preferredTemporalLayer.value(); if (this->preferredLayers.temporal > encoding.temporalLayers - 1) { this->preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); } } else { this->preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); } } else { // Initially set preferredSpatialLayer and preferredTemporalLayer to the // maximum value. this->preferredLayers.spatial = static_cast(encoding.spatialLayers - 1); this->preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); } // Reserve space for the Producer RTP streams by filling all the possible // entries with nullptr. this->producerRtpStreams.insert( this->producerRtpStreams.begin(), this->consumableRtpEncodings.size(), nullptr); // Create the encoding context. const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); if (!RTC::RTP::Codecs::Tools::IsValidTypeForCodec(this->type, mediaCodec->mimeType)) { MS_THROW_TYPE_ERROR( "%s codec not supported for simulcast", mediaCodec->mimeType.ToString().c_str()); } // Let's chosee an initial output seq number between 1000 and 32768 to avoid // libsrtp bug: // https://github.com/versatica/mediasoup/issues/1437 const uint16_t initialOutputSeq = Utils::Crypto::GetRandomUInt(1000u, std::numeric_limits::max() / 2); this->rtpSeqManager = RTC::SeqManager(initialOutputSeq); RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = encoding.spatialLayers; params.temporalLayers = encoding.temporalLayers; this->encodingContext.reset( RTC::RTP::Codecs::Tools::GetEncodingContext(mediaCodec->mimeType, params)); MS_ASSERT(this->encodingContext, "no encoding context for this codec"); // Create RtpStreamSend instance for sending a single stream to the remote. CreateRtpStream(); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelRequestHandler*/ nullptr); } SimulcastConsumer::~SimulcastConsumer() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); delete this->rtpStream; this->targetLayerRetransmissionBuffer.clear(); } flatbuffers::Offset SimulcastConsumer::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Call the parent method. auto base = RTC::Consumer::FillBuffer(builder); // Add rtpStream. std::vector> rtpStreams; rtpStreams.emplace_back(this->rtpStream->FillBuffer(builder)); auto dump = FBS::Consumer::CreateConsumerDumpDirect( builder, base, &rtpStreams, this->preferredLayers.spatial, this->targetLayers.spatial, this->currentSpatialLayer, this->preferredLayers.temporal, this->targetLayers.temporal, this->encodingContext->GetCurrentTemporalLayer()); return FBS::Consumer::CreateDumpResponse(builder, dump); } flatbuffers::Offset SimulcastConsumer::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); std::vector> rtpStreams; // Add stats of our send stream. rtpStreams.emplace_back(this->rtpStream->FillBufferStats(builder)); auto* producerCurrentRtpStream = GetProducerCurrentRtpStream(); // Add stats of our recv stream. if (producerCurrentRtpStream) { rtpStreams.emplace_back(producerCurrentRtpStream->FillBufferStats(builder)); } return FBS::Consumer::CreateGetStatsResponseDirect(builder, &rtpStreams); } flatbuffers::Offset SimulcastConsumer::FillBufferScore( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); MS_ASSERT(this->producerRtpStreamScores, "producerRtpStreamScores not set"); auto* producerCurrentRtpStream = GetProducerCurrentRtpStream(); uint8_t producerScore{ 0 }; if (producerCurrentRtpStream) { producerScore = producerCurrentRtpStream->GetScore(); } else { producerScore = 0; } return FBS::Consumer::CreateConsumerScoreDirect( builder, this->rtpStream->GetScore(), producerScore, this->producerRtpStreamScores); } void SimulcastConsumer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::CONSUMER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Consumer_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::CONSUMER_REQUEST_KEY_FRAME: { if (IsActive()) { RequestKeyFrames(); } request->Accept(); break; } case Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS: { auto previousPreferredLayers = this->preferredLayers; const auto* body = request->data->body_as(); const auto* preferredLayers = body->preferredLayers(); // Spatial layer. this->preferredLayers.spatial = preferredLayers->spatialLayer(); if (this->preferredLayers.spatial > this->rtpStream->GetSpatialLayers() - 1) { this->preferredLayers.spatial = static_cast(this->rtpStream->GetSpatialLayers() - 1); } // preferredTemporaLayer is optional. auto preferredTemporalLayer = preferredLayers->temporalLayer(); if (preferredTemporalLayer.has_value()) { this->preferredLayers.temporal = preferredTemporalLayer.value(); if (this->preferredLayers.temporal > this->rtpStream->GetTemporalLayers() - 1) { this->preferredLayers.temporal = static_cast(this->rtpStream->GetTemporalLayers() - 1); } } else { this->preferredLayers.temporal = static_cast(this->rtpStream->GetTemporalLayers() - 1); } MS_DEBUG_DEV( "preferred layers changed [spatial:%" PRIi16 ", temporal:%" PRIi16 ", consumerId:%s]", this->preferredLayers.spatial, this->preferredLayers.temporal, this->id.c_str()); preferredTemporalLayer = this->preferredLayers.temporal; auto preferredLayersOffset = FBS::Consumer::CreateConsumerLayers( request->GetBufferBuilder(), this->preferredLayers.spatial, preferredTemporalLayer); auto responseOffset = FBS::Consumer::CreateSetPreferredLayersResponse( request->GetBufferBuilder(), preferredLayersOffset); request->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset); if (IsActive() && this->preferredLayers != previousPreferredLayers) { MayChangeLayers(/*force*/ true); } break; } default: { // Pass it to the parent class. RTC::Consumer::HandleRequest(request); } } } void SimulcastConsumer::ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) { MS_TRACE(); auto it = this->mapMappedSsrcSpatialLayer.find(mappedSsrc); MS_ASSERT(it != this->mapMappedSsrcSpatialLayer.end(), "unknown mappedSsrc"); const int16_t spatialLayer = it->second; this->producerRtpStreams[spatialLayer] = rtpStream; } void SimulcastConsumer::ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) { MS_TRACE(); auto it = this->mapMappedSsrcSpatialLayer.find(mappedSsrc); MS_ASSERT(it != this->mapMappedSsrcSpatialLayer.end(), "unknown mappedSsrc"); const int16_t spatialLayer = it->second; this->producerRtpStreams[spatialLayer] = rtpStream; // Emit the score event. EmitScore(); if (IsActive()) { MayChangeLayers(); } } void SimulcastConsumer::ProducerRtpStreamScore( RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t score, uint8_t previousScore) { MS_TRACE(); // Emit the score event. EmitScore(); if (RTC::Consumer::IsActive()) { // All Producer streams are dead. if (!IsActive()) { UpdateTargetLayers(-1, -1); } // Just check target layers if the stream has died or reborned. else if (!this->externallyManagedBitrate || (score == 0u || previousScore == 0u)) { MayChangeLayers(); } } } void SimulcastConsumer::ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* rtpStream, bool first) { MS_TRACE(); // Just interested if this is the first Sender Report for a RTP stream. if (!first) { return; } MS_DEBUG_TAG(simulcast, "first SenderReport [ssrc:%" PRIu32 "]", rtpStream->GetSsrc()); // If our RTP timestamp reference stream does not yet have SR, do nothing // since we know we won't be able to switch. auto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream(); if (!producerTsReferenceRtpStream || !producerTsReferenceRtpStream->GetSenderReportNtpMs()) { return; } if (IsActive()) { MayChangeLayers(); } } uint8_t SimulcastConsumer::GetBitratePriority() const { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); if (!IsActive()) { return 0u; } return this->priority; } uint32_t SimulcastConsumer::IncreaseLayer(uint32_t bitrate, bool considerLoss) { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(IsActive(), "should be active"); // If already in the preferred layers, do nothing. if (this->provisionalTargetLayers == this->preferredLayers) { return 0u; } uint32_t virtualBitrate; if (considerLoss) { // Calculate virtual available bitrate based on given bitrate and our // packet lost. auto lossPercentage = this->rtpStream->GetLossPercentage(); if (lossPercentage < 2) { virtualBitrate = 1.08 * bitrate; } else if (lossPercentage > 10) { virtualBitrate = (1 - 0.5 * (lossPercentage / 100)) * bitrate; } else { virtualBitrate = bitrate; } } else { virtualBitrate = bitrate; } uint32_t requiredBitrate{ 0u }; int16_t spatialLayer{ 0 }; int16_t temporalLayer{ 0 }; auto nowMs = this->shared->GetTimeMs(); for (size_t sIdx{ 0u }; sIdx < this->producerRtpStreams.size(); ++sIdx) { spatialLayer = static_cast(sIdx); // If this is higher than current spatial layer and we moved to to current // spatial layer due to BWE limitations, check how much it has elapsed // since then. if (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs) { if (this->provisionalTargetLayers.spatial > -1 && spatialLayer > this->currentSpatialLayer) { MS_DEBUG_DEV( "avoid upgrading to spatial layer %" PRIi16 " due to recent BWE downgrade", spatialLayer); goto done; } } // Ignore spatial layers lower than the one we already have. if (spatialLayer < this->provisionalTargetLayers.spatial) { continue; } // If this is the higher than preferred spatial layer, abort. else if (spatialLayer > this->preferredLayers.spatial) { MS_DEBUG_DEV( "avoid upgrading to spatial layer %" PRIi16 " since it's higher than preferred spatial layer %" PRIi16, spatialLayer, this->preferredLayers.spatial); goto done; } // This can be null. auto* producerRtpStream = this->producerRtpStreams.at(spatialLayer); // Producer stream does not exist. Ignore. if (!producerRtpStream) { continue; } // Ignore spatial layers (streams) with score 0. if (producerRtpStream->GetScore() == 0) { continue; } // If the stream has not been active time enough and we have an active one // already, move to the next spatial layer. if ( spatialLayer != this->provisionalTargetLayers.spatial && this->provisionalTargetLayers.spatial != -1 && producerRtpStream->GetActiveMs() < StreamMinActiveMs) { const auto* provisionalProducerRtpStream = this->producerRtpStreams.at(this->provisionalTargetLayers.spatial); // The stream for the current provisional spatial layer has been active // for enough time, move to the next spatial layer. if (provisionalProducerRtpStream->GetActiveMs() >= StreamMinActiveMs) { continue; } } // We may not yet switch to this spatial layer. if (!CanSwitchToSpatialLayer(spatialLayer)) { continue; } temporalLayer = 0; // Check bitrate of every temporal layer. for (; temporalLayer < producerRtpStream->GetTemporalLayers(); ++temporalLayer) { // Ignore temporal layers lower than the one we already have (taking // into account the spatial layer too). if ( spatialLayer == this->provisionalTargetLayers.spatial && temporalLayer <= this->provisionalTargetLayers.temporal) { continue; } requiredBitrate = producerRtpStream->GetLayerBitrate(nowMs, 0, temporalLayer); // This is simulcast so we must substract the bitrate of the current // temporal spatial layer if this is the temporal layer 0 of a higher // spatial layer. if ( requiredBitrate && temporalLayer == 0 && this->provisionalTargetLayers.spatial > -1 && spatialLayer > this->provisionalTargetLayers.spatial) { auto* provisionalProducerRtpStream = this->producerRtpStreams.at(this->provisionalTargetLayers.spatial); auto provisionalRequiredBitrate = provisionalProducerRtpStream->GetBitrate( nowMs, 0, this->provisionalTargetLayers.temporal); if (requiredBitrate > provisionalRequiredBitrate) { requiredBitrate -= provisionalRequiredBitrate; } else { requiredBitrate = 1u; // Don't set 0 since it would be ignored. } } MS_DEBUG_DEV( "testing layers %" PRIi16 ":%" PRIi16 " [virtual bitrate:%" PRIu32 ", required bitrate:%" PRIu32 "]", spatialLayer, temporalLayer, virtualBitrate, requiredBitrate); // If active layer, end iterations here. Otherwise move to next spatial // layer. if (requiredBitrate) { goto done; } else { break; } } // If this is the preferred spatial layer or higher, take it and exit. if (spatialLayer >= this->preferredLayers.spatial) { break; } } done: // No higher active layers found. if (!requiredBitrate) { return 0u; } // No luck. if (requiredBitrate > virtualBitrate) { return 0u; } // Set provisional layers. this->provisionalTargetLayers.spatial = spatialLayer; this->provisionalTargetLayers.temporal = temporalLayer; MS_DEBUG_DEV( "setting provisional layers to %" PRIi16 ":%" PRIi16 " [virtual bitrate:%" PRIu32 ", required bitrate:%" PRIu32 "]", this->provisionalTargetLayers.spatial, this->provisionalTargetLayers.temporal, virtualBitrate, requiredBitrate); if (requiredBitrate <= bitrate) { return requiredBitrate; } else if (requiredBitrate <= virtualBitrate) { return bitrate; } else { return requiredBitrate; // NOTE: This cannot happen. } } void SimulcastConsumer::ApplyLayers() { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(IsActive(), "should be active"); auto provisionalTargetLayers = this->provisionalTargetLayers; // Reset provisional target layers. this->provisionalTargetLayers.Reset(); if (provisionalTargetLayers != this->targetLayers) { UpdateTargetLayers(provisionalTargetLayers.spatial, provisionalTargetLayers.temporal); // If this looks like a spatial layer downgrade due to BWE limitations, set // member. if ( this->rtpStream->GetActiveMs() > BweDowngradeMinActiveMs && this->targetLayers.spatial < this->currentSpatialLayer && this->currentSpatialLayer <= this->preferredLayers.spatial) { MS_DEBUG_DEV( "possible target spatial layer downgrade (from %" PRIi16 " to %" PRIi16 ") due to BWE limitation", this->currentSpatialLayer, this->targetLayers.spatial); this->lastBweDowngradeAtMs = this->shared->GetTimeMs(); } } } uint32_t SimulcastConsumer::GetDesiredBitrate() const { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); if (!IsActive()) { return 0u; } auto nowMs = this->shared->GetTimeMs(); uint32_t desiredBitrate{ 0u }; // Let's iterate all streams of the Producer (from highest to lowest) and // obtain their bitrate. Choose the highest one. // NOTE: When the Producer enables a higher stream, initially the bitrate of // it could be less than the bitrate of a lower stream. That's why we // iterate all streams here anyway. for (auto sIdx{ static_cast(this->producerRtpStreams.size() - 1) }; sIdx >= 0; --sIdx) { auto* producerRtpStream = this->producerRtpStreams.at(sIdx); if (!producerRtpStream) { continue; } auto streamBitrate = producerRtpStream->GetBitrate(nowMs); desiredBitrate = std::max(streamBitrate, desiredBitrate); } // If consumer.rtpParameters.encodings[0].maxBitrate was given and it's // greater than computed one, then use it. auto maxBitrate = this->rtpParameters.encodings[0].maxBitrate; desiredBitrate = std::max(maxBitrate, desiredBitrate); return desiredBitrate; } // NOLINTNEXTLINE(misc-no-recursion) void SimulcastConsumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) { MS_TRACE(); #ifdef MS_RTC_LOGGER_RTP packet->logger.consumerId = this->id; #endif auto spatialLayer = this->mapMappedSsrcSpatialLayer.at(packet->GetSsrc()); if (!IsActive()) { // Only drop the packet in the RTP sequence manager if it belongs to the // current spatial layer. if (spatialLayer == this->currentSpatialLayer) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); } return; } if (this->targetLayers.temporal == -1) { // Only drop the packet in the RTP sequence manager if it belongs to the // current spatial layer. if (spatialLayer == this->currentSpatialLayer) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::INVALID_TARGET_LAYER); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); } return; } auto payloadType = packet->GetPayloadType(); // NOTE: This may happen if this Consumer supports just some codecs of those // in the corresponding Producer. if (!this->supportedCodecPayloadTypes[payloadType]) { // Only drop the packet in the RTP sequence manager if it belongs to the // current spatial layer. if (spatialLayer == this->currentSpatialLayer) { MS_WARN_DEV("payload type not supported [payloadType:%" PRIu8 "]", payloadType); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); } return; } bool shouldSwitchCurrentSpatialLayer{ false }; // Check whether this is the packet we are waiting for in order to update // the current spatial layer. if ( this->currentSpatialLayer != this->targetLayers.spatial && spatialLayer == this->targetLayers.spatial) { // Ignore if not a key frame. if (!packet->IsKeyFrame()) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); #endif // NOTE: Don't drop the packet in the RTP sequence manager since this // packet doesn't belong to the current spatial layer. // Store the packet for the scenario in which this packet is part of the // key frame and it arrived before the first packet of the key frame. StorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket); return; } shouldSwitchCurrentSpatialLayer = true; // Need to resync the stream. this->syncRequired = true; this->spatialLayerToSync = spatialLayer; } // If the packet belongs to different spatial layer than the one being sent, // drop it. else if (spatialLayer != this->currentSpatialLayer) { // NOTE: Don't drop the packet in the RTP sequence manager since this // packet doesn't belong to the current spatial layer. return; } // If we need to sync and this is not a key frame, ignore the packet. // NOTE: syncRequired is true if packet is a key frame of the target spatial // layer or if transport just connected or consumer resumed. if (this->syncRequired && !packet->IsKeyFrame()) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); #endif // NOTE: No need to drop the packet in the RTP sequence manager since here // we are blocking all packets but the key frame that would trigger sync // below. // Store the packet for the scenario in which this packet is part of the // key frame and it arrived before the first packet of the key frame. StorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket); return; } // Packets with only padding are not forwarded. if (packet->GetPayloadLength() == 0) { // Only drop the packet in the RTP sequence manager if it belongs to the // current spatial layer. if (spatialLayer == this->currentSpatialLayer) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); } return; } // Whether this is the first packet after re-sync. const bool isSyncPacket = this->syncRequired; // Whether packets stored in the target layer retransmission buffer must be // sent once this packet is sent. bool sendPacketsInTargetLayerRetransmissionBuffer{ false }; // Sync sequence number and timestamp if required. if (isSyncPacket && (this->spatialLayerToSync == -1 || spatialLayer == this->spatialLayerToSync)) { if (packet->IsKeyFrame()) { MS_DEBUG_TAG( rtp, "sync key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); sendPacketsInTargetLayerRetransmissionBuffer = true; } uint32_t tsOffset{ 0u }; // Sync our RTP stream's RTP timestamp. if (spatialLayer == this->tsReferenceSpatialLayer) { tsOffset = 0u; } // If this is not the RTP stream we use as TS reference, do NTP based RTP // TS synchronization. else { auto* producerTsReferenceRtpStream = GetProducerTsReferenceRtpStream(); auto* producerTargetRtpStream = GetProducerTargetRtpStream(); // NOTE: If we are here is because we have Sender Reports for both the // TS reference stream and the target one. MS_ASSERT( producerTsReferenceRtpStream->GetSenderReportNtpMs(), "no Sender Report for TS reference RTP stream"); MS_ASSERT( producerTargetRtpStream->GetSenderReportNtpMs(), "no Sender Report for current RTP stream"); // Calculate NTP and TS stuff. auto ntpMs1 = producerTsReferenceRtpStream->GetSenderReportNtpMs(); auto ts1 = producerTsReferenceRtpStream->GetSenderReportTs(); auto ntpMs2 = producerTargetRtpStream->GetSenderReportNtpMs(); auto ts2 = producerTargetRtpStream->GetSenderReportTs(); int64_t diffMs; if (ntpMs2 >= ntpMs1) { diffMs = ntpMs2 - ntpMs1; } else { diffMs = -1 * (ntpMs1 - ntpMs2); } const int64_t diffTs = diffMs * this->rtpStream->GetClockRate() / 1000; const uint32_t newTs2 = ts2 - diffTs; // Apply offset. This is the difference that later must be removed from the // sending RTP packet. tsOffset = newTs2 - ts1; } // When switching to a new stream it may happen that the timestamp of this // key frame is lower than the highest timestamp sent to the remote endpoint. // If so, apply an extra offset to "fix" it for the whole live of this // selected Producer stream. if (shouldSwitchCurrentSpatialLayer && (packet->GetTimestamp() - tsOffset <= this->rtpStream->GetMaxPacketTs())) { // Max delay in ms we allow for the stream when switching. // https://en.wikipedia.org/wiki/Audio-to-video_synchronization#Recommendations static const uint32_t MaxExtraOffsetMs{ 75u }; // Outgoing packet matches the highest timestamp seen in the previous // stream. Apply an expected offset for a new frame in a 30fps stream. static const uint8_t MsOffset{ 33u }; // (1 / 30 * 1000). const int64_t maxTsExtraOffset = MaxExtraOffsetMs * this->rtpStream->GetClockRate() / 1000; uint32_t tsExtraOffset = this->rtpStream->GetMaxPacketTs() - packet->GetTimestamp() + tsOffset + (MsOffset * this->rtpStream->GetClockRate() / 1000); // NOTE: Don't ask for a key frame if already done. if (this->keyFrameForTsOffsetRequested) { // Give up and use the theoretical offset. if (tsExtraOffset > maxTsExtraOffset) { MS_WARN_TAG( simulcast, "giving up on proper stream switching after got a requested keyframe for which still too high RTP timestamp extra offset is needed (%" PRIu32 ")", tsExtraOffset); tsExtraOffset = 1u; } } else if (tsExtraOffset > maxTsExtraOffset) { MS_WARN_TAG( simulcast, "cannot switch stream due to too high RTP timestamp extra offset needed (%" PRIu32 "), requesting keyframe", tsExtraOffset); RequestKeyFrameForTargetSpatialLayer(); this->keyFrameForTsOffsetRequested = true; // Reset flags since we are discarding this key frame. this->syncRequired = false; this->spatialLayerToSync = -1; #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded( RTC::RtcLogger::RtpPacket::DiscardReason::TOO_HIGH_TIMESTAMP_EXTRA_NEEDED); #endif // NOTE: Don't drop the packet in the RTP sequence manager since this // packet doesn't belong to the current spatial layer. return; } if (tsExtraOffset > 0u) { MS_DEBUG_TAG( simulcast, "RTP timestamp extra offset generated for stream switching: %" PRIu32, tsExtraOffset); // Increase the timestamp offset for the whole life of this Producer stream // (until switched to a different one). tsOffset -= tsExtraOffset; } } this->tsOffset = tsOffset; // Sync our RTP stream's sequence number. // If previous frame has not been sent completely when we switch layer, // we can tell libwebrtc that previous frame is incomplete by skipping // one RTP sequence number. // 'packet->GetSequenceNumber() -2' may increase SeqManager::base and // increase the output sequence number. // https://github.com/versatica/mediasoup/issues/408 this->rtpSeqManager.Sync(packet->GetSequenceNumber() - (this->lastSentPacketHasMarker ? 1 : 2)); this->encodingContext->SyncRequired(); this->syncRequired = false; this->spatialLayerToSync = -1; this->keyFrameForTsOffsetRequested = false; } if (!shouldSwitchCurrentSpatialLayer && this->checkingForOldPacketsInSpatialLayer) { // If this is a packet previous to the spatial layer switch, ignore the // packet. // NOTE: We drop it in RTP sequence manager because this packet belongs // to current spatial layer. if (SeqManager::IsSeqLowerThan(packet->GetSequenceNumber(), this->snReferenceSpatialLayer)) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded( RTC::RtcLogger::RtpPacket::DiscardReason::PACKET_PREVIOUS_TO_SPATIAL_LAYER_SWITCH); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } else if ( SeqManager::IsSeqHigherThan( packet->GetSequenceNumber(), this->snReferenceSpatialLayer + MaxSequenceNumberGap)) { this->checkingForOldPacketsInSpatialLayer = false; } } bool marker{ false }; if (shouldSwitchCurrentSpatialLayer) { // Update current spatial layer. this->currentSpatialLayer = this->targetLayers.spatial; this->snReferenceSpatialLayer = packet->GetSequenceNumber(); this->checkingForOldPacketsInSpatialLayer = true; // Update target and current temporal layer. this->encodingContext->SetTargetTemporalLayer(this->targetLayers.temporal); this->encodingContext->SetCurrentTemporalLayer(packet->GetTemporalLayer()); // Reset the score of our RtpStream to 10. this->rtpStream->ResetScore(10u, /*notify*/ false); // Emit the layersChange event. EmitLayersChange(); // Emit the score event. EmitScore(); // Rewrite payload if needed. packet->ProcessPayload(this->encodingContext.get(), marker); } else { auto previousTemporalLayer = this->encodingContext->GetCurrentTemporalLayer(); // Rewrite payload if needed. Drop packet if necessary. // NOTE: We drop it in RTP sequence manager because this packet belongs // to current spatial layer. if (!packet->ProcessPayload(this->encodingContext.get(), marker)) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } if (previousTemporalLayer != this->encodingContext->GetCurrentTemporalLayer()) { EmitLayersChange(); } } // Update RTP seq number and timestamp based on NTP offset. uint16_t seq; const uint32_t timestamp = packet->GetTimestamp() - this->tsOffset; this->rtpSeqManager.Input(packet->GetSequenceNumber(), seq); // Save original packet fields. auto origSsrc = packet->GetSsrc(); auto origSeq = packet->GetSequenceNumber(); auto origTimestamp = packet->GetTimestamp(); // Rewrite packet. packet->SetSsrc(this->rtpParameters.encodings[0].ssrc); packet->SetSequenceNumber(seq); packet->SetTimestamp(timestamp); #ifdef MS_RTC_LOGGER_RTP packet->logger.sendRtpTimestamp = timestamp; packet->logger.sendSeqNumber = seq; #endif if (isSyncPacket) { MS_DEBUG_TAG( rtp, "sending sync packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] from original [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp(), origSsrc, origSeq, origTimestamp); } const RTC::RTP::RtpStreamSend::ReceivePacketResult result = this->rtpStream->ReceivePacket(packet, sharedPacket); if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { if (this->rtpSeqManager.GetMaxOutput() == packet->GetSequenceNumber()) { this->lastSentPacketHasMarker = packet->HasMarker(); } // Send the packet. this->listener->OnConsumerSendRtpPacket(this, packet); // May emit 'trace' event. EmitTraceEventRtpAndKeyFrameTypes(packet); } else { MS_WARN_TAG( rtp, "failed to send packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] from original [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp(), origSsrc, origSeq, origTimestamp); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED); #endif } // Restore packet fields. packet->SetSsrc(origSsrc); packet->SetSequenceNumber(origSeq); packet->SetTimestamp(origTimestamp); // Restore the original payload if needed. packet->RestorePayload(); // If sharedPacket doesn't have a packet inside and it has been stored we // need to clone the packet into it. if (!sharedPacket.HasPacket() && result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED) { sharedPacket.Assign(packet); } // If sent packet was the first packet of a key frame, let's send buffered // packets belonging to the same key frame that arrived earlier due to // packet misorder. if (sendPacketsInTargetLayerRetransmissionBuffer) { // NOTE: Only send buffered packets if the first packet containing the key // frame was sent. if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { for (auto& kv : this->targetLayerRetransmissionBuffer) { auto& bufferedSharedPacket = kv.second; auto* bufferedPacket = bufferedSharedPacket.GetPacket(); if (bufferedPacket->GetSequenceNumber() > origSeq) { MS_DEBUG_DEV( "sending packet buffered in the target layer retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] after sending first packet of the key frame [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", bufferedPacket->GetSsrc(), bufferedPacket->GetSequenceNumber(), bufferedPacket->GetTimestamp(), packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); SendRtpPacket(bufferedPacket, bufferedSharedPacket); // Be sure that the target layer retransmission buffer has not been // emptied as a result of sending this packet. If so, exit the loop. if (this->targetLayerRetransmissionBuffer.empty()) { MS_DEBUG_DEV( "target layer retransmission buffer emptied while iterating it, exiting the loop"); break; } } } } this->targetLayerRetransmissionBuffer.clear(); } } bool SimulcastConsumer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) { MS_TRACE(); if (static_cast((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval) { return true; } auto* senderReport = this->rtpStream->GetRtcpSenderReport(nowMs); if (!senderReport) { return true; } // Build SDES chunk for this sender. auto* sdesChunk = this->rtpStream->GetRtcpSdesChunk(); auto* delaySinceLastRrSsrcInfo = this->rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs); // RTCP Compound packet buffer cannot hold the data. if (!packet->Add(senderReport, sdesChunk, delaySinceLastRrSsrcInfo)) { return false; } this->lastRtcpSentTime = nowMs; return true; } void SimulcastConsumer::NeedWorstRemoteFractionLost( uint32_t /*mappedSsrc*/, uint8_t& worstRemoteFractionLost) { MS_TRACE(); if (!IsActive()) { return; } auto fractionLost = this->rtpStream->GetFractionLost(); // If our fraction lost is worse than the given one, update it. worstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost); } void SimulcastConsumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) { MS_TRACE(); if (!IsActive()) { return; } // May emit 'trace' event. EmitTraceEventNackType(); this->rtpStream->ReceiveNack(nackPacket); } void SimulcastConsumer::ReceiveKeyFrameRequest( RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) { MS_TRACE(); switch (messageType) { case RTC::RTCP::FeedbackPs::MessageType::PLI: { EmitTraceEventPliType(ssrc); break; } case RTC::RTCP::FeedbackPs::MessageType::FIR: { EmitTraceEventFirType(ssrc); break; } default:; } this->rtpStream->ReceiveKeyFrameRequest(messageType); if (IsActive()) { RequestKeyFrameForCurrentSpatialLayer(); } } void SimulcastConsumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) { MS_TRACE(); this->rtpStream->ReceiveRtcpReceiverReport(report); } void SimulcastConsumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) { MS_TRACE(); this->rtpStream->ReceiveRtcpXrReceiverReferenceTime(report); } uint32_t SimulcastConsumer::GetTransmissionRate(uint64_t nowMs) { MS_TRACE(); if (!IsActive()) { return 0u; } return this->rtpStream->GetBitrate(nowMs); } float SimulcastConsumer::GetRtt() const { MS_TRACE(); return this->rtpStream->GetRtt(); } void SimulcastConsumer::UserOnTransportConnected() { MS_TRACE(); this->syncRequired = true; this->spatialLayerToSync = -1; this->keyFrameForTsOffsetRequested = false; if (IsActive()) { MayChangeLayers(); } } void SimulcastConsumer::UserOnTransportDisconnected() { MS_TRACE(); this->lastBweDowngradeAtMs = 0u; this->rtpStream->Pause(); this->targetLayerRetransmissionBuffer.clear(); UpdateTargetLayers(-1, -1); } void SimulcastConsumer::UserOnPaused() { MS_TRACE(); this->lastBweDowngradeAtMs = 0u; this->rtpStream->Pause(); this->targetLayerRetransmissionBuffer.clear(); UpdateTargetLayers(-1, -1); if (this->externallyManagedBitrate) { this->listener->OnConsumerNeedZeroBitrate(this); } } void SimulcastConsumer::UserOnResumed() { MS_TRACE(); this->syncRequired = true; this->spatialLayerToSync = -1; this->keyFrameForTsOffsetRequested = false; this->checkingForOldPacketsInSpatialLayer = false; if (IsActive()) { MayChangeLayers(); } } void SimulcastConsumer::CreateRtpStream() { MS_TRACE(); auto& encoding = this->rtpParameters.encodings[0]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); MS_DEBUG_TAG( rtp, "[ssrc:%" PRIu32 ", payloadType:%" PRIu8 "]", encoding.ssrc, mediaCodec->payloadType); // Set stream params. RTC::RTP::RtpStream::Params params; params.ssrc = encoding.ssrc; params.payloadType = mediaCodec->payloadType; params.mimeType = mediaCodec->mimeType; params.clockRate = mediaCodec->clockRate; params.cname = this->rtpParameters.rtcp.cname; params.spatialLayers = encoding.spatialLayers; params.temporalLayers = encoding.temporalLayers; // Check in band FEC in codec parameters. if (mediaCodec->parameters.HasInteger("useinbandfec") && mediaCodec->parameters.GetInteger("useinbandfec") == 1) { MS_DEBUG_TAG(rtp, "in band FEC enabled"); params.useInBandFec = true; } // Check DTX in codec parameters. if (mediaCodec->parameters.HasInteger("usedtx") && mediaCodec->parameters.GetInteger("usedtx") == 1) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } // Check DTX in the encoding. if (encoding.dtx) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } for (const auto& fb : mediaCodec->rtcpFeedback) { if (!params.useNack && fb.type == "nack" && fb.parameter.empty()) { MS_DEBUG_2TAGS(rtp, rtcp, "NACK supported"); params.useNack = true; } else if (!params.usePli && fb.type == "nack" && fb.parameter == "pli") { MS_DEBUG_2TAGS(rtp, rtcp, "PLI supported"); params.usePli = true; } else if (!params.useFir && fb.type == "ccm" && fb.parameter == "fir") { MS_DEBUG_2TAGS(rtp, rtcp, "FIR supported"); params.useFir = true; } } this->rtpStream = new RTC::RTP::RtpStreamSend(this, this->shared, params, this->rtpParameters.mid); this->rtpStreams.push_back(this->rtpStream); // If the Consumer is paused, tell the RtpStreamSend. if (IsPaused() || IsProducerPaused()) { this->rtpStream->Pause(); } const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); if (rtxCodec && encoding.hasRtx) { this->rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc); } } void SimulcastConsumer::RequestKeyFrames() { MS_TRACE(); if (this->kind != RTC::Media::Kind::VIDEO) { return; } auto* producerTargetRtpStream = GetProducerTargetRtpStream(); auto* producerCurrentRtpStream = GetProducerCurrentRtpStream(); if (producerTargetRtpStream) { auto mappedSsrc = this->consumableRtpEncodings[this->targetLayers.spatial].ssrc; this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); } if (producerCurrentRtpStream && producerCurrentRtpStream != producerTargetRtpStream) { auto mappedSsrc = this->consumableRtpEncodings[this->currentSpatialLayer].ssrc; this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); } } void SimulcastConsumer::RequestKeyFrameForTargetSpatialLayer() { MS_TRACE(); if (this->kind != RTC::Media::Kind::VIDEO) { return; } auto* producerTargetRtpStream = GetProducerTargetRtpStream(); if (!producerTargetRtpStream) { return; } auto mappedSsrc = this->consumableRtpEncodings[this->targetLayers.spatial].ssrc; this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); } void SimulcastConsumer::RequestKeyFrameForCurrentSpatialLayer() { MS_TRACE(); if (this->kind != RTC::Media::Kind::VIDEO) { return; } auto* producerCurrentRtpStream = GetProducerCurrentRtpStream(); if (!producerCurrentRtpStream) { return; } auto mappedSsrc = this->consumableRtpEncodings[this->currentSpatialLayer].ssrc; this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); } void SimulcastConsumer::MayChangeLayers(bool force) { MS_TRACE(); RTC::ConsumerTypes::VideoLayers newTargetLayers; if (RecalculateTargetLayers(newTargetLayers)) { // If bitrate externally managed, don't bother the transport unless // the newTargetSpatialLayer has changed (or force is true). // This is because, if bitrate is externally managed, the target temporal // layer is managed by the available given bitrate so the transport // will let us change it when it considers. if (this->externallyManagedBitrate) { if (newTargetLayers.spatial != this->targetLayers.spatial || force) { this->listener->OnConsumerNeedBitrateChange(this); } } else { UpdateTargetLayers(newTargetLayers.spatial, newTargetLayers.temporal); } } } bool SimulcastConsumer::RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const { MS_TRACE(); // Start with no layers. newTargetLayers.Reset(); auto nowMs = this->shared->GetTimeMs(); for (size_t sIdx{ 0u }; sIdx < this->producerRtpStreams.size(); ++sIdx) { auto spatialLayer = static_cast(sIdx); auto* producerRtpStream = this->producerRtpStreams.at(sIdx); auto producerScore = producerRtpStream ? producerRtpStream->GetScore() : 0u; // If this is higher than current spatial layer and we moved to to current spatial // layer due to BWE limitations, check how much it has elapsed since then. if (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs) { if (newTargetLayers.spatial > -1 && spatialLayer > this->currentSpatialLayer) { continue; } } // Ignore spatial layers for non existing Producer streams or for those // with score 0. if (producerScore == 0u) { continue; } // If the stream has not been active time enough and we have an active one // already, move to the next spatial layer. // NOTE: Require bitrate externally managed for this. if (this->externallyManagedBitrate && newTargetLayers.spatial != -1 && producerRtpStream->GetActiveMs() < StreamMinActiveMs) { continue; } // We may not yet switch to this spatial layer. if (!CanSwitchToSpatialLayer(spatialLayer)) { continue; } newTargetLayers.spatial = spatialLayer; // If this is the preferred or higher spatial layer take it and exit. if (spatialLayer >= this->preferredLayers.spatial) { break; } } if (newTargetLayers.spatial != -1) { if (newTargetLayers.spatial == this->preferredLayers.spatial) { newTargetLayers.temporal = this->preferredLayers.temporal; } else if (newTargetLayers.spatial < this->preferredLayers.spatial) { newTargetLayers.temporal = static_cast(this->rtpStream->GetTemporalLayers() - 1); } else { newTargetLayers.temporal = 0; } } // Return true if any target layer changed. return (newTargetLayers != this->targetLayers); } void SimulcastConsumer::UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) { MS_TRACE(); // If we don't have yet a RTP timestamp reference, set it now. if ( newTargetSpatialLayer != -1 && (this->tsReferenceSpatialLayer == -1 || !GetProducerTsReferenceRtpStream()->GetSenderReportNtpMs())) { MS_DEBUG_TAG( simulcast, "using spatial layer %" PRIi16 " as RTP timestamp reference", newTargetSpatialLayer); this->tsReferenceSpatialLayer = newTargetSpatialLayer; } // If the new target spatial layer doesn't match the current one, clear the // target layer retransmission buffer. if (newTargetSpatialLayer != this->targetLayers.spatial) { this->targetLayerRetransmissionBuffer.clear(); } if (newTargetSpatialLayer == -1) { // Unset current and target layers. this->targetLayers.spatial = -1; this->targetLayers.temporal = -1; this->currentSpatialLayer = -1; this->encodingContext->SetTargetTemporalLayer(-1); this->encodingContext->SetCurrentTemporalLayer(-1); MS_DEBUG_TAG( simulcast, "target layers changed [spatial:-1, temporal:-1, consumerId:%s]", this->id.c_str()); EmitLayersChange(); return; } this->targetLayers.spatial = newTargetSpatialLayer; this->targetLayers.temporal = newTargetTemporalLayer; // If the new target spatial layer matches the current one, apply the new // target temporal layer now. if (this->targetLayers.spatial == this->currentSpatialLayer) { this->encodingContext->SetTargetTemporalLayer(this->targetLayers.temporal); } MS_DEBUG_TAG( simulcast, "target layers changed [spatial:%" PRIi16 ", temporal:%" PRIi16 ", consumerId:%s]", this->targetLayers.spatial, this->targetLayers.temporal, this->id.c_str()); // If the target spatial layer is different than the current one, request // a key frame. if (this->targetLayers.spatial != this->currentSpatialLayer) { RequestKeyFrameForTargetSpatialLayer(); } } bool SimulcastConsumer::CanSwitchToSpatialLayer(int16_t spatialLayer) const { MS_TRACE(); // This method assumes that the caller has verified that there is a valid // Producer RtpStream for the given spatial layer. MS_ASSERT( this->producerRtpStreams.at(spatialLayer), "no Producer RtpStream for the given spatialLayer:%" PRIi16, spatialLayer); // We can switch to the given spatial layer if: // - we don't have any TS reference spatial layer yet, or // - the given spatial layer matches the TS reference spatial layer, or // - both , the RTP streams of our TS reference spatial layer and the given // spatial layer, have Sender Report. return ( this->tsReferenceSpatialLayer == -1 || spatialLayer == this->tsReferenceSpatialLayer || this->producerRtpStreams.at(spatialLayer)->GetSenderReportNtpMs()); } void SimulcastConsumer::StorePacketInTargetLayerRetransmissionBuffer( RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) { MS_TRACE(); MS_DEBUG_DEV( "storing packet in target layer retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); // Store original packet into the buffer. Only clone once and only if // necessary. if (!sharedPacket.HasPacket()) { sharedPacket.Assign(packet); } // Assert that, if sharedPacket was already filled, both packet and // sharedPacket are the very same RTP packet. else { sharedPacket.AssertSamePacket(packet); } this->targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket; if (this->targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize) { this->targetLayerRetransmissionBuffer.erase(this->targetLayerRetransmissionBuffer.begin()); } } void SimulcastConsumer::EmitScore() const { MS_TRACE(); auto scoreOffset = FillBufferScore(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto notificationOffset = FBS::Consumer::CreateScoreNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), scoreOffset); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_SCORE, FBS::Notification::Body::Consumer_ScoreNotification, notificationOffset); } void SimulcastConsumer::EmitLayersChange() const { MS_TRACE(); MS_DEBUG_DEV( "current layers changed to [spatial:%" PRIi16 ", temporal:%" PRIi16 ", consumerId:%s]", this->currentSpatialLayer, this->encodingContext->GetCurrentTemporalLayer(), this->id.c_str()); flatbuffers::Offset layersOffset; if (this->currentSpatialLayer >= 0) { layersOffset = FBS::Consumer::CreateConsumerLayers( this->shared->GetChannelNotifier()->GetBufferBuilder(), this->currentSpatialLayer, this->encodingContext->GetCurrentTemporalLayer()); } auto notificationOffset = FBS::Consumer::CreateLayersChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), layersOffset); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_LAYERS_CHANGE, FBS::Notification::Body::Consumer_LayersChangeNotification, notificationOffset); } RTC::RTP::RtpStreamRecv* SimulcastConsumer::GetProducerCurrentRtpStream() const { MS_TRACE(); if (this->currentSpatialLayer == -1) { return nullptr; } // This may return nullptr. return this->producerRtpStreams.at(this->currentSpatialLayer); } RTC::RTP::RtpStreamRecv* SimulcastConsumer::GetProducerTargetRtpStream() const { MS_TRACE(); if (this->targetLayers.spatial == -1) { return nullptr; } // This may return nullptr. return this->producerRtpStreams.at(this->targetLayers.spatial); } RTC::RTP::RtpStreamRecv* SimulcastConsumer::GetProducerTsReferenceRtpStream() const { MS_TRACE(); if (this->tsReferenceSpatialLayer == -1) { return nullptr; } // This may return nullptr. return this->producerRtpStreams.at(this->tsReferenceSpatialLayer); } void SimulcastConsumer::OnRtpStreamScore( RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) { MS_TRACE(); // Emit the score event. EmitScore(); if (IsActive()) { // Just check target layers if our bitrate is not externally managed. // NOTE: For now this is a bit useless since, when locally managed, we do // not check the Consumer score at all. if (!this->externallyManagedBitrate) { MayChangeLayers(); } } } void SimulcastConsumer::OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet) { MS_TRACE(); this->listener->OnConsumerRetransmitRtpPacket(this, packet); // May emit 'trace' event. EmitTraceEventRtpAndKeyFrameTypes(packet, this->rtpStream->HasRtx()); } } // namespace RTC ================================================ FILE: worker/src/RTC/SrtpSession.cpp ================================================ #define MS_CLASS "RTC::SrtpSession" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SrtpSession.hpp" #include "DepLibSRTP.hpp" #ifdef MS_LIBURING_SUPPORTED #include "DepLibUring.hpp" #endif #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::memset() namespace RTC { /* Static. */ static constexpr size_t EncryptBufferSize{ 65536 }; alignas(4) static thread_local uint8_t EncryptBuffer[EncryptBufferSize]; /* Class methods. */ void SrtpSession::ClassInit() { // Set libsrtp event handler. const srtp_err_status_t err = srtp_install_event_handler(static_cast(OnSrtpEvent)); if (DepLibSRTP::IsError(err)) { MS_THROW_ERROR( "srtp_install_event_handler() failed: %s", DepLibSRTP::GetErrorString(err).c_str()); } } FBS::SrtpParameters::SrtpCryptoSuite SrtpSession::CryptoSuiteToFbs(CryptoSuite cryptoSuite) { switch (cryptoSuite) { case SrtpSession::CryptoSuite::AEAD_AES_256_GCM: { return FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_256_GCM; } case SrtpSession::CryptoSuite::AEAD_AES_128_GCM: { return FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_128_GCM; } case SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80: { return FBS::SrtpParameters::SrtpCryptoSuite::AES_CM_128_HMAC_SHA1_80; } case SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32: { return FBS::SrtpParameters::SrtpCryptoSuite::AES_CM_128_HMAC_SHA1_32; } NO_DEFAULT_GCC(); } } SrtpSession::CryptoSuite SrtpSession::CryptoSuiteFromFbs(FBS::SrtpParameters::SrtpCryptoSuite cryptoSuite) { switch (cryptoSuite) { case FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_256_GCM: { return SrtpSession::CryptoSuite::AEAD_AES_256_GCM; } case FBS::SrtpParameters::SrtpCryptoSuite::AEAD_AES_128_GCM: { return SrtpSession::CryptoSuite::AEAD_AES_128_GCM; } case FBS::SrtpParameters::SrtpCryptoSuite::AES_CM_128_HMAC_SHA1_80: { return SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_80; } case FBS::SrtpParameters::SrtpCryptoSuite::AES_CM_128_HMAC_SHA1_32: { return SrtpSession::CryptoSuite::AES_CM_128_HMAC_SHA1_32; } NO_DEFAULT_GCC(); } } void SrtpSession::OnSrtpEvent(srtp_event_data_t* data) { MS_TRACE(); switch (data->event) { case event_ssrc_collision: { MS_WARN_TAG(srtp, "SSRC collision occurred"); break; } case event_key_soft_limit: { MS_WARN_TAG(srtp, "stream reached the soft key usage limit and will expire soon"); break; } case event_key_hard_limit: { MS_WARN_TAG(srtp, "stream reached the hard key usage limit and has expired"); break; } case event_packet_index_limit: { MS_WARN_TAG(srtp, "stream reached the hard packet limit (2^48 packets)"); break; } } } /* Instance methods. */ SrtpSession::SrtpSession(Type type, CryptoSuite cryptoSuite, uint8_t* key, size_t keyLen) { MS_TRACE(); srtp_policy_t policy; // NOLINT(cppcoreguidelines-pro-type-member-init) // Set all policy fields to 0. std::memset(&policy, 0, sizeof(srtp_policy_t)); switch (cryptoSuite) { case CryptoSuite::AEAD_AES_256_GCM: { srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtp); srtp_crypto_policy_set_aes_gcm_256_16_auth(&policy.rtcp); break; } case CryptoSuite::AEAD_AES_128_GCM: { srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtp); srtp_crypto_policy_set_aes_gcm_128_16_auth(&policy.rtcp); break; } case CryptoSuite::AES_CM_128_HMAC_SHA1_80: { srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp); srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); break; } case CryptoSuite::AES_CM_128_HMAC_SHA1_32: { srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp); // NOTE: Must be 80 for RTCP. srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); break; } default: { MS_ABORT("unknown SRTP crypto suite"); } } MS_ASSERT( keyLen == policy.rtp.cipher_key_len, "given keyLen does not match policy.rtp.cipher_keyLen"); switch (type) { case Type::INBOUND: { policy.ssrc.type = ssrc_any_inbound; break; } case Type::OUTBOUND: { policy.ssrc.type = ssrc_any_outbound; break; } } policy.ssrc.value = 0; policy.key = key; // Required for sending RTP retransmission without RTX. policy.allow_repeat_tx = true; policy.window_size = 1024; policy.next = nullptr; // Set the SRTP session. const srtp_err_status_t err = srtp_create(&this->session, &policy); if (DepLibSRTP::IsError(err)) { MS_THROW_ERROR("srtp_create() failed: %s", DepLibSRTP::GetErrorString(err).c_str()); } } SrtpSession::~SrtpSession() { MS_TRACE(); if (this->session != nullptr) { const srtp_err_status_t err = srtp_dealloc(this->session); if (DepLibSRTP::IsError(err)) { try { MS_ABORT("srtp_dealloc() failed: %s", DepLibSRTP::GetErrorString(err).c_str()); } catch (const std::exception& error) // NOLINT(bugprone-empty-catch) { // NOTE: This is to avoid a warning: // '~SrtpSession' has a non-throwing exception specification but can // still throw [-Wexceptions] } } } } bool SrtpSession::EncryptRtp(const uint8_t** data, size_t* len) { MS_TRACE(); // Ensure that the resulting SRTP packet fits into the encrypt buffer. if (*len + SRTP_MAX_TRAILER_LEN > EncryptBufferSize) { MS_WARN_TAG(srtp, "cannot encrypt RTP packet, size too big (%zu bytes)", *len); return false; } uint8_t* encryptBuffer = EncryptBuffer; size_t encryptLen = EncryptBufferSize; #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { if (!DepLibUring::IsActive()) { goto protect; } // Use a preallocated buffer, if available. auto* sendBuffer = DepLibUring::GetSendBuffer(); if (sendBuffer) { encryptBuffer = sendBuffer; encryptLen = DepLibUring::SendBufferSize; } } protect: #endif const srtp_err_status_t err = srtp_protect( /*srtp_t ctx*/ this->session, /*const uint8_t* rtp*/ *data, /*size_t rtp_len*/ *len, /*uint8_t* srtp*/ encryptBuffer, /*size_t* srtp_len*/ std::addressof(encryptLen), /*size_t mki_index*/ 0); if (DepLibSRTP::IsError(err)) { MS_WARN_TAG(srtp, "srtp_protect() failed: %s", DepLibSRTP::GetErrorString(err).c_str()); return false; } // Update the given data pointer and len. *data = const_cast(encryptBuffer); *len = encryptLen; return true; } bool SrtpSession::DecryptSrtp(uint8_t* data, size_t* len) { MS_TRACE(); size_t decryptLen = *len; const srtp_err_status_t err = srtp_unprotect( /*srtp_t ctx*/ this->session, /*const uint8_t* srtp*/ data, /*size_t srtp_len*/ *len, /*uint8_t* rtp*/ data, /*size_t* rtp_len*/ std::addressof(decryptLen)); if (DepLibSRTP::IsError(err)) { MS_DEBUG_TAG(srtp, "srtp_unprotect() failed: %s", DepLibSRTP::GetErrorString(err).c_str()); return false; } // Update the given len. *len = decryptLen; return true; } bool SrtpSession::EncryptRtcp(const uint8_t** data, size_t* len) { MS_TRACE(); // Ensure that the resulting SRTCP packet fits into the encrypt buffer. if (*len + SRTP_MAX_TRAILER_LEN > EncryptBufferSize) { MS_WARN_TAG(srtp, "cannot encrypt RTCP packet, size too big (%zu bytes)", *len); return false; } uint8_t* encryptBuffer = EncryptBuffer; size_t encryptLen = EncryptBufferSize; const srtp_err_status_t err = srtp_protect_rtcp( /*srtp_t ctx*/ this->session, /*const uint8_t* rtcp*/ *data, /*size_t rtcp_len*/ *len, /*uint8_t* srtcp*/ encryptBuffer, /*size_t* srtcp_len*/ std::addressof(encryptLen), /*size_t mki_index*/ 0); if (DepLibSRTP::IsError(err)) { MS_WARN_TAG(srtp, "srtp_protect_rtcp() failed: %s", DepLibSRTP::GetErrorString(err).c_str()); return false; } // Update the given data pointer and len. *data = const_cast(EncryptBuffer); *len = encryptLen; return true; } bool SrtpSession::DecryptSrtcp(uint8_t* data, size_t* len) { MS_TRACE(); size_t decryptLen = *len; const srtp_err_status_t err = srtp_unprotect_rtcp( /*srtp_t ctx*/ this->session, /*const uint8_t* srtcp*/ data, /*size_t srtcp_len*/ *len, /*uint8_t* rtcp*/ data, /*size_t* rtcp_len*/ std::addressof(decryptLen)); if (DepLibSRTP::IsError(err)) { MS_DEBUG_TAG(srtp, "srtp_unprotect_rtcp() failed: %s", DepLibSRTP::GetErrorString(err).c_str()); return false; } // Update the given len. *len = decryptLen; return true; } } // namespace RTC ================================================ FILE: worker/src/RTC/SvcConsumer.cpp ================================================ #define MS_CLASS "RTC::SvcConsumer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/SvcConsumer.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "RTC/RTP/Codecs/Tools.hpp" #ifdef MS_RTC_LOGGER_RTP #include "RTC/RtcLogger.hpp" #endif #include // std::numeric_limits namespace RTC { /* Static. */ static constexpr uint64_t BweDowngradeConservativeMs{ 10000u }; static constexpr uint64_t BweDowngradeMinActiveMs{ 8000u }; static constexpr size_t TargetLayerRetransmissionBufferSize{ 20u }; /* Instance methods. */ SvcConsumer::SvcConsumer( SharedInterface* shared, const std::string& id, const std::string& producerId, RTC::Consumer::Listener* listener, const FBS::Transport::ConsumeRequest* data) : RTC::Consumer::Consumer(shared, id, producerId, listener, data, RTC::RtpParameters::Type::SVC) { MS_TRACE(); // Ensure there is a single encoding. if (this->consumableRtpEncodings.size() != 1u) { MS_THROW_TYPE_ERROR("invalid consumableRtpEncodings with size != 1"); } auto& encoding = this->rtpParameters.encodings[0]; // Ensure there are multiple spatial or temporal layers. if (encoding.spatialLayers < 2u && encoding.temporalLayers < 2u) { MS_THROW_TYPE_ERROR("invalid number of layers"); } // Set preferredLayers (if given). if (flatbuffers::IsFieldPresent(data, FBS::Transport::ConsumeRequest::VT_PREFERREDLAYERS)) { this->preferredLayers.spatial = data->preferredLayers()->spatialLayer(); if (this->preferredLayers.spatial > encoding.spatialLayers - 1) { this->preferredLayers.spatial = static_cast(encoding.spatialLayers - 1); } if (flatbuffers::IsFieldPresent(data->preferredLayers(), FBS::Consumer::ConsumerLayers::VT_TEMPORALLAYER)) { if (this->preferredLayers.temporal > encoding.temporalLayers - 1) { this->preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); } } else { this->preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); } } else { // Initially set preferredSpatialLayer and preferredTemporalLayer to the // maximum value. this->preferredLayers.spatial = static_cast(encoding.spatialLayers - 1); this->preferredLayers.temporal = static_cast(encoding.temporalLayers - 1); } // Create the encoding context. const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); if (!RTC::RTP::Codecs::Tools::IsValidTypeForCodec(this->type, mediaCodec->mimeType)) { MS_THROW_TYPE_ERROR("%s codec not supported for svc", mediaCodec->mimeType.ToString().c_str()); } // Let's chosee an initial output seq number between 1000 and 32768 to avoid // libsrtp bug: // https://github.com/versatica/mediasoup/issues/1437 const uint16_t initialOutputSeq = Utils::Crypto::GetRandomUInt(1000u, std::numeric_limits::max() / 2); this->rtpSeqManager = RTC::SeqManager(initialOutputSeq); RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = encoding.spatialLayers; params.temporalLayers = encoding.temporalLayers; params.ksvc = encoding.ksvc; this->encodingContext.reset( RTC::RTP::Codecs::Tools::GetEncodingContext(mediaCodec->mimeType, params)); MS_ASSERT(this->encodingContext, "no encoding context for this codec"); // Create RtpStreamSend instance for sending a single stream to the remote. CreateRtpStream(); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ nullptr); } SvcConsumer::~SvcConsumer() { MS_TRACE(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); delete this->rtpStream; this->targetLayerRetransmissionBuffer.clear(); } flatbuffers::Offset SvcConsumer::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Call the parent method. auto base = RTC::Consumer::FillBuffer(builder); // Add rtpStream. std::vector> rtpStreams; rtpStreams.emplace_back(this->rtpStream->FillBuffer(builder)); auto dump = FBS::Consumer::CreateConsumerDumpDirect( builder, base, &rtpStreams, this->preferredLayers.spatial, this->encodingContext->GetTargetSpatialLayer(), this->encodingContext->GetCurrentSpatialLayer(), this->preferredLayers.temporal, this->encodingContext->GetTargetTemporalLayer(), this->encodingContext->GetCurrentTemporalLayer()); return FBS::Consumer::CreateDumpResponse(builder, dump); } flatbuffers::Offset SvcConsumer::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); std::vector> rtpStreams; // Add stats of our send stream. rtpStreams.emplace_back(this->rtpStream->FillBufferStats(builder)); // Add stats of our recv stream. if (this->producerRtpStream) { rtpStreams.emplace_back(producerRtpStream->FillBufferStats(builder)); } return FBS::Consumer::CreateGetStatsResponseDirect(builder, &rtpStreams); } flatbuffers::Offset SvcConsumer::FillBufferScore( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); MS_ASSERT(this->producerRtpStreamScores, "producerRtpStreamScores not set"); uint8_t producerScore{ 0 }; if (this->producerRtpStream) { producerScore = this->producerRtpStream->GetScore(); } else { producerScore = 0; } return FBS::Consumer::CreateConsumerScoreDirect( builder, this->rtpStream->GetScore(), producerScore, this->producerRtpStreamScores); } void SvcConsumer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::CONSUMER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Consumer_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::CONSUMER_REQUEST_KEY_FRAME: { if (IsActive()) { RequestKeyFrame(); } request->Accept(); break; } case Channel::ChannelRequest::Method::CONSUMER_SET_PREFERRED_LAYERS: { auto previousPreferredLayers = this->preferredLayers; const auto* body = request->data->body_as(); const auto* preferredLayers = body->preferredLayers(); // Spatial layer. this->preferredLayers.spatial = preferredLayers->spatialLayer(); if (this->preferredLayers.spatial > this->rtpStream->GetSpatialLayers() - 1) { this->preferredLayers.spatial = static_cast(this->rtpStream->GetSpatialLayers() - 1); } // preferredTemporaLayer is optional. auto preferredTemporalLayer = preferredLayers->temporalLayer(); if (preferredTemporalLayer.has_value()) { this->preferredLayers.temporal = preferredTemporalLayer.value(); if (this->preferredLayers.temporal > this->rtpStream->GetTemporalLayers() - 1) { this->preferredLayers.temporal = static_cast(this->rtpStream->GetTemporalLayers() - 1); } } else { this->preferredLayers.temporal = this->rtpStream->GetTemporalLayers() - 1; } MS_DEBUG_DEV( "preferred layers changed [spatial:%" PRIi16 ", temporal:%" PRIi16 ", consumerId:%s]", this->preferredLayers.spatial, this->preferredLayers.temporal, this->id.c_str()); preferredTemporalLayer = this->preferredLayers.temporal; auto preferredLayersOffset = FBS::Consumer::CreateConsumerLayers( request->GetBufferBuilder(), this->preferredLayers.spatial, preferredTemporalLayer); auto responseOffset = FBS::Consumer::CreateSetPreferredLayersResponse( request->GetBufferBuilder(), preferredLayersOffset); request->Accept(FBS::Response::Body::Consumer_SetPreferredLayersResponse, responseOffset); if (IsActive() && this->preferredLayers != previousPreferredLayers) { MayChangeLayers(/*force*/ true); } break; } default: { // Pass it to the parent class. RTC::Consumer::HandleRequest(request); } } } void SvcConsumer::ProducerRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/) { MS_TRACE(); this->producerRtpStream = rtpStream; } void SvcConsumer::ProducerNewRtpStream(RTC::RTP::RtpStreamRecv* rtpStream, uint32_t /*mappedSsrc*/) { MS_TRACE(); this->producerRtpStream = rtpStream; // Emit the score event. EmitScore(); if (IsActive()) { MayChangeLayers(); } } void SvcConsumer::ProducerRtpStreamScore( RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t score, uint8_t previousScore) { MS_TRACE(); // Emit score event. EmitScore(); if (RTC::Consumer::IsActive()) { // Just check target layers if the stream has died or reborned. if (!this->externallyManagedBitrate || (score == 0u || previousScore == 0u)) { MayChangeLayers(); } } } void SvcConsumer::ProducerRtcpSenderReport(RTC::RTP::RtpStreamRecv* /*rtpStream*/, bool /*first*/) { MS_TRACE(); // Do nothing. } uint8_t SvcConsumer::GetBitratePriority() const { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); if (!IsActive()) { return 0u; } return this->priority; } uint32_t SvcConsumer::IncreaseLayer(uint32_t bitrate, bool considerLoss) { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(IsActive(), "should be active"); if (this->producerRtpStream->GetScore() == 0u) { return 0u; } // If already in the preferred layers, do nothing. if (this->provisionalTargetLayers == this->preferredLayers) { return 0u; } uint32_t virtualBitrate; if (considerLoss) { // Calculate virtual available bitrate based on given bitrate and our // packet lost. auto lossPercentage = this->rtpStream->GetLossPercentage(); if (lossPercentage < 2) { virtualBitrate = 1.08 * bitrate; } else if (lossPercentage > 10) { virtualBitrate = (1 - 0.5 * (lossPercentage / 100)) * bitrate; } else { virtualBitrate = bitrate; } } else { virtualBitrate = bitrate; } uint32_t requiredBitrate{ 0u }; int16_t spatialLayer{ 0 }; int16_t temporalLayer{ 0 }; auto nowMs = this->shared->GetTimeMs(); for (; spatialLayer < this->producerRtpStream->GetSpatialLayers(); ++spatialLayer) { // If this is higher than current spatial layer and we moved to to current spatial // layer due to BWE limitations, check how much it has elapsed since then. if (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs) { if (this->provisionalTargetLayers.spatial > -1 && spatialLayer > this->encodingContext->GetCurrentSpatialLayer()) { MS_DEBUG_DEV( "avoid upgrading to spatial layer %" PRIi16 " due to recent BWE downgrade", spatialLayer); goto done; } } // Ignore spatial layers lower than the one we already have. if (spatialLayer < this->provisionalTargetLayers.spatial) { continue; } temporalLayer = 0; // Check bitrate of every temporal layer. for (; temporalLayer < this->producerRtpStream->GetTemporalLayers(); ++temporalLayer) { // Ignore temporal layers lower than the one we already have (taking into account // the spatial layer too). if ( spatialLayer == this->provisionalTargetLayers.spatial && temporalLayer <= this->provisionalTargetLayers.temporal) { continue; } requiredBitrate = this->producerRtpStream->GetLayerBitrate(nowMs, spatialLayer, temporalLayer); // When using K-SVC we must subtract the bitrate of the current used layer // if the new layer is the temporal layer 0 of an higher spatial layer. // if ( this->encodingContext->IsKSvc() && requiredBitrate && temporalLayer == 0 && this->provisionalTargetLayers.spatial > -1 && spatialLayer > this->provisionalTargetLayers.spatial) { auto provisionalRequiredBitrate = this->producerRtpStream->GetSpatialLayerBitrate( nowMs, this->provisionalTargetLayers.spatial); if (requiredBitrate > provisionalRequiredBitrate) { requiredBitrate -= provisionalRequiredBitrate; } else { requiredBitrate = 1u; // Don't set 0 since it would be ignored. } } MS_DEBUG_DEV( "testing layers %" PRIi16 ":%" PRIi16 " [virtual bitrate:%" PRIu32 ", required bitrate:%" PRIu32 "]", spatialLayer, temporalLayer, virtualBitrate, requiredBitrate); // If active layer, end iterations here. Otherwise move to next spatial layer. if (requiredBitrate) { goto done; } else { break; } } // If this is the preferred or higher spatial layer, take it and exit. if (spatialLayer >= this->preferredLayers.spatial) { break; } } done: // No higher active layers found. if (!requiredBitrate) { return 0u; } // No luck. if (requiredBitrate > virtualBitrate) { return 0u; } // Set provisional layers. this->provisionalTargetLayers.spatial = spatialLayer; this->provisionalTargetLayers.temporal = temporalLayer; MS_DEBUG_DEV( "upgrading to layers %" PRIi16 ":%" PRIi16 " [virtual bitrate:%" PRIu32 ", required bitrate:%" PRIu32 "]", this->provisionalTargetLayers.spatial, this->provisionalTargetLayers.temporal, virtualBitrate, requiredBitrate); if (requiredBitrate <= bitrate) { return requiredBitrate; } else if (requiredBitrate <= virtualBitrate) { return bitrate; } else { return requiredBitrate; // NOTE: This cannot happen. } } void SvcConsumer::ApplyLayers() { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); MS_ASSERT(IsActive(), "should be active"); auto provisionalTargetLayers = this->provisionalTargetLayers; // Reset provisional target layers. this->provisionalTargetLayers.Reset(); if (provisionalTargetLayers != this->encodingContext->GetTargetLayers()) { UpdateTargetLayers(provisionalTargetLayers.spatial, provisionalTargetLayers.temporal); // If this looks like a spatial layer downgrade due to BWE limitations, set member. if ( this->rtpStream->GetActiveMs() > BweDowngradeMinActiveMs && this->encodingContext->GetTargetSpatialLayer() < this->encodingContext->GetCurrentSpatialLayer() && this->encodingContext->GetCurrentSpatialLayer() <= this->preferredLayers.spatial) { MS_DEBUG_DEV( "possible target spatial layer downgrade (from %" PRIi16 " to %" PRIi16 ") due to BWE limitation", this->encodingContext->GetCurrentSpatialLayer(), this->encodingContext->GetTargetSpatialLayer()); this->lastBweDowngradeAtMs = this->shared->GetTimeMs(); } } } uint32_t SvcConsumer::GetDesiredBitrate() const { MS_TRACE(); MS_ASSERT(this->externallyManagedBitrate, "bitrate is not externally managed"); if (!IsActive()) { return 0u; } auto nowMs = this->shared->GetTimeMs(); uint32_t desiredBitrate{ 0u }; // When using K-SVC each spatial layer is independent of the others. if (this->encodingContext->IsKSvc()) { // Let's iterate all spatial layers of the Producer (from highest to lowest) and // obtain their bitrate. Choose the highest one. // NOTE: When the Producer enables a higher spatial layer, initially the bitrate // oft could be less than the bitrate of a lower one. That's why we iterate all // spatial layers here anyway. for (auto spatialLayer{ this->producerRtpStream->GetSpatialLayers() - 1 }; spatialLayer >= 0; --spatialLayer) { auto spatialLayerBitrate = this->producerRtpStream->GetSpatialLayerBitrate(nowMs, spatialLayer); desiredBitrate = std::max(spatialLayerBitrate, desiredBitrate); } } else { desiredBitrate = this->producerRtpStream->GetBitrate(nowMs); } // If consumer.rtpParameters.encodings[0].maxBitrate was given and it's // greater than computed one, then use it. auto maxBitrate = this->rtpParameters.encodings[0].maxBitrate; desiredBitrate = std::max(maxBitrate, desiredBitrate); return desiredBitrate; } // NOLINTNEXTLINE(misc-no-recursion) void SvcConsumer::SendRtpPacket(RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) { MS_TRACE(); #ifdef MS_RTC_LOGGER_RTP packet->logger.consumerId = this->id; #endif if (!IsActive()) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::CONSUMER_INACTIVE); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } if (this->encodingContext->GetTargetSpatialLayer() == -1 || this->encodingContext->GetTargetTemporalLayer() == -1) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::INVALID_TARGET_LAYER); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } // If we need to sync and this is not a key frame, ignore the packet. if (this->syncRequired && !packet->IsKeyFrame()) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::NOT_A_KEYFRAME); #endif // NOTE: No need to drop the packet in the RTP sequence manager since here // we are blocking all packets but the key frame that would trigger sync // below. // Store the packet for the scenario in which this packet is part of the // key frame and it arrived before the first packet of the key frame. StorePacketInTargetLayerRetransmissionBuffer(packet, sharedPacket); return; } auto payloadType = packet->GetPayloadType(); // NOTE: This may happen if this Consumer supports just some codecs of those // in the corresponding Producer. if (!this->supportedCodecPayloadTypes[payloadType]) { MS_WARN_DEV("payload type not supported [payloadType:%" PRIu8 "]", payloadType); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::UNSUPPORTED_PAYLOAD_TYPE); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } // Packets with only padding are not forwarded. if (packet->GetPayloadLength() == 0) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::EMPTY_PAYLOAD); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } // Whether this is the first packet after re-sync. const bool isSyncPacket = this->syncRequired; // Whether packets stored in the target layer retransmission buffer must be // sent once this packet is sent. bool sendPacketsInTargetLayerRetransmissionBuffer{ false }; // Sync sequence number and timestamp if required. if (isSyncPacket) { if (packet->IsKeyFrame()) { MS_DEBUG_TAG( rtp, "sync key frame received [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); sendPacketsInTargetLayerRetransmissionBuffer = true; } this->rtpSeqManager.Sync(packet->GetSequenceNumber() - 1); this->encodingContext->SyncRequired(); this->syncRequired = false; } auto previousLayers = this->encodingContext->GetCurrentLayers(); bool marker{ false }; const bool origMarker = packet->HasMarker(); if (!packet->ProcessPayload(this->encodingContext.get(), marker)) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::DROPPED_BY_CODEC); #endif this->rtpSeqManager.Drop(packet->GetSequenceNumber()); return; } if (previousLayers != this->encodingContext->GetCurrentLayers()) { // Emit the layersChange event. EmitLayersChange(); } // Update RTP seq number and timestamp based on NTP offset. uint16_t seq; this->rtpSeqManager.Input(packet->GetSequenceNumber(), seq); // Save original packet fields. auto origSsrc = packet->GetSsrc(); auto origSeq = packet->GetSequenceNumber(); // Rewrite packet. packet->SetSsrc(this->rtpParameters.encodings[0].ssrc); packet->SetSequenceNumber(seq); #ifdef MS_RTC_LOGGER_RTP packet->logger.sendRtpTimestamp = packet->GetTimestamp(); packet->logger.sendSeqNumber = seq; #endif packet->SetMarker(marker); if (isSyncPacket) { MS_DEBUG_TAG( rtp, "sending sync packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] from original [seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp(), origSeq); } const RTC::RTP::RtpStreamSend::ReceivePacketResult result = this->rtpStream->ReceivePacket(packet, sharedPacket); if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { // Send the packet. this->listener->OnConsumerSendRtpPacket(this, packet); // May emit 'trace' event. EmitTraceEventRtpAndKeyFrameTypes(packet); } else { MS_WARN_TAG( rtp, "failed to send packet [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] from original [ssrc:%" PRIu32 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp(), origSsrc, origSeq); #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::SEND_RTP_STREAM_DISCARDED); #endif } // Restore packet fields. packet->SetSsrc(origSsrc); packet->SetSequenceNumber(origSeq); packet->SetMarker(origMarker); // Restore the original payload if needed. packet->RestorePayload(); // If sharedPacket doesn't have a packet inside and it has been stored we // need to clone the packet into it. if (!sharedPacket.HasPacket() && result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED) { sharedPacket.Assign(packet); } // If sent packet was the first packet of a key frame, let's send buffered // packets belonging to the same key frame that arrived earlier due to // packet misorder. if (sendPacketsInTargetLayerRetransmissionBuffer) { // NOTE: Only send buffered packets if the first packet containing the key // frame was sent. if (result != RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED) { for (auto& kv : this->targetLayerRetransmissionBuffer) { auto& bufferedSharedPacket = kv.second; auto* bufferedPacket = bufferedSharedPacket.GetPacket(); if (bufferedPacket->GetSequenceNumber() > origSeq) { MS_DEBUG_DEV( "sending packet buffered in the target layer retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "] after sending first packet of the key frame [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", bufferedPacket->GetSsrc(), bufferedPacket->GetSequenceNumber(), bufferedPacket->GetTimestamp(), packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); SendRtpPacket(bufferedPacket, bufferedSharedPacket); // Be sure that the target layer retransmission buffer has not been // emptied as a result of sending this packet. If so, exit the loop. if (this->targetLayerRetransmissionBuffer.empty()) { MS_DEBUG_DEV( "target layer retransmission buffer emptied while iterating it, exiting the loop"); break; } } } } this->targetLayerRetransmissionBuffer.clear(); } } bool SvcConsumer::GetRtcp(RTC::RTCP::CompoundPacket* packet, uint64_t nowMs) { MS_TRACE(); if (static_cast((nowMs - this->lastRtcpSentTime) * 1.15) < this->maxRtcpInterval) { return true; } auto* senderReport = this->rtpStream->GetRtcpSenderReport(nowMs); if (!senderReport) { return true; } // Build SDES chunk for this sender. auto* sdesChunk = this->rtpStream->GetRtcpSdesChunk(); auto* delaySinceLastRrSsrcInfo = this->rtpStream->GetRtcpXrDelaySinceLastRrSsrcInfo(nowMs); // RTCP Compound packet buffer cannot hold the data. if (!packet->Add(senderReport, sdesChunk, delaySinceLastRrSsrcInfo)) { return false; } this->lastRtcpSentTime = nowMs; return true; } void SvcConsumer::NeedWorstRemoteFractionLost(uint32_t /*mappedSsrc*/, uint8_t& worstRemoteFractionLost) { MS_TRACE(); if (!IsActive()) { return; } auto fractionLost = this->rtpStream->GetFractionLost(); // If our fraction lost is worse than the given one, update it. worstRemoteFractionLost = std::max(fractionLost, worstRemoteFractionLost); } void SvcConsumer::ReceiveNack(RTC::RTCP::FeedbackRtpNackPacket* nackPacket) { MS_TRACE(); if (!IsActive()) { return; } // May emit 'trace' event. EmitTraceEventNackType(); this->rtpStream->ReceiveNack(nackPacket); } void SvcConsumer::ReceiveKeyFrameRequest(RTC::RTCP::FeedbackPs::MessageType messageType, uint32_t ssrc) { MS_TRACE(); switch (messageType) { case RTC::RTCP::FeedbackPs::MessageType::PLI: { EmitTraceEventPliType(ssrc); break; } case RTC::RTCP::FeedbackPs::MessageType::FIR: { EmitTraceEventFirType(ssrc); break; } default:; } this->rtpStream->ReceiveKeyFrameRequest(messageType); if (IsActive()) { RequestKeyFrame(); } } void SvcConsumer::ReceiveRtcpReceiverReport(RTC::RTCP::ReceiverReport* report) { MS_TRACE(); this->rtpStream->ReceiveRtcpReceiverReport(report); } void SvcConsumer::ReceiveRtcpXrReceiverReferenceTime(RTC::RTCP::ReceiverReferenceTime* report) { MS_TRACE(); this->rtpStream->ReceiveRtcpXrReceiverReferenceTime(report); } uint32_t SvcConsumer::GetTransmissionRate(uint64_t nowMs) { MS_TRACE(); if (!IsActive()) { return 0u; } return this->rtpStream->GetBitrate(nowMs); } float SvcConsumer::GetRtt() const { MS_TRACE(); return this->rtpStream->GetRtt(); } void SvcConsumer::UserOnTransportConnected() { MS_TRACE(); this->syncRequired = true; if (IsActive()) { MayChangeLayers(); } } void SvcConsumer::UserOnTransportDisconnected() { MS_TRACE(); this->lastBweDowngradeAtMs = 0u; this->rtpStream->Pause(); this->targetLayerRetransmissionBuffer.clear(); UpdateTargetLayers(-1, -1); } void SvcConsumer::UserOnPaused() { MS_TRACE(); this->lastBweDowngradeAtMs = 0u; this->rtpStream->Pause(); this->targetLayerRetransmissionBuffer.clear(); UpdateTargetLayers(-1, -1); if (this->externallyManagedBitrate) { this->listener->OnConsumerNeedZeroBitrate(this); } } void SvcConsumer::UserOnResumed() { MS_TRACE(); this->syncRequired = true; if (IsActive()) { MayChangeLayers(); } } void SvcConsumer::CreateRtpStream() { MS_TRACE(); auto& encoding = this->rtpParameters.encodings[0]; const auto* mediaCodec = this->rtpParameters.GetCodecForEncoding(encoding); MS_DEBUG_TAG( rtp, "[ssrc:%" PRIu32 ", payloadType:%" PRIu8 "]", encoding.ssrc, mediaCodec->payloadType); // Set stream params. RTC::RTP::RtpStream::Params params; params.ssrc = encoding.ssrc; params.payloadType = mediaCodec->payloadType; params.mimeType = mediaCodec->mimeType; params.clockRate = mediaCodec->clockRate; params.cname = this->rtpParameters.rtcp.cname; params.spatialLayers = encoding.spatialLayers; params.temporalLayers = encoding.temporalLayers; // Check in band FEC in codec parameters. if (mediaCodec->parameters.HasInteger("useinbandfec") && mediaCodec->parameters.GetInteger("useinbandfec") == 1) { MS_DEBUG_TAG(rtp, "in band FEC enabled"); params.useInBandFec = true; } // Check DTX in codec parameters. if (mediaCodec->parameters.HasInteger("usedtx") && mediaCodec->parameters.GetInteger("usedtx") == 1) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } // Check DTX in the encoding. if (encoding.dtx) { MS_DEBUG_TAG(rtp, "DTX enabled"); params.useDtx = true; } for (const auto& fb : mediaCodec->rtcpFeedback) { if (!params.useNack && fb.type == "nack" && fb.parameter.empty()) { MS_DEBUG_2TAGS(rtp, rtcp, "NACK supported"); params.useNack = true; } else if (!params.usePli && fb.type == "nack" && fb.parameter == "pli") { MS_DEBUG_2TAGS(rtp, rtcp, "PLI supported"); params.usePli = true; } else if (!params.useFir && fb.type == "ccm" && fb.parameter == "fir") { MS_DEBUG_2TAGS(rtp, rtcp, "FIR supported"); params.useFir = true; } } this->rtpStream = new RTC::RTP::RtpStreamSend(this, this->shared, params, this->rtpParameters.mid); this->rtpStreams.push_back(this->rtpStream); // If the Consumer is paused, tell the RtpStreamSend. if (IsPaused() || IsProducerPaused()) { this->rtpStream->Pause(); } const auto* rtxCodec = this->rtpParameters.GetRtxCodecForEncoding(encoding); if (rtxCodec && encoding.hasRtx) { this->rtpStream->SetRtx(rtxCodec->payloadType, encoding.rtx.ssrc); } } void SvcConsumer::RequestKeyFrame() { MS_TRACE(); if (this->kind != RTC::Media::Kind::VIDEO) { return; } auto mappedSsrc = this->consumableRtpEncodings[0].ssrc; this->listener->OnConsumerKeyFrameRequested(this, mappedSsrc); } void SvcConsumer::MayChangeLayers(bool force) { MS_TRACE(); RTC::ConsumerTypes::VideoLayers newTargetLayers; if (RecalculateTargetLayers(newTargetLayers)) { // If bitrate externally managed, don't bother the transport unless // the newTargetSpatialLayer has changed (or force is true). // This is because, if bitrate is externally managed, the target temporal // layer is managed by the available given bitrate so the transport // will let us change it when it considers. if (this->externallyManagedBitrate) { if (newTargetLayers.spatial != this->encodingContext->GetTargetSpatialLayer() || force) { this->listener->OnConsumerNeedBitrateChange(this); } } else { UpdateTargetLayers(newTargetLayers.spatial, newTargetLayers.temporal); } } } bool SvcConsumer::RecalculateTargetLayers(RTC::ConsumerTypes::VideoLayers& newTargetLayers) const { MS_TRACE(); // Start with no layers. newTargetLayers.Reset(); auto nowMs = this->shared->GetTimeMs(); int16_t spatialLayer{ 0 }; if (!this->producerRtpStream) { goto done; } if (this->producerRtpStream->GetScore() == 0u) { goto done; } for (; spatialLayer < this->producerRtpStream->GetSpatialLayers(); ++spatialLayer) { // If this is higher than current spatial layer and we moved to to current spatial // layer due to BWE limitations, check how much it has elapsed since then. if (nowMs - this->lastBweDowngradeAtMs < BweDowngradeConservativeMs) { if (newTargetLayers.spatial > -1 && spatialLayer > this->encodingContext->GetCurrentSpatialLayer()) { continue; } } if (!this->producerRtpStream->GetSpatialLayerBitrate(nowMs, spatialLayer)) { continue; } newTargetLayers.spatial = spatialLayer; // If this is the preferred or higher spatial layer and has bitrate, // take it and exit. if (spatialLayer >= this->preferredLayers.spatial) { break; } } if (newTargetLayers.spatial != -1) { if (newTargetLayers.spatial == this->preferredLayers.spatial) { newTargetLayers.temporal = this->preferredLayers.temporal; } else if (newTargetLayers.spatial < this->preferredLayers.spatial) { newTargetLayers.temporal = static_cast(this->rtpStream->GetTemporalLayers() - 1); } else { newTargetLayers.temporal = 0; } } done: // Return true if any target layer changed. return newTargetLayers != this->encodingContext->GetTargetLayers(); } void SvcConsumer::UpdateTargetLayers(int16_t newTargetSpatialLayer, int16_t newTargetTemporalLayer) { MS_TRACE(); if (newTargetSpatialLayer == -1) { // Unset current and target layers. this->encodingContext->SetTargetSpatialLayer(-1); this->encodingContext->SetCurrentSpatialLayer(-1); this->encodingContext->SetTargetTemporalLayer(-1); this->encodingContext->SetCurrentTemporalLayer(-1); MS_DEBUG_TAG( svc, "target layers changed [spatial:-1, temporal:-1, consumerId:%s]", this->id.c_str()); EmitLayersChange(); return; } this->encodingContext->SetTargetSpatialLayer(newTargetSpatialLayer); this->encodingContext->SetTargetTemporalLayer(newTargetTemporalLayer); MS_DEBUG_TAG( svc, "target layers changed [spatial:%" PRIi16 ", temporal:%" PRIi16 ", consumerId:%s]", newTargetSpatialLayer, newTargetTemporalLayer, this->id.c_str()); // Target spatial layer has changed. if (newTargetSpatialLayer != this->encodingContext->GetCurrentSpatialLayer()) { // In K-SVC always ask for a keyframe when changing target spatial layer. if (this->encodingContext->IsKSvc()) { MS_DEBUG_DEV("K-SVC: requesting keyframe to target spatial change"); RequestKeyFrame(); } // In full SVC just ask for a keyframe when upgrading target spatial layer. // NOTE: This is because nobody implements RTCP LRR yet. else if (newTargetSpatialLayer > this->encodingContext->GetCurrentSpatialLayer()) { MS_DEBUG_DEV("full SVC: requesting keyframe to target spatial upgrade"); RequestKeyFrame(); } } } void SvcConsumer::StorePacketInTargetLayerRetransmissionBuffer( RTC::RTP::Packet* packet, RTC::RTP::SharedPacket& sharedPacket) { MS_TRACE(); MS_DEBUG_DEV( "storing packet in target layer retransmission buffer [ssrc:%" PRIu32 ", seq:%" PRIu16 ", ts:%" PRIu32 "]", packet->GetSsrc(), packet->GetSequenceNumber(), packet->GetTimestamp()); // Store original packet into the buffer. Only clone once and only if // necessary. if (!sharedPacket.HasPacket()) { sharedPacket.Assign(packet); } // Assert that, if sharedPacket was already filled, both packet and // sharedPacket are the very same RTP packet. else { sharedPacket.AssertSamePacket(packet); } this->targetLayerRetransmissionBuffer[packet->GetSequenceNumber()] = sharedPacket; if (this->targetLayerRetransmissionBuffer.size() > TargetLayerRetransmissionBufferSize) { this->targetLayerRetransmissionBuffer.erase(this->targetLayerRetransmissionBuffer.begin()); } } void SvcConsumer::EmitScore() const { MS_TRACE(); auto scoreOffset = FillBufferScore(this->shared->GetChannelNotifier()->GetBufferBuilder()); auto notificationOffset = FBS::Consumer::CreateScoreNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), scoreOffset); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_SCORE, FBS::Notification::Body::Consumer_ScoreNotification, notificationOffset); } void SvcConsumer::EmitLayersChange() const { MS_TRACE(); MS_DEBUG_DEV( "current layers changed to [spatial:%" PRIi16 ", temporal:%" PRIi16 ", consumerId:%s]", this->encodingContext->GetCurrentSpatialLayer(), this->encodingContext->GetCurrentTemporalLayer(), this->id.c_str()); flatbuffers::Offset layersOffset; if (this->encodingContext->GetCurrentSpatialLayer() >= 0) { layersOffset = FBS::Consumer::CreateConsumerLayers( this->shared->GetChannelNotifier()->GetBufferBuilder(), this->encodingContext->GetCurrentSpatialLayer(), this->encodingContext->GetCurrentTemporalLayer()); } auto notificationOffset = FBS::Consumer::CreateLayersChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), layersOffset); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::CONSUMER_LAYERS_CHANGE, FBS::Notification::Body::Consumer_LayersChangeNotification, notificationOffset); } void SvcConsumer::OnRtpStreamScore( RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) { MS_TRACE(); // Emit the score event. EmitScore(); if (IsActive()) { // Just check target layers if our bitrate is not externally managed. // NOTE: For now this is a bit useless since, when locally managed, we do // not check the Consumer score at all. if (!this->externallyManagedBitrate) { MayChangeLayers(); } } } void SvcConsumer::OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet) { MS_TRACE(); this->listener->OnConsumerRetransmitRtpPacket(this, packet); // May emit 'trace' event. EmitTraceEventRtpAndKeyFrameTypes(packet, this->rtpStream->HasRtx()); } } // namespace RTC ================================================ FILE: worker/src/RTC/TcpConnection.cpp ================================================ #define MS_CLASS "RTC::TcpConnection" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/TcpConnection.hpp" #include "Logger.hpp" #include "Utils.hpp" #include // std::memmove(), std::memcpy() namespace RTC { /* Static. */ static constexpr size_t ReadBufferSize{ 65536 }; // NOTE: Buffer must be 4-byte aligned since RTP/RTCP/STUN packet parsing casts // it to structs (e.g. RTP::Packet::FixedHeader) that require 4-byte alignment. // Without this, accessing multi-byte fields would be undefined behavior on // strict-alignment architectures. alignas(4) static thread_local uint8_t ReadBuffer[ReadBufferSize]; /* Instance methods. */ TcpConnection::TcpConnection(Listener* listener, size_t bufferSize) : ::TcpConnectionHandle::TcpConnectionHandle(bufferSize), listener(listener) { MS_TRACE(); } TcpConnection::~TcpConnection() { MS_TRACE(); } void TcpConnection::UserOnTcpConnectionRead() { MS_TRACE(); MS_DEBUG_DEV( "data received [local:%s :%" PRIu16 ", remote:%s :%" PRIu16 "]", GetLocalIp().c_str(), GetLocalPort(), GetPeerIp().c_str(), GetPeerPort()); /* * Framing RFC 4571 * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * --------------------------------------------------------------- * | LENGTH | STUN / DTLS / RTP / RTCP | * --------------------------------------------------------------- * * A 16-bit unsigned integer LENGTH field, coded in network byte order * (big-endian), begins the frame. If LENGTH is non-zero, an RTP or * RTCP packet follows the LENGTH field. The value coded in the LENGTH * field MUST equal the number of octets in the RTP or RTCP packet. * Zero is a valid value for LENGTH, and it codes the null packet. */ // Be ready to parse more than a single frame in a single TCP chunk. while (true) { // We may receive multiple packets in the same TCP chunk. If one of them is // a DTLS Close Alert this would be closed (Close() called) so we cannot call // our listeners anymore. if (IsClosed()) { return; } const size_t dataLen = this->bufferDataLen - this->frameStart; size_t packetLen; if (dataLen >= 2) { packetLen = size_t{ Utils::Byte::Get2Bytes(this->buffer + this->frameStart, 0) }; } // We have packetLen bytes. if (dataLen >= 2 && dataLen >= 2 + packetLen) { const uint8_t* packet = this->buffer + this->frameStart + 2; // Update received bytes and notify the listener. if (packetLen != 0) { // Copy the received packet into the static buffer so it can be expanded // later. std::memcpy(ReadBuffer, packet, packetLen); this->listener->OnTcpConnectionPacketReceived(this, ReadBuffer, packetLen, ReadBufferSize); } // If there is no more space available in the buffer and that is because // the latest parsed frame filled it, then empty the full buffer. if ((this->frameStart + 2 + packetLen) == this->bufferSize) { MS_DEBUG_DEV("no more space in the buffer, emptying the buffer data"); this->frameStart = 0; this->bufferDataLen = 0; } // If there is still space in the buffer, set the beginning of the next // frame to the next position after the parsed frame. else { this->frameStart += 2 + packetLen; } // If there is more data in the buffer after the parsed frame then // parse again. Otherwise break here and wait for more data. if (this->bufferDataLen > this->frameStart) { MS_DEBUG_DEV("there is more data after the parsed frame, continue parsing"); continue; } break; } // Incomplete packet. // Check if the buffer is full. if (this->bufferDataLen == this->bufferSize) { // First case: the incomplete frame does not begin at position 0 of // the buffer, so move the frame to the position 0. if (this->frameStart != 0) { MS_DEBUG_DEV( "no more space in the buffer, moving parsed bytes to the beginning of " "the buffer and wait for more data"); std::memmove( this->buffer, this->buffer + this->frameStart, this->bufferSize - this->frameStart); this->bufferDataLen = this->bufferSize - this->frameStart; this->frameStart = 0; } // Second case: the incomplete frame begins at position 0 of the buffer. // The frame is too big. else { MS_WARN_DEV( "no more space in the buffer for the unfinished frame being parsed, closing the " "connection"); ErrorReceiving(); // And exit fast since we are supposed to be deallocated. return; } } // The buffer is not full. else { MS_DEBUG_DEV("frame not finished yet, waiting for more data"); } // Exit the parsing loop. break; } } void TcpConnection::Send(const uint8_t* data, size_t len, ::TcpConnectionHandle::onSendCallback* cb) { MS_TRACE(); // Write according to Framing RFC 4571. uint8_t frameLen[2]; Utils::Byte::Set2Bytes(frameLen, 0, len); ::TcpConnectionHandle::Write(frameLen, 2, data, len, cb); } } // namespace RTC ================================================ FILE: worker/src/RTC/TcpServer.cpp ================================================ #define MS_CLASS "RTC::TcpServer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/TcpServer.hpp" #include "Logger.hpp" #include "RTC/PortManager.hpp" #include namespace RTC { /* Static. */ static constexpr size_t TcpConnectionBufferSize{ 65536 }; /* Instance methods. */ TcpServer::TcpServer( Listener* listener, RTC::TcpConnection::Listener* connListener, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags) : // This may throw. ::TcpServerHandle::TcpServerHandle(RTC::PortManager::BindTcp(ip, port, flags)), listener(listener), connListener(connListener), fixedPort(true) { MS_TRACE(); } TcpServer::TcpServer( Listener* listener, RTC::TcpConnection::Listener* connListener, std::string& ip, uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, uint64_t& portRangeHash) : // This may throw. ::TcpServerHandle::TcpServerHandle( RTC::PortManager::BindTcp(ip, minPort, maxPort, flags, portRangeHash)), listener(listener), connListener(connListener) { MS_TRACE(); this->portRangeHash = portRangeHash; } TcpServer::~TcpServer() { MS_TRACE(); if (!this->fixedPort) { RTC::PortManager::Unbind(this->portRangeHash, this->localPort); } } void TcpServer::UserOnTcpConnectionAlloc() { MS_TRACE(); // Allocate a new RTC::TcpConnection for the TcpServer to handle it. auto* connection = new RTC::TcpConnection(this->connListener, TcpConnectionBufferSize); // Accept it. AcceptTcpConnection(connection); } void TcpServer::UserOnTcpConnectionClosed(::TcpConnectionHandle* connection) { MS_TRACE(); this->listener->OnRtcTcpConnectionClosed(this, static_cast(connection)); } } // namespace RTC ================================================ FILE: worker/src/RTC/Transport.cpp ================================================ #define MS_CLASS "RTC::Transport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/Transport.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "Utils.hpp" #ifdef MS_LIBURING_SUPPORTED #include "DepLibUring.hpp" #endif #include "FBS/transport.h" #include "RTC/BweType.hpp" #include "RTC/Consts.hpp" #include "RTC/PipeConsumer.hpp" #include "RTC/RTCP/FeedbackPs.hpp" #include "RTC/RTCP/FeedbackPsAfb.hpp" #include "RTC/RTCP/FeedbackPsRemb.hpp" #include "RTC/RTCP/FeedbackRtpNack.hpp" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include "RTC/RTCP/XrDelaySinceLastRr.hpp" #include "RTC/RtpDictionaries.hpp" #include "RTC/SCTP/association/Association.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SimpleConsumer.hpp" #include "RTC/SimulcastConsumer.hpp" #include "RTC/SvcConsumer.hpp" #ifdef MS_RTC_LOGGER_RTP #include "RTC/RtcLogger.hpp" #endif #include // webrtc::RtpPacketSendInfo #include // std::ostream_iterator #include // std::multimap namespace RTC { static const size_t DefaultSctpSendBufferSize{ 262144 }; // 2^18 bytes. static const size_t MaxSctpSendBufferSize{ 268435456 }; // 2^28 bytes. /* Instance methods. */ Transport::Transport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::Transport::Options* options) : id(id), shared(shared), listener(listener), recvRtpTransmission(shared, /*ignorePaddingOnlyPackets*/ false), sendRtpTransmission(shared, /*ignorePaddingOnlyPackets*/ false), recvRtxTransmission(shared, /*ignorePaddingOnlyPackets*/ false, 1000u), sendRtxTransmission(shared, /*ignorePaddingOnlyPackets*/ false, 1000u), sendProbationTransmission(shared, /*ignorePaddingOnlyPackets*/ false, 100u) { MS_TRACE(); if (options->direct()) { this->direct = true; if (auto maxMessageSize = options->maxMessageSize(); maxMessageSize.has_value()) { this->maxMessageSize = maxMessageSize.value(); } } if ( auto initialAvailableOutgoingBitrate = options->initialAvailableOutgoingBitrate(); initialAvailableOutgoingBitrate.has_value()) { this->initialAvailableOutgoingBitrate = initialAvailableOutgoingBitrate.value(); } if (options->enableSctp()) { if (this->direct) { MS_THROW_TYPE_ERROR("cannot enable SCTP in a direct Transport"); } // numSctpStreams is mandatory. if (!flatbuffers::IsFieldPresent(options, FBS::Transport::Options::VT_NUMSCTPSTREAMS)) { MS_THROW_TYPE_ERROR("numSctpStreams missing"); } // maxSctpMessageSize is mandatory. if (!flatbuffers::IsFieldPresent(options, FBS::Transport::Options::VT_MAXSCTPMESSAGESIZE)) { MS_THROW_TYPE_ERROR("maxSctpMessageSize missing"); } this->maxMessageSize = options->maxSctpMessageSize(); size_t sctpSendBufferSize; // sctpSendBufferSize is optional. if (flatbuffers::IsFieldPresent(options, FBS::Transport::Options::VT_SCTPSENDBUFFERSIZE)) { if (options->sctpSendBufferSize() > MaxSctpSendBufferSize) { MS_THROW_TYPE_ERROR("wrong sctpSendBufferSize (maximum value exceeded)"); } sctpSendBufferSize = options->sctpSendBufferSize(); } else { sctpSendBufferSize = DefaultSctpSendBufferSize; } if (Settings::configuration.useBuiltInSctpStack) { // TODO: SCTP: Many interesting options missing. // NOTE: When using the built-in SCTP stack, `numSctpStreams` given to the // transport is ignored. const RTC::SCTP::SctpOptions sctpOptions = { // TODO: SCTP: Sure? .maxSendMessageSize = this->maxMessageSize, .maxSendBufferSize = sctpSendBufferSize }; this->sctpAssociation = std::make_unique(sctpOptions, this, this->shared); } // TODO: SCTP: Remove once we only use built-in SCTP stack. else { // This may throw. this->oldSctpAssociation = new RTC::SctpAssociation( this, options->numSctpStreams()->os(), options->numSctpStreams()->mis(), this->maxMessageSize, sctpSendBufferSize, options->isDataChannel()); } } // Create the RTCP timer. this->rtcpTimer = this->shared->CreateTimer(this); } Transport::~Transport() { MS_TRACE(); // The destructor must delete and clear everything silently. // Delete all Producers. for (auto& kv : this->mapProducers) { auto* producer = kv.second; delete producer; } this->mapProducers.clear(); // Delete all Consumers. for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; delete consumer; } this->mapConsumers.clear(); this->mapSsrcConsumer.clear(); this->mapRtxSsrcConsumer.clear(); // Delete all DataProducers. for (auto& kv : this->mapDataProducers) { auto* dataProducer = kv.second; delete dataProducer; } this->mapDataProducers.clear(); // Delete all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; delete dataConsumer; } this->mapDataConsumers.clear(); if (Settings::configuration.useBuiltInSctpStack) { // NOTE: When using the built-in SCTP stack we don't do anything here since // the `SetDestroying()` method has already been called by the Transport // subclass and it closed the SCTP Association. // // NOTE: We cannot do it here in the destructor because here we are no longer // the Transport subclass but Transport parent (this is how the destruction // chain works in C++). } // TODO: SCTP: Remove once we only use built-in SCTP stack. else { // Delete SCTP association. // TODO: SCTP: Remove once we only use built-in SCTP stack. delete this->oldSctpAssociation; this->oldSctpAssociation = nullptr; } // Delete the RTCP timer. delete this->rtcpTimer; this->rtcpTimer = nullptr; } void Transport::CloseProducersAndConsumers() { MS_TRACE(); // This method is called by the Router and must notify him about all Producers // and Consumers that we are gonna close. // // The caller is supposed to delete this Transport instance after calling // this method. // Close all Producers. for (auto& kv : this->mapProducers) { auto* producer = kv.second; // Notify the listener. this->listener->OnTransportProducerClosed(this, producer); delete producer; } this->mapProducers.clear(); // Delete all Consumers. for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; // Notify the listener. this->listener->OnTransportConsumerClosed(this, consumer); delete consumer; } this->mapConsumers.clear(); this->mapSsrcConsumer.clear(); this->mapRtxSsrcConsumer.clear(); // Delete all DataProducers. for (auto& kv : this->mapDataProducers) { auto* dataProducer = kv.second; // Notify the listener. this->listener->OnTransportDataProducerClosed(this, dataProducer); delete dataProducer; } this->mapDataProducers.clear(); // Delete all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; // Notify the listener. this->listener->OnTransportDataConsumerClosed(this, dataConsumer); delete dataConsumer; } this->mapDataConsumers.clear(); } void Transport::ListenServerClosed() { MS_TRACE(); // Ask our parent Router to close/delete us. this->listener->OnTransportListenServerClosed(this); } flatbuffers::Offset Transport::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add producerIds. std::vector> producerIds; for (const auto& kv : this->mapProducers) { const auto& producerId = kv.first; producerIds.emplace_back(builder.CreateString(producerId)); } // Add consumerIds. std::vector> consumerIds; for (const auto& kv : this->mapConsumers) { const auto& consumerId = kv.first; consumerIds.emplace_back(builder.CreateString(consumerId)); } // Add mapSsrcConsumerId. std::vector> mapSsrcConsumerId; for (const auto& kv : this->mapSsrcConsumer) { auto ssrc = kv.first; auto* consumer = kv.second; mapSsrcConsumerId.emplace_back( FBS::Common::CreateUint32StringDirect(builder, ssrc, consumer->id.c_str())); } // Add mapRtxSsrcConsumerId. std::vector> mapRtxSsrcConsumerId; for (const auto& kv : this->mapRtxSsrcConsumer) { auto ssrc = kv.first; auto* consumer = kv.second; mapRtxSsrcConsumerId.emplace_back( FBS::Common::CreateUint32StringDirect(builder, ssrc, consumer->id.c_str())); } // Add dataProducerIds. std::vector> dataProducerIds; for (const auto& kv : this->mapDataProducers) { const auto& dataProducerId = kv.first; dataProducerIds.emplace_back(builder.CreateString(dataProducerId)); } // Add dataConsumerIds. std::vector> dataConsumerIds; for (const auto& kv : this->mapDataConsumers) { const auto& dataConsumerId = kv.first; dataConsumerIds.emplace_back(builder.CreateString(dataConsumerId)); } // Add headerExtensionIds. auto recvRtpHeaderExtensions = FBS::Transport::CreateRecvRtpHeaderExtensions( builder, this->recvRtpHeaderExtensionIds.mid != 0u ? flatbuffers::Optional(this->recvRtpHeaderExtensionIds.mid) : flatbuffers::nullopt, this->recvRtpHeaderExtensionIds.rid != 0u ? flatbuffers::Optional(this->recvRtpHeaderExtensionIds.rid) : flatbuffers::nullopt, this->recvRtpHeaderExtensionIds.rrid != 0u ? flatbuffers::Optional(this->recvRtpHeaderExtensionIds.rrid) : flatbuffers::nullopt, this->recvRtpHeaderExtensionIds.absSendTime != 0u ? flatbuffers::Optional(this->recvRtpHeaderExtensionIds.absSendTime) : flatbuffers::nullopt, this->recvRtpHeaderExtensionIds.transportWideCc01 != 0u ? flatbuffers::Optional(this->recvRtpHeaderExtensionIds.transportWideCc01) : flatbuffers::nullopt); auto rtpListenerOffset = this->rtpListener.FillBuffer(builder); // Add sctpParameters. flatbuffers::Offset sctpParameters; // Add sctpState. FBS::SctpAssociation::SctpState sctpState{ FBS::SctpAssociation::SctpState::NEW }; // Add sctpListener. flatbuffers::Offset sctpListener; if (Settings::configuration.useBuiltInSctpStack && this->sctpAssociation) { // Add sctpParameters. sctpParameters = this->sctpAssociation->FillBuffer(builder); // NOTE: There is never permanent FAILED state. switch (this->sctpAssociation->GetAssociationState()) { case RTC::SCTP::Types::AssociationState::NEW: { sctpState = FBS::SctpAssociation::SctpState::NEW; break; } case RTC::SCTP::Types::AssociationState::CONNECTING: { sctpState = FBS::SctpAssociation::SctpState::CONNECTING; break; } case RTC::SCTP::Types::AssociationState::CONNECTED: { sctpState = FBS::SctpAssociation::SctpState::CONNECTED; break; } case RTC::SCTP::Types::AssociationState::SHUTTING_DOWN: case RTC::SCTP::Types::AssociationState::CLOSED: { sctpState = FBS::SctpAssociation::SctpState::CLOSED; break; } } sctpListener = this->sctpListener.FillBuffer(builder); } // TODO: SCTP: Remove once we only use built-in SCTP stack. else if (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation) { // Add sctpParameters. sctpParameters = this->oldSctpAssociation->FillBuffer(builder); switch (this->oldSctpAssociation->GetState()) { case RTC::SctpAssociation::SctpState::NEW: { sctpState = FBS::SctpAssociation::SctpState::NEW; break; } case RTC::SctpAssociation::SctpState::CONNECTING: { sctpState = FBS::SctpAssociation::SctpState::CONNECTING; break; } case RTC::SctpAssociation::SctpState::CONNECTED: { sctpState = FBS::SctpAssociation::SctpState::CONNECTED; break; } case RTC::SctpAssociation::SctpState::FAILED: { sctpState = FBS::SctpAssociation::SctpState::FAILED; break; } case RTC::SctpAssociation::SctpState::CLOSED: { sctpState = FBS::SctpAssociation::SctpState::CLOSED; break; } } sctpListener = this->sctpListener.FillBuffer(builder); } // Add traceEventTypes. std::vector traceEventTypes; if (this->traceEventTypes.probation) { traceEventTypes.emplace_back(FBS::Transport::TraceEventType::PROBATION); } if (this->traceEventTypes.bwe) { traceEventTypes.emplace_back(FBS::Transport::TraceEventType::BWE); } return FBS::Transport::CreateDumpDirect( builder, this->id.c_str(), this->direct, &producerIds, &consumerIds, &mapSsrcConsumerId, &mapRtxSsrcConsumerId, &dataProducerIds, &dataConsumerIds, recvRtpHeaderExtensions, rtpListenerOffset, this->maxMessageSize, sctpParameters, (this->sctpAssociation || this->oldSctpAssociation) ? flatbuffers::Optional(sctpState) : flatbuffers::nullopt, sctpListener, &traceEventTypes); } flatbuffers::Offset Transport::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); auto nowMs = this->shared->GetTimeMs(); // Add sctpState. FBS::SctpAssociation::SctpState sctpState{ FBS::SctpAssociation::SctpState::NEW }; // Add sctpState. if (Settings::configuration.useBuiltInSctpStack && this->sctpAssociation) { // NOTE: There is never permanent FAILED state. switch (this->sctpAssociation->GetAssociationState()) { case RTC::SCTP::Types::AssociationState::NEW: { sctpState = FBS::SctpAssociation::SctpState::NEW; break; } case RTC::SCTP::Types::AssociationState::CONNECTING: { sctpState = FBS::SctpAssociation::SctpState::CONNECTING; break; } case RTC::SCTP::Types::AssociationState::CONNECTED: { sctpState = FBS::SctpAssociation::SctpState::CONNECTED; break; } case RTC::SCTP::Types::AssociationState::SHUTTING_DOWN: case RTC::SCTP::Types::AssociationState::CLOSED: { sctpState = FBS::SctpAssociation::SctpState::CLOSED; break; } } } // TODO: SCTP: Remove once we only use built-in SCTP stack. else if (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation) { switch (this->oldSctpAssociation->GetState()) { case RTC::SctpAssociation::SctpState::NEW: { sctpState = FBS::SctpAssociation::SctpState::NEW; break; } case RTC::SctpAssociation::SctpState::CONNECTING: { sctpState = FBS::SctpAssociation::SctpState::CONNECTING; break; } case RTC::SctpAssociation::SctpState::CONNECTED: { sctpState = FBS::SctpAssociation::SctpState::CONNECTED; break; } case RTC::SctpAssociation::SctpState::FAILED: { sctpState = FBS::SctpAssociation::SctpState::FAILED; break; } case RTC::SctpAssociation::SctpState::CLOSED: { sctpState = FBS::SctpAssociation::SctpState::CLOSED; break; } } } return FBS::Transport::CreateStatsDirect( builder, // transportId. this->id.c_str(), // timestamp. nowMs, // sctpState. (this->sctpAssociation || this->oldSctpAssociation) ? flatbuffers::Optional(sctpState) : flatbuffers::nullopt, // bytesReceived. this->recvTransmission.GetBytes(), // recvBitrate. this->recvTransmission.GetRate(nowMs), // bytesSent. this->sendTransmission.GetBytes(), // sendBitrate. this->sendTransmission.GetRate(nowMs), // rtpBytesReceived. this->recvRtpTransmission.GetBytes(), // rtpRecvBitrate. this->recvRtpTransmission.GetBitrate(nowMs), // rtpBytesSent. this->sendRtpTransmission.GetBytes(), // rtpSendBitrate. this->sendRtpTransmission.GetBitrate(nowMs), // rtxBytesReceived. this->recvRtxTransmission.GetBytes(), // rtxRecvBitrate. this->recvRtxTransmission.GetBitrate(nowMs), // rtxBytesSent. this->sendRtxTransmission.GetBytes(), // rtxSendBitrate. this->sendRtxTransmission.GetBitrate(nowMs), // probationBytesSent. this->sendProbationTransmission.GetBytes(), // probationSendBitrate. this->sendProbationTransmission.GetBitrate(nowMs), // availableOutgoingBitrate. this->tccClient ? flatbuffers::Optional(this->tccClient->GetAvailableBitrate()) : flatbuffers::nullopt, // availableIncomingBitrate. this->tccServer ? flatbuffers::Optional(this->tccServer->GetAvailableBitrate()) : flatbuffers::nullopt, // maxIncomingBitrate. this->maxIncomingBitrate ? flatbuffers::Optional(this->maxIncomingBitrate) : flatbuffers::nullopt, // maxOutgoingBitrate. this->maxOutgoingBitrate ? flatbuffers::Optional(this->maxOutgoingBitrate) : flatbuffers::nullopt, // minOutgoingBitrate. this->minOutgoingBitrate ? flatbuffers::Optional(this->minOutgoingBitrate) : flatbuffers::nullopt, // rtpPacketLossReceived. this->tccServer ? flatbuffers::Optional(this->tccServer->GetPacketLoss()) : flatbuffers::nullopt, // rtpPacketLossSent. this->tccClient ? flatbuffers::Optional(this->tccClient->GetPacketLoss()) : flatbuffers::nullopt); } void Transport::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::TRANSPORT_SET_MAX_INCOMING_BITRATE: { const auto* body = request->data->body_as(); this->maxIncomingBitrate = body->maxIncomingBitrate(); MS_DEBUG_TAG(bwe, "maximum incoming bitrate set to %" PRIu32, this->maxIncomingBitrate); request->Accept(); if (this->tccServer) { this->tccServer->SetMaxIncomingBitrate(this->maxIncomingBitrate); } break; } case Channel::ChannelRequest::Method::TRANSPORT_SET_MAX_OUTGOING_BITRATE: { const auto* body = request->data->body_as(); const uint32_t bitrate = body->maxOutgoingBitrate(); if (bitrate > 0u && bitrate < RTC::TransportCongestionControlMinOutgoingBitrate) { MS_THROW_TYPE_ERROR( "bitrate must be >= %" PRIu32 " or 0 (unlimited)", RTC::TransportCongestionControlMinOutgoingBitrate); } else if (bitrate > 0u && bitrate < this->minOutgoingBitrate) { MS_THROW_TYPE_ERROR( "bitrate must be >= current min outgoing bitrate (%" PRIu32 ") or 0 (unlimited)", this->minOutgoingBitrate); } if (this->tccClient) { // NOTE: This may throw so don't update things before calling this // method. this->tccClient->SetMaxOutgoingBitrate(bitrate); this->maxOutgoingBitrate = bitrate; MS_DEBUG_TAG(bwe, "maximum outgoing bitrate set to %" PRIu32, this->maxOutgoingBitrate); ComputeOutgoingDesiredBitrate(); } else { this->maxOutgoingBitrate = bitrate; } request->Accept(); break; } case Channel::ChannelRequest::Method::TRANSPORT_SET_MIN_OUTGOING_BITRATE: { const auto* body = request->data->body_as(); const uint32_t bitrate = body->minOutgoingBitrate(); if (bitrate > 0u && bitrate < RTC::TransportCongestionControlMinOutgoingBitrate) { MS_THROW_TYPE_ERROR( "bitrate must be >= %" PRIu32 " or 0 (unlimited)", RTC::TransportCongestionControlMinOutgoingBitrate); } else if (bitrate > 0u && this->maxOutgoingBitrate > 0 && bitrate > this->maxOutgoingBitrate) { MS_THROW_TYPE_ERROR( "bitrate must be <= current max outgoing bitrate (%" PRIu32 ") or 0 (unlimited)", this->maxOutgoingBitrate); } if (this->tccClient) { // NOTE: This may throw so don't update things before calling this // method. this->tccClient->SetMinOutgoingBitrate(bitrate); this->minOutgoingBitrate = bitrate; MS_DEBUG_TAG(bwe, "minimum outgoing bitrate set to %" PRIu32, this->minOutgoingBitrate); ComputeOutgoingDesiredBitrate(); } else { this->minOutgoingBitrate = bitrate; } request->Accept(); break; } case Channel::ChannelRequest::Method::TRANSPORT_PRODUCE: { const auto* body = request->data->body_as(); auto producerId = body->producerId()->str(); if (this->mapProducers.find(producerId) != this->mapProducers.end()) { MS_THROW_ERROR("a Producer with same producerId already exists"); } // This may throw. auto* producer = new RTC::Producer(this->shared, producerId, this, body); // Insert the Producer into the RtpListener. // This may throw. If so, delete the Producer and throw. try { this->rtpListener.AddProducer(producer); } catch (const MediaSoupError& error) { delete producer; throw; } // Notify the listener. // This may throw if a Producer with same id already exists. try { this->listener->OnTransportNewProducer(this, producer); } catch (const MediaSoupError& error) { this->rtpListener.RemoveProducer(producer); delete producer; throw; } // Insert into the map. this->mapProducers[producerId] = producer; MS_DEBUG_DEV("Producer created [producerId:%s]", producerId.c_str()); // Take the transport related RTP header extensions of the Producer and // add them to the Transport. // NOTE: Producer::GetRtpHeaderExtensionIds() returns the original // header extension ids of the Producer (and not their mapped values). const auto& producerRtpHeaderExtensionIds = producer->GetRtpHeaderExtensionIds(); if (producerRtpHeaderExtensionIds.mid != 0u) { this->recvRtpHeaderExtensionIds.mid = producerRtpHeaderExtensionIds.mid; } if (producerRtpHeaderExtensionIds.rid != 0u) { this->recvRtpHeaderExtensionIds.rid = producerRtpHeaderExtensionIds.rid; } if (producerRtpHeaderExtensionIds.rrid != 0u) { this->recvRtpHeaderExtensionIds.rrid = producerRtpHeaderExtensionIds.rrid; } if (producerRtpHeaderExtensionIds.absSendTime != 0u) { this->recvRtpHeaderExtensionIds.absSendTime = producerRtpHeaderExtensionIds.absSendTime; } if (producerRtpHeaderExtensionIds.transportWideCc01 != 0u) { this->recvRtpHeaderExtensionIds.transportWideCc01 = producerRtpHeaderExtensionIds.transportWideCc01; } if (producerRtpHeaderExtensionIds.dependencyDescriptor != 0u) { this->recvRtpHeaderExtensionIds.dependencyDescriptor = producerRtpHeaderExtensionIds.dependencyDescriptor; } // Create status response. auto responseOffset = FBS::Transport::CreateProduceResponse( request->GetBufferBuilder(), FBS::RtpParameters::Type(producer->GetType())); request->Accept(FBS::Response::Body::Transport_ProduceResponse, responseOffset); // Check if TransportCongestionControlServer or REMB server must be // created. const auto& rtpHeaderExtensionIds = producer->GetRtpHeaderExtensionIds(); const auto& codecs = producer->GetRtpParameters().codecs; // Set TransportCongestionControlServer. if (!this->tccServer) { bool createTccServer{ false }; RTC::BweType bweType; // Use transport-cc if: // - there is transport-wide-cc-01 RTP header extension, and // - there is "transport-cc" in codecs RTCP feedback. // if ( rtpHeaderExtensionIds.transportWideCc01 != 0u && std::any_of( codecs.begin(), codecs.end(), [](const RTC::RtpCodecParameters& codec) { return std::any_of( codec.rtcpFeedback.begin(), codec.rtcpFeedback.end(), [](const RTC::RtcpFeedback& fb) { return fb.type == "transport-cc"; }); })) { MS_DEBUG_TAG(bwe, "enabling TransportCongestionControlServer with transport-cc"); createTccServer = true; bweType = RTC::BweType::TRANSPORT_CC; } // Use REMB if: // - there is abs-send-time RTP header extension, and // - there is "remb" in codecs RTCP feedback. // else if ( rtpHeaderExtensionIds.absSendTime != 0u && std::any_of( codecs.begin(), codecs.end(), [](const RTC::RtpCodecParameters& codec) { return std::any_of( codec.rtcpFeedback.begin(), codec.rtcpFeedback.end(), [](const RTC::RtcpFeedback& fb) { return fb.type == "goog-remb"; }); })) { MS_DEBUG_TAG(bwe, "enabling TransportCongestionControlServer with REMB"); createTccServer = true; bweType = RTC::BweType::REMB; } if (createTccServer) { this->tccServer = std::make_shared( this, this->shared, bweType, RTC::Consts::RtcpPacketMaxSize); if (this->maxIncomingBitrate != 0u) { this->tccServer->SetMaxIncomingBitrate(this->maxIncomingBitrate); } if (IsConnected()) { this->tccServer->TransportConnected(); } } } break; } case Channel::ChannelRequest::Method::TRANSPORT_CONSUME: { const auto* body = request->data->body_as(); const std::string producerId = body->producerId()->str(); const std::string consumerId = body->consumerId()->str(); if (this->mapConsumers.find(consumerId) != this->mapConsumers.end()) { MS_THROW_ERROR("a Consumer with same consumerId already exists"); } auto type = RTC::RtpParameters::Type(body->type()); RTC::Consumer* consumer{ nullptr }; switch (type) { case RTC::RtpParameters::Type::SIMPLE: { // This may throw. consumer = new RTC::SimpleConsumer(this->shared, consumerId, producerId, this, body); break; } case RTC::RtpParameters::Type::SIMULCAST: { // This may throw. consumer = new RTC::SimulcastConsumer(this->shared, consumerId, producerId, this, body); break; } case RTC::RtpParameters::Type::SVC: { // This may throw. consumer = new RTC::SvcConsumer(this->shared, consumerId, producerId, this, body); break; } case RTC::RtpParameters::Type::PIPE: { // This may throw. consumer = new RTC::PipeConsumer(this->shared, consumerId, producerId, this, body); break; } } // Notify the listener. // This may throw if no Producer is found. try { this->listener->OnTransportNewConsumer(this, consumer, producerId); } catch (const MediaSoupError& error) { delete consumer; throw; } // Insert into the maps. this->mapConsumers[consumerId] = consumer; for (auto ssrc : consumer->GetMediaSsrcs()) { this->mapSsrcConsumer[ssrc] = consumer; } for (auto ssrc : consumer->GetRtxSsrcs()) { this->mapRtxSsrcConsumer[ssrc] = consumer; } MS_DEBUG_DEV( "Consumer created [consumerId:%s, producerId:%s]", consumerId.c_str(), producerId.c_str()); flatbuffers::Offset preferredLayersOffset; auto preferredLayers = consumer->GetPreferredLayers(); if (preferredLayers.spatial > -1 && preferredLayers.temporal > -1) { const flatbuffers::Optional preferredTemporalLayer{ preferredLayers.temporal }; preferredLayersOffset = FBS::Consumer::CreateConsumerLayers( request->GetBufferBuilder(), preferredLayers.spatial, preferredTemporalLayer); } auto scoreOffset = consumer->FillBufferScore(request->GetBufferBuilder()); auto responseOffset = FBS::Transport::CreateConsumeResponse( request->GetBufferBuilder(), consumer->IsPaused(), consumer->IsProducerPaused(), scoreOffset, preferredLayersOffset); request->Accept(FBS::Response::Body::Transport_ConsumeResponse, responseOffset); // Check if Transport Congestion Control client must be created. const auto& rtpHeaderExtensionIds = consumer->GetRtpHeaderExtensionIds(); const auto& codecs = consumer->GetRtpParameters().codecs; // Set TransportCongestionControlClient. if (!this->tccClient) { bool createTccClient{ false }; RTC::BweType bweType; // Use transport-cc if: // - it's a video Consumer, and // - there is transport-wide-cc-01 RTP header extension, and // - there is "transport-cc" in codecs RTCP feedback. // if ( consumer->GetKind() == RTC::Media::Kind::VIDEO && rtpHeaderExtensionIds.transportWideCc01 != 0u && std::any_of( codecs.begin(), codecs.end(), [](const RTC::RtpCodecParameters& codec) { return std::any_of( codec.rtcpFeedback.begin(), codec.rtcpFeedback.end(), [](const RTC::RtcpFeedback& fb) { return fb.type == "transport-cc"; }); })) { MS_DEBUG_TAG(bwe, "enabling TransportCongestionControlClient with transport-cc"); createTccClient = true; bweType = RTC::BweType::TRANSPORT_CC; } // Use REMB if: // - it's a video Consumer, and // - there is abs-send-time RTP header extension, and // - there is "remb" in codecs RTCP feedback. // else if ( consumer->GetKind() == RTC::Media::Kind::VIDEO && rtpHeaderExtensionIds.absSendTime != 0u && std::any_of( codecs.begin(), codecs.end(), [](const RTC::RtpCodecParameters& codec) { return std::any_of( codec.rtcpFeedback.begin(), codec.rtcpFeedback.end(), [](const RTC::RtcpFeedback& fb) { return fb.type == "goog-remb"; }); })) { MS_DEBUG_TAG(bwe, "enabling TransportCongestionControlClient with REMB"); createTccClient = true; bweType = RTC::BweType::REMB; } if (createTccClient) { // Tell all the Consumers that we are gonna manage their bitrate. for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; consumer->SetExternallyManagedBitrate(); }; this->tccClient = std::make_shared( this, this->shared, bweType, this->initialAvailableOutgoingBitrate, this->maxOutgoingBitrate, this->minOutgoingBitrate); if (IsConnected()) { this->tccClient->TransportConnected(); } } } // If applicable, tell the new Consumer that we are gonna manage its // bitrate. if (this->tccClient) { consumer->SetExternallyManagedBitrate(); } #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR // Create SenderBandwidthEstimator if: // - not already created, // - it's a video Consumer, and // - there is transport-wide-cc-01 RTP header extension, and // - there is "transport-cc" in codecs RTCP feedback. // if ( !this->senderBwe && consumer->GetKind() == RTC::Media::Kind::VIDEO && rtpHeaderExtensionIds.transportWideCc01 != 0u && std::any_of( codecs.begin(), codecs.end(), [](const RTC::RtpCodecParameters& codec) { return std::any_of( codec.rtcpFeedback.begin(), codec.rtcpFeedback.end(), [](const RTC::RtcpFeedback& fb) { return fb.type == "transport-cc"; }); })) { MS_DEBUG_TAG(bwe, "enabling SenderBandwidthEstimator"); // Tell all the Consumers that we are gonna manage their bitrate. for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; consumer->SetExternallyManagedBitrate(); }; this->senderBwe = std::make_shared( this, this->shared, this->initialAvailableOutgoingBitrate); if (IsConnected()) { this->senderBwe->TransportConnected(); } } // If applicable, tell the new Consumer that we are gonna manage its // bitrate. if (this->senderBwe) { consumer->SetExternallyManagedBitrate(); } #endif if (IsConnected()) { consumer->TransportConnected(); } break; } case Channel::ChannelRequest::Method::TRANSPORT_PRODUCE_DATA: { // Early check. The Transport must support SCTP or be direct. if ( ((Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) || (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) && !this->direct) { MS_THROW_ERROR("SCTP not enabled and not a direct Transport"); } const auto* body = request->data->body_as(); auto dataProducerId = body->dataProducerId()->str(); // This may throw. CheckNoDataProducer(dataProducerId); // This may throw. auto* dataProducer = new RTC::DataProducer(this->shared, dataProducerId, this->maxMessageSize, this, body); // Verify the type of the DataProducer. switch (dataProducer->GetType()) { case RTC::DataProducer::Type::SCTP: { if ( (Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) || (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) { delete dataProducer; MS_THROW_TYPE_ERROR( "cannot create a DataProducer of type 'sctp', SCTP not enabled in this Transport"); ; } break; } case RTC::DataProducer::Type::DIRECT: { if (!this->direct) { delete dataProducer; MS_THROW_TYPE_ERROR( "cannot create a DataProducer of type 'direct', not a direct Transport"); ; } break; } } if (dataProducer->GetType() == RTC::DataProducer::Type::SCTP) { // Insert the DataProducer into the SctpListener. // This may throw. If so, delete the DataProducer and throw. try { this->sctpListener.AddDataProducer(dataProducer); } catch (const MediaSoupError& error) { delete dataProducer; throw; } } // Notify the listener. // This may throw if a DataProducer with same id already exists. try { this->listener->OnTransportNewDataProducer(this, dataProducer); } catch (const MediaSoupError& error) { if (dataProducer->GetType() == RTC::DataProducer::Type::SCTP) { this->sctpListener.RemoveDataProducer(dataProducer); } delete dataProducer; throw; } // Insert into the map. this->mapDataProducers[dataProducerId] = dataProducer; MS_DEBUG_DEV("DataProducer created [dataProducerId:%s]", dataProducerId.c_str()); auto dumpOffset = dataProducer->FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::DataProducer_DumpResponse, dumpOffset); if (dataProducer->GetType() == RTC::DataProducer::Type::SCTP) { // Tell to the SCTP association. if (Settings::configuration.useBuiltInSctpStack) { this->sctpAssociation->MayConnect(); } // TODO: SCTP: Remove once we only use built-in SCTP stack. else { this->oldSctpAssociation->HandleDataProducer(dataProducer); } } break; } case Channel::ChannelRequest::Method::TRANSPORT_CONSUME_DATA: { // Early check. The Transport must support SCTP or be direct. if ( ((Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) || (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) && !this->direct) { MS_THROW_ERROR("SCTP not enabled and not a direct Transport"); } const auto* body = request->data->body_as(); auto dataProducerId = body->dataProducerId()->str(); auto dataConsumerId = body->dataConsumerId()->str(); // This may throw. CheckNoDataConsumer(dataConsumerId); // This may throw. auto* dataConsumer = new RTC::DataConsumer( this->shared, dataConsumerId, dataProducerId, this, body, this->maxMessageSize); // Verify the type of the DataConsumer. switch (dataConsumer->GetType()) { case RTC::DataConsumer::Type::SCTP: { if ( (Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) || (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) { delete dataConsumer; MS_THROW_TYPE_ERROR( "cannot create a DataConsumer of type 'sctp', SCTP not enabled in this Transport"); ; } break; } case RTC::DataConsumer::Type::DIRECT: { if (!this->direct) { delete dataConsumer; MS_THROW_TYPE_ERROR( "cannot create a DataConsumer of type 'direct', not a direct Transport"); ; } break; } } // Notify the listener. // This may throw if no DataProducer is found. try { this->listener->OnTransportNewDataConsumer(this, dataConsumer, dataProducerId); } catch (const MediaSoupError& error) { delete dataConsumer; throw; } // Insert into the maps. this->mapDataConsumers[dataConsumerId] = dataConsumer; MS_DEBUG_DEV( "DataConsumer created [dataConsumerId:%s, dataProducerId:%s]", dataConsumerId.c_str(), dataProducerId.c_str()); auto dumpOffset = dataConsumer->FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::DataConsumer_DumpResponse, dumpOffset); if (IsConnected()) { dataConsumer->TransportConnected(); } if (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { if (Settings::configuration.useBuiltInSctpStack) { if (this->sctpAssociation->GetAssociationState() == RTC::SCTP::Types::AssociationState::CONNECTED) { // Tell to the DataConsumer. dataConsumer->SctpAssociationConnected(); } // Tell to the SCTP association. this->sctpAssociation->MayConnect(); } // TODO: SCTP: Remove once we only use built-in SCTP stack. else { if (this->oldSctpAssociation->GetState() == RTC::SctpAssociation::SctpState::CONNECTED) { // Tell to the DataConsumer. dataConsumer->SctpAssociationConnected(); } // Tell to the SCTP association. this->oldSctpAssociation->HandleDataConsumer(dataConsumer); } } break; } case Channel::ChannelRequest::Method::TRANSPORT_ENABLE_TRACE_EVENT: { const auto* body = request->data->body_as(); // Reset traceEventTypes. struct TraceEventTypes newTraceEventTypes; for (const auto& type : *body->events()) { switch (type) { case FBS::Transport::TraceEventType::PROBATION: { newTraceEventTypes.probation = true; break; } case FBS::Transport::TraceEventType::BWE: { newTraceEventTypes.bwe = true; break; } } } this->traceEventTypes = newTraceEventTypes; request->Accept(); break; } case Channel::ChannelRequest::Method::TRANSPORT_CLOSE_PRODUCER: { const auto* body = request->data->body_as(); // This may throw. RTC::Producer* producer = GetProducerById(body->producerId()->str()); // Remove it from the RtpListener. this->rtpListener.RemoveProducer(producer); // Remove it from the map. this->mapProducers.erase(producer->id); // Tell the child class to clear associated SSRCs. for (const auto& kv : producer->GetRtpStreams()) { auto* rtpStream = kv.first; RecvStreamClosed(rtpStream->GetSsrc()); if (rtpStream->HasRtx()) { RecvStreamClosed(rtpStream->GetRtxSsrc()); } } // Notify the listener. this->listener->OnTransportProducerClosed(this, producer); MS_DEBUG_DEV("Producer closed [producerId:%s]", producer->id.c_str()); // Delete it. delete producer; request->Accept(); break; } case Channel::ChannelRequest::Method::TRANSPORT_CLOSE_CONSUMER: { const auto* body = request->data->body_as(); // This may throw. RTC::Consumer* consumer = GetConsumerById(body->consumerId()->str()); // Remove it from the maps. this->mapConsumers.erase(consumer->id); for (auto ssrc : consumer->GetMediaSsrcs()) { this->mapSsrcConsumer.erase(ssrc); // Tell the child class to clear associated SSRCs. SendStreamClosed(ssrc); } for (auto ssrc : consumer->GetRtxSsrcs()) { this->mapRtxSsrcConsumer.erase(ssrc); // Tell the child class to clear associated SSRCs. SendStreamClosed(ssrc); } // Notify the listener. this->listener->OnTransportConsumerClosed(this, consumer); MS_DEBUG_DEV("Consumer closed [consumerId:%s]", consumer->id.c_str()); // Delete it. delete consumer; request->Accept(); // This may be the latest active Consumer with BWE. If so we have to stop // probation. if (this->tccClient) { ComputeOutgoingDesiredBitrate(/*forceBitrate*/ true); } break; } case Channel::ChannelRequest::Method::TRANSPORT_CLOSE_DATAPRODUCER: { if ( ((Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) || (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) && !this->direct) { MS_THROW_ERROR("cannot close DataProducer, SCTP not enabled and not a direct Transport"); } const auto* body = request->data->body_as(); // This may throw. RTC::DataProducer* dataProducer = GetDataProducerById(body->dataProducerId()->str()); if (dataProducer->GetType() == RTC::DataProducer::Type::SCTP) { // Remove it from the SctpListener. this->sctpListener.RemoveDataProducer(dataProducer); } // Remove it from the map. this->mapDataProducers.erase(dataProducer->id); // Notify the listener. this->listener->OnTransportDataProducerClosed(this, dataProducer); MS_DEBUG_DEV("DataProducer closed [dataProducerId:%s]", dataProducer->id.c_str()); if (dataProducer->GetType() == RTC::DataProducer::Type::SCTP) { if (Settings::configuration.useBuiltInSctpStack) { // TODO: SCTP } // TODO: SCTP: Remove once we only use built-in SCTP stack. else { // Tell the SctpAssociation so it can reset the SCTP stream. this->oldSctpAssociation->DataProducerClosed(dataProducer); } } // Delete it. delete dataProducer; request->Accept(); break; } case Channel::ChannelRequest::Method::TRANSPORT_CLOSE_DATACONSUMER: { if ( ((Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) || (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) && !this->direct) { MS_THROW_ERROR("cannot close DataConsumer, SCTP not enabled and not a direct Transport"); } const auto* body = request->data->body_as(); // This may throw. RTC::DataConsumer* dataConsumer = GetDataConsumerById(body->dataConsumerId()->str()); // Remove it from the maps. this->mapDataConsumers.erase(dataConsumer->id); // Notify the listener. this->listener->OnTransportDataConsumerClosed(this, dataConsumer); MS_DEBUG_DEV("DataConsumer closed [dataConsumerId:%s]", dataConsumer->id.c_str()); if (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { if (Settings::configuration.useBuiltInSctpStack) { // TODO: SCTP } // TODO: SCTP: Remove once we only use built-in SCTP stack. else { // Tell the SctpAssociation so it can reset the SCTP stream. this->oldSctpAssociation->DataConsumerClosed(dataConsumer); } } // Delete it. delete dataConsumer; request->Accept(); break; } default: { MS_THROW_ERROR("unknown method '%s'", request->methodCStr); } } return; switch (request->method) { default: { MS_ERROR("unknown method"); } } } void Transport::HandleNotification(Channel::ChannelNotification* notification) { MS_TRACE(); switch (notification->event) { default: { MS_ERROR("unknown event '%s'", notification->eventCStr); } } } void Transport::SetDestroying() { MS_TRACE(); if (Settings::configuration.useBuiltInSctpStack) { if (this->sctpAssociation) { // NOTE: We don't invoke `Shutdown()` but `Close()` in the SCTP Association // because at this point we are closing everything and we won't have any // chance to complete the SCTP SHUTDOWN + SHUTDOWN_ACK + SHUTDOWN_COMPLETE // dance, so we invoke `Close()` which just sends a SCTP ABORT. this->sctpAssociation->Close(); } } this->isDestroying = true; } void Transport::Connected() { MS_TRACE(); // Tell all Consumers. for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; consumer->TransportConnected(); } // Tell all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; dataConsumer->TransportConnected(); } // Tell the SctpAssociation. if (Settings::configuration.useBuiltInSctpStack && this->sctpAssociation) { this->sctpAssociation->MayConnect(); } // TODO: SCTP: Remove once we only use built-in SCTP stack. else if (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation) { this->oldSctpAssociation->TransportConnected(); } // Start the RTCP timer. this->rtcpTimer->Start(static_cast(RTC::RTCP::MaxVideoIntervalMs / 2)); // Tell the TransportCongestionControlClient. if (this->tccClient) { this->tccClient->TransportConnected(); } // Tell the TransportCongestionControlServer. if (this->tccServer) { this->tccServer->TransportConnected(); } #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR // Tell the SenderBandwidthEstimator. if (this->senderBwe) { this->senderBwe->TransportConnected(); } #endif } void Transport::Disconnected() { MS_TRACE(); // Tell all Consumers. for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; consumer->TransportDisconnected(); } // Tell all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; dataConsumer->TransportDisconnected(); } // TODO: SCTP: Remove once we only use built-in SCTP stack. // Tell the SctpAssociation. if (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation) { this->oldSctpAssociation->TransportDisconnected(); } // Stop the RTCP timer. this->rtcpTimer->Stop(); // Tell the TransportCongestionControlClient. if (this->tccClient) { this->tccClient->TransportDisconnected(); } // Tell the TransportCongestionControlServer. if (this->tccServer) { this->tccServer->TransportDisconnected(); } #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR // Tell the SenderBandwidthEstimator. if (this->senderBwe) { this->senderBwe->TransportDisconnected(); } #endif } void Transport::ReceiveRtpPacket(RTC::RTP::Packet* packet) { MS_TRACE(); #ifdef MS_RTC_LOGGER_RTP packet->logger.recvTransportId = this->id; #endif // Apply the Transport RTP header extension ids so the RTP listener can use // them. packet->AssignExtensionIds(this->recvRtpHeaderExtensionIds); auto nowMs = this->shared->GetTimeMs(); // Feed the TransportCongestionControlServer. if (this->tccServer) { this->tccServer->IncomingPacket(nowMs, packet); } // Get the associated Producer. RTC::Producer* producer = this->rtpListener.GetProducer(packet); if (!producer) { #ifdef MS_RTC_LOGGER_RTP packet->logger.Discarded(RTC::RtcLogger::RtpPacket::DiscardReason::PRODUCER_NOT_FOUND); #endif MS_WARN_TAG( rtp, "no suitable Producer for received RTP packet [ssrc:%" PRIu32 ", payloadType:%" PRIu8 "]", packet->GetSsrc(), packet->GetPayloadType()); // Tell the child class to remove this SSRC. RecvStreamClosed(packet->GetSsrc()); delete packet; return; } // MS_DEBUG_DEV( // "RTP packet received [ssrc:%" PRIu32 ", payloadType:%" PRIu8 ", producerId:%s]", // packet->GetSsrc(), // packet->GetPayloadType(), // producer->id.c_str()); // Pass the RTP packet to the corresponding Producer. auto result = producer->ReceiveRtpPacket(packet); switch (result) { case RTC::Producer::ReceiveRtpPacketResult::MEDIA: { this->recvRtpTransmission.Update(packet); break; } case RTC::Producer::ReceiveRtpPacketResult::RETRANSMISSION: { this->recvRtxTransmission.Update(packet); break; } case RTC::Producer::ReceiveRtpPacketResult::DISCARDED: { // Tell the child class to remove this SSRC. RecvStreamClosed(packet->GetSsrc()); break; } default:; } delete packet; } void Transport::ReceiveRtcpPacket(RTC::RTCP::Packet* packet) { MS_TRACE(); // Handle each RTCP packet. while (packet) { HandleRtcpPacket(packet); auto* previousPacket = packet; packet = packet->GetNext(); delete previousPacket; } } void Transport::ReceiveSctpData(const uint8_t* data, size_t len) { MS_TRACE(); if ( (Settings::configuration.useBuiltInSctpStack && !this->sctpAssociation) || (!Settings::configuration.useBuiltInSctpStack && !this->oldSctpAssociation)) { MS_DEBUG_TAG(sctp, "ignoring SCTP packet (SCTP not enabled)"); return; } // Pass it to the SctpAssociation. if (Settings::configuration.useBuiltInSctpStack) { this->sctpAssociation->ReceiveSctpData(data, len); } else { this->oldSctpAssociation->ProcessSctpData(data, len); } } // TODO: SCTP: Remove when we have our own SCTP stack running. void Transport::SendSctpMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb) { MS_TRACE(); MS_ASSERT( !Settings::configuration.useBuiltInSctpStack, "cannot use this method when built-in SCTP stack is enabled"); if (!this->oldSctpAssociation) { MS_THROW_ERROR("SCTP not enabled"); if (cb) { (*cb)(false, false); delete cb; } return; } this->oldSctpAssociation->SendSctpMessage(dataConsumer, msg, len, ppid, cb); } void Transport::SendSctpMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) { MS_TRACE(); // NOTE: The `message` must already have its `streamId` pointing to the same // as in the `dataConsumer` if its type is "sctp", or 0 otherwise. MS_ASSERT( Settings::configuration.useBuiltInSctpStack, "cannot use this method when built-in SCTP stack is not enabled"); if (!this->sctpAssociation) { MS_THROW_ERROR("SCTP not enabled"); if (cb) { (*cb)(false, false); delete cb; } return; } const auto& sctpStreamParameters = dataConsumer->GetSctpStreamParameters(); const RTC::SCTP::SendMessageOptions sendMessageOptions{ .unordered = !sctpStreamParameters.ordered, .lifetimeMs = sctpStreamParameters.ordered ? std::nullopt : std::optional(sctpStreamParameters.maxPacketLifeTime), .maxRetransmissions = sctpStreamParameters.ordered ? std::nullopt : std::optional(sctpStreamParameters.maxRetransmits), // NOTE: We don't set `lifecyleId` in production. }; const auto sendStatus = this->sctpAssociation->SendMessage(std::move(message), sendMessageOptions); switch (sendStatus) { case RTC::SCTP::Types::SendMessageStatus::SUCCESS: { if (cb) { (*cb)(true, /*sctpSendBufferFull*/ false); } break; } case RTC::SCTP::Types::SendMessageStatus::ERROR_RESOURCE_EXHAUSTION: { const auto sendStatusStringView = RTC::SCTP::Types::SendMessageStatusToString(sendStatus); MS_WARN_TAG( sctp, "failed to send SCTP message [sendStatus:%.*s]", static_cast(sendStatusStringView.size()), sendStatusStringView.data()); if (cb) { (*cb)(false, /*sctpSendBufferFull*/ true); } // TODO: SCTP: We don't want this probably since we have events in Association // for this. dataConsumer->SctpAssociationSendBufferFull(); break; } default: { const auto sendStatusStringView = RTC::SCTP::Types::SendMessageStatusToString(sendStatus); MS_WARN_TAG( sctp, "failed to send SCTP message [sendStatus:%.*s]", static_cast(sendStatusStringView.size()), sendStatusStringView.data()); if (cb) { (*cb)(false, /*sctpSendBufferFull*/ false); } break; } } delete cb; } void Transport::CheckNoDataProducer(const std::string& dataProducerId) const { if (this->mapDataProducers.find(dataProducerId) != this->mapDataProducers.end()) { MS_THROW_ERROR("a DataProducer with same dataProducerId already exists"); } } void Transport::CheckNoDataConsumer(const std::string& dataConsumerId) const { MS_TRACE(); if (this->mapDataConsumers.find(dataConsumerId) != this->mapDataConsumers.end()) { MS_THROW_ERROR("a DataConsumer with same dataConsumerId already exists"); } } RTC::Producer* Transport::GetProducerById(const std::string& producerId) const { MS_TRACE(); auto it = this->mapProducers.find(producerId); if (it == this->mapProducers.end()) { MS_THROW_ERROR("Producer not found"); } return it->second; } RTC::Consumer* Transport::GetConsumerById(const std::string& consumerId) const { MS_TRACE(); auto it = this->mapConsumers.find(consumerId); if (it == this->mapConsumers.end()) { MS_THROW_ERROR("Consumer not found"); } return it->second; } inline RTC::Consumer* Transport::GetConsumerByMediaSsrc(uint32_t ssrc) const { MS_TRACE(); auto mapSsrcConsumerIt = this->mapSsrcConsumer.find(ssrc); if (mapSsrcConsumerIt == this->mapSsrcConsumer.end()) { return nullptr; } auto* consumer = mapSsrcConsumerIt->second; return consumer; } inline RTC::Consumer* Transport::GetConsumerByRtxSsrc(uint32_t ssrc) const { MS_TRACE(); auto mapRtxSsrcConsumerIt = this->mapRtxSsrcConsumer.find(ssrc); if (mapRtxSsrcConsumerIt == this->mapRtxSsrcConsumer.end()) { return nullptr; } auto* consumer = mapRtxSsrcConsumerIt->second; return consumer; } RTC::DataProducer* Transport::GetDataProducerById(const std::string& dataProducerId) const { MS_TRACE(); auto it = this->mapDataProducers.find(dataProducerId); if (it == this->mapDataProducers.end()) { MS_THROW_ERROR("DataProducer not found"); } return it->second; } RTC::DataConsumer* Transport::GetDataConsumerById(const std::string& dataConsumerId) const { MS_TRACE(); auto it = this->mapDataConsumers.find(dataConsumerId); if (it == this->mapDataConsumers.end()) { MS_THROW_ERROR("DataConsumer not found"); } return it->second; } void Transport::HandleRtcpPacket(RTC::RTCP::Packet* packet) { MS_TRACE(); switch (packet->GetType()) { case RTC::RTCP::Type::RR: { auto* rr = static_cast(packet); for (auto it = rr->Begin(); it != rr->End(); ++it) { auto& report = *it; auto* consumer = GetConsumerByMediaSsrc(report->GetSsrc()); if (!consumer) { // Special case for the RTP probator. if (report->GetSsrc() == RTC::RTP::ProbationGenerator::Ssrc) { continue; } // Special case for (unused) RTCP-RR from the RTX stream. if (GetConsumerByRtxSsrc(report->GetSsrc()) != nullptr) { continue; } MS_DEBUG_TAG( rtcp, "no Consumer found for received Receiver Report [ssrc:%" PRIu32 "]", report->GetSsrc()); continue; } consumer->ReceiveRtcpReceiverReport(report); } if (this->tccClient && !this->mapConsumers.empty()) { float rtt = 0; // Retrieve the RTT from the first active consumer. for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; if (consumer->IsActive()) { rtt = consumer->GetRtt(); break; } } this->tccClient->ReceiveRtcpReceiverReport(rr, rtt, this->shared->GetTimeMsInt64()); } break; } case RTC::RTCP::Type::PSFB: { auto* feedback = static_cast(packet); switch (feedback->GetMessageType()) { case RTC::RTCP::FeedbackPs::MessageType::PLI: { auto* consumer = GetConsumerByMediaSsrc(feedback->GetMediaSsrc()); if (feedback->GetMediaSsrc() == RTC::RTP::ProbationGenerator::Ssrc) { break; } else if (!consumer) { MS_DEBUG_TAG( rtcp, "no Consumer found for received PLI Feedback packet " "[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 "]", feedback->GetSenderSsrc(), feedback->GetMediaSsrc()); break; } MS_DEBUG_TAG( rtcp, "PLI received, requesting key frame for Consumer " "[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 "]", feedback->GetSenderSsrc(), feedback->GetMediaSsrc()); consumer->ReceiveKeyFrameRequest( RTC::RTCP::FeedbackPs::MessageType::PLI, feedback->GetMediaSsrc()); break; } case RTC::RTCP::FeedbackPs::MessageType::FIR: { // Must iterate FIR items. auto* fir = static_cast(packet); for (auto it = fir->Begin(); it != fir->End(); ++it) { auto& item = *it; auto* consumer = GetConsumerByMediaSsrc(item->GetSsrc()); if (item->GetSsrc() == RTC::RTP::ProbationGenerator::Ssrc) { continue; } else if (!consumer) { MS_DEBUG_TAG( rtcp, "no Consumer found for received FIR Feedback packet " "[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 ", item ssrc:%" PRIu32 "]", feedback->GetSenderSsrc(), feedback->GetMediaSsrc(), item->GetSsrc()); continue; } MS_DEBUG_TAG( rtcp, "FIR received, requesting key frame for Consumer " "[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 ", item ssrc:%" PRIu32 "]", feedback->GetSenderSsrc(), feedback->GetMediaSsrc(), item->GetSsrc()); consumer->ReceiveKeyFrameRequest(feedback->GetMessageType(), item->GetSsrc()); } break; } case RTC::RTCP::FeedbackPs::MessageType::AFB: { auto* afb = static_cast(feedback); // Store REMB info. if (afb->GetApplication() == RTC::RTCP::FeedbackPsAfbPacket::Application::REMB) { auto* remb = static_cast(afb); // Pass it to the TCC client. if (this->tccClient && this->tccClient->GetBweType() == RTC::BweType::REMB) { this->tccClient->ReceiveEstimatedBitrate(remb->GetBitrate()); } break; } else { MS_DEBUG_TAG( rtcp, "ignoring unsupported %s Feedback PS AFB packet " "[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 "]", RTC::RTCP::FeedbackPsPacket::MessageTypeToString(feedback->GetMessageType()).c_str(), feedback->GetSenderSsrc(), feedback->GetMediaSsrc()); break; } } default: { MS_DEBUG_TAG( rtcp, "ignoring unsupported %s Feedback packet " "[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 "]", RTC::RTCP::FeedbackPsPacket::MessageTypeToString(feedback->GetMessageType()).c_str(), feedback->GetSenderSsrc(), feedback->GetMediaSsrc()); } } break; } case RTC::RTCP::Type::RTPFB: { auto* feedback = static_cast(packet); auto* consumer = GetConsumerByMediaSsrc(feedback->GetMediaSsrc()); // If no Consumer is found and this is not a Transport Feedback for the // probation SSRC or any Consumer RTX SSRC, ignore it. if ( !consumer && feedback->GetMessageType() != RTC::RTCP::FeedbackRtp::MessageType::TCC && (feedback->GetMediaSsrc() != RTC::RTP::ProbationGenerator::Ssrc || !GetConsumerByRtxSsrc(feedback->GetMediaSsrc()))) { MS_DEBUG_TAG( rtcp, "no Consumer found for received Feedback packet " "[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 "]", feedback->GetSenderSsrc(), feedback->GetMediaSsrc()); break; } switch (feedback->GetMessageType()) { case RTC::RTCP::FeedbackRtp::MessageType::NACK: { if (!consumer) { MS_DEBUG_TAG( rtcp, "no Consumer found for received NACK Feedback packet " "[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 "]", feedback->GetSenderSsrc(), feedback->GetMediaSsrc()); break; } auto* nackPacket = static_cast(packet); consumer->ReceiveNack(nackPacket); break; } case RTC::RTCP::FeedbackRtp::MessageType::TCC: { auto* feedback = static_cast(packet); if (this->tccClient) { this->tccClient->ReceiveRtcpTransportFeedback(feedback); } #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR // Pass it to the SenderBandwidthEstimator client. if (this->senderBwe) { this->senderBwe->ReceiveRtcpTransportFeedback(feedback); } #endif break; } default: { MS_DEBUG_TAG( rtcp, "ignoring unsupported %s Feedback packet " "[sender ssrc:%" PRIu32 ", media ssrc:%" PRIu32 "]", RTC::RTCP::FeedbackRtpPacket::MessageTypeToString(feedback->GetMessageType()).c_str(), feedback->GetSenderSsrc(), feedback->GetMediaSsrc()); } } break; } case RTC::RTCP::Type::SR: { auto* sr = static_cast(packet); // Even if Sender Report packet can only contains one report. for (auto it = sr->Begin(); it != sr->End(); ++it) { auto& report = *it; auto* producer = this->rtpListener.GetProducer(report->GetSsrc()); if (!producer) { MS_DEBUG_TAG( rtcp, "no Producer found for received Sender Report [ssrc:%" PRIu32 "]", report->GetSsrc()); continue; } producer->ReceiveRtcpSenderReport(report); } break; } case RTC::RTCP::Type::SDES: { // According to RFC 3550 section 6.1 "a CNAME item MUST be included in // each compound RTCP packet". So this is true even for compound // packets sent by endpoints that are not sending any RTP stream to us // (thus chunks in such a SDES will have an SSCR does not match with // any Producer created in this Transport). // Therefore, and given that we do nothing with SDES, just ignore them. break; } case RTC::RTCP::Type::BYE: { MS_DEBUG_TAG(rtcp, "ignoring received RTCP BYE"); break; } case RTC::RTCP::Type::XR: { auto* xr = static_cast(packet); for (auto it = xr->Begin(); it != xr->End(); ++it) { auto& report = *it; switch (report->GetType()) { case RTC::RTCP::ExtendedReportBlock::Type::DLRR: { auto* dlrr = static_cast(report); for (auto it2 = dlrr->Begin(); it2 != dlrr->End(); ++it2) { auto& ssrcInfo = *it2; // SSRC should be filled in the sub-block. if (ssrcInfo->GetSsrc() == 0) { ssrcInfo->SetSsrc(xr->GetSsrc()); } auto* producer = this->rtpListener.GetProducer(ssrcInfo->GetSsrc()); if (!producer) { MS_DEBUG_TAG( rtcp, "no Producer found for received Sender Extended Report [ssrc:%" PRIu32 "]", ssrcInfo->GetSsrc()); continue; } producer->ReceiveRtcpXrDelaySinceLastRr(ssrcInfo); } break; } case RTC::RTCP::ExtendedReportBlock::Type::RRT: { auto* rrt = static_cast(report); for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; consumer->ReceiveRtcpXrReceiverReferenceTime(rrt); } break; } default:; } } break; } default: { MS_DEBUG_TAG( rtcp, "unhandled RTCP type received [type:%" PRIu8 "]", static_cast(packet->GetType())); } } } void Transport::SendRtcp(uint64_t nowMs) { MS_TRACE(); std::unique_ptr packet{ new RTC::RTCP::CompoundPacket() }; #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Activate liburing usage. DepLibUring::SetActive(); } #endif for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; auto rtcpAdded = consumer->GetRtcp(packet.get(), nowMs); // RTCP data couldn't be added because the Compound packet is full. // Send the RTCP compound packet and request for RTCP again. if (!rtcpAdded) { SendRtcpCompoundPacket(packet.get()); // Create a new compount packet. packet.reset(new RTC::RTCP::CompoundPacket()); // Retrieve the RTCP again. consumer->GetRtcp(packet.get(), nowMs); } } for (auto& kv : this->mapProducers) { auto* producer = kv.second; auto rtcpAdded = producer->GetRtcp(packet.get(), nowMs); // RTCP data couldn't be added because the Compound packet is full. // Send the RTCP compound packet and request for RTCP again. if (!rtcpAdded) { SendRtcpCompoundPacket(packet.get()); // Create a new compount packet. packet.reset(new RTC::RTCP::CompoundPacket()); // Retrieve the RTCP again. producer->GetRtcp(packet.get(), nowMs); } } // Send the RTCP compound packet if there is any sender or receiver report. if (packet->GetReceiverReportCount() > 0u || packet->GetSenderReportCount() > 0u) { SendRtcpCompoundPacket(packet.get()); } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Submit all prepared submission entries. DepLibUring::Submit(); } #endif } void Transport::DistributeAvailableOutgoingBitrate() { MS_TRACE(); MS_ASSERT(this->tccClient, "no TransportCongestionClient"); std::multimap multimapPriorityConsumer; // Fill the map with Consumers and their priority (if > 0). for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; auto priority = consumer->GetBitratePriority(); if (priority > 0u) { multimapPriorityConsumer.emplace(priority, consumer); } } // Nobody wants bitrate. Exit. if (multimapPriorityConsumer.empty()) { return; } bool baseAllocation = true; uint32_t availableBitrate = this->tccClient->GetAvailableBitrate(); this->tccClient->RescheduleNextAvailableBitrateEvent(); MS_DEBUG_DEV("before layer-by-layer iterations [availableBitrate:%" PRIu32 "]", availableBitrate); // Redistribute the available bitrate by allowing Consumers to increase // layer by layer. Initially try to spread the bitrate across all // consumers. Then allocate the excess bitrate to Consumers starting // with the highest priorty. while (availableBitrate > 0u) { auto previousAvailableBitrate = availableBitrate; for (auto it = multimapPriorityConsumer.rbegin(); it != multimapPriorityConsumer.rend(); ++it) { auto priority = it->first; auto* consumer = it->second; auto bweType = this->tccClient->GetBweType(); // NOLINTNEXTLINE(bugprone-too-small-loop-variable) for (uint8_t i{ 1u }; i <= (baseAllocation ? 1u : priority); ++i) { uint32_t usedBitrate{ 0u }; const bool considerLoss = (bweType == RTC::BweType::REMB); usedBitrate = consumer->IncreaseLayer(availableBitrate, considerLoss); MS_ASSERT(usedBitrate <= availableBitrate, "Consumer used more layer bitrate than given"); availableBitrate -= usedBitrate; // Exit the loop fast if used bitrate is 0. if (usedBitrate == 0u) { break; } } } // If no Consumer used bitrate, exit the loop. if (availableBitrate == previousAvailableBitrate) { break; } baseAllocation = false; } MS_DEBUG_DEV("after layer-by-layer iterations [availableBitrate:%" PRIu32 "]", availableBitrate); // Finally instruct Consumers to apply their computed layers. for (auto it = multimapPriorityConsumer.rbegin(); it != multimapPriorityConsumer.rend(); ++it) { auto* consumer = it->second; consumer->ApplyLayers(); } } void Transport::ComputeOutgoingDesiredBitrate(bool forceBitrate) { MS_TRACE(); MS_ASSERT(this->tccClient, "no TransportCongestionClient"); uint32_t totalDesiredBitrate{ 0u }; for (auto& kv : this->mapConsumers) { auto* consumer = kv.second; auto desiredBitrate = consumer->GetDesiredBitrate(); totalDesiredBitrate += desiredBitrate; } MS_DEBUG_DEV("total desired bitrate: %" PRIu32, totalDesiredBitrate); this->tccClient->SetDesiredBitrate(totalDesiredBitrate, forceBitrate); } inline void Transport::EmitTraceEventProbationType(RTC::RTP::Packet* /*packet*/) const { MS_TRACE(); if (!this->traceEventTypes.probation) { return; } // TODO: Missing trace info (RTP packet dump). auto notification = FBS::Transport::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Transport::TraceEventType::PROBATION, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_OUT); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_TRACE, FBS::Notification::Body::Transport_TraceNotification, notification); } inline void Transport::EmitTraceEventBweType( RTC::TransportCongestionControlClient::Bitrates& bitrates) const { MS_TRACE(); if (!this->traceEventTypes.bwe) { return; } auto traceInfo = FBS::Transport::CreateBweTraceInfo( this->shared->GetChannelNotifier()->GetBufferBuilder(), this->tccClient->GetBweType() == RTC::BweType::TRANSPORT_CC ? FBS::Transport::BweType::TRANSPORT_CC : FBS::Transport::BweType::REMB, bitrates.desiredBitrate, bitrates.effectiveDesiredBitrate, bitrates.minBitrate, bitrates.maxBitrate, bitrates.startBitrate, bitrates.maxPaddingBitrate, bitrates.availableBitrate); auto notification = FBS::Transport::CreateTraceNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::Transport::TraceEventType::BWE, this->shared->GetTimeMs(), FBS::Common::TraceDirection::DIRECTION_OUT, FBS::Transport::TraceInfo::BweTraceInfo, traceInfo.Union()); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_TRACE, FBS::Notification::Body::Transport_TraceNotification, notification); } void Transport::OnProducerPaused(RTC::Producer* producer) { MS_TRACE(); this->listener->OnTransportProducerPaused(this, producer); } void Transport::OnProducerResumed(RTC::Producer* producer) { MS_TRACE(); this->listener->OnTransportProducerResumed(this, producer); } void Transport::OnProducerNewRtpStream( RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint32_t mappedSsrc) { MS_TRACE(); this->listener->OnTransportProducerNewRtpStream(this, producer, rtpStream, mappedSsrc); } void Transport::OnProducerRtpStreamScore( RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, uint8_t score, uint8_t previousScore) { MS_TRACE(); this->listener->OnTransportProducerRtpStreamScore(this, producer, rtpStream, score, previousScore); } void Transport::OnProducerRtcpSenderReport( RTC::Producer* producer, RTC::RTP::RtpStreamRecv* rtpStream, bool first) { MS_TRACE(); this->listener->OnTransportProducerRtcpSenderReport(this, producer, rtpStream, first); } void Transport::OnProducerRtpPacketReceived(RTC::Producer* producer, RTC::RTP::Packet* packet) { MS_TRACE(); this->listener->OnTransportProducerRtpPacketReceived(this, producer, packet); } void Transport::OnProducerSendRtcpPacket(RTC::Producer* /*producer*/, RTC::RTCP::Packet* packet) { MS_TRACE(); SendRtcpPacket(packet); } void Transport::OnProducerNeedWorstRemoteFractionLost( RTC::Producer* producer, uint32_t mappedSsrc, uint8_t& worstRemoteFractionLost) { MS_TRACE(); this->listener->OnTransportNeedWorstRemoteFractionLost( this, producer, mappedSsrc, worstRemoteFractionLost); } void Transport::OnConsumerSendRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) { MS_TRACE(); #ifdef MS_RTC_LOGGER_RTP packet->logger.sendTransportId = this->id; packet->logger.Sent(); #endif // Update abs-send-time if present. packet->UpdateAbsSendTime(this->shared->GetTimeMs()); // Update transport wide sequence number if present. if ( this->tccClient && this->tccClient->GetBweType() == RTC::BweType::TRANSPORT_CC && packet->UpdateTransportWideCc01(this->transportWideCcSeq + 1)) { this->transportWideCcSeq++; webrtc::RtpPacketSendInfo packetInfo; packetInfo.ssrc = packet->GetSsrc(); packetInfo.transport_sequence_number = this->transportWideCcSeq; packetInfo.has_rtp_sequence_number = true; packetInfo.rtp_sequence_number = packet->GetSequenceNumber(); packetInfo.length = packet->GetLength(); packetInfo.pacing_info = this->tccClient->GetPacingInfo(); // Indicate the pacer (and prober) that a packet is to be sent. this->tccClient->InsertPacket(packetInfo); // When using WebRtcServer, the lifecycle of a RTC::UdpSocket maybe longer // than WebRtcTransport so there is a chance for the send callback to be // invoked *after* the WebRtcTransport has been closed (freed). To avoid // invalid memory access we need to use weak_ptr. Same applies in other // send callbacks. const std::weak_ptr tccClientWeakPtr(this->tccClient); auto* shared = this->shared; #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR std::weak_ptr senderBweWeakPtr(this->senderBwe); RTC::SenderBandwidthEstimator::SentInfo sentInfo; sentInfo.wideSeq = this->transportWideCcSeq; sentInfo.size = packet->GetLength(); sentInfo.sendingAtMs = this->shared->GetTimeMs(); const auto* cb = new onSendCallback( [tccClientWeakPtr, shared, packetInfo, senderBweWeakPtr, sentInfo](bool sent) mutable { if (sent) { auto tccClient = tccClientWeakPtr.lock(); if (tccClient) { tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64()); } auto senderBwe = senderBweWeakPtr.lock(); if (senderBwe) { sentInfo.sentAtMs = shared->GetTimeMs(); senderBwe->RtpPacketSent(sentInfo); } } }); SendRtpPacket(consumer, packet, cb); #else const auto* cb = new onSendCallback( [tccClientWeakPtr, shared, packetInfo](bool sent) { if (sent) { auto tccClient = tccClientWeakPtr.lock(); if (tccClient) { tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64()); } } }); SendRtpPacket(consumer, packet, cb); #endif } else { SendRtpPacket(consumer, packet); } this->sendRtpTransmission.Update(packet); } void Transport::OnConsumerRetransmitRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) { MS_TRACE(); // Update abs-send-time if present. packet->UpdateAbsSendTime(this->shared->GetTimeMs()); // Update transport wide sequence number if present. if ( this->tccClient && this->tccClient->GetBweType() == RTC::BweType::TRANSPORT_CC && packet->UpdateTransportWideCc01(this->transportWideCcSeq + 1)) { this->transportWideCcSeq++; webrtc::RtpPacketSendInfo packetInfo; packetInfo.ssrc = packet->GetSsrc(); packetInfo.transport_sequence_number = this->transportWideCcSeq; packetInfo.has_rtp_sequence_number = true; packetInfo.rtp_sequence_number = packet->GetSequenceNumber(); packetInfo.length = packet->GetLength(); packetInfo.pacing_info = this->tccClient->GetPacingInfo(); // Indicate the pacer (and prober) that a packet is to be sent. this->tccClient->InsertPacket(packetInfo); const std::weak_ptr tccClientWeakPtr(this->tccClient); auto* shared = this->shared; #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR std::weak_ptr senderBweWeakPtr = this->senderBwe; RTC::SenderBandwidthEstimator::SentInfo sentInfo; sentInfo.wideSeq = this->transportWideCcSeq; sentInfo.size = packet->GetLength(); sentInfo.sendingAtMs = this->shared->GetTimeMs(); const auto* cb = new onSendCallback( [tccClientWeakPtr, shared, packetInfo, senderBweWeakPtr, sentInfo](bool sent) mutable { if (sent) { auto tccClient = tccClientWeakPtr.lock(); if (tccClient) { tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64()); } auto senderBwe = senderBweWeakPtr.lock(); if (senderBwe) { sentInfo.sentAtMs = shared->GetTimeMs(); senderBwe->RtpPacketSent(sentInfo); } } }); SendRtpPacket(consumer, packet, cb); #else const auto* cb = new onSendCallback( [tccClientWeakPtr, shared, packetInfo](bool sent) { if (sent) { auto tccClient = tccClientWeakPtr.lock(); if (tccClient) { tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64()); } } }); SendRtpPacket(consumer, packet, cb); #endif } else { SendRtpPacket(consumer, packet); } this->sendRtxTransmission.Update(packet); } void Transport::OnConsumerKeyFrameRequested(RTC::Consumer* consumer, uint32_t mappedSsrc) { MS_TRACE(); if (!IsConnected()) { MS_WARN_TAG(rtcp, "ignoring key rame request (transport not connected)"); return; } this->listener->OnTransportConsumerKeyFrameRequested(this, consumer, mappedSsrc); } void Transport::OnConsumerNeedBitrateChange(RTC::Consumer* /*consumer*/) { MS_TRACE(); MS_ASSERT(this->tccClient, "no TransportCongestionClient"); DistributeAvailableOutgoingBitrate(); ComputeOutgoingDesiredBitrate(); } void Transport::OnConsumerNeedZeroBitrate(RTC::Consumer* /*consumer*/) { MS_TRACE(); MS_ASSERT(this->tccClient, "no TransportCongestionClient"); DistributeAvailableOutgoingBitrate(); // This may be the latest active Consumer with BWE. If so we have to stop probation. ComputeOutgoingDesiredBitrate(/*forceBitrate*/ true); } void Transport::OnConsumerProducerClosed(RTC::Consumer* consumer) { MS_TRACE(); // Remove it from the maps. this->mapConsumers.erase(consumer->id); for (auto ssrc : consumer->GetMediaSsrcs()) { this->mapSsrcConsumer.erase(ssrc); // Tell the child class to clear associated SSRCs. SendStreamClosed(ssrc); } for (auto ssrc : consumer->GetRtxSsrcs()) { this->mapRtxSsrcConsumer.erase(ssrc); // Tell the child class to clear associated SSRCs. SendStreamClosed(ssrc); } // Notify the listener. this->listener->OnTransportConsumerProducerClosed(this, consumer); // Delete it. delete consumer; // This may be the latest active Consumer with BWE. If so we have to stop probation. if (this->tccClient) { ComputeOutgoingDesiredBitrate(/*forceBitrate*/ true); } } void Transport::OnDataProducerMessageReceived( RTC::DataProducer* dataProducer, const uint8_t* msg, size_t len, uint32_t ppid, std::vector& subchannels, std::optional requiredSubchannel) { MS_TRACE(); this->listener->OnTransportDataProducerMessageReceived( this, dataProducer, msg, len, ppid, subchannels, requiredSubchannel); } void Transport::OnDataProducerMessageReceived( RTC::DataProducer* dataProducer, RTC::SCTP::Message message, std::vector& subchannels, std::optional requiredSubchannel) { MS_TRACE(); this->listener->OnTransportDataProducerMessageReceived( this, dataProducer, std::move(message), subchannels, requiredSubchannel); } void Transport::OnDataProducerPaused(RTC::DataProducer* dataProducer) { MS_TRACE(); this->listener->OnTransportDataProducerPaused(this, dataProducer); } void Transport::OnDataProducerResumed(RTC::DataProducer* dataProducer) { MS_TRACE(); this->listener->OnTransportDataProducerResumed(this, dataProducer); } // TODO: SCTP: Remove once we only use built-in SCTP stack. void Transport::OnDataConsumerSendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb) { MS_TRACE(); SendMessage(dataConsumer, msg, len, ppid, cb); } void Transport::OnDataConsumerSendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) { MS_TRACE(); SendMessage(dataConsumer, std::move(message), cb); } void Transport::OnDataConsumerNeedBufferedAmount( RTC::DataConsumer* /*dataConsumer*/, uint32_t& bufferedAmount) { MS_TRACE(); if (Settings::configuration.useBuiltInSctpStack && this->sctpAssociation) { // TODO: SCTP: Let's see how to obtain `streamId` argument from the DataConsumer. // bufferedAmount = this->sctpAssociation->GetStreamBufferedAmount(streamId); } // TODO: SCTP: Remove once we only use built-in SCTP stack. else if (!Settings::configuration.useBuiltInSctpStack && this->oldSctpAssociation) { // NOTE: The underlaying SCTP association uses a common send buffer for all // data consumers, hence the value given by this method indicates the data // buffered for all data consumers in the transport. bufferedAmount = this->oldSctpAssociation->GetSctpBufferedAmount(); } else { bufferedAmount = 0; } } void Transport::OnDataConsumerDataProducerClosed(RTC::DataConsumer* dataConsumer) { MS_TRACE(); // Remove it from the maps. this->mapDataConsumers.erase(dataConsumer->id); // Notify the listener. this->listener->OnTransportDataConsumerDataProducerClosed(this, dataConsumer); if (Settings::configuration.useBuiltInSctpStack) { if (this->sctpAssociation && dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { // TODO: SCTP } } // TODO: SCTP: Remove once we only use built-in SCTP stack. else { if (this->oldSctpAssociation && dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { // Tell the SctpAssociation so it can reset the SCTP stream. this->oldSctpAssociation->DataConsumerClosed(dataConsumer); } } // Delete it. delete dataConsumer; } bool Transport::OnAssociationSendData(const uint8_t* data, size_t len) { MS_TRACE(); // Ignore if destroying. // NOTE: This is because when the child class (i.e. WebRtcTransport) is deleted, // its destructor is called first and then the parent Transport's destructor, // and we would end here calling SendData() which is an abstract method. if (this->isDestroying) { MS_WARN_DEV("ignoring sending data because Transport is being destroying"); return false; } return SendData(data, len); } void Transport::OnAssociationConnecting() { MS_TRACE(); // Notify the Node Transport. auto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::SctpAssociation::SctpState::CONNECTING); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE, FBS::Notification::Body::Transport_SctpStateChangeNotification, sctpStateChangeOffset); } void Transport::OnAssociationConnected() { MS_TRACE(); // Tell all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; if (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { dataConsumer->SctpAssociationConnected(); } } // Notify the Node Transport. auto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::SctpAssociation::SctpState::CONNECTED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE, FBS::Notification::Body::Transport_SctpStateChangeNotification, sctpStateChangeOffset); // TODO: SCTP: REMOVE MS_DUMP("---- SCTP association connected, dump():"); this->sctpAssociation->Dump(); } void Transport::OnAssociationFailed(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) { MS_TRACE(); const auto errorKindStringView = RTC::SCTP::Types::ErrorKindToString(errorKind); MS_WARN_TAG( sctp, "SCTP association failed [errorKind:%.*s, message:%.*s]", static_cast(errorKindStringView.size()), errorKindStringView.data(), static_cast(errorMessage.size()), errorMessage.data()); // Tell all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; if (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { dataConsumer->SctpAssociationClosed(); } } // Notify the Node Transport. auto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::SctpAssociation::SctpState::FAILED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE, FBS::Notification::Body::Transport_SctpStateChangeNotification, sctpStateChangeOffset); } void Transport::OnAssociationClosed(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) { MS_TRACE(); if (errorKind != RTC::SCTP::Types::ErrorKind::SUCCESS) { const auto errorKindStringView = RTC::SCTP::Types::ErrorKindToString(errorKind); MS_WARN_TAG( sctp, "SCTP association closed [errorKind:%.*s, message:%.*s]", static_cast(errorKindStringView.size()), errorKindStringView.data(), static_cast(errorMessage.size()), errorMessage.data()); } // Tell all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; if (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { dataConsumer->SctpAssociationClosed(); } } // Notify the Node Transport. auto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::SctpAssociation::SctpState::CLOSED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE, FBS::Notification::Body::Transport_SctpStateChangeNotification, sctpStateChangeOffset); } void Transport::OnAssociationRestarted() { MS_TRACE(); MS_DEBUG_TAG(sctp, "SCTP association restarted"); } void Transport::OnAssociationError(RTC::SCTP::Types::ErrorKind errorKind, std::string_view errorMessage) { MS_TRACE(); const auto errorKindStringView = RTC::SCTP::Types::ErrorKindToString(errorKind); MS_WARN_TAG( sctp, "SCTP association error [errorKind:%.*s, message:%.*s]", static_cast(errorKindStringView.size()), errorKindStringView.data(), static_cast(errorMessage.size()), errorMessage.data()); } void Transport::OnAssociationMessageReceived(RTC::SCTP::Message message) { MS_TRACE(); RTC::DataProducer* dataProducer = this->sctpListener.GetDataProducer(message.GetStreamId()); if (!dataProducer) { MS_WARN_TAG( sctp, "no suitable DataProducer for received SCTP message [streamId:%" PRIu16 "]", message.GetStreamId()); return; } // Pass the SCTP message to the corresponding DataProducer. try { static thread_local std::vector emptySubchannels; dataProducer->ReceiveMessage( std::move(message), emptySubchannels, /*requiredSubchannel*/ std::nullopt); } catch (std::exception& error) { MS_WARN_TAG( sctp, "DataProducer::ReceiveMessage() failed for received SCTP message [streamId:%" PRIu16 "]: %s", message.GetStreamId(), error.what()); } } void Transport::OnAssociationStreamsResetPerformed(std::span /*outboundStreamIds*/) { MS_TRACE(); // TODO: SCTP } void Transport::OnAssociationStreamsResetFailed( std::span /*outboundStreamIds*/, std::string_view /*errorMessage*/) { MS_TRACE(); // TODO: SCTP } void Transport::OnAssociationInboundStreamsReset(std::span /*inboundStreamIds*/) { MS_TRACE(); // TODO: SCTP } void Transport::OnAssociationStreamBufferedAmountLow(uint16_t /*streamId*/) { MS_TRACE(); // TODO: SCTP } void Transport::OnAssociationTotalBufferedAmountLow() { MS_TRACE(); // TODO: SCTP } bool Transport::OnAssociationIsTransportReadyForSctp() { MS_TRACE(); // We are ready for SCTP traffic if the transport is connected (e.g. the // WebRtcTransport has ICE and DTLS connected) and there is at least a // DataProducer or DataConsumer. // // NOTE: We don't want to start SCTP connection if there are no DataProducers // and DataConsumers because the peer (e.g. a browser) may have not started // its SCTP stack (e.g. no "m=application" media section in its SDP) so if we // initiate the SCTP connection it would fail after some time. return IsConnected() && (this->mapDataProducers.size() > 0 || this->mapDataConsumers.size() > 0); } // TODO: SCTP: Add OnAssociationLifecycleMessageXxxxxx() methods. void Transport::OnSctpAssociationConnecting(RTC::SctpAssociation* /*sctpAssociation*/) { MS_TRACE(); // Notify the Node Transport. auto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::SctpAssociation::SctpState::CONNECTING); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE, FBS::Notification::Body::Transport_SctpStateChangeNotification, sctpStateChangeOffset); } void Transport::OnSctpAssociationConnected(RTC::SctpAssociation* /*sctpAssociation*/) { MS_TRACE(); // Tell all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; if (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { dataConsumer->SctpAssociationConnected(); } } // Notify the Node Transport. auto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::SctpAssociation::SctpState::CONNECTED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE, FBS::Notification::Body::Transport_SctpStateChangeNotification, sctpStateChangeOffset); } void Transport::OnSctpAssociationFailed(RTC::SctpAssociation* /*sctpAssociation*/) { MS_TRACE(); // Tell all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; if (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { dataConsumer->SctpAssociationClosed(); } } // Notify the Node Transport. auto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::SctpAssociation::SctpState::FAILED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE, FBS::Notification::Body::Transport_SctpStateChangeNotification, sctpStateChangeOffset); } void Transport::OnSctpAssociationClosed(RTC::SctpAssociation* /*sctpAssociation*/) { MS_TRACE(); // Tell all DataConsumers. for (auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; if (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { dataConsumer->SctpAssociationClosed(); } } // Notify the Node Transport. auto sctpStateChangeOffset = FBS::Transport::CreateSctpStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::SctpAssociation::SctpState::CLOSED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::TRANSPORT_SCTP_STATE_CHANGE, FBS::Notification::Body::Transport_SctpStateChangeNotification, sctpStateChangeOffset); } void Transport::OnSctpAssociationSendData( RTC::SctpAssociation* /*sctpAssociation*/, const uint8_t* data, size_t len) { MS_TRACE(); // Ignore if destroying. // NOTE: This is because when the child class (i.e. WebRtcTransport) is deleted, // its destructor is called first and then the parent Transport's destructor, // and we would end here calling SendSctpData() which is an abstract method. if (this->isDestroying) { MS_WARN_DEV("ignoring sending data because Transport is being destroying"); return; } SendData(data, len); } void Transport::OnSctpAssociationMessageReceived( RTC::SctpAssociation* /*sctpAssociation*/, uint16_t streamId, const uint8_t* msg, size_t len, uint32_t ppid) { MS_TRACE(); RTC::DataProducer* dataProducer = this->sctpListener.GetDataProducer(streamId); if (!dataProducer) { MS_WARN_TAG( sctp, "no suitable DataProducer for received SCTP message [streamId:%" PRIu16 "]", streamId); return; } // Pass the SCTP message to the corresponding DataProducer. try { static thread_local std::vector emptySubchannels; dataProducer->ReceiveMessage( msg, len, ppid, emptySubchannels, /*requiredSubchannel*/ std::nullopt); } catch (std::exception& error) { MS_WARN_TAG( sctp, "DataProducer::ReceiveMessage() failed for received SCTP message [streamId:%" PRIu16 "]: %s", streamId, error.what()); } } void Transport::OnSctpAssociationBufferedAmount( RTC::SctpAssociation* /*sctpAssociation*/, uint32_t bufferedAmount) { MS_TRACE(); for (const auto& kv : this->mapDataConsumers) { auto* dataConsumer = kv.second; if (dataConsumer->GetType() == RTC::DataConsumer::Type::SCTP) { dataConsumer->SetSctpAssociationBufferedAmount(bufferedAmount); } } } void Transport::OnTransportCongestionControlClientBitrates( RTC::TransportCongestionControlClient* /*tccClient*/, RTC::TransportCongestionControlClient::Bitrates& bitrates) { MS_TRACE(); MS_DEBUG_DEV("outgoing available bitrate:%" PRIu32, bitrates.availableBitrate); DistributeAvailableOutgoingBitrate(); ComputeOutgoingDesiredBitrate(); // May emit 'trace' event. EmitTraceEventBweType(bitrates); } void Transport::OnTransportCongestionControlClientSendRtpPacket( RTC::TransportCongestionControlClient* /*tccClient*/, RTC::RTP::Packet* packet, const webrtc::PacedPacketInfo& pacingInfo) { MS_TRACE(); // Update abs-send-time if present. packet->UpdateAbsSendTime(this->shared->GetTimeMs()); // Update transport wide sequence number if present. if ( this->tccClient->GetBweType() == RTC::BweType::TRANSPORT_CC && packet->UpdateTransportWideCc01(this->transportWideCcSeq + 1)) { this->transportWideCcSeq++; // May emit 'trace' event. EmitTraceEventProbationType(packet); webrtc::RtpPacketSendInfo packetInfo; packetInfo.ssrc = packet->GetSsrc(); packetInfo.transport_sequence_number = this->transportWideCcSeq; packetInfo.has_rtp_sequence_number = true; packetInfo.rtp_sequence_number = packet->GetSequenceNumber(); packetInfo.length = packet->GetLength(); packetInfo.pacing_info = pacingInfo; // Indicate the pacer (and prober) that a packet is to be sent. this->tccClient->InsertPacket(packetInfo); const std::weak_ptr tccClientWeakPtr(this->tccClient); auto* shared = this->shared; #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR std::weak_ptr senderBweWeakPtr = this->senderBwe; RTC::SenderBandwidthEstimator::SentInfo sentInfo; sentInfo.wideSeq = this->transportWideCcSeq; sentInfo.size = packet->GetLength(); sentInfo.isProbation = true; sentInfo.sendingAtMs = this->shared->GetTimeMs(); const auto* cb = new onSendCallback( [tccClientWeakPtr, shared, packetInfo, senderBweWeakPtr, sentInfo](bool sent) mutable { if (sent) { auto tccClient = tccClientWeakPtr.lock(); if (tccClient) { tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64()); } auto senderBwe = senderBweWeakPtr.lock(); if (senderBwe) { sentInfo.sentAtMs = shared->GetTimeMs(); senderBwe->RtpPacketSent(sentInfo); } } }); SendRtpPacket(nullptr, packet, cb); #else const auto* cb = new onSendCallback( [tccClientWeakPtr, shared, packetInfo](bool sent) { if (sent) { auto tccClient = tccClientWeakPtr.lock(); if (tccClient) { tccClient->PacketSent(packetInfo, shared->GetTimeMsInt64()); } } }); SendRtpPacket(nullptr, packet, cb); #endif } else { // May emit 'trace' event. EmitTraceEventProbationType(packet); SendRtpPacket(nullptr, packet); } this->sendProbationTransmission.Update(packet); MS_DEBUG_DEV( "probation sent [seq:%" PRIu16 ", wideSeq:%" PRIu16 ", size:%zu, bitrate:%" PRIu32 "]", packet->GetSequenceNumber(), this->transportWideCcSeq, packet->GetLength(), this->sendProbationTransmission.GetBitrate(this->shared->GetTimeMs())); } void Transport::OnTransportCongestionControlServerSendRtcpPacket( RTC::TransportCongestionControlServer* /*tccServer*/, RTC::RTCP::Packet* packet) { MS_TRACE(); packet->Serialize(RTC::RTCP::SerializationBuffer); SendRtcpPacket(packet); } #ifdef ENABLE_RTC_SENDER_BANDWIDTH_ESTIMATOR void Transport::OnSenderBandwidthEstimatorAvailableBitrate( RTC::SenderBandwidthEstimator* /*senderBwe*/, uint32_t availableBitrate, uint32_t previousAvailableBitrate) { MS_TRACE(); MS_DEBUG_DEV( "outgoing available bitrate [now:%" PRIu32 ", before:%" PRIu32 "]", availableBitrate, previousAvailableBitrate); // TODO: Uncomment once just SenderBandwidthEstimator is used. // DistributeAvailableOutgoingBitrate(); // ComputeOutgoingDesiredBitrate(); } #endif void Transport::OnTimer(TimerHandleInterface* timer) { MS_TRACE(); // RTCP timer. if (timer == this->rtcpTimer) { auto interval = static_cast(RTC::RTCP::MaxVideoIntervalMs); const uint64_t nowMs = this->shared->GetTimeMs(); SendRtcp(nowMs); /* * The interval between RTCP packets is varied randomly over the range * [1.0, 1.5] times the calculated interval to avoid unintended * synchronization of all participants. */ interval *= static_cast(Utils::Crypto::GetRandomUInt(10, 15)) / 10; this->rtcpTimer->Start(interval); } } } // namespace RTC ================================================ FILE: worker/src/RTC/TransportCongestionControlClient.cpp ================================================ #define MS_CLASS "RTC::TransportCongestionControlClient" // #define MS_LOG_DEV_LEVEL 3 #define USE_TREND_CALCULATOR #include "RTC/TransportCongestionControlClient.hpp" #include "Logger.hpp" #include // webrtc::TargetRateConstraints #include // std::numeric_limits namespace RTC { /* Static. */ // NOTE: TransportCongestionControlMinOutgoingBitrate is defined in // TransportCongestionControlClient.hpp and exposed publicly. static constexpr float MaxBitrateMarginFactor{ 0.1f }; static constexpr float MaxBitrateIncrementFactor{ 1.35f }; static constexpr float MaxPaddingBitrateFactor{ 0.85f }; static constexpr uint64_t AvailableBitrateEventInterval{ 1000u }; // In ms. static constexpr size_t PacketLossHistogramLength{ 24 }; /* Instance methods. */ TransportCongestionControlClient::TransportCongestionControlClient( RTC::TransportCongestionControlClient::Listener* listener, SharedInterface* shared, RTC::BweType bweType, uint32_t initialAvailableBitrate, uint32_t maxOutgoingBitrate, uint32_t minOutgoingBitrate) : listener(listener), shared(shared), bweType(bweType), initialAvailableBitrate( std::max( initialAvailableBitrate, RTC::TransportCongestionControlMinOutgoingBitrate)), maxOutgoingBitrate(maxOutgoingBitrate), minOutgoingBitrate(minOutgoingBitrate) { MS_TRACE(); webrtc::GoogCcFactoryConfig config; // Provide RTCP feedback as well as Receiver Reports. config.feedback_only = true; this->controllerFactory = new webrtc::GoogCcNetworkControllerFactory(std::move(config)); } TransportCongestionControlClient::~TransportCongestionControlClient() { MS_TRACE(); delete this->controllerFactory; this->controllerFactory = nullptr; DestroyController(); } void TransportCongestionControlClient::InitializeController() { MS_ASSERT(this->rtpTransportControllerSend == nullptr, "transport controller already initialized"); webrtc::BitrateConstraints bitrateConfig; bitrateConfig.start_bitrate_bps = static_cast(this->initialAvailableBitrate); this->rtpTransportControllerSend = new webrtc::RtpTransportControllerSend(this, nullptr, this->controllerFactory, bitrateConfig); this->rtpTransportControllerSend->RegisterTargetTransferRateObserver(this); this->probationGenerator = new RTC::RTP::ProbationGenerator(); // This makes sure that periodic probing is used when the application is send // less bitrate than needed to measure the bandwidth estimation. (f.e. when // videos are muted or using screensharing with still images) this->rtpTransportControllerSend->EnablePeriodicAlrProbing(true); this->processTimer = this->shared->CreateTimer(this); this->processTimer->Start( std::min( // Depends on probation being done and WebRTC-Pacer-MinPacketLimitMs field trial. this->rtpTransportControllerSend->packet_sender()->TimeUntilNextProcess(), // Fixed value (25ms), libwebrtc/api/transport/goog_cc_factory.cc. this->controllerFactory->GetProcessInterval().ms())); } void TransportCongestionControlClient::DestroyController() { delete this->rtpTransportControllerSend; this->rtpTransportControllerSend = nullptr; delete this->probationGenerator; this->probationGenerator = nullptr; delete this->processTimer; this->processTimer = nullptr; } void TransportCongestionControlClient::TransportConnected() { MS_TRACE(); if (this->rtpTransportControllerSend == nullptr) { InitializeController(); } this->rtpTransportControllerSend->OnNetworkAvailability(true); } void TransportCongestionControlClient::TransportDisconnected() { MS_TRACE(); #ifdef USE_TREND_CALCULATOR const auto nowMs = this->shared->GetTimeMsInt64(); #endif this->bitrates.desiredBitrate = 0u; this->bitrates.effectiveDesiredBitrate = 0u; #ifdef USE_TREND_CALCULATOR this->desiredBitrateTrend.ForceUpdate(0u, nowMs); #endif this->rtpTransportControllerSend->OnNetworkAvailability(false); } void TransportCongestionControlClient::InsertPacket(webrtc::RtpPacketSendInfo& packetInfo) { MS_TRACE(); if (this->rtpTransportControllerSend == nullptr) { return; } this->rtpTransportControllerSend->packet_sender()->InsertPacket(packetInfo.length); this->rtpTransportControllerSend->OnAddPacket(packetInfo); } webrtc::PacedPacketInfo TransportCongestionControlClient::GetPacingInfo() { MS_TRACE(); if (this->rtpTransportControllerSend == nullptr) { return {}; } return this->rtpTransportControllerSend->packet_sender()->GetPacingInfo(); } void TransportCongestionControlClient::PacketSent( const webrtc::RtpPacketSendInfo& packetInfo, int64_t nowMs) { MS_TRACE(); if (this->rtpTransportControllerSend == nullptr) { return; } // Notify the transport feedback adapter about the sent packet. const rtc::SentPacket sentPacket(packetInfo.transport_sequence_number, nowMs); this->rtpTransportControllerSend->OnSentPacket(sentPacket, packetInfo.length); } void TransportCongestionControlClient::ReceiveEstimatedBitrate(uint32_t bitrate) { MS_TRACE(); if (this->rtpTransportControllerSend == nullptr) { return; } this->rtpTransportControllerSend->OnReceivedEstimatedBitrate(bitrate); } void TransportCongestionControlClient::ReceiveRtcpReceiverReport( RTC::RTCP::ReceiverReportPacket* packet, float rtt, int64_t nowMs) { MS_TRACE(); webrtc::ReportBlockList reportBlockList; for (auto it = packet->Begin(); it != packet->End(); ++it) { auto& report = *it; reportBlockList.emplace_back( packet->GetSsrc(), report->GetSsrc(), report->GetFractionLost(), report->GetTotalLost(), report->GetLastSeq(), report->GetJitter(), report->GetLastSenderReport(), report->GetDelaySinceLastSenderReport()); } if (this->rtpTransportControllerSend == nullptr) { return; } this->rtpTransportControllerSend->OnReceivedRtcpReceiverReport( reportBlockList, static_cast(rtt), nowMs); } void TransportCongestionControlClient::ReceiveRtcpTransportFeedback( const RTC::RTCP::FeedbackRtpTransportPacket* feedback) { MS_TRACE(); // Update packet loss history. const size_t expectedPackets = feedback->GetPacketStatusCount(); size_t lostPackets = 0; for (const auto& result : feedback->GetPacketResults()) { if (!result.received) { lostPackets += 1; } } if (expectedPackets > 0) { this->UpdatePacketLoss(static_cast(lostPackets) / expectedPackets); } if (this->rtpTransportControllerSend == nullptr) { return; } this->rtpTransportControllerSend->OnTransportFeedback(*feedback); } void TransportCongestionControlClient::UpdatePacketLoss(double packetLoss) { // Add the lost into the histogram. if (this->packetLossHistory.size() == PacketLossHistogramLength) { this->packetLossHistory.pop_front(); } this->packetLossHistory.push_back(packetLoss); /* * Scoring mechanism is a weighted average. * * The more recent the score is, the more weight it has. * The oldest score has a weight of 1 and subsequent scores weight is * increased by one sequentially. * * Ie: * - scores: [1,2,3,4] * - this->scores = ((1) + (2+2) + (3+3+3) + (4+4+4+4)) / 10 = 2.8 => 3 */ size_t weight{ 0 }; size_t samples{ 0 }; double totalPacketLoss{ 0 }; for (auto packetLossEntry : this->packetLossHistory) { weight++; samples += weight; totalPacketLoss += weight * packetLossEntry; } // clang-tidy "thinks" that this can lead to division by zero but we are // smarter. // NOLINTNEXTLINE(clang-analyzer-core.DivideZero) this->packetLoss = totalPacketLoss / samples; } void TransportCongestionControlClient::SetMaxOutgoingBitrate(uint32_t maxBitrate) { this->maxOutgoingBitrate = maxBitrate; ApplyBitrateUpdates(); if (this->maxOutgoingBitrate > 0u) { this->bitrates.availableBitrate = std::min(this->maxOutgoingBitrate, this->bitrates.availableBitrate); } } void TransportCongestionControlClient::SetMinOutgoingBitrate(uint32_t minBitrate) { this->minOutgoingBitrate = minBitrate; ApplyBitrateUpdates(); this->bitrates.minBitrate = std::max( this->minOutgoingBitrate, RTC::TransportCongestionControlMinOutgoingBitrate); } void TransportCongestionControlClient::SetDesiredBitrate(uint32_t desiredBitrate, bool force) { MS_TRACE(); #ifdef USE_TREND_CALCULATOR const auto nowMs = this->shared->GetTimeMsInt64(); #endif // Manage it via trending and increase it a bit to avoid immediate oscillations. #ifdef USE_TREND_CALCULATOR if (!force) { this->desiredBitrateTrend.Update(desiredBitrate, nowMs); } else { this->desiredBitrateTrend.ForceUpdate(desiredBitrate, nowMs); } #endif this->bitrates.desiredBitrate = desiredBitrate; #ifdef USE_TREND_CALCULATOR this->bitrates.effectiveDesiredBitrate = this->desiredBitrateTrend.GetValue(); #else this->bitrates.effectiveDesiredBitrate = desiredBitrate; #endif this->bitrates.minBitrate = std::max( this->minOutgoingBitrate, RTC::TransportCongestionControlMinOutgoingBitrate); // NOTE: Setting 'startBitrate' to 'availableBitrate' has proven to generate // more stable values. this->bitrates.startBitrate = std::max( RTC::TransportCongestionControlMinOutgoingBitrate, this->bitrates.availableBitrate); ApplyBitrateUpdates(); } void TransportCongestionControlClient::ApplyBitrateUpdates() { auto currentMaxBitrate = this->bitrates.maxBitrate; uint32_t newMaxBitrate = 0; #ifdef USE_TREND_CALCULATOR if (this->desiredBitrateTrend.GetValue() > 0u) #else if (this->bitrates.desiredBitrate > 0u) #endif { newMaxBitrate = std::max( this->initialAvailableBitrate, #ifdef USE_TREND_CALCULATOR this->desiredBitrateTrend.GetValue() * MaxBitrateIncrementFactor); #else this->bitrates.desiredBitrate * MaxBitrateIncrementFactor); #endif // If max bitrate requested didn't change by more than a small % keep the // previous settings to avoid constant small fluctuations requiring extra // probing and making the estimation less stable (requires constant // redistribution of bitrate accross consumers). auto maxBitrateMargin = newMaxBitrate * MaxBitrateMarginFactor; if (currentMaxBitrate > newMaxBitrate - maxBitrateMargin && currentMaxBitrate < newMaxBitrate + maxBitrateMargin) { newMaxBitrate = currentMaxBitrate; } } else { newMaxBitrate = this->initialAvailableBitrate; } if (this->maxOutgoingBitrate > 0u) { newMaxBitrate = std::min(this->maxOutgoingBitrate, newMaxBitrate); } if (newMaxBitrate != currentMaxBitrate) { this->bitrates.maxPaddingBitrate = newMaxBitrate * MaxPaddingBitrateFactor; this->bitrates.maxBitrate = newMaxBitrate; } this->bitrates.minBitrate = std::max( this->minOutgoingBitrate, RTC::TransportCongestionControlMinOutgoingBitrate); MS_DEBUG_DEV( "[desiredBitrate:%" PRIu32 ", desiredBitrateTrend:%" PRIu32 ", startBitrate:%" PRIu32 ", minBitrate:%" PRIu32 ", maxBitrate:%" PRIu32 ", maxPaddingBitrate:%" PRIu32 "]", this->bitrates.desiredBitrate, this->desiredBitrateTrend.GetValue(), this->bitrates.startBitrate, this->bitrates.minBitrate, this->bitrates.maxBitrate, this->bitrates.maxPaddingBitrate); if (this->rtpTransportControllerSend == nullptr) { return; } this->rtpTransportControllerSend->SetAllocatedSendBitrateLimits( this->bitrates.minBitrate, this->bitrates.maxPaddingBitrate, this->bitrates.maxBitrate); webrtc::TargetRateConstraints constraints; constraints.at_time = webrtc::Timestamp::ms(this->shared->GetTimeMs()); constraints.min_data_rate = webrtc::DataRate::bps(this->bitrates.minBitrate); constraints.max_data_rate = webrtc::DataRate::bps(this->bitrates.maxBitrate); constraints.starting_rate = webrtc::DataRate::bps(this->bitrates.startBitrate); this->rtpTransportControllerSend->SetClientBitratePreferences(constraints); } uint32_t TransportCongestionControlClient::GetAvailableBitrate() const { MS_TRACE(); return this->bitrates.availableBitrate; } double TransportCongestionControlClient::GetPacketLoss() const { MS_TRACE(); return this->packetLoss; } void TransportCongestionControlClient::RescheduleNextAvailableBitrateEvent() { MS_TRACE(); this->lastAvailableBitrateEventAtMs = this->shared->GetTimeMs(); } void TransportCongestionControlClient::MayEmitAvailableBitrateEvent(uint32_t previousAvailableBitrate) { MS_TRACE(); const uint64_t nowMs = this->shared->GetTimeMsInt64(); bool notify{ false }; // Ignore if first event. // NOTE: Otherwise it will make the Transport crash since this event also happens // during the constructor of this class. if (this->lastAvailableBitrateEventAtMs == 0u) { this->lastAvailableBitrateEventAtMs = nowMs; return; } // Emit if this is the first valid event. if (!this->availableBitrateEventCalled) { this->availableBitrateEventCalled = true; notify = true; } // Emit event if AvailableBitrateEventInterval elapsed. else if (nowMs - this->lastAvailableBitrateEventAtMs >= AvailableBitrateEventInterval) { notify = true; } // Also emit the event fast if we detect a high BWE value decrease. else if (this->bitrates.availableBitrate < previousAvailableBitrate * 0.75) { MS_WARN_TAG( bwe, "high BWE value decrease detected, notifying the listener [now:%" PRIu32 ", before:%" PRIu32 "]", this->bitrates.availableBitrate, previousAvailableBitrate); notify = true; } // Also emit the event fast if we detect a high BWE value increase. else if (this->bitrates.availableBitrate > previousAvailableBitrate * 1.50) { MS_DEBUG_TAG( bwe, "high BWE value increase detected, notifying the listener [now:%" PRIu32 ", before:%" PRIu32 "]", this->bitrates.availableBitrate, previousAvailableBitrate); notify = true; } if (notify) { MS_DEBUG_DEV( "notifying the listener with new available bitrate:%" PRIu32, this->bitrates.availableBitrate); this->lastAvailableBitrateEventAtMs = nowMs; this->listener->OnTransportCongestionControlClientBitrates(this, this->bitrates); } } void TransportCongestionControlClient::OnTargetTransferRate(webrtc::TargetTransferRate targetTransferRate) { MS_TRACE(); // NOTE: The same value as 'this->initialAvailableBitrate' is received // periodically regardless of the real available bitrate. Skip such value // except for the first time this event is called. if (this->availableBitrateEventCalled && targetTransferRate.target_rate.bps() == this->initialAvailableBitrate) { return; } auto previousAvailableBitrate = this->bitrates.availableBitrate; // Update availableBitrate. // NOTE: Just in case. if (targetTransferRate.target_rate.bps() > std::numeric_limits::max()) { this->bitrates.availableBitrate = std::numeric_limits::max(); } else { this->bitrates.availableBitrate = static_cast(targetTransferRate.target_rate.bps()); } MS_DEBUG_DEV("new available bitrate:%" PRIu32, this->bitrates.availableBitrate); MayEmitAvailableBitrateEvent(previousAvailableBitrate); } // Called from PacedSender in order to send probation packets. void TransportCongestionControlClient::SendPacket( RTC::RTP::Packet* packet, const webrtc::PacedPacketInfo& pacingInfo) { MS_TRACE(); // Send the packet. this->listener->OnTransportCongestionControlClientSendRtpPacket(this, packet, pacingInfo); } RTC::RTP::Packet* TransportCongestionControlClient::GeneratePadding(size_t size) { MS_TRACE(); MS_ASSERT(this->probationGenerator, "probation generator not initialized") return this->probationGenerator->GetNextPacket(size); } void TransportCongestionControlClient::OnTimer(TimerHandleInterface* timer) { MS_TRACE(); if (timer == this->processTimer) { // Time to call RtpTransportControllerSend::Process(). this->rtpTransportControllerSend->Process(); // Time to call PacedSender::Process(). this->rtpTransportControllerSend->packet_sender()->Process(); this->processTimer->Start( std::min( // Depends on probation being done and WebRTC-Pacer-MinPacketLimitMs field trial. this->rtpTransportControllerSend->packet_sender()->TimeUntilNextProcess(), // Fixed value (25ms), libwebrtc/api/transport/goog_cc_factory.cc. this->controllerFactory->GetProcessInterval().ms())); MayEmitAvailableBitrateEvent(this->bitrates.availableBitrate); } } } // namespace RTC ================================================ FILE: worker/src/RTC/TransportCongestionControlServer.cpp ================================================ #define MS_CLASS "RTC::TransportCongestionControlServer" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/TransportCongestionControlServer.hpp" #include "Logger.hpp" #include "RTC/RTCP/FeedbackPsRemb.hpp" namespace RTC { /* Static. */ static constexpr uint64_t TransportCcFeedbackSendInterval{ 100u }; // In ms. static constexpr uint64_t LimitationRembInterval{ 1500u }; // In ms. static constexpr uint64_t PacketArrivalTimestampWindow{ 500u }; // In ms. static constexpr uint8_t UnlimitedRembNumPackets{ 4u }; static constexpr size_t PacketLossHistogramLength{ 24 }; /* Instance methods. */ TransportCongestionControlServer::TransportCongestionControlServer( RTC::TransportCongestionControlServer::Listener* listener, SharedInterface* shared, RTC::BweType bweType, size_t maxRtcpPacketLen) : listener(listener), shared(shared), bweType(bweType), maxRtcpPacketLen(maxRtcpPacketLen) { MS_TRACE(); switch (this->bweType) { case RTC::BweType::TRANSPORT_CC: { // Create a feedback packet. ResetTransportCcFeedback(0u); // Create the feedback send periodic timer. this->transportCcFeedbackSendPeriodicTimer = this->shared->CreateTimer(this); break; } case RTC::BweType::REMB: { this->rembServer = new webrtc::RemoteBitrateEstimatorAbsSendTime(this); break; } } } TransportCongestionControlServer::~TransportCongestionControlServer() { MS_TRACE(); delete this->transportCcFeedbackSendPeriodicTimer; this->transportCcFeedbackSendPeriodicTimer = nullptr; // Delete REMB server. delete this->rembServer; this->rembServer = nullptr; } void TransportCongestionControlServer::TransportConnected() { MS_TRACE(); switch (this->bweType) { case RTC::BweType::TRANSPORT_CC: { this->transportCcFeedbackSendPeriodicTimer->Start( TransportCcFeedbackSendInterval, TransportCcFeedbackSendInterval); break; } default:; } } void TransportCongestionControlServer::TransportDisconnected() { MS_TRACE(); switch (this->bweType) { case RTC::BweType::TRANSPORT_CC: { this->transportCcFeedbackSendPeriodicTimer->Stop(); // Create a new feedback packet. ResetTransportCcFeedback(this->transportCcFeedbackPacketCount); break; } default:; } } double TransportCongestionControlServer::GetPacketLoss() const { MS_TRACE(); return this->packetLoss; } void TransportCongestionControlServer::IncomingPacket(uint64_t nowMs, const RTC::RTP::Packet* packet) { MS_TRACE(); switch (this->bweType) { case RTC::BweType::TRANSPORT_CC: { uint16_t wideSeqNumber{ 0 }; if (!packet->ReadTransportWideCc01(wideSeqNumber)) { break; } // Only insert the packet when receiving it for the first time. if (!this->mapPacketArrivalTimes.try_emplace(wideSeqNumber, nowMs).second) { break; } // We may receive packets with sequence number lower than the one in // previous tcc feedback, these packets may have been reported as lost // previously, therefore we need to reset the start sequence num for the // next tcc feedback. if ( !this->transportWideSeqNumberReceived || RTC::SeqManager::IsSeqLowerThan( wideSeqNumber, this->transportCcFeedbackWideSeqNumStart)) { this->transportCcFeedbackWideSeqNumStart = wideSeqNumber; } this->transportWideSeqNumberReceived = true; MayDropOldPacketArrivalTimes(wideSeqNumber, nowMs); // Update the RTCP media SSRC of the ongoing Transport-CC Feedback packet. this->transportCcFeedbackSenderSsrc = 0u; this->transportCcFeedbackMediaSsrc = packet->GetSsrc(); MaySendLimitationRembFeedback(nowMs); break; } case RTC::BweType::REMB: { uint32_t absSendTime{ 0 }; if (!packet->ReadAbsSendTime(absSendTime)) { break; } // NOTE: nowMs is uint64_t but we need to "convert" it to int64_t before // we give it to libwebrtc lib (althought this is implicit in the // conversion so it would be converted within the method call). auto nowMsInt64 = static_cast(nowMs); this->rembServer->IncomingPacket(nowMsInt64, packet->GetPayloadLength(), *packet, absSendTime); break; } } } void TransportCongestionControlServer::FillAndSendTransportCcFeedback() { MS_TRACE(); if (!this->transportWideSeqNumberReceived) { return; } auto it = this->mapPacketArrivalTimes.lower_bound(this->transportCcFeedbackWideSeqNumStart); if (it == this->mapPacketArrivalTimes.end()) { return; } for (; it != this->mapPacketArrivalTimes.end(); ++it) { auto sequenceNumber = it->first; auto timestamp = it->second; // If the base is not set in this packet let's set it. // NOTE: This maybe needed many times during this loop since the current // feedback packet maybe a fresh new one if the previous one was full (so // already sent) or failed to be built. if (!this->transportCcFeedbackPacket->IsBaseSet()) { // Set base sequence num and reference time. this->transportCcFeedbackPacket->SetBase(this->transportCcFeedbackWideSeqNumStart, timestamp); } auto result = this->transportCcFeedbackPacket->AddPacket( sequenceNumber, timestamp, this->maxRtcpPacketLen); switch (result) { case RTC::RTCP::FeedbackRtpTransportPacket::AddPacketResult::SUCCESS: { // If the feedback packet is full, send it now. if (this->transportCcFeedbackPacket->IsFull()) { MS_DEBUG_DEV("transport-cc feedback packet is full, sending feedback now"); auto sent = SendTransportCcFeedback(); if (sent) { ++this->transportCcFeedbackPacketCount; } // Create a new feedback packet. ResetTransportCcFeedback(this->transportCcFeedbackPacketCount); } break; } case RTC::RTCP::FeedbackRtpTransportPacket::AddPacketResult::MAX_SIZE_EXCEEDED: { // Send ongoing feedback packet. auto sent = SendTransportCcFeedback(); if (sent) { ++this->transportCcFeedbackPacketCount; } // Create a new feedback packet. ResetTransportCcFeedback(this->transportCcFeedbackPacketCount); // Decrease iterator to add current packet again. --it; break; } case RTC::RTCP::FeedbackRtpTransportPacket::AddPacketResult::FATAL: { // Create a new feedback packet. // NOTE: Do not increment packet count it since the previous ongoing // feedback packet was not sent. ResetTransportCcFeedback(this->transportCcFeedbackPacketCount); break; } } } // It may happen that the packet is empty (no deltas) but in that case // SendTransportCcFeedback() won't send it so we are safe. auto sent = SendTransportCcFeedback(); if (sent) { ++this->transportCcFeedbackPacketCount; } // Create a new feedback packet. ResetTransportCcFeedback(this->transportCcFeedbackPacketCount); } void TransportCongestionControlServer::SetMaxIncomingBitrate(uint32_t bitrate) { MS_TRACE(); auto previousMaxIncomingBitrate = this->maxIncomingBitrate; this->maxIncomingBitrate = bitrate; if (previousMaxIncomingBitrate != 0u && this->maxIncomingBitrate == 0u) { // This is to ensure that we send N REMB packets with bitrate 0 (unlimited). this->unlimitedRembCounter = UnlimitedRembNumPackets; auto nowMs = this->shared->GetTimeMs(); MaySendLimitationRembFeedback(nowMs); } } bool TransportCongestionControlServer::SendTransportCcFeedback() { MS_TRACE(); this->transportCcFeedbackPacket->Finish(); if (!this->transportCcFeedbackPacket->IsSerializable()) { MS_WARN_TAG(rtcp, "couldn't send feedback-cc packet because it is not serializable"); return false; } auto latestWideSeqNumber = this->transportCcFeedbackPacket->GetLatestSequenceNumber(); // Notify the listener. this->listener->OnTransportCongestionControlServerSendRtcpPacket( this, this->transportCcFeedbackPacket.get()); // Update packet loss history. const size_t expectedPackets = this->transportCcFeedbackPacket->GetPacketStatusCount(); size_t lostPackets = 0; for (const auto& result : this->transportCcFeedbackPacket->GetPacketResults()) { if (!result.received) { lostPackets += 1; } } if (expectedPackets > 0) { this->UpdatePacketLoss(static_cast(lostPackets) / expectedPackets); } this->transportCcFeedbackWideSeqNumStart = latestWideSeqNumber + 1; return true; } void TransportCongestionControlServer::MayDropOldPacketArrivalTimes(uint16_t seqNum, uint64_t nowMs) { MS_TRACE(); // Ignore nowMs value if it's smaller than PacketArrivalTimestampWindow in // order to avoid negative values (should never happen) and return early if // the condition is met. if (nowMs >= PacketArrivalTimestampWindow) { const uint64_t expiryTimestamp = nowMs - PacketArrivalTimestampWindow; auto it = this->mapPacketArrivalTimes.begin(); while (it != this->mapPacketArrivalTimes.end() && it->first != this->transportCcFeedbackWideSeqNumStart && RTC::SeqManager::IsSeqLowerThan(it->first, seqNum) && it->second <= expiryTimestamp) { it = this->mapPacketArrivalTimes.erase(it); } } } void TransportCongestionControlServer::MaySendLimitationRembFeedback(uint64_t nowMs) { MS_TRACE(); // May fix unlimitedRembCounter. if (this->unlimitedRembCounter > 0u && this->maxIncomingBitrate != 0u) { this->unlimitedRembCounter = 0u; } // In case this is the first unlimited REMB packet, send it fast. if ( ((this->bweType != RTC::BweType::REMB && this->maxIncomingBitrate != 0u) || this->unlimitedRembCounter > 0u) && (nowMs - this->limitationRembSentAtMs > LimitationRembInterval || this->unlimitedRembCounter == UnlimitedRembNumPackets)) { MS_DEBUG_DEV( "sending limitation RTCP REMB packet [bitrate:%" PRIu32 "]", this->maxIncomingBitrate); RTC::RTCP::FeedbackPsRembPacket packet(0u, 0u); packet.SetBitrate(this->maxIncomingBitrate); packet.Serialize(RTC::RTCP::SerializationBuffer); // Notify the listener. this->listener->OnTransportCongestionControlServerSendRtcpPacket(this, &packet); this->limitationRembSentAtMs = nowMs; if (this->unlimitedRembCounter > 0u) { this->unlimitedRembCounter--; } } } void TransportCongestionControlServer::UpdatePacketLoss(double packetLoss) { // Add the lost into the histogram. if (this->packetLossHistory.size() == PacketLossHistogramLength) { this->packetLossHistory.pop_front(); } this->packetLossHistory.push_back(packetLoss); // Calculate a weighted average size_t weight{ 0 }; size_t samples{ 0 }; double totalPacketLoss{ 0 }; for (auto packetLossEntry : this->packetLossHistory) { weight++; samples += weight; totalPacketLoss += weight * packetLossEntry; } // clang-tidy "thinks" that this can lead to division by zero but we are // smarter. // NOLINTNEXTLINE(clang-analyzer-core.DivideZero) this->packetLoss = totalPacketLoss / samples; } void TransportCongestionControlServer::ResetTransportCcFeedback(uint8_t feedbackPacketCount) { MS_TRACE(); this->transportCcFeedbackPacket.reset(new RTC::RTCP::FeedbackRtpTransportPacket( this->transportCcFeedbackSenderSsrc, this->transportCcFeedbackMediaSsrc)); this->transportCcFeedbackPacket->SetFeedbackPacketCount(feedbackPacketCount); } void TransportCongestionControlServer::OnRembServerAvailableBitrate( const webrtc::RemoteBitrateEstimator* /*rembServer*/, const std::vector& ssrcs, uint32_t availableBitrate) { MS_TRACE(); // Limit announced bitrate if requested via API. if (this->maxIncomingBitrate != 0u) { availableBitrate = std::min(availableBitrate, this->maxIncomingBitrate); } #if MS_LOG_DEV_LEVEL == 3 std::ostringstream ssrcsStream; if (!ssrcs.empty()) { std::copy(ssrcs.begin(), ssrcs.end() - 1, std::ostream_iterator(ssrcsStream, ",")); ssrcsStream << ssrcs.back(); } MS_DEBUG_DEV( "sending RTCP REMB packet [bitrate:%" PRIu32 ", ssrcs:%s]", availableBitrate, ssrcsStream.str().c_str()); #endif RTC::RTCP::FeedbackPsRembPacket packet(0u, 0u); packet.SetBitrate(availableBitrate); packet.SetSsrcs(ssrcs); packet.Serialize(RTC::RTCP::SerializationBuffer); // Notify the listener. this->listener->OnTransportCongestionControlServerSendRtcpPacket(this, &packet); } void TransportCongestionControlServer::OnTimer(TimerHandleInterface* timer) { MS_TRACE(); if (timer == this->transportCcFeedbackSendPeriodicTimer) { FillAndSendTransportCcFeedback(); } } } // namespace RTC ================================================ FILE: worker/src/RTC/TransportTuple.cpp ================================================ #define MS_CLASS "RTC::TransportTuple" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/TransportTuple.hpp" #include "Logger.hpp" #include // std::memcpy() namespace RTC { /* Static methods. */ TransportTuple::Protocol TransportTuple::ProtocolFromFbs(FBS::Transport::Protocol protocol) { MS_TRACE(); switch (protocol) { case FBS::Transport::Protocol::UDP: { return TransportTuple::Protocol::UDP; } case FBS::Transport::Protocol::TCP: { return TransportTuple::Protocol::TCP; } NO_DEFAULT_GCC(); } } FBS::Transport::Protocol TransportTuple::ProtocolToFbs(TransportTuple::Protocol protocol) { MS_TRACE(); switch (protocol) { case TransportTuple::Protocol::UDP: { return FBS::Transport::Protocol::UDP; } case TransportTuple::Protocol::TCP: { return FBS::Transport::Protocol::TCP; } NO_DEFAULT_GCC(); } } uint64_t TransportTuple::GenerateFnv1aHash(const uint8_t* data, size_t size) { MS_TRACE(); const uint64_t fnvOffsetBasis = 14695981039346656037ull; const uint64_t fnvPrime = 1099511628211ull; uint64_t hash = fnvOffsetBasis; for (size_t i = 0; i < size; ++i) { hash = (hash ^ data[i]) * fnvPrime; } return hash; } /* Instance methods. */ void TransportTuple::CloseTcpConnection() { MS_TRACE(); if (this->protocol == Protocol::UDP) { MS_ABORT("cannot delete a UDP socket"); } this->tcpConnection->TriggerClose(); } flatbuffers::Offset TransportTuple::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); int family; std::string localIp; uint16_t localPort; Utils::IP::GetAddressInfo(GetLocalAddress(), family, localIp, localPort); std::string remoteIp; uint16_t remotePort; Utils::IP::GetAddressInfo(GetRemoteAddress(), family, remoteIp, remotePort); auto protocol = TransportTuple::ProtocolToFbs(GetProtocol()); return FBS::Transport::CreateTupleDirect( builder, (this->localAnnouncedAddress.empty() ? localIp : this->localAnnouncedAddress).c_str(), localPort, remoteIp.c_str(), remotePort, protocol); } void TransportTuple::Dump(int indentation) const { MS_TRACE(); MS_DUMP_CLEAN(indentation, ""); int family; std::string ip; uint16_t port; Utils::IP::GetAddressInfo(GetLocalAddress(), family, ip, port); MS_DUMP_CLEAN(indentation, " localIp: %s", ip.c_str()); MS_DUMP_CLEAN(indentation, " localPort: %" PRIu16, port); Utils::IP::GetAddressInfo(GetRemoteAddress(), family, ip, port); MS_DUMP_CLEAN(indentation, " remoteIp: %s", ip.c_str()); MS_DUMP_CLEAN(indentation, " remotePort: %" PRIu16, port); switch (GetProtocol()) { case Protocol::UDP: { MS_DUMP_CLEAN(indentation, " protocol: udp"); break; } case Protocol::TCP: { MS_DUMP_CLEAN(indentation, " protocol: tcp"); break; } } MS_DUMP_CLEAN(indentation, " hash: %" PRIu64, this->hash); MS_DUMP_CLEAN(indentation, ""); } void TransportTuple::GenerateHash() { MS_TRACE(); const auto* localSockAddr = GetLocalAddress(); const auto* remoteSockAddr = GetRemoteAddress(); // Maximum buffer length for two IPv6 addresses and ports plus protocol. static constexpr size_t BufferSize = ((16 + 2) * 2) + 1; uint8_t buffer[BufferSize] = {}; size_t idx = 0; auto appendSockAddr = [&](const struct sockaddr* addr) { if (addr->sa_family == AF_INET) { const auto* in = reinterpret_cast(addr); const auto* ip = reinterpret_cast(&in->sin_addr.s_addr); const uint16_t port = ntohs(in->sin_port); std::memcpy(buffer + idx, ip, 4); idx += 4; buffer[idx++] = (port >> 8) & 0xFF; buffer[idx++] = port & 0xFF; } else if (addr->sa_family == AF_INET6) { const auto* in6 = reinterpret_cast(addr); const auto* ip = reinterpret_cast(&in6->sin6_addr); const uint16_t port = ntohs(in6->sin6_port); std::memcpy(buffer + idx, ip, 16); idx += 16; buffer[idx++] = (port >> 8) & 0xFF; buffer[idx++] = port & 0xFF; } }; appendSockAddr(localSockAddr); appendSockAddr(remoteSockAddr); buffer[idx] = static_cast(this->protocol); this->hash = TransportTuple::GenerateFnv1aHash(buffer, idx + 1); } } // namespace RTC ================================================ FILE: worker/src/RTC/TrendCalculator.cpp ================================================ #define MS_CLASS "RTC::TrendCalculator" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/TrendCalculator.hpp" #include "Logger.hpp" namespace RTC { TrendCalculator::TrendCalculator(float decreaseFactor) : decreaseFactor(decreaseFactor) { MS_TRACE(); } void TrendCalculator::Update(uint32_t value, uint64_t nowMs) { MS_TRACE(); if (this->value == 0u) { this->value = value; this->highestValue = value; this->highestValueUpdatedAtMs = nowMs; return; } // If new value is bigger or equal than current one, use it. if (value >= this->value) { this->value = value; this->highestValue = value; this->highestValueUpdatedAtMs = nowMs; } // Otherwise decrease current value. else { const uint64_t elapsedMs = nowMs - this->highestValueUpdatedAtMs; auto subtraction = static_cast(this->highestValue * this->decreaseFactor * (elapsedMs / 1000.0)); this->value = std::max( value, this->highestValue > subtraction ? (this->highestValue - subtraction) : value); } } void TrendCalculator::ForceUpdate(uint32_t value, uint64_t nowMs) { MS_TRACE(); this->value = value; this->highestValue = value; this->highestValueUpdatedAtMs = nowMs; } } // namespace RTC ================================================ FILE: worker/src/RTC/UdpSocket.cpp ================================================ #define MS_CLASS "RTC::UdpSocket" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/UdpSocket.hpp" #include "Logger.hpp" #include "RTC/PortManager.hpp" #include namespace RTC { /* Instance methods. */ UdpSocket::UdpSocket( Listener* listener, std::string& ip, uint16_t port, RTC::Transport::SocketFlags& flags) : // This may throw. ::UdpSocketHandle::UdpSocketHandle(RTC::PortManager::BindUdp(ip, port, flags)), listener(listener), fixedPort(true) { MS_TRACE(); } UdpSocket::UdpSocket( Listener* listener, std::string& ip, uint16_t minPort, uint16_t maxPort, RTC::Transport::SocketFlags& flags, uint64_t& portRangeHash) : // This may throw. ::UdpSocketHandle::UdpSocketHandle( RTC::PortManager::BindUdp(ip, minPort, maxPort, flags, portRangeHash)), listener(listener) { MS_TRACE(); this->portRangeHash = portRangeHash; } UdpSocket::~UdpSocket() { MS_TRACE(); if (!this->fixedPort) { RTC::PortManager::Unbind(this->portRangeHash, this->localPort); } } void UdpSocket::UserOnUdpDatagramReceived( const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* addr) { MS_TRACE(); if (!this->listener) { MS_ERROR("no listener set"); return; } // Notify the reader. this->listener->OnUdpSocketPacketReceived(this, data, len, bufferLen, addr); } } // namespace RTC ================================================ FILE: worker/src/RTC/WebRtcServer.cpp ================================================ #include "SharedInterface.hpp" #define MS_CLASS "RTC::WebRtcServer" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "Utils.hpp" #include "RTC/WebRtcServer.hpp" #include // std::pow() namespace RTC { /* Static. */ static constexpr uint16_t IceCandidateDefaultLocalPriority{ 10000 }; // We just provide "host" candidates so type preference is fixed. static constexpr uint16_t IceTypePreference{ 64 }; // We do not support non rtcp-mux so component is always 1. static constexpr uint16_t IceComponent{ 1 }; static inline uint32_t generateIceCandidatePriority(uint16_t localPreference) { MS_TRACE(); return (std::pow(2, 24) * IceTypePreference) + (std::pow(2, 8) * localPreference) + (std::pow(2, 0) * (256 - IceComponent)); } /* Class methods. */ inline std::string WebRtcServer::GetLocalIceUsernameFragmentFromReceivedStunPacket( const RTC::ICE::StunPacket* packet) { MS_TRACE(); // Here we inspect the USERNAME attribute of a received STUN request and // extract its remote usernameFragment (the one given to our IceServer as // local usernameFragment) which is the first value in the attribute value // before the ":" symbol. std::string username{ packet->GetUsername() }; const size_t colonPos = username.find(':'); // If no colon is found just return the whole USERNAME attribute anyway. if (colonPos == std::string::npos) { return username; } return username.substr(0, colonPos); } /* Instance methods. */ WebRtcServer::WebRtcServer( SharedInterface* shared, const std::string& id, const flatbuffers::Vector>* listenInfos) : id(id), shared(shared) { MS_TRACE(); if (listenInfos->size() == 0) { MS_THROW_TYPE_ERROR("wrong listenInfos (empty array)"); } try { for (const auto* listenInfo : *listenInfos) { auto ip = listenInfo->ip()->str(); // This may throw. Utils::IP::NormalizeIp(ip); std::string announcedAddress; if (flatbuffers::IsFieldPresent(listenInfo, FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS)) { announcedAddress = listenInfo->announcedAddress()->str(); } const bool exposeInternalIp = listenInfo->exposeInternalIp(); RTC::Transport::SocketFlags flags; flags.ipv6Only = listenInfo->flags()->ipv6Only(); flags.udpReusePort = listenInfo->flags()->udpReusePort(); if (listenInfo->protocol() == FBS::Transport::Protocol::UDP) { // This may throw. RTC::UdpSocket* udpSocket; if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0) { uint64_t portRangeHash{ 0u }; udpSocket = new RTC::UdpSocket( this, ip, listenInfo->portRange()->min(), listenInfo->portRange()->max(), flags, portRangeHash); } else if (listenInfo->port() != 0) { udpSocket = new RTC::UdpSocket(this, ip, listenInfo->port(), flags); } // NOTE: This is temporal to allow deprecated usage of worker port range. // In the future this should throw since |port| or |portRange| will be // required. else { uint64_t portRangeHash{ 0u }; udpSocket = new RTC::UdpSocket( this, ip, Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, flags, portRangeHash); } this->udpSocketOrTcpServers.emplace_back( udpSocket, nullptr, announcedAddress, exposeInternalIp); if (listenInfo->sendBufferSize() != 0) { udpSocket->SetSendBufferSize(listenInfo->sendBufferSize()); } if (listenInfo->recvBufferSize() != 0) { udpSocket->SetRecvBufferSize(listenInfo->recvBufferSize()); } MS_DEBUG_TAG( info, "UDP socket send buffer size: %d, recv buffer size: %d", udpSocket->GetSendBufferSize(), udpSocket->GetRecvBufferSize()); } else if (listenInfo->protocol() == FBS::Transport::Protocol::TCP) { // This may throw. RTC::TcpServer* tcpServer; if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0) { uint64_t portRangeHash{ 0u }; tcpServer = new RTC::TcpServer( this, this, ip, listenInfo->portRange()->min(), listenInfo->portRange()->max(), flags, portRangeHash); } else if (listenInfo->port() != 0) { tcpServer = new RTC::TcpServer(this, this, ip, listenInfo->port(), flags); } // NOTE: This is temporal to allow deprecated usage of worker port range. // In the future this should throw since |port| or |portRange| will be // required. else { uint64_t portRangeHash{ 0u }; tcpServer = new RTC::TcpServer( this, this, ip, Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, flags, portRangeHash); } this->udpSocketOrTcpServers.emplace_back( nullptr, tcpServer, announcedAddress, exposeInternalIp); if (listenInfo->sendBufferSize() != 0) { tcpServer->SetSendBufferSize(listenInfo->sendBufferSize()); } if (listenInfo->recvBufferSize() != 0) { tcpServer->SetRecvBufferSize(listenInfo->recvBufferSize()); } MS_DEBUG_TAG( info, "TCP server send buffer size: %d, recv buffer size: %d", tcpServer->GetSendBufferSize(), tcpServer->GetRecvBufferSize()); } } // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ nullptr); } catch (const MediaSoupError& error) { // Must delete everything since the destructor won't be called. for (auto& item : this->udpSocketOrTcpServers) { delete item.udpSocket; item.udpSocket = nullptr; delete item.tcpServer; item.tcpServer = nullptr; } this->udpSocketOrTcpServers.clear(); throw; } } WebRtcServer::~WebRtcServer() { MS_TRACE(); this->closing = true; this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); // NOTE: We need to close WebRtcTransports first since they may need to // send DTLS Close Alert so UDP sockets and TCP connections must remain // open. for (auto* webRtcTransport : this->webRtcTransports) { webRtcTransport->ListenServerClosed(); } this->webRtcTransports.clear(); for (auto& item : this->udpSocketOrTcpServers) { delete item.udpSocket; item.udpSocket = nullptr; delete item.tcpServer; item.tcpServer = nullptr; } this->udpSocketOrTcpServers.clear(); } flatbuffers::Offset WebRtcServer::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add udpSockets and tcpServers. std::vector> udpSockets; std::vector> tcpServers; for (const auto& item : this->udpSocketOrTcpServers) { if (item.udpSocket) { udpSockets.emplace_back( FBS::WebRtcServer::CreateIpPortDirect( builder, item.udpSocket->GetLocalIp().c_str(), item.udpSocket->GetLocalPort())); } else if (item.tcpServer) { tcpServers.emplace_back( FBS::WebRtcServer::CreateIpPortDirect( builder, item.tcpServer->GetLocalIp().c_str(), item.tcpServer->GetLocalPort())); } } // Add webRtcTransportIds. std::vector> webRtcTransportIds; for (auto* webRtcTransport : this->webRtcTransports) { webRtcTransportIds.emplace_back(builder.CreateString(webRtcTransport->id)); } // Add localIceUsernameFragments. std::vector> localIceUsernameFragments; for (const auto& kv : this->mapLocalIceUsernameFragmentWebRtcTransport) { const auto& localIceUsernameFragment = kv.first; const auto* webRtcTransport = kv.second; localIceUsernameFragments.emplace_back( FBS::WebRtcServer::CreateIceUserNameFragmentDirect( builder, localIceUsernameFragment.c_str(), webRtcTransport->id.c_str())); } // Add tupleHashes. std::vector> tupleHashes; for (const auto& kv : this->mapTupleWebRtcTransport) { const auto& tupleHash = kv.first; const auto* webRtcTransport = kv.second; tupleHashes.emplace_back( FBS::WebRtcServer::CreateTupleHashDirect(builder, tupleHash, webRtcTransport->id.c_str())); } return FBS::WebRtcServer::CreateDumpResponseDirect( builder, this->id.c_str(), &udpSockets, &tcpServers, &webRtcTransportIds, &localIceUsernameFragments, &tupleHashes); } void WebRtcServer::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::WEBRTCSERVER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::WebRtcServer_DumpResponse, dumpOffset); break; } default: { MS_THROW_ERROR("unknown method '%s'", request->methodCStr); } } } std::vector WebRtcServer::GetIceCandidates( bool enableUdp, bool enableTcp, bool preferUdp, bool preferTcp) const { MS_TRACE(); std::vector iceCandidates; uint16_t iceLocalPreferenceDecrement{ 0 }; // Optimistic preallocation which takes into account worst case (each // listening item has |exposeInternalIp| set to true). iceCandidates.reserve(this->udpSocketOrTcpServers.size() * 2); for (const auto& item : this->udpSocketOrTcpServers) { if (item.udpSocket && enableUdp) { uint16_t iceLocalPreference = IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement; if (preferUdp) { iceLocalPreference += 1000; } const uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference); if (item.announcedAddress.empty()) { iceCandidates.emplace_back(item.udpSocket, icePriority); } else { iceCandidates.emplace_back( item.udpSocket, icePriority, const_cast(item.announcedAddress)); if (item.exposeInternalIp) { iceCandidates.emplace_back(item.udpSocket, icePriority - 1000); } } } else if (item.tcpServer && enableTcp) { uint16_t iceLocalPreference = IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement; if (preferTcp) { iceLocalPreference += 1000; } const uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference); if (item.announcedAddress.empty()) { iceCandidates.emplace_back(item.tcpServer, icePriority); } else { iceCandidates.emplace_back( item.tcpServer, icePriority, const_cast(item.announcedAddress)); if (item.exposeInternalIp) { iceCandidates.emplace_back(item.tcpServer, icePriority - 1000); } } } // Decrement initial ICE local preference for next IP. iceLocalPreferenceDecrement += 100; } return iceCandidates; } inline void WebRtcServer::OnPacketReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); if (RTC::ICE::StunPacket::IsStun(data, len)) { OnStunDataReceived(tuple, data, len); } else { OnNonStunDataReceived(tuple, data, len, bufferLen); } } inline void WebRtcServer::OnStunDataReceived(RTC::TransportTuple* tuple, const uint8_t* data, size_t len) { MS_TRACE(); const auto* packet = RTC::ICE::StunPacket::Parse(data, len); if (!packet) { MS_WARN_TAG(ice, "ignoring wrong STUN packet received"); return; } // First try doing lookup in the tuples table. auto it1 = this->mapTupleWebRtcTransport.find(tuple->hash); if (it1 != this->mapTupleWebRtcTransport.end()) { auto* webRtcTransport = it1->second; webRtcTransport->ProcessStunPacketFromWebRtcServer(tuple, packet); delete packet; return; } // Otherwise try to match the local ICE username fragment. auto key = WebRtcServer::GetLocalIceUsernameFragmentFromReceivedStunPacket(packet); auto it2 = this->mapLocalIceUsernameFragmentWebRtcTransport.find(key); if (it2 == this->mapLocalIceUsernameFragmentWebRtcTransport.end()) { MS_WARN_TAG(ice, "ignoring received STUN packet with unknown remote ICE usernameFragment"); delete packet; return; } auto* webRtcTransport = it2->second; webRtcTransport->ProcessStunPacketFromWebRtcServer(tuple, packet); delete packet; } inline void WebRtcServer::OnNonStunDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); auto it = this->mapTupleWebRtcTransport.find(tuple->hash); if (it == this->mapTupleWebRtcTransport.end()) { MS_WARN_TAG(ice, "ignoring received non STUN data from unknown tuple"); return; } auto* webRtcTransport = it->second; webRtcTransport->ProcessNonStunPacketFromWebRtcServer(tuple, data, len, bufferLen); } inline void WebRtcServer::OnWebRtcTransportCreated(RTC::WebRtcTransport* webRtcTransport) { MS_TRACE(); MS_ASSERT( this->webRtcTransports.find(webRtcTransport) == this->webRtcTransports.end(), "WebRtcTransport already handled"); this->webRtcTransports.insert(webRtcTransport); } inline void WebRtcServer::OnWebRtcTransportClosed(RTC::WebRtcTransport* webRtcTransport) { MS_TRACE(); MS_ASSERT( this->webRtcTransports.find(webRtcTransport) != this->webRtcTransports.end(), "WebRtcTransport not handled"); // NOTE: If WebRtcServer is closing then do not remove the transport from // the set since it would modify the set while the WebRtcServer destructor // is iterating it. // See: https://github.com/versatica/mediasoup/pull/1369#issuecomment-2044672247 if (!this->closing) { this->webRtcTransports.erase(webRtcTransport); } } inline void WebRtcServer::OnWebRtcTransportLocalIceUsernameFragmentAdded( RTC::WebRtcTransport* webRtcTransport, const std::string& usernameFragment) { MS_TRACE(); MS_ASSERT( this->mapLocalIceUsernameFragmentWebRtcTransport.find(usernameFragment) == this->mapLocalIceUsernameFragmentWebRtcTransport.end(), "local ICE username fragment already exists in the table"); this->mapLocalIceUsernameFragmentWebRtcTransport[usernameFragment] = webRtcTransport; } inline void WebRtcServer::OnWebRtcTransportLocalIceUsernameFragmentRemoved( RTC::WebRtcTransport* /*webRtcTransport*/, const std::string& usernameFragment) { MS_TRACE(); MS_ASSERT( this->mapLocalIceUsernameFragmentWebRtcTransport.find(usernameFragment) != this->mapLocalIceUsernameFragmentWebRtcTransport.end(), "local ICE username fragment not found in the table"); this->mapLocalIceUsernameFragmentWebRtcTransport.erase(usernameFragment); } inline void WebRtcServer::OnWebRtcTransportTransportTupleAdded( RTC::WebRtcTransport* webRtcTransport, RTC::TransportTuple* tuple) { MS_TRACE(); if (this->mapTupleWebRtcTransport.find(tuple->hash) != this->mapTupleWebRtcTransport.end()) { MS_WARN_TAG(ice, "tuple hash already exists in the table"); return; } this->mapTupleWebRtcTransport[tuple->hash] = webRtcTransport; } inline void WebRtcServer::OnWebRtcTransportTransportTupleRemoved( RTC::WebRtcTransport* /*webRtcTransport*/, RTC::TransportTuple* tuple) { MS_TRACE(); if (this->mapTupleWebRtcTransport.find(tuple->hash) == this->mapTupleWebRtcTransport.end()) { MS_DEBUG_TAG(ice, "tuple hash not found in the table"); return; } this->mapTupleWebRtcTransport.erase(tuple->hash); } inline void WebRtcServer::OnUdpSocketPacketReceived( RTC::UdpSocket* socket, const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* remoteAddr) { MS_TRACE(); RTC::TransportTuple tuple(socket, remoteAddr); OnPacketReceived(&tuple, data, len, bufferLen); } inline void WebRtcServer::OnRtcTcpConnectionClosed( RTC::TcpServer* /*tcpServer*/, RTC::TcpConnection* connection) { MS_TRACE(); RTC::TransportTuple tuple(connection); // NOTE: We cannot assert whether this tuple is still in our // mapTupleWebRtcTransport because this event may be called after the tuple // was removed from it. auto it = this->mapTupleWebRtcTransport.find(tuple.hash); if (it == this->mapTupleWebRtcTransport.end()) { return; } auto* webRtcTransport = it->second; webRtcTransport->RemoveTuple(&tuple); } inline void WebRtcServer::OnTcpConnectionPacketReceived( RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); RTC::TransportTuple tuple(connection); OnPacketReceived(&tuple, data, len, bufferLen); } } // namespace RTC ================================================ FILE: worker/src/RTC/WebRtcTransport.cpp ================================================ #define MS_CLASS "RTC::WebRtcTransport" // #define MS_LOG_DEV_LEVEL 3 #include "RTC/WebRtcTransport.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "Utils.hpp" #include "FBS/webRtcTransport.h" #include // std::pow() namespace RTC { /* Static. */ static constexpr uint16_t IceCandidateDefaultLocalPriority{ 10000 }; // We just provide "host" candidates so type preference is fixed. static constexpr uint16_t IceTypePreference{ 64 }; // We do not support non rtcp-mux so component is always 1. static constexpr uint16_t IceComponent{ 1 }; static inline uint32_t generateIceCandidatePriority(uint16_t localPreference) { MS_TRACE(); return (std::pow(2, 24) * IceTypePreference) + (std::pow(2, 8) * localPreference) + (std::pow(2, 0) * (256 - IceComponent)); } /* Instance methods. */ /** * This constructor is used when the WebRtcTransport doesn't use a WebRtcServer. */ WebRtcTransport::WebRtcTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, const FBS::WebRtcTransport::WebRtcTransportOptions* options) : RTC::Transport::Transport(shared, id, listener, options->base()) { MS_TRACE(); try { const auto* listenIndividual = options->listen_as(); const auto* listenInfos = listenIndividual->listenInfos(); uint16_t iceLocalPreferenceDecrement{ 0u }; // Multiply by 2 to preallocate space in case |exposeInternalIp| is set. this->iceCandidates.reserve(listenInfos->size() * 2); for (const auto* listenInfo : *listenInfos) { auto ip = listenInfo->ip()->str(); // This may throw. Utils::IP::NormalizeIp(ip); std::string announcedAddress; if (flatbuffers::IsFieldPresent(listenInfo, FBS::Transport::ListenInfo::VT_ANNOUNCEDADDRESS)) { announcedAddress = listenInfo->announcedAddress()->str(); } const bool exposeInternalIp = listenInfo->exposeInternalIp(); RTC::Transport::SocketFlags flags; flags.ipv6Only = listenInfo->flags()->ipv6Only(); flags.udpReusePort = listenInfo->flags()->udpReusePort(); const uint16_t iceLocalPreference = IceCandidateDefaultLocalPriority - iceLocalPreferenceDecrement; const uint32_t icePriority = generateIceCandidatePriority(iceLocalPreference); if (listenInfo->protocol() == FBS::Transport::Protocol::UDP) { RTC::UdpSocket* udpSocket; if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0) { uint64_t portRangeHash{ 0u }; udpSocket = new RTC::UdpSocket( this, ip, listenInfo->portRange()->min(), listenInfo->portRange()->max(), flags, portRangeHash); } else if (listenInfo->port() != 0) { udpSocket = new RTC::UdpSocket(this, ip, listenInfo->port(), flags); } // NOTE: This is temporal to allow deprecated usage of worker port range. // In the future this should throw since |port| or |portRange| will be // required. else { uint64_t portRangeHash{ 0u }; udpSocket = new RTC::UdpSocket( this, ip, Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, flags, portRangeHash); } this->udpSockets[udpSocket] = announcedAddress; if (announcedAddress.empty()) { this->iceCandidates.emplace_back(udpSocket, icePriority); } else { this->iceCandidates.emplace_back(udpSocket, icePriority, announcedAddress); if (exposeInternalIp) { this->iceCandidates.emplace_back(udpSocket, icePriority - 1000); } } if (listenInfo->sendBufferSize() != 0) { // NOTE: This may throw. udpSocket->SetSendBufferSize(listenInfo->sendBufferSize()); } if (listenInfo->recvBufferSize() != 0) { // NOTE: This may throw. udpSocket->SetRecvBufferSize(listenInfo->recvBufferSize()); } MS_DEBUG_TAG( info, "UDP socket buffer sizes [send:%" PRIu32 ", recv:%" PRIu32 "]", udpSocket->GetSendBufferSize(), udpSocket->GetRecvBufferSize()); } else if (listenInfo->protocol() == FBS::Transport::Protocol::TCP) { RTC::TcpServer* tcpServer; if (listenInfo->portRange()->min() != 0 && listenInfo->portRange()->max() != 0) { uint64_t portRangeHash{ 0u }; tcpServer = new RTC::TcpServer( this, this, ip, listenInfo->portRange()->min(), listenInfo->portRange()->max(), flags, portRangeHash); } else if (listenInfo->port() != 0) { tcpServer = new RTC::TcpServer(this, this, ip, listenInfo->port(), flags); } // NOTE: This is temporal to allow deprecated usage of worker port range. // In the future this should throw since |port| or |portRange| will be // required. else { uint64_t portRangeHash{ 0u }; tcpServer = new RTC::TcpServer( this, this, ip, Settings::configuration.rtcMinPort, Settings::configuration.rtcMaxPort, flags, portRangeHash); } this->tcpServers[tcpServer] = announcedAddress; if (announcedAddress.empty()) { this->iceCandidates.emplace_back(tcpServer, icePriority); } else { this->iceCandidates.emplace_back(tcpServer, icePriority, announcedAddress); if (exposeInternalIp) { this->iceCandidates.emplace_back(tcpServer, icePriority - 1000); } } if (listenInfo->sendBufferSize() != 0) { // NOTE: This may throw. tcpServer->SetSendBufferSize(listenInfo->sendBufferSize()); } if (listenInfo->recvBufferSize() != 0) { // NOTE: This may throw. tcpServer->SetRecvBufferSize(listenInfo->recvBufferSize()); } MS_DEBUG_TAG( info, "TCP sockets buffer sizes [send:%" PRIu32 ", recv:%" PRIu32 "]", tcpServer->GetSendBufferSize(), tcpServer->GetRecvBufferSize()); } // Decrement initial ICE local preference for next IP. iceLocalPreferenceDecrement += 100; } auto iceConsentTimeout = options->iceConsentTimeout(); // Create a ICE server. this->iceServer = new RTC::ICE::IceServer( this, this->shared, Utils::Crypto::GetRandomString(32), Utils::Crypto::GetRandomString(32), iceConsentTimeout); // Create a DTLS transport. this->dtlsTransport = new RTC::DtlsTransport(this, this->shared); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ this); } catch (const MediaSoupError& error) { // Must delete everything since the destructor won't be called. delete this->dtlsTransport; this->dtlsTransport = nullptr; delete this->iceServer; this->iceServer = nullptr; for (auto& kv : this->udpSockets) { auto* udpSocket = kv.first; delete udpSocket; } this->udpSockets.clear(); for (auto& kv : this->tcpServers) { auto* tcpServer = kv.first; delete tcpServer; } this->tcpServers.clear(); this->iceCandidates.clear(); throw; } } /** * This constructor is used when the WebRtcTransport uses a WebRtcServer. */ WebRtcTransport::WebRtcTransport( SharedInterface* shared, const std::string& id, RTC::Transport::Listener* listener, WebRtcTransportListener* webRtcTransportListener, const std::vector& iceCandidates, const FBS::WebRtcTransport::WebRtcTransportOptions* options) : RTC::Transport::Transport(shared, id, listener, options->base()), webRtcTransportListener(webRtcTransportListener), iceCandidates(iceCandidates) { MS_TRACE(); try { if (iceCandidates.empty()) { MS_THROW_TYPE_ERROR("empty iceCandidates"); } auto iceConsentTimeout = options->iceConsentTimeout(); // Create a ICE server. this->iceServer = new RTC::ICE::IceServer( this, this->shared, Utils::Crypto::GetRandomString(32), Utils::Crypto::GetRandomString(32), iceConsentTimeout); // Create a DTLS transport. this->dtlsTransport = new RTC::DtlsTransport(this, this->shared); // Notify the webRtcTransportListener. this->webRtcTransportListener->OnWebRtcTransportCreated(this); // NOTE: This may throw. this->shared->GetChannelMessageRegistrator()->RegisterHandler( this->id, /*channelRequestHandler*/ this, /*channelNotificationHandler*/ this); } catch (const MediaSoupError& error) { // Must delete everything since the destructor won't be called. delete this->dtlsTransport; this->dtlsTransport = nullptr; delete this->iceServer; this->iceServer = nullptr; throw; } } WebRtcTransport::~WebRtcTransport() { MS_TRACE(); // We need to tell the Transport parent class that we are about to destroy // the class instance. This is because child's destructor runs before // parent's destructor. See comment in Transport::OnSctpAssociationSendData(). SetDestroying(); this->shared->GetChannelMessageRegistrator()->UnregisterHandler(this->id); // Must delete the DTLS transport first since it will generate a DTLS alert // to be sent. delete this->dtlsTransport; this->dtlsTransport = nullptr; delete this->iceServer; this->iceServer = nullptr; for (auto& kv : this->udpSockets) { auto* udpSocket = kv.first; delete udpSocket; } this->udpSockets.clear(); for (auto& kv : this->tcpServers) { auto* tcpServer = kv.first; delete tcpServer; } this->tcpServers.clear(); this->iceCandidates.clear(); delete this->srtpSendSession; this->srtpSendSession = nullptr; delete this->srtpRecvSession; this->srtpRecvSession = nullptr; // Notify the webRtcTransportListener. if (this->webRtcTransportListener) { this->webRtcTransportListener->OnWebRtcTransportClosed(this); } } flatbuffers::Offset WebRtcTransport::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); // Add iceParameters. auto iceParameters = FBS::WebRtcTransport::CreateIceParametersDirect( builder, this->iceServer->GetUsernameFragment().c_str(), this->iceServer->GetPassword().c_str(), true); std::vector> iceCandidates; iceCandidates.reserve(this->iceCandidates.size()); for (const auto& iceCandidate : this->iceCandidates) { iceCandidates.emplace_back(iceCandidate.FillBuffer(builder)); } // Add iceState. auto iceState = RTC::ICE::IceServer::IceStateToFbs(this->iceServer->GetState()); // Add iceSelectedTuple. flatbuffers::Offset iceSelectedTuple; if (this->iceServer->GetSelectedTuple()) { iceSelectedTuple = this->iceServer->GetSelectedTuple()->FillBuffer(builder); } // Add dtlsParameters.fingerprints. std::vector> fingerprints; for (const auto& fingerprint : RTC::DtlsTransport::GetLocalFingerprints()) { auto algorithm = DtlsTransport::AlgorithmToFbs(fingerprint.algorithm); const auto& value = fingerprint.value; fingerprints.emplace_back( FBS::WebRtcTransport::CreateFingerprintDirect(builder, algorithm, value.c_str())); } // Add dtlsParameters.role. auto dtlsRole = DtlsTransport::RoleToFbs(this->dtlsRole); auto dtlsState = DtlsTransport::StateToFbs(this->dtlsTransport->GetState()); // Add base transport dump. auto base = Transport::FillBuffer(builder); // Add dtlsParameters. auto dtlsParameters = FBS::WebRtcTransport::CreateDtlsParametersDirect(builder, &fingerprints, dtlsRole); return FBS::WebRtcTransport::CreateDumpResponseDirect( builder, base, FBS::WebRtcTransport::IceRole::CONTROLLED, iceParameters, &iceCandidates, iceState, iceSelectedTuple, dtlsParameters, dtlsState); } flatbuffers::Offset WebRtcTransport::FillBufferStats( flatbuffers::FlatBufferBuilder& builder) { MS_TRACE(); // Add iceState. auto iceState = RTC::ICE::IceServer::IceStateToFbs(this->iceServer->GetState()); // Add iceSelectedTuple. flatbuffers::Offset iceSelectedTuple; if (this->iceServer->GetSelectedTuple()) { iceSelectedTuple = this->iceServer->GetSelectedTuple()->FillBuffer(builder); } auto dtlsState = DtlsTransport::StateToFbs(this->dtlsTransport->GetState()); // Base Transport stats. auto base = Transport::FillBufferStats(builder); return FBS::WebRtcTransport::CreateGetStatsResponse( builder, base, // iceRole (we are always "controlled"). FBS::WebRtcTransport::IceRole::CONTROLLED, iceState, iceSelectedTuple, dtlsState); } void WebRtcTransport::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::TRANSPORT_GET_STATS: { auto responseOffset = FillBufferStats(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::WebRtcTransport_GetStatsResponse, responseOffset); break; } case Channel::ChannelRequest::Method::TRANSPORT_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::WebRtcTransport_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::WEBRTCTRANSPORT_CONNECT: { // Ensure this method is not called twice. if (this->connectCalled) { MS_THROW_ERROR("connect() already called"); } const auto* body = request->data->body_as(); const auto* dtlsParameters = body->dtlsParameters(); RTC::DtlsTransport::Fingerprint dtlsRemoteFingerprint; RTC::DtlsTransport::Role dtlsRemoteRole; if (dtlsParameters->fingerprints()->size() == 0) { MS_THROW_TYPE_ERROR("empty dtlsParameters.fingerprints array"); } // NOTE: Just take the first fingerprint. for (const auto& fingerprint : *dtlsParameters->fingerprints()) { dtlsRemoteFingerprint.algorithm = DtlsTransport::AlgorithmFromFbs(fingerprint->algorithm()); dtlsRemoteFingerprint.value = fingerprint->value()->str(); // Just use the first fingerprint. break; } dtlsRemoteRole = RTC::DtlsTransport::RoleFromFbs(dtlsParameters->role()); // Set local DTLS role. switch (dtlsRemoteRole) { case RTC::DtlsTransport::Role::CLIENT: { this->dtlsRole = RTC::DtlsTransport::Role::SERVER; break; } // If the peer has role "auto" we become "client" since we are ICE controlled. case RTC::DtlsTransport::Role::SERVER: case RTC::DtlsTransport::Role::AUTO: { this->dtlsRole = RTC::DtlsTransport::Role::CLIENT; break; } } this->connectCalled = true; // Pass the remote fingerprint to the DTLS transport. if (this->dtlsTransport->SetRemoteFingerprint(dtlsRemoteFingerprint)) { // If everything is fine, we may run the DTLS transport if ready. MayRunDtlsTransport(); } // Tell the caller about the selected local DTLS role. auto dtlsLocalRole = DtlsTransport::RoleToFbs(this->dtlsRole); auto responseOffset = FBS::WebRtcTransport::CreateConnectResponse(request->GetBufferBuilder(), dtlsLocalRole); request->Accept(FBS::Response::Body::WebRtcTransport_ConnectResponse, responseOffset); break; } case Channel::ChannelRequest::Method::TRANSPORT_RESTART_ICE: { const std::string usernameFragment = Utils::Crypto::GetRandomString(32); const std::string password = Utils::Crypto::GetRandomString(32); this->iceServer->RestartIce(usernameFragment, password); MS_DEBUG_DEV( "WebRtcTransport ICE usernameFragment and password changed [id:%s]", this->id.c_str()); // Reply with the updated ICE local parameters. auto responseOffset = FBS::Transport::CreateRestartIceResponseDirect( request->GetBufferBuilder(), this->iceServer->GetUsernameFragment().c_str(), this->iceServer->GetPassword().c_str(), true /* iceLite */ ); request->Accept(FBS::Response::Body::Transport_RestartIceResponse, responseOffset); break; } default: { // Pass it to the parent class. RTC::Transport::HandleRequest(request); } } } void WebRtcTransport::HandleNotification(Channel::ChannelNotification* notification) { MS_TRACE(); // Pass it to the parent class. RTC::Transport::HandleNotification(notification); } void WebRtcTransport::ProcessStunPacketFromWebRtcServer( RTC::TransportTuple* tuple, const RTC::ICE::StunPacket* packet) { MS_TRACE(); // Pass it to the IceServer. this->iceServer->ProcessStunPacket(packet, tuple); } void WebRtcTransport::ProcessNonStunPacketFromWebRtcServer( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); // Increase receive transmission. RTC::Transport::DataReceived(len); // Check if it's RTCP. if (RTC::RTCP::Packet::IsRtcp(data, len)) { OnRtcpDataReceived(tuple, data, len); } // Check if it's RTP. else if (RTC::RTP::Packet::IsRtp(data, len)) { OnRtpDataReceived(tuple, data, len, bufferLen); } // Check if it's DTLS. else if (RTC::DtlsTransport::IsDtls(data, len)) { OnDtlsDataReceived(tuple, data, len); } else { MS_WARN_DEV("ignoring received packet of unknown type"); } } void WebRtcTransport::RemoveTuple(RTC::TransportTuple* tuple) { MS_TRACE(); this->iceServer->RemoveTuple(tuple); } inline bool WebRtcTransport::IsConnected() const { MS_TRACE(); return ( (this->iceServer->GetState() == RTC::ICE::IceServer::IceState::CONNECTED || this->iceServer->GetState() == RTC::ICE::IceServer::IceState::COMPLETED) && this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED); } void WebRtcTransport::MayRunDtlsTransport() { MS_TRACE(); // Dont' start DTLS handshake if ICE is not connected/completed. if (!this->iceServer->GetSelectedTuple()) { return; } // Do nothing if we have the same local DTLS role as the DTLS transport. // NOTE: local role in DTLS transport can be NONE, but not ours. if (this->dtlsTransport->GetLocalRole() == this->dtlsRole) { return; } // Check our local DTLS role. switch (this->dtlsRole) { // If still 'auto' then transition to 'server' if ICE is 'connected' or // 'completed'. case RTC::DtlsTransport::Role::AUTO: { if ( this->iceServer->GetState() == RTC::ICE::IceServer::IceState::CONNECTED || this->iceServer->GetState() == RTC::ICE::IceServer::IceState::COMPLETED) { MS_DEBUG_TAG( dtls, "transition from DTLS local role 'auto' to 'server' and running DTLS transport"); this->dtlsRole = RTC::DtlsTransport::Role::SERVER; this->dtlsTransport->Run(RTC::DtlsTransport::Role::SERVER); } break; } // 'client' is only set if a 'connect' request was previously called with // remote DTLS role 'server'. // // If 'client' then wait for ICE to be 'completed' (got USE-CANDIDATE). // // NOTE: This is the theory, however let's be more flexible as told here: // https://bugs.chromium.org/p/webrtc/issues/detail?id=3661 case RTC::DtlsTransport::Role::CLIENT: { if ( this->iceServer->GetState() == RTC::ICE::IceServer::IceState::CONNECTED || this->iceServer->GetState() == RTC::ICE::IceServer::IceState::COMPLETED) { MS_DEBUG_TAG(dtls, "running DTLS transport in local role 'client'"); this->dtlsTransport->Run(RTC::DtlsTransport::Role::CLIENT); } break; } // If 'server' then run the DTLS transport if ICE is 'connected' (not yet // USE-CANDIDATE) or 'completed'. case RTC::DtlsTransport::Role::SERVER: { if ( this->iceServer->GetState() == RTC::ICE::IceServer::IceState::CONNECTED || this->iceServer->GetState() == RTC::ICE::IceServer::IceState::COMPLETED) { MS_DEBUG_TAG(dtls, "running DTLS transport in local role 'server'"); this->dtlsTransport->Run(RTC::DtlsTransport::Role::SERVER); } break; } } } void WebRtcTransport::SendRtpPacket( RTC::Consumer* /*consumer*/, RTC::RTP::Packet* packet, const RTC::Transport::onSendCallback* cb) { MS_TRACE(); if (!IsConnected()) { if (cb) { (*cb)(false); delete cb; } return; } // Ensure there is sending SRTP session. if (!this->srtpSendSession) { MS_WARN_DEV("ignoring RTP packet due to non sending SRTP session"); if (cb) { (*cb)(false); delete cb; } return; } const uint8_t* data = packet->GetBuffer(); auto len = packet->GetLength(); if (!this->srtpSendSession->EncryptRtp(&data, &len)) { if (cb) { (*cb)(false); delete cb; } return; } this->iceServer->GetSelectedTuple()->Send(data, len, cb); // Increase send transmission. RTC::Transport::DataSent(len); } void WebRtcTransport::SendRtcpPacket(RTC::RTCP::Packet* packet) { MS_TRACE(); if (!IsConnected()) { return; } const uint8_t* data = packet->GetData(); auto len = packet->GetSize(); // Ensure there is sending SRTP session. if (!this->srtpSendSession) { MS_WARN_DEV("ignoring RTCP packet due to non sending SRTP session"); return; } if (!this->srtpSendSession->EncryptRtcp(&data, &len)) { return; } this->iceServer->GetSelectedTuple()->Send(data, len); // Increase send transmission. RTC::Transport::DataSent(len); } void WebRtcTransport::SendRtcpCompoundPacket(RTC::RTCP::CompoundPacket* packet) { MS_TRACE(); if (!IsConnected()) { return; } packet->Serialize(RTC::RTCP::SerializationBuffer); const uint8_t* data = packet->GetData(); auto len = packet->GetSize(); // Ensure there is sending SRTP session. if (!this->srtpSendSession) { MS_WARN_TAG(rtcp, "ignoring RTCP compound packet due to non sending SRTP session"); return; } if (!this->srtpSendSession->EncryptRtcp(&data, &len)) { return; } this->iceServer->GetSelectedTuple()->Send(data, len); // Increase send transmission. RTC::Transport::DataSent(len); } // TODO: SCTP: Remove once we only use built-in SCTP stack. void WebRtcTransport::SendMessage( RTC::DataConsumer* dataConsumer, const uint8_t* msg, size_t len, uint32_t ppid, onQueuedCallback* cb) { MS_TRACE(); SendSctpMessage(dataConsumer, msg, len, ppid, cb); } void WebRtcTransport::SendMessage( RTC::DataConsumer* dataConsumer, RTC::SCTP::Message message, onQueuedCallback* cb) { MS_TRACE(); SendSctpMessage(dataConsumer, std::move(message), cb); } bool WebRtcTransport::SendData(const uint8_t* data, size_t len) { MS_TRACE(); if (!IsConnected()) { MS_WARN_TAG(sctp, "DTLS not connected, cannot send SCTP data"); return false; } return this->dtlsTransport->SendApplicationData(data, len); } void WebRtcTransport::RecvStreamClosed(uint32_t ssrc) { MS_TRACE(); if (this->srtpRecvSession) { this->srtpRecvSession->RemoveStream(ssrc); } } void WebRtcTransport::SendStreamClosed(uint32_t ssrc) { MS_TRACE(); if (this->srtpSendSession) { this->srtpSendSession->RemoveStream(ssrc); } } inline void WebRtcTransport::OnPacketReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); // Increase receive transmission. RTC::Transport::DataReceived(len); // Check if it's STUN. if (RTC::ICE::StunPacket::IsStun(data, len)) { OnStunDataReceived(tuple, data, len); } // Check if it's RTCP. else if (RTC::RTCP::Packet::IsRtcp(data, len)) { OnRtcpDataReceived(tuple, data, len); } // Check if it's RTP. else if (RTC::RTP::Packet::IsRtp(data, len)) { OnRtpDataReceived(tuple, data, len, bufferLen); } // Check if it's DTLS. else if (RTC::DtlsTransport::IsDtls(data, len)) { OnDtlsDataReceived(tuple, data, len); } else { MS_WARN_DEV("ignoring received packet of unknown type"); } } inline void WebRtcTransport::OnStunDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len) { MS_TRACE(); const auto* packet = RTC::ICE::StunPacket::Parse(data, len); if (!packet) { MS_WARN_DEV("ignoring wrong STUN packet received"); return; } // Pass it to the IceServer. this->iceServer->ProcessStunPacket(packet, tuple); delete packet; } inline void WebRtcTransport::OnDtlsDataReceived( const RTC::TransportTuple* tuple, const uint8_t* data, size_t len) { MS_TRACE(); // Ensure it comes from a valid tuple. if (!this->iceServer->IsValidTuple(tuple)) { MS_WARN_TAG(dtls, "ignoring DTLS data coming from an invalid tuple"); return; } // Trick for clients performing aggressive ICE regardless we are ICE-Lite. this->iceServer->MayForceSelectedTuple(tuple); // Check that DTLS status is 'connecting' or 'connected'. if ( this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTING || this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED) { MS_DEBUG_DEV("DTLS data received, passing it to the DTLS transport"); this->dtlsTransport->ProcessDtlsData(data, len); } else { MS_WARN_TAG(dtls, "Transport is not 'connecting' or 'connected', ignoring received DTLS data"); return; } } inline void WebRtcTransport::OnRtpDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); // Ensure DTLS is connected. if (this->dtlsTransport->GetState() != RTC::DtlsTransport::DtlsState::CONNECTED) { MS_DEBUG_2TAGS(dtls, rtp, "ignoring RTP packet while DTLS not connected"); return; } // Ensure there is receiving SRTP session. if (!this->srtpRecvSession) { MS_DEBUG_TAG(srtp, "ignoring RTP packet due to non receiving SRTP session"); return; } // Ensure it comes from a valid tuple. if (!this->iceServer->IsValidTuple(tuple)) { MS_WARN_TAG(rtp, "ignoring RTP packet coming from an invalid tuple"); return; } // Decrypt the SRTP packet. if (!this->srtpRecvSession->DecryptSrtp(const_cast(data), &len)) { const auto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen); if (!packet) { MS_WARN_TAG(srtp, "DecryptSrtp() failed due to an invalid RTP packet"); } else { MS_WARN_TAG( srtp, "DecryptSrtp() failed [ssrc:%" PRIu32 ", payloadType:%" PRIu8 ", seq:%" PRIu16 "]", packet->GetSsrc(), packet->GetPayloadType(), packet->GetSequenceNumber()); delete packet; } return; } auto* packet = RTC::RTP::Packet::Parse(data, len, bufferLen); if (!packet) { MS_WARN_TAG(rtp, "received data is not a valid RTP packet"); return; } // Trick for clients performing aggressive ICE regardless we are ICE-Lite. this->iceServer->MayForceSelectedTuple(tuple); // Pass the packet to the parent transport. RTC::Transport::ReceiveRtpPacket(packet); } inline void WebRtcTransport::OnRtcpDataReceived( RTC::TransportTuple* tuple, const uint8_t* data, size_t len) { MS_TRACE(); // Ensure DTLS is connected. if (this->dtlsTransport->GetState() != RTC::DtlsTransport::DtlsState::CONNECTED) { MS_DEBUG_2TAGS(dtls, rtcp, "ignoring RTCP packet while DTLS not connected"); return; } // Ensure there is receiving SRTP session. if (!this->srtpRecvSession) { MS_DEBUG_TAG(srtp, "ignoring RTCP packet due to non receiving SRTP session"); return; } // Ensure it comes from a valid tuple. if (!this->iceServer->IsValidTuple(tuple)) { MS_WARN_TAG(rtcp, "ignoring RTCP packet coming from an invalid tuple"); return; } // Decrypt the SRTCP packet. if (!this->srtpRecvSession->DecryptSrtcp(const_cast(data), &len)) { return; } auto* packet = RTC::RTCP::Packet::Parse(data, len); if (!packet) { MS_WARN_TAG(rtcp, "received data is not a valid RTCP compound or single packet"); return; } // Pass the packet to the parent transport. RTC::Transport::ReceiveRtcpPacket(packet); } inline void WebRtcTransport::OnUdpSocketPacketReceived( RTC::UdpSocket* socket, const uint8_t* data, size_t len, size_t bufferLen, const struct sockaddr* remoteAddr) { MS_TRACE(); RTC::TransportTuple tuple(socket, remoteAddr); OnPacketReceived(&tuple, data, len, bufferLen); } inline void WebRtcTransport::OnRtcTcpConnectionClosed( RTC::TcpServer* /*tcpServer*/, RTC::TcpConnection* connection) { MS_TRACE(); RTC::TransportTuple tuple(connection); this->iceServer->RemoveTuple(&tuple); } inline void WebRtcTransport::OnTcpConnectionPacketReceived( RTC::TcpConnection* connection, const uint8_t* data, size_t len, size_t bufferLen) { MS_TRACE(); RTC::TransportTuple tuple(connection); OnPacketReceived(&tuple, data, len, bufferLen); } inline void WebRtcTransport::OnIceServerSendStunPacket( const RTC::ICE::IceServer* /*iceServer*/, const RTC::ICE::StunPacket* packet, RTC::TransportTuple* tuple) { MS_TRACE(); // Send the STUN response over the same transport tuple. tuple->Send(packet->GetBuffer(), packet->GetLength()); // Increase send transmission. RTC::Transport::DataSent(packet->GetLength()); } inline void WebRtcTransport::OnIceServerLocalUsernameFragmentAdded( const RTC::ICE::IceServer* /*iceServer*/, const std::string& usernameFragment) { MS_TRACE(); if (this->webRtcTransportListener) { this->webRtcTransportListener->OnWebRtcTransportLocalIceUsernameFragmentAdded( this, usernameFragment); } } inline void WebRtcTransport::OnIceServerLocalUsernameFragmentRemoved( const RTC::ICE::IceServer* /*iceServer*/, const std::string& usernameFragment) { MS_TRACE(); if (this->webRtcTransportListener) { this->webRtcTransportListener->OnWebRtcTransportLocalIceUsernameFragmentRemoved( this, usernameFragment); } } inline void WebRtcTransport::OnIceServerTupleAdded( const RTC::ICE::IceServer* /*iceServer*/, RTC::TransportTuple* tuple) { MS_TRACE(); if (this->webRtcTransportListener) { this->webRtcTransportListener->OnWebRtcTransportTransportTupleAdded(this, tuple); } } inline void WebRtcTransport::OnIceServerTupleRemoved( const RTC::ICE::IceServer* /*iceServer*/, RTC::TransportTuple* tuple) { MS_TRACE(); if (this->webRtcTransportListener) { this->webRtcTransportListener->OnWebRtcTransportTransportTupleRemoved(this, tuple); } // If this is a TCP tuple, close its underlaying TCP connection. if (tuple->GetProtocol() == RTC::TransportTuple::Protocol::TCP) { tuple->CloseTcpConnection(); } } inline void WebRtcTransport::OnIceServerSelectedTuple( const RTC::ICE::IceServer* /*iceServer*/, RTC::TransportTuple* /*tuple*/) { MS_TRACE(); /* * RFC 5245 section 11.2 "Receiving Media": * * ICE implementations MUST be prepared to receive media on each component * on any candidates provided for that component. */ MS_DEBUG_TAG(ice, "ICE selected tuple"); // Notify the Node WebRtcTransport. auto tuple = this->iceServer->GetSelectedTuple()->FillBuffer( this->shared->GetChannelNotifier()->GetBufferBuilder()); auto notification = FBS::WebRtcTransport::CreateIceSelectedTupleChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), tuple); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::WEBRTCTRANSPORT_ICE_SELECTED_TUPLE_CHANGE, FBS::Notification::Body::WebRtcTransport_IceSelectedTupleChangeNotification, notification); } inline void WebRtcTransport::OnIceServerConnected(const RTC::ICE::IceServer* /*iceServer*/) { MS_TRACE(); MS_DEBUG_TAG(ice, "ICE connected"); // Notify the Node WebRtcTransport. auto iceStateChangeOffset = FBS::WebRtcTransport::CreateIceStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::WebRtcTransport::IceState::CONNECTED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::WEBRTCTRANSPORT_ICE_STATE_CHANGE, FBS::Notification::Body::WebRtcTransport_IceStateChangeNotification, iceStateChangeOffset); // If ready, run the DTLS handler. MayRunDtlsTransport(); // If DTLS was already connected, notify the parent class. if (this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED) { RTC::Transport::Connected(); } } inline void WebRtcTransport::OnIceServerCompleted(const RTC::ICE::IceServer* /*iceServer*/) { MS_TRACE(); MS_DEBUG_TAG(ice, "ICE completed"); // Notify the Node WebRtcTransport. auto iceStateChangeOffset = FBS::WebRtcTransport::CreateIceStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::WebRtcTransport::IceState::COMPLETED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::WEBRTCTRANSPORT_ICE_STATE_CHANGE, FBS::Notification::Body::WebRtcTransport_IceStateChangeNotification, iceStateChangeOffset); // If ready, run the DTLS handler. MayRunDtlsTransport(); // If DTLS was already connected, notify the parent class. if (this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED) { RTC::Transport::Connected(); } } inline void WebRtcTransport::OnIceServerDisconnected(const RTC::ICE::IceServer* /*iceServer*/) { MS_TRACE(); MS_DEBUG_TAG(ice, "ICE disconnected"); // Notify the Node WebRtcTransport. auto iceStateChangeOffset = FBS::WebRtcTransport::CreateIceStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::WebRtcTransport::IceState::DISCONNECTED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::WEBRTCTRANSPORT_ICE_STATE_CHANGE, FBS::Notification::Body::WebRtcTransport_IceStateChangeNotification, iceStateChangeOffset); // If DTLS was already connected, notify the parent class. if (this->dtlsTransport->GetState() == RTC::DtlsTransport::DtlsState::CONNECTED) { RTC::Transport::Disconnected(); } } inline void WebRtcTransport::OnDtlsTransportConnecting(const RTC::DtlsTransport* /*dtlsTransport*/) { MS_TRACE(); MS_DEBUG_TAG(dtls, "DTLS connecting"); // Notify the Node WebRtcTransport. auto dtlsStateChangeOffset = FBS::WebRtcTransport::CreateDtlsStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::WebRtcTransport::DtlsState::CONNECTING); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::WEBRTCTRANSPORT_DTLS_STATE_CHANGE, FBS::Notification::Body::WebRtcTransport_DtlsStateChangeNotification, dtlsStateChangeOffset); } inline void WebRtcTransport::OnDtlsTransportConnected( const RTC::DtlsTransport* /*dtlsTransport*/, RTC::SrtpSession::CryptoSuite srtpCryptoSuite, uint8_t* srtpLocalKey, size_t srtpLocalKeyLen, uint8_t* srtpRemoteKey, size_t srtpRemoteKeyLen, std::string& remoteCert) { MS_TRACE(); MS_DEBUG_TAG(dtls, "DTLS connected"); // Close it if it was already set and update it. delete this->srtpSendSession; this->srtpSendSession = nullptr; delete this->srtpRecvSession; this->srtpRecvSession = nullptr; try { this->srtpSendSession = new RTC::SrtpSession( RTC::SrtpSession::Type::OUTBOUND, srtpCryptoSuite, srtpLocalKey, srtpLocalKeyLen); } catch (const MediaSoupError& error) { MS_ERROR("error creating SRTP sending session: %s", error.what()); } try { this->srtpRecvSession = new RTC::SrtpSession( RTC::SrtpSession::Type::INBOUND, srtpCryptoSuite, srtpRemoteKey, srtpRemoteKeyLen); // Notify the Node WebRtcTransport. auto dtlsStateChangeOffset = FBS::WebRtcTransport::CreateDtlsStateChangeNotificationDirect( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::WebRtcTransport::DtlsState::CONNECTED, remoteCert.c_str()); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::WEBRTCTRANSPORT_DTLS_STATE_CHANGE, FBS::Notification::Body::WebRtcTransport_DtlsStateChangeNotification, dtlsStateChangeOffset); // Tell the parent class. RTC::Transport::Connected(); } catch (const MediaSoupError& error) { MS_ERROR("error creating SRTP receiving session: %s", error.what()); delete this->srtpSendSession; this->srtpSendSession = nullptr; } } inline void WebRtcTransport::OnDtlsTransportFailed(const RTC::DtlsTransport* /*dtlsTransport*/) { MS_TRACE(); MS_WARN_TAG(dtls, "DTLS failed"); // Notify the Node WebRtcTransport. auto dtlsStateChangeOffset = FBS::WebRtcTransport::CreateDtlsStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::WebRtcTransport::DtlsState::FAILED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::WEBRTCTRANSPORT_DTLS_STATE_CHANGE, FBS::Notification::Body::WebRtcTransport_DtlsStateChangeNotification, dtlsStateChangeOffset); } inline void WebRtcTransport::OnDtlsTransportClosed(const RTC::DtlsTransport* /*dtlsTransport*/) { MS_TRACE(); MS_WARN_TAG(dtls, "DTLS remotely closed"); // Notify the Node WebRtcTransport. auto dtlsStateChangeOffset = FBS::WebRtcTransport::CreateDtlsStateChangeNotification( this->shared->GetChannelNotifier()->GetBufferBuilder(), FBS::WebRtcTransport::DtlsState::CLOSED); this->shared->GetChannelNotifier()->Emit( this->id, FBS::Notification::Event::WEBRTCTRANSPORT_DTLS_STATE_CHANGE, FBS::Notification::Body::WebRtcTransport_DtlsStateChangeNotification, dtlsStateChangeOffset); // Tell the parent class. RTC::Transport::Disconnected(); } inline void WebRtcTransport::OnDtlsTransportSendData( const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* data, size_t len) { MS_TRACE(); if (!this->iceServer->GetSelectedTuple()) { MS_WARN_TAG(dtls, "no selected tuple set, cannot send DTLS packet"); return; } this->iceServer->GetSelectedTuple()->Send(data, len); // Increase send transmission. RTC::Transport::DataSent(len); } inline void WebRtcTransport::OnDtlsTransportApplicationDataReceived( const RTC::DtlsTransport* /*dtlsTransport*/, const uint8_t* data, size_t len) { MS_TRACE(); // Pass it to the parent transport. RTC::Transport::ReceiveSctpData(data, len); } } // namespace RTC ================================================ FILE: worker/src/Settings.cpp ================================================ #define MS_CLASS "Settings" // #define MS_LOG_DEV_LEVEL 3 #include "Settings.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include #include // isprint() #include // std::ostream_iterator #include #include // std::ostringstream extern "C" { #include } /* Static. */ static std::mutex GlobalSyncMutex; /* Class variables. */ thread_local struct Settings::Configuration Settings::configuration; // clang-format off const absl::flat_hash_map Settings::String2LogLevel = { { "debug", LogLevel::LOG_DEBUG }, { "warn", LogLevel::LOG_WARN }, { "error", LogLevel::LOG_ERROR }, { "none", LogLevel::LOG_NONE } }; const absl::flat_hash_map Settings::LogLevel2String = { { LogLevel::LOG_DEBUG, "debug" }, { LogLevel::LOG_WARN, "warn" }, { LogLevel::LOG_ERROR, "error" }, { LogLevel::LOG_NONE, "none" } }; // clang-format on /* Class methods. */ void Settings::SetConfiguration(int argc, char* argv[]) { MS_TRACE(); /* Variables for getopt. */ int c; int optionIdx{ 0 }; // clang-format off struct option options[] = { { .name="logLevel", .has_arg=optional_argument, .flag=nullptr, .val='l' }, { .name="logTags", .has_arg=optional_argument, .flag=nullptr, .val='t' }, { .name="rtcMinPort", .has_arg=optional_argument, .flag=nullptr, .val='m' }, { .name="rtcMaxPort", .has_arg=optional_argument, .flag=nullptr, .val='M' }, { .name="dtlsCertificateFile", .has_arg=optional_argument, .flag=nullptr, .val='c' }, { .name="dtlsPrivateKeyFile", .has_arg=optional_argument, .flag=nullptr, .val='p' }, { .name="libwebrtcFieldTrials", .has_arg=optional_argument, .flag=nullptr, .val='W' }, { .name="disableLiburing", .has_arg=optional_argument, .flag=nullptr, .val='d' }, { .name="useBuiltInSctpStack", .has_arg=optional_argument, .flag=nullptr, .val='s' }, { .name=nullptr, .has_arg=0, .flag=nullptr, .val=0 } }; // clang-format on std::string stringValue; std::vector logTags; /* Parse command line options. */ // getopt_long_only() is not thread-safe const std::scoped_lock lock(GlobalSyncMutex); optind = 1; // Set explicitly, otherwise subsequent runs will fail. opterr = 0; // Don't allow getopt to print error messages. while ((c = getopt_long_only(argc, argv, "", options, std::addressof(optionIdx))) != -1) { if (!optarg) { MS_THROW_TYPE_ERROR("missing value in command line argument in option '%c'", c); } switch (c) { case 'l': { stringValue = std::string(optarg); SetLogLevel(stringValue); break; } case 't': { stringValue = std::string(optarg); logTags.push_back(stringValue); break; } case 'm': { try { Settings::configuration.rtcMinPort = static_cast(std::stoi(optarg)); } catch (const std::exception& error) { MS_THROW_TYPE_ERROR("%s", error.what()); } break; } case 'M': { try { Settings::configuration.rtcMaxPort = static_cast(std::stoi(optarg)); } catch (const std::exception& error) { MS_THROW_TYPE_ERROR("%s", error.what()); } break; } case 'c': { stringValue = std::string(optarg); Settings::configuration.dtlsCertificateFile = stringValue; break; } case 'p': { stringValue = std::string(optarg); Settings::configuration.dtlsPrivateKeyFile = stringValue; break; } case 'W': { stringValue = std::string(optarg); if (stringValue != Settings::configuration.libwebrtcFieldTrials) { MS_WARN_TAG( info, "overriding default value of libwebrtcFieldTrials may generate crashes in mediasoup-worker"); Settings::configuration.libwebrtcFieldTrials = stringValue; } break; } case 'd': { stringValue = std::string(optarg); if (stringValue == "true") { Settings::configuration.disableLiburing = true; } break; } case 's': { stringValue = std::string(optarg); if (stringValue == "true") { Settings::configuration.useBuiltInSctpStack = true; } else { Settings::configuration.useBuiltInSctpStack = false; } break; } // Invalid option. case '?': { if (isprint(optopt) != 0) { MS_THROW_TYPE_ERROR("invalid option '-%c'", (char)optopt); } else { MS_THROW_TYPE_ERROR("unknown long option given as argument"); } } // Valid option, but it requires and argument that is not given. case ':': { MS_THROW_TYPE_ERROR("option '%c' requires an argument", (char)optopt); } // This should never happen. default: { MS_THROW_TYPE_ERROR("'default' should never happen"); } } } /* Post configuration. */ // Set logTags. if (!logTags.empty()) { Settings::SetLogTags(logTags); } // Validate RTC ports. if (Settings::configuration.rtcMaxPort < Settings::configuration.rtcMinPort) { MS_THROW_TYPE_ERROR("rtcMaxPort cannot be less than rtcMinPort"); } // Set DTLS certificate files (if provided), Settings::SetDtlsCertificateAndPrivateKeyFiles(); } void Settings::SetLogLevel(std::string& level) { MS_TRACE(); // Lowcase given level. Utils::String::ToLowerCase(level); if (Settings::String2LogLevel.find(level) == Settings::String2LogLevel.end()) { MS_THROW_TYPE_ERROR("invalid value '%s' for logLevel", level.c_str()); } Settings::configuration.logLevel = Settings::String2LogLevel.at(level); } void Settings::SetLogTags(const std::vector& tags) { MS_TRACE(); // Reset logTags. struct LogTags logTags; for (const auto& tag : tags) { if (tag == "info") { logTags.info = true; } else if (tag == "ice") { logTags.ice = true; } else if (tag == "dtls") { logTags.dtls = true; } else if (tag == "rtp") { logTags.rtp = true; } else if (tag == "srtp") { logTags.srtp = true; } else if (tag == "rtcp") { logTags.rtcp = true; } else if (tag == "rtx") { logTags.rtx = true; } else if (tag == "bwe") { logTags.bwe = true; } else if (tag == "score") { logTags.score = true; } else if (tag == "simulcast") { logTags.simulcast = true; } else if (tag == "svc") { logTags.svc = true; } else if (tag == "sctp") { logTags.sctp = true; } else if (tag == "message") { logTags.message = true; } } Settings::configuration.logTags = logTags; } void Settings::PrintConfiguration() { MS_TRACE(); std::vector logTags; std::ostringstream logTagsStream; if (Settings::configuration.logTags.info) { logTags.emplace_back("info"); } if (Settings::configuration.logTags.ice) { logTags.emplace_back("ice"); } if (Settings::configuration.logTags.dtls) { logTags.emplace_back("dtls"); } if (Settings::configuration.logTags.rtp) { logTags.emplace_back("rtp"); } if (Settings::configuration.logTags.srtp) { logTags.emplace_back("srtp"); } if (Settings::configuration.logTags.rtcp) { logTags.emplace_back("rtcp"); } if (Settings::configuration.logTags.rtx) { logTags.emplace_back("rtx"); } if (Settings::configuration.logTags.bwe) { logTags.emplace_back("bwe"); } if (Settings::configuration.logTags.score) { logTags.emplace_back("score"); } if (Settings::configuration.logTags.simulcast) { logTags.emplace_back("simulcast"); } if (Settings::configuration.logTags.svc) { logTags.emplace_back("svc"); } if (Settings::configuration.logTags.sctp) { logTags.emplace_back("sctp"); } if (Settings::configuration.logTags.message) { logTags.emplace_back("message"); } if (!logTags.empty()) { std::copy( logTags.begin(), logTags.end() - 1, std::ostream_iterator(logTagsStream, ",")); logTagsStream << logTags.back(); } MS_DEBUG_TAG(info, ""); MS_DEBUG_TAG( info, " logLevel: %s", Settings::LogLevel2String.at(Settings::configuration.logLevel).c_str()); MS_DEBUG_TAG(info, " logTags: %s", logTagsStream.str().c_str()); MS_DEBUG_TAG(info, " rtcMinPort: %" PRIu16, Settings::configuration.rtcMinPort); MS_DEBUG_TAG(info, " rtcMaxPort: %" PRIu16, Settings::configuration.rtcMaxPort); if (!Settings::configuration.dtlsCertificateFile.empty()) { MS_DEBUG_TAG( info, " dtlsCertificateFile: %s", Settings::configuration.dtlsCertificateFile.c_str()); MS_DEBUG_TAG(info, " dtlsPrivateKeyFile: %s", Settings::configuration.dtlsPrivateKeyFile.c_str()); } if (!Settings::configuration.libwebrtcFieldTrials.empty()) { MS_DEBUG_TAG( info, " libwebrtcFieldTrials: %s", Settings::configuration.libwebrtcFieldTrials.c_str()); } MS_DEBUG_TAG(info, " disableLiburing: %s", Settings::configuration.disableLiburing ? "yes" : "no"); MS_DEBUG_TAG( info, " useBuiltInSctpStack: %s", Settings::configuration.useBuiltInSctpStack ? "yes" : "no"); MS_DEBUG_TAG(info, ""); } void Settings::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); switch (request->method) { case Channel::ChannelRequest::Method::WORKER_UPDATE_SETTINGS: { const auto* body = request->data->body_as(); if (flatbuffers::IsFieldPresent(body, FBS::Worker::UpdateSettingsRequest::VT_LOGLEVEL)) { auto logLevel = body->logLevel()->str(); // This may throw. Settings::SetLogLevel(logLevel); } // Update logTags if requested. if (flatbuffers::IsFieldPresent(body, FBS::Worker::UpdateSettingsRequest::VT_LOGTAGS)) { std::vector logTags; for (const auto& logTag : *body->logTags()) { logTags.push_back(logTag->str()); } Settings::SetLogTags(logTags); } // Print the new effective configuration. Settings::PrintConfiguration(); request->Accept(); break; } default: { MS_THROW_ERROR("unknown method '%s'", request->methodCStr); } } } void Settings::SetDtlsCertificateAndPrivateKeyFiles() { MS_TRACE(); if ( !Settings::configuration.dtlsCertificateFile.empty() && Settings::configuration.dtlsPrivateKeyFile.empty()) { MS_THROW_TYPE_ERROR("missing dtlsPrivateKeyFile"); } else if ( Settings::configuration.dtlsCertificateFile.empty() && !Settings::configuration.dtlsPrivateKeyFile.empty()) { MS_THROW_TYPE_ERROR("missing dtlsCertificateFile"); } else if ( Settings::configuration.dtlsCertificateFile.empty() && Settings::configuration.dtlsPrivateKeyFile.empty()) { return; } const std::string& dtlsCertificateFile = Settings::configuration.dtlsCertificateFile; const std::string& dtlsPrivateKeyFile = Settings::configuration.dtlsPrivateKeyFile; try { Utils::File::CheckFile(dtlsCertificateFile.c_str()); } catch (const MediaSoupError& error) { MS_THROW_TYPE_ERROR("dtlsCertificateFile: %s", error.what()); } try { Utils::File::CheckFile(dtlsPrivateKeyFile.c_str()); } catch (const MediaSoupError& error) { MS_THROW_TYPE_ERROR("dtlsPrivateKeyFile: %s", error.what()); } } ================================================ FILE: worker/src/Shared.cpp ================================================ #define MS_CLASS "Shared" // #define MS_LOG_DEV_LEVEL 3 #include "Shared.hpp" #include "Logger.hpp" #include "handles/BackoffTimerHandle.hpp" #include "handles/TimerHandle.hpp" Shared::Shared( Channel::ChannelMessageRegistrator* channelMessageRegistrator, Channel::ChannelNotifier* channelNotifier) : channelMessageRegistrator(channelMessageRegistrator), channelNotifier(channelNotifier) { MS_TRACE(); } Shared::~Shared() { MS_TRACE(); } TimerHandleInterface* Shared::CreateTimer(TimerHandleInterface::Listener* listener) { MS_TRACE(); return new TimerHandle(listener); } BackoffTimerHandleInterface* Shared::CreateBackoffTimer( const BackoffTimerHandleInterface::BackoffTimerHandleOptions& options) { MS_TRACE(); return new BackoffTimerHandle(options); } ================================================ FILE: worker/src/Utils/BitStream.cpp ================================================ #define MS_CLASS "Utils::BitStream" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "Utils.hpp" namespace Utils { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init, hicpp-member-init) BitStream::BitStream(uint8_t* data, size_t len) : len(len) { MS_TRACE(); std::memcpy(this->data, data, len); } const uint8_t* BitStream::GetData() const { MS_TRACE(); return this->data; } size_t BitStream::GetLength() const { MS_TRACE(); return this->len; } uint32_t BitStream::GetOffset() const { MS_TRACE(); return this->offset; } void BitStream::Reset() { MS_TRACE(); this->offset = 0; this->len = sizeof(this->data); std::memset(this->data, 0, this->len); } uint8_t BitStream::GetBit() { MS_TRACE(); auto bit = ((*(data + (this->offset >> 0x3))) >> (0x7 - (this->offset & 0x7))) & 0x1; this->offset++; return bit; } uint32_t BitStream::GetBits(size_t count) { MS_TRACE(); uint32_t bits = 0; for (unsigned i = 0; i < count; ++i) { bits = (2 * bits) + GetBit(); } return bits; } uint32_t BitStream::GetLeftBits() const { MS_TRACE(); if (this->offset >= this->len * 8) { return 0; } auto leftBits = (this->len * 8) - this->offset; return leftBits; } void BitStream::SkipBits(size_t count) { MS_TRACE(); this->offset += count; } /* * non-symmetric unsigned encoded integer with maximum * number of values n (i.e., output in range 0..n-1). * Returns std::nullopt if there are not enough bits left. */ std::optional BitStream::ReadNs(uint32_t n) { unsigned w = 0; unsigned x = n; while (x != 0) { x = x >> 1; ++w; } if (this->GetLeftBits() < w - 1) { return std::nullopt; } const unsigned v = this->GetBits(w - 1); const unsigned m = (1u << w) - n; if (v < m) { return v; } if (this->GetLeftBits() < 1) { return std::nullopt; } const unsigned extraBit = this->GetBit(); return (v << 1) - m + extraBit; } void BitStream::Write(uint32_t offset, uint32_t n, uint32_t v) { MS_TRACE(); unsigned w = 0; unsigned x = n; while (x != 0) { x = x >> 1; ++w; } const unsigned m = (1 << w) - n; if (v < m) { this->PutBits(offset, w - 1, v); } else { this->PutBits(offset, w, v + m); } } void BitStream::PutBit(uint8_t bit) { MS_TRACE(); this->PutBit(this->offset, bit); } void BitStream::PutBits(uint32_t count, uint32_t bits) { MS_TRACE(); this->PutBits(this->offset, count, bits); } void BitStream::PutBit(uint32_t offset, uint8_t bit) { MS_TRACE(); // Retrieve the current byte position. const size_t byteOffset = offset >> 0x3; // Calculate the bitmask for the target bit within the current byte. const auto bitmask = (1u << (0x7 - (offset & 0x7))); if (bit) { this->data[byteOffset] |= bitmask; } else { this->data[byteOffset] &= ~bitmask; } ++this->offset; } void BitStream::PutBits(uint32_t offset, uint32_t count, uint32_t bits) { MS_TRACE(); MS_ASSERT( count <= 32, "count cannot be bigger than 32 [count:%" PRIu32 ", bits:%" PRIu32 "]", count, bits); for (unsigned i = 0; i < count; ++i) { const uint32_t shift = count - i - 1; const uint8_t bit = (bits >> shift) & 0x1; this->PutBit(offset++, bit); } } } // namespace Utils ================================================ FILE: worker/src/Utils/Crypto.cpp ================================================ #define MS_CLASS "Utils::Crypto" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "Utils.hpp" #include #include namespace Utils { /* Class variables. */ thread_local std::mt19937_64 Crypto::rng; thread_local EVP_MAC* Crypto::mac{ nullptr }; thread_local EVP_MAC_CTX* Crypto::hmacSha1Ctx{ nullptr }; thread_local uint8_t Crypto::hmacSha1Buffer[SHA_DIGEST_LENGTH]; // clang-format off const uint32_t Crypto::Crc32Table[] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; // clang-format on // clang-format off const uint32_t Crypto::Crc32cTable[] = { 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB, 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B, 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A, 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198, 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789, 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829, 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC, 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982, 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F, 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F, 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E, 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351 }; // clang-format on /* Static methods. */ void Crypto::ClassInit() { MS_TRACE(); std::random_device rd; const uint64_t seed = (uint64_t(rd()) << 32) | uint64_t(rd()); Crypto::rng.seed(seed); // Create an OpenSSL HMAC_CTX context for HMAC SHA1 calculation. Crypto::mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr); Crypto::hmacSha1Ctx = EVP_MAC_CTX_new(mac); } void Crypto::ClassDestroy() { MS_TRACE(); if (Crypto::hmacSha1Ctx != nullptr) { EVP_MAC_CTX_free(Crypto::hmacSha1Ctx); } if (Crypto::mac != nullptr) { EVP_MAC_free(Crypto::mac); } } std::string Crypto::GetRandomString(size_t len) { MS_TRACE(); char buffer[64]; static const char Chars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; len = std::min(len, 64); for (size_t i{ 0 }; i < len; ++i) { buffer[i] = Chars[GetRandomUInt(0, sizeof(Chars) - 1)]; } return { buffer, len }; } uint32_t Crypto::GetCRC32(const uint8_t* data, size_t size) { MS_TRACE(); uint32_t crc{ 0xFFFFFFFF }; const uint8_t* p = data; while (size--) { crc = Crypto::Crc32Table[(crc ^ *p++) & 0xFF] ^ (crc >> 8); } return crc ^ ~0U; } /** * Obtained from https://datatracker.ietf.org/doc/html/rfc9260#appendix-A */ uint32_t Crypto::GetCRC32c(const uint8_t* data, size_t size) { MS_TRACE(); uint32_t crc32 = 0xFFFFFFFF; for (size_t i{ 0 }; i < size; ++i) { crc32 = (crc32 >> 8) ^ Crypto::Crc32cTable[(crc32 ^ data[i]) & 0xFF]; } const uint32_t result = ~crc32; const uint32_t byte0 = result & 0xff; const uint32_t byte1 = (result >> 8) & 0xff; const uint32_t byte2 = (result >> 16) & 0xff; const uint32_t byte3 = (result >> 24) & 0xff; crc32 = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3); return crc32; } const uint8_t* Crypto::GetHmacSha1(const char* key, size_t keyLen, const uint8_t* data, size_t len) { MS_TRACE(); int ret; OSSL_PARAM sha1[] = { { "digest", OSSL_PARAM_UTF8_STRING, (void*)"sha1", 4, 0 }, OSSL_PARAM_END }; ret = EVP_MAC_init(Crypto::hmacSha1Ctx, reinterpret_cast(key), keyLen, sha1); MS_ASSERT(ret == 1, "OpenSSL EVP_MAC_init() failed with key '%s'", key); ret = EVP_MAC_update(Crypto::hmacSha1Ctx, data, len); MS_ASSERT( ret == 1, "OpenSSL EVP_MAC_update() failed with key '%s' and data length %zu bytes", key, len); size_t resultLen; ret = EVP_MAC_final(Crypto::hmacSha1Ctx, Crypto::hmacSha1Buffer, &resultLen, SHA_DIGEST_LENGTH); MS_ASSERT( ret == 1, "OpenSSL HMAC_Final() failed with key '%s' and data length %zu bytes", key, len); MS_ASSERT( resultLen == SHA_DIGEST_LENGTH, "OpenSSL HMAC_Final() resultLen is %zu instead of 20", resultLen); return Crypto::hmacSha1Buffer; } void Crypto::WriteRandomBytes(uint8_t* buffer, size_t len) { MS_TRACE(); if (RAND_bytes(buffer, len) != 1) { MS_ABORT("OpenSSL RAND_bytes() failed"); } } } // namespace Utils ================================================ FILE: worker/src/Utils/File.cpp ================================================ #define MS_CLASS "Utils::File" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include #include // stat() #ifdef _WIN32 #include #define __S_ISTYPE(mode, mask) (((mode) & _S_IFMT) == (mask)) #define S_ISREG(mode) __S_ISTYPE((mode), _S_IFREG) #else #include // access(), R_OK #endif namespace Utils { void Utils::File::CheckFile(const char* file) { MS_TRACE(); struct stat fileStat{}; // NOLINT(cppcoreguidelines-pro-type-member-init) int err; // Ensure the given file exists. err = stat(file, &fileStat); if (err != 0) { MS_THROW_ERROR("cannot read file '%s': %s", file, std::strerror(errno)); } // Ensure it is a regular file. if (!S_ISREG(fileStat.st_mode)) { MS_THROW_ERROR("'%s' is not a regular file", file); } // Ensure it is readable. err = access(file, R_OK); if (err != 0) { MS_THROW_ERROR("cannot read file '%s': %s", file, std::strerror(errno)); } } } // namespace Utils ================================================ FILE: worker/src/Utils/IP.cpp ================================================ #define MS_CLASS "Utils::IP" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include namespace Utils { int IP::GetFamily(const std::string& ip) { MS_TRACE(); if (ip.size() >= INET6_ADDRSTRLEN) { return AF_UNSPEC; } const auto* ipPtr = ip.c_str(); char ipBuffer[INET6_ADDRSTRLEN] = { 0 }; if (uv_inet_pton(AF_INET, ipPtr, ipBuffer) == 0) { return AF_INET; } else if (uv_inet_pton(AF_INET6, ipPtr, ipBuffer) == 0) { return AF_INET6; } else { return AF_UNSPEC; } } void IP::GetAddressInfo(const struct sockaddr* addr, int& family, std::string& ip, uint16_t& port) { MS_TRACE(); char ipBuffer[INET6_ADDRSTRLEN] = { 0 }; int err; switch (addr->sa_family) { case AF_INET: { err = uv_inet_ntop( AF_INET, std::addressof(reinterpret_cast(addr)->sin_addr), ipBuffer, sizeof(ipBuffer)); if (err) { MS_ABORT("uv_inet_ntop() failed: %s", uv_strerror(err)); } port = ntohs(reinterpret_cast(addr)->sin_port); break; } case AF_INET6: { err = uv_inet_ntop( AF_INET6, std::addressof(reinterpret_cast(addr)->sin6_addr), ipBuffer, sizeof(ipBuffer)); if (err) { MS_ABORT("uv_inet_ntop() failed: %s", uv_strerror(err)); } port = ntohs(reinterpret_cast(addr)->sin6_port); break; } default: { MS_ABORT("unknown network family: %d", static_cast(addr->sa_family)); } } family = addr->sa_family; ip.assign(ipBuffer); } size_t IP::GetAddressLen(const struct sockaddr* addr) { MS_TRACE(); switch (addr->sa_family) { case AF_INET: { return sizeof(struct sockaddr_in); } case AF_INET6: { return sizeof(struct sockaddr_in6); } default: { MS_ABORT("unknown network family: %d", static_cast(addr->sa_family)); } } } std::string IP::NormalizeIp(std::string& ip) { MS_TRACE(); sockaddr_storage addrStorage{}; char ipBuffer[INET6_ADDRSTRLEN] = { 0 }; int err; switch (IP::GetFamily(ip)) { case AF_INET: { err = uv_ip4_addr(ip.c_str(), 0, reinterpret_cast(&addrStorage)); if (err != 0) { MS_THROW_TYPE_ERROR("uv_ip4_addr() failed [ip:'%s']: %s", ip.c_str(), uv_strerror(err)); } err = uv_ip4_name( reinterpret_cast(std::addressof(addrStorage)), ipBuffer, sizeof(ipBuffer)); if (err != 0) { MS_THROW_TYPE_ERROR("uv_ipv4_name() failed [ip:'%s']: %s", ip.c_str(), uv_strerror(err)); } ip.assign(ipBuffer); return ip; } case AF_INET6: { err = uv_ip6_addr(ip.c_str(), 0, reinterpret_cast(&addrStorage)); if (err != 0) { MS_THROW_TYPE_ERROR("uv_ip6_addr() failed [ip:'%s']: %s", ip.c_str(), uv_strerror(err)); } err = uv_ip6_name( reinterpret_cast(std::addressof(addrStorage)), ipBuffer, sizeof(ipBuffer)); if (err != 0) { MS_THROW_TYPE_ERROR("uv_ipv6_name() failed [ip:'%s']: %s", ip.c_str(), uv_strerror(err)); } ip.assign(ipBuffer); return ip; } default: { MS_THROW_TYPE_ERROR("invalid IP '%s'", ip.c_str()); } } } } // namespace Utils ================================================ FILE: worker/src/Utils/README_BASE64_UTILS ================================================ wpa_supplicant and hostapd -------------------------- Copyright (c) 2002-2012, Jouni Malinen and contributors All Rights Reserved. These programs are licensed under the BSD license (the one with advertisement clause removed). If you are submitting changes to the project, please see CONTRIBUTIONS file for more instructions. This package may include either wpa_supplicant, hostapd, or both. See README file respective subdirectories (wpa_supplicant/README or hostapd/README) for more details. Source code files were moved around in v0.6.x releases and compared to earlier releases, the programs are now built by first going to a subdirectory (wpa_supplicant or hostapd) and creating build configuration (.config) and running 'make' there (for Linux/BSD/cygwin builds). License ------- This software may be distributed, used, and modified under the terms of BSD license: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name(s) of the above-listed copyright holder(s) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: worker/src/Utils/String.cpp ================================================ /* * Code from http://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c * * Base64 encoding/decoding (RFC1341) * Copyright (c) 2005-2011, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README_BASE64_UTILS for more details. */ #define MS_CLASS "Utils::String" // #define MS_LOG_DEV_LEVEL 3 #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include // std::memset() /* Static. */ static constexpr size_t BufferOutSize{ 65536 }; static thread_local uint8_t BufferOut[BufferOutSize]; static const uint8_t Base64Table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; namespace Utils { std::string Utils::String::Base64Encode(const uint8_t* data, size_t len) { MS_TRACE(); const uint8_t* out = BufferOut; uint8_t* pos; const uint8_t* end; const uint8_t* in; size_t olen; olen = (len * 4 / 3) + 4; // 3-byte blocks to 4-byte. if (olen < len) { MS_THROW_TYPE_ERROR("integer overflow"); } else if (olen > BufferOutSize - 1) { MS_THROW_TYPE_ERROR("data too big"); } end = data + len; in = data; pos = const_cast(out); while (end - in >= 3) { *pos++ = Base64Table[in[0] >> 2]; *pos++ = Base64Table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; *pos++ = Base64Table[((in[1] & 0x0f) << 2) | (in[2] >> 6)]; *pos++ = Base64Table[in[2] & 0x3f]; in += 3; } if (end - in) { *pos++ = Base64Table[in[0] >> 2]; if (end - in == 1) { *pos++ = Base64Table[(in[0] & 0x03) << 4]; *pos++ = '='; } else { *pos++ = Base64Table[((in[0] & 0x03) << 4) | (in[1] >> 4)]; *pos++ = Base64Table[(in[1] & 0x0f) << 2]; } *pos++ = '='; } return { reinterpret_cast(out), static_cast(pos - out) }; } std::string Utils::String::Base64Encode(const std::string& str) { MS_TRACE(); const auto* data = reinterpret_cast(str.c_str()); return Base64Encode(data, str.size()); } uint8_t* Utils::String::Base64Decode(const uint8_t* data, size_t len, size_t& outLen) { MS_TRACE(); uint8_t dtable[256]; uint8_t* out = BufferOut; uint8_t* pos; uint8_t block[4]; uint8_t tmp; size_t i; size_t count; int pad{ 0 }; // NOTE: This is not really accurate but anyway. if (len > BufferOutSize - 1) { MS_THROW_TYPE_ERROR("data too big"); } std::memset(dtable, 0x80, 256); for (i = 0; i < sizeof(Base64Table) - 1; ++i) { dtable[Base64Table[i]] = static_cast(i); } dtable[uint8_t{ '=' }] = 0; count = 0; for (i = 0; i < len; ++i) { if (dtable[data[i]] != 0x80) { count++; } } if (count == 0 || count % 4) { MS_THROW_TYPE_ERROR("invalid data"); } pos = out; count = 0; for (i = 0; i < len; ++i) { tmp = dtable[data[i]]; if (tmp == 0x80) { continue; } if (data[i] == '=') { pad++; } block[count] = tmp; count++; if (count == 4) { *pos++ = (block[0] << 2) | (block[1] >> 4); *pos++ = (block[1] << 4) | (block[2] >> 2); *pos++ = (block[2] << 6) | block[3]; count = 0; if (pad) { if (pad == 1) { pos--; } else if (pad == 2) { pos -= 2; } else { MS_THROW_TYPE_ERROR("integer padding"); } break; } } } outLen = pos - out; return out; } uint8_t* Utils::String::Base64Decode(const std::string& str, size_t& outLen) { MS_TRACE(); const auto* data = reinterpret_cast(str.c_str()); return Base64Decode(data, str.size(), outLen); } } // namespace Utils ================================================ FILE: worker/src/Worker.cpp ================================================ #define MS_CLASS "Worker" // #define MS_LOG_DEV_LEVEL 3 #include "Worker.hpp" #ifdef MS_LIBURING_SUPPORTED #include "DepLibUring.hpp" #endif #include "DepLibUV.hpp" // TODO: Remove once we only use built-in SCTP stack. #include "DepUsrSCTP.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "FBS/response.h" #include "FBS/worker.h" /* Instance methods. */ Worker::Worker(::Channel::ChannelSocket* channel, SharedInterface* shared) : channel(channel), shared(shared) { MS_TRACE(); // Set us as Channel's listener. this->channel->SetListener(this); // Set the SignalHandle. this->signalHandle = new SignalHandle(this); #ifdef MS_EXECUTABLE { // Add signals to handle. this->signalHandle->AddSignal(SIGINT, "INT"); this->signalHandle->AddSignal(SIGTERM, "TERM"); } #endif // TODO: Remove once we only use built-in SCTP stack. if (!Settings::configuration.useBuiltInSctpStack) { // Create the Checker instance in DepUsrSCTP. DepUsrSCTP::CreateChecker(this->shared); } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Start polling CQEs, which will create a uv_pool_t handle. DepLibUring::StartPollingCQEs(); } #endif // Tell the Node process that we are running. this->shared->GetChannelNotifier()->Emit( std::to_string(Logger::Pid), FBS::Notification::Event::WORKER_RUNNING); MS_DEBUG_DEV("starting libuv loop"); DepLibUV::RunLoop(); MS_DEBUG_DEV("libuv loop ended"); } Worker::~Worker() { MS_TRACE(); if (!this->closed) { Close(); } } void Worker::Close() { MS_TRACE(); if (this->closed) { return; } this->closed = true; // Delete the SignalHandle. delete this->signalHandle; // Delete all Routers. for (auto& kv : this->mapRouters) { auto* router = kv.second; delete router; } this->mapRouters.clear(); // Delete all WebRtcServers. for (auto& kv : this->mapWebRtcServers) { auto* webRtcServer = kv.second; delete webRtcServer; } this->mapWebRtcServers.clear(); // TODO: Remove once we only use built-in SCTP stack. if (!Settings::configuration.useBuiltInSctpStack) { // Close the Checker instance in DepUsrSCTP. DepUsrSCTP::CloseChecker(); } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { // Stop polling CQEs, which will close the uv_pool_t handle. DepLibUring::StopPollingCQEs(); } #endif // Close the Channel. this->channel->Close(); } flatbuffers::Offset Worker::FillBuffer( flatbuffers::FlatBufferBuilder& builder) const { // Add webRtcServerIds. std::vector> webRtcServerIds; webRtcServerIds.reserve(this->mapWebRtcServers.size()); for (const auto& kv : this->mapWebRtcServers) { const auto& webRtcServerId = kv.first; webRtcServerIds.push_back(builder.CreateString(webRtcServerId)); } // Add routerIds. std::vector> routerIds; routerIds.reserve(this->mapRouters.size()); for (const auto& kv : this->mapRouters) { const auto& routerId = kv.first; routerIds.push_back(builder.CreateString(routerId)); } // Add channelMessageHandlers. auto channelMessageHandlers = this->shared->GetChannelMessageRegistrator()->FillBuffer(builder); #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { return FBS::Worker::CreateDumpResponseDirect( builder, Logger::Pid, &webRtcServerIds, &routerIds, channelMessageHandlers, DepLibUring::FillBuffer(builder)); } else { return FBS::Worker::CreateDumpResponseDirect( builder, Logger::Pid, &webRtcServerIds, &routerIds, channelMessageHandlers); } #else return FBS::Worker::CreateDumpResponseDirect( builder, Logger::Pid, &webRtcServerIds, &routerIds, channelMessageHandlers); #endif } flatbuffers::Offset Worker::FillBufferResourceUsage( flatbuffers::FlatBufferBuilder& builder) const { MS_TRACE(); int err; uv_rusage_t uvRusage{}; // NOLINT(cppcoreguidelines-pro-type-member-init) err = uv_getrusage(std::addressof(uvRusage)); if (err != 0) { MS_THROW_ERROR("uv_getrusage() failed: %s", uv_strerror(err)); } return FBS::Worker::CreateResourceUsageResponse( builder, // Add ru_utime (uv_timeval_t, user CPU time used, converted to ms). (uvRusage.ru_utime.tv_sec * static_cast(1000)) + (uvRusage.ru_utime.tv_usec / 1000), // Add ru_stime (uv_timeval_t, system CPU time used, converted to ms). (uvRusage.ru_stime.tv_sec * static_cast(1000)) + (uvRusage.ru_stime.tv_usec / 1000), // Add ru_maxrss (uint64_t, maximum resident set size). uvRusage.ru_maxrss, // Add ru_ixrss (uint64_t, integral shared memory size). uvRusage.ru_ixrss, // Add ru_idrss (uint64_t, integral unshared data size). uvRusage.ru_idrss, // Add ru_isrss (uint64_t, integral unshared stack size). uvRusage.ru_isrss, // Add ru_minflt (uint64_t, page reclaims, soft page faults). uvRusage.ru_minflt, // Add ru_majflt (uint64_t, page faults, hard page faults). uvRusage.ru_majflt, // Add ru_nswap (uint64_t, swaps). uvRusage.ru_nswap, // Add ru_inblock (uint64_t, block input operations). uvRusage.ru_inblock, // Add ru_oublock (uint64_t, block output operations). uvRusage.ru_oublock, // Add ru_msgsnd (uint64_t, IPC messages sent). uvRusage.ru_msgsnd, // Add ru_msgrcv (uint64_t, IPC messages received). uvRusage.ru_msgrcv, // Add ru_nsignals (uint64_t, signals received). uvRusage.ru_nsignals, // Add ru_nvcsw (uint64_t, voluntary context switches). uvRusage.ru_nvcsw, // Add ru_nivcsw (uint64_t, involuntary context switches). uvRusage.ru_nivcsw); } RTC::WebRtcServer* Worker::GetWebRtcServer(const std::string& webRtcServerId) const { auto it = this->mapWebRtcServers.find(webRtcServerId); if (it == this->mapWebRtcServers.end()) { MS_THROW_ERROR("WebRtcServer not found"); } return it->second; } RTC::Router* Worker::GetRouter(const std::string& routerId) const { MS_TRACE(); auto it = this->mapRouters.find(routerId); if (it == this->mapRouters.end()) { MS_THROW_ERROR("Router not found"); } return it->second; } void Worker::CheckNoWebRtcServer(const std::string& webRtcServerId) const { if (this->mapWebRtcServers.find(webRtcServerId) != this->mapWebRtcServers.end()) { MS_THROW_ERROR("a WebRtcServer with same webRtcServerId already exists"); } } void Worker::CheckNoRouter(const std::string& routerId) const { if (this->mapRouters.find(routerId) != this->mapRouters.end()) { MS_THROW_ERROR("a Router with same routerId already exists"); } } void Worker::HandleRequest(Channel::ChannelRequest* request) { MS_TRACE(); MS_DEBUG_DEV( "Channel request received [method:%s, id:%" PRIu32 "]", request->methodCStr, request->id); switch (request->method) { case Channel::ChannelRequest::Method::WORKER_DUMP: { auto dumpOffset = FillBuffer(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Worker_DumpResponse, dumpOffset); break; } case Channel::ChannelRequest::Method::WORKER_GET_RESOURCE_USAGE: { auto resourceUsageOffset = FillBufferResourceUsage(request->GetBufferBuilder()); request->Accept(FBS::Response::Body::Worker_ResourceUsageResponse, resourceUsageOffset); break; } case Channel::ChannelRequest::Method::WORKER_UPDATE_SETTINGS: { Settings::HandleRequest(request); break; } case Channel::ChannelRequest::Method::WORKER_CREATE_WEBRTCSERVER: { try { const auto* const body = request->data->body_as(); const std::string webRtcServerId = body->webRtcServerId()->str(); CheckNoWebRtcServer(webRtcServerId); auto* webRtcServer = new RTC::WebRtcServer(this->shared, webRtcServerId, body->listenInfos()); this->mapWebRtcServers[webRtcServerId] = webRtcServer; MS_DEBUG_DEV("WebRtcServer created [webRtcServerId:%s]", webRtcServerId.c_str()); request->Accept(); } catch (const MediaSoupTypeError& error) { MS_THROW_TYPE_ERROR("%s [method:%s]", error.what(), request->methodCStr); } catch (const MediaSoupError& error) { MS_THROW_ERROR("%s [method:%s]", error.what(), request->methodCStr); } break; } case Channel::ChannelRequest::Method::WORKER_WEBRTCSERVER_CLOSE: { const RTC::WebRtcServer* webRtcServer{ nullptr }; const auto* body = request->data->body_as(); auto webRtcServerId = body->webRtcServerId()->str(); try { webRtcServer = GetWebRtcServer(webRtcServerId); } catch (const MediaSoupError& error) { MS_THROW_ERROR("%s [method:%s]", error.what(), request->methodCStr); } // Remove it from the map and delete it. this->mapWebRtcServers.erase(webRtcServer->GetId()); delete webRtcServer; MS_DEBUG_DEV("WebRtcServer closed [id:%s]", webRtcServer->id.c_str()); request->Accept(); break; } case Channel::ChannelRequest::Method::WORKER_CREATE_ROUTER: { const auto* body = request->data->body_as(); auto routerId = body->routerId()->str(); try { CheckNoRouter(routerId); } catch (const MediaSoupError& error) { MS_THROW_ERROR("%s [method:%s]", error.what(), request->methodCStr); } auto* router = new RTC::Router(this->shared, routerId, this); this->mapRouters[routerId] = router; MS_DEBUG_DEV("Router created [routerId:%s]", routerId.c_str()); request->Accept(); break; } case Channel::ChannelRequest::Method::WORKER_CLOSE_ROUTER: { const RTC::Router* router{ nullptr }; const auto* body = request->data->body_as(); auto routerId = body->routerId()->str(); try { router = GetRouter(routerId); } catch (const MediaSoupError& error) { MS_THROW_ERROR("%s [method:%s]", error.what(), request->methodCStr); } // Remove it from the map and delete it. this->mapRouters.erase(router->id); delete router; MS_DEBUG_DEV("Router closed [id:%s]", router->id.c_str()); request->Accept(); break; } // Any other request must be delivered to the corresponding Router. default: { try { auto* handler = this->shared->GetChannelMessageRegistrator()->GetChannelRequestHandler(request->handlerId); if (handler == nullptr) { MS_THROW_ERROR("Channel request handler with ID %s not found", request->handlerId.c_str()); } handler->HandleRequest(request); } catch (const MediaSoupTypeError& error) { MS_THROW_TYPE_ERROR("%s [method:%s]", error.what(), request->methodCStr); } catch (const MediaSoupError& error) { MS_THROW_ERROR("%s [method:%s]", error.what(), request->methodCStr); } break; } } } void Worker::HandleNotification(Channel::ChannelNotification* notification) { MS_TRACE(); MS_DEBUG_DEV("Channel notification received [event:%s]", notification->eventCStr); switch (notification->event) { case Channel::ChannelNotification::Event::WORKER_CLOSE: { if (this->closed) { return; } MS_DEBUG_DEV("closing Worker"); Close(); break; } default: { try { auto* handler = this->shared->GetChannelMessageRegistrator()->GetChannelNotificationHandler( notification->handlerId); if (handler == nullptr) { MS_THROW_ERROR( "Channel notification handler with ID %s not found", notification->handlerId.c_str()); } handler->HandleNotification(notification); } catch (const MediaSoupTypeError& error) { MS_THROW_TYPE_ERROR("%s [event:%s]", error.what(), notification->eventCStr); } catch (const MediaSoupError& error) { MS_THROW_ERROR("%s [event:%s]", error.what(), notification->eventCStr); } } } } void Worker::OnChannelClosed(Channel::ChannelSocket* /*socket*/) { MS_TRACE_STD(); // Only needed for executable, library user can close channel earlier and it // is fine. #ifdef MS_EXECUTABLE // If the pipe is remotely closed it may mean that mediasoup Node process // abruptly died (SIGKILL?) so we must die. MS_ERROR_STD("channel remotely closed, closing myself"); #endif Close(); } void Worker::OnSignal(SignalHandle* /*signalHandle*/, int signum) { MS_TRACE(); if (this->closed) { return; } switch (signum) { case SIGINT: case SIGTERM: { MS_DEBUG_DEV("%s signal received, closing myself", signum == SIGINT ? "INT" : "TERM"); Close(); break; } default: { MS_WARN_DEV("ignoring received non handled signal [signum:%d]", signum); } } } RTC::WebRtcServer* Worker::OnRouterNeedWebRtcServer(RTC::Router* /*router*/, std::string& webRtcServerId) { MS_TRACE(); RTC::WebRtcServer* webRtcServer{ nullptr }; // NOLINT(misc-const-correctness) const auto it = this->mapWebRtcServers.find(webRtcServerId); if (it != this->mapWebRtcServers.end()) { webRtcServer = it->second; } return webRtcServer; } ================================================ FILE: worker/src/handles/BackoffTimerHandle.cpp ================================================ #define MS_CLASS "BackoffTimerHandle" // #define MS_LOG_DEV_LEVEL 3 #include "handles/BackoffTimerHandle.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::min() /* Instance methods. */ BackoffTimerHandle::BackoffTimerHandle(BackoffTimerHandleOptions options) : listener(options.listener), label(std::move(options.label)), baseTimeoutMs(options.baseTimeoutMs), backoffAlgorithm(options.backoffAlgorithm), maxBackoffTimeoutMs(options.maxBackoffTimeoutMs), maxRestarts(options.maxRestarts) { MS_TRACE(); if (!this->listener) { MS_THROW_TYPE_ERROR("options.listener must be given"); } if (this->label.empty()) { MS_THROW_TYPE_ERROR("options.label must be given"); } if (this->baseTimeoutMs > BackoffTimerHandleInterface::MaxTimeoutMs) { MS_THROW_ERROR( "[%s] base timeout (%" PRIu64 " ms) cannot be greater than %" PRIu64 " ms", this->label.c_str(), this->baseTimeoutMs, BackoffTimerHandleInterface::MaxTimeoutMs); } this->timer = new TimerHandle(this); } BackoffTimerHandle::~BackoffTimerHandle() { MS_TRACE(); delete this->timer; this->timer = nullptr; } void BackoffTimerHandle::Start() { MS_TRACE(); this->timer->Start(this->baseTimeoutMs); this->running = true; this->expirationCount = 0; } void BackoffTimerHandle::Stop() { MS_TRACE(); this->timer->Stop(); this->running = false; this->expirationCount = 0; } void BackoffTimerHandle::SetBaseTimeoutMs(uint64_t baseTimeoutMs) { MS_TRACE(); if (baseTimeoutMs > BackoffTimerHandleInterface::MaxTimeoutMs) { MS_THROW_ERROR( "[%s] base timeout (%" PRIu64 " ms) cannot be greater than %" PRIu64 " ms", this->label.c_str(), baseTimeoutMs, BackoffTimerHandleInterface::MaxTimeoutMs); } this->baseTimeoutMs = baseTimeoutMs; } uint64_t BackoffTimerHandle::ComputeNextTimeoutMs() const { MS_TRACE(); auto expirationCount = this->expirationCount; switch (this->backoffAlgorithm) { case BackoffAlgorithm::FIXED: { return this->baseTimeoutMs; } case BackoffAlgorithm::EXPONENTIAL: { auto timeoutMs = this->baseTimeoutMs; while (expirationCount > 0 && timeoutMs < BackoffTimerHandleInterface::MaxTimeoutMs) { timeoutMs *= 2; --expirationCount; if (this->maxBackoffTimeoutMs.has_value() && timeoutMs > this->maxBackoffTimeoutMs.value()) { return this->maxBackoffTimeoutMs.value(); } } return std::min(timeoutMs, BackoffTimerHandleInterface::MaxTimeoutMs); } NO_DEFAULT_GCC(); } } void BackoffTimerHandle::OnTimer(TimerHandleInterface* /*timer*/) { MS_TRACE(); this->expirationCount++; // Compute whether the BackoffTimer should still be running after this timeout // expiration so the parent can check IsRunning() within the `OnBackoffTimer()` // callback. this->running = !this->maxRestarts.has_value() || this->expirationCount <= this->maxRestarts.value(); uint64_t baseTimeoutMs{ this->baseTimeoutMs }; bool stop{ false }; // Call the listener by passing base timeout as reference so the parent has // a chance to change it and affect the next timeout. this->listener->OnBackoffTimer(this, baseTimeoutMs, stop); // If the parent has set `stop` to true it means that it has deleted the // instance, so stop here. if (stop) { return; } // NOTE: This may throw. SetBaseTimeoutMs(baseTimeoutMs); // The caller may have called Stop() within the callback so we must check // the `running` flag. if (this->running) { auto nextTimeoutMs = ComputeNextTimeoutMs(); this->timer->Start(nextTimeoutMs); } } ================================================ FILE: worker/src/handles/SignalHandle.cpp ================================================ #define MS_CLASS "SignalHandle" // #define MS_LOG_DEV_LEVEL 3 #include "handles/SignalHandle.hpp" #include "DepLibUV.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" /* Static methods for UV callbacks. */ inline static void onSignal(uv_signal_t* handle, int signum) { static_cast(handle->data)->OnUvSignal(signum); } inline static void onCloseSignal(uv_handle_t* handle) { delete reinterpret_cast(handle); } /* Instance methods. */ SignalHandle::SignalHandle(Listener* listener) : listener(listener) { MS_TRACE(); } SignalHandle::~SignalHandle() { MS_TRACE(); if (!this->closed) { InternalClose(); } } void SignalHandle::AddSignal(int signum, const std::string& name) { MS_TRACE(); if (this->closed) { MS_THROW_ERROR("closed"); } int err; auto* uvHandle = new uv_signal_t; uvHandle->data = static_cast(this); err = uv_signal_init(DepLibUV::GetLoop(), uvHandle); if (err != 0) { delete uvHandle; MS_THROW_ERROR("uv_signal_init() failed for signal %s: %s", name.c_str(), uv_strerror(err)); } err = uv_signal_start(uvHandle, static_cast(onSignal), signum); if (err != 0) { MS_THROW_ERROR("uv_signal_start() failed for signal %s: %s", name.c_str(), uv_strerror(err)); } // Enter the UV handle into the vector. this->uvHandles.push_back(uvHandle); } void SignalHandle::InternalClose() { MS_TRACE(); if (this->closed) { return; } this->closed = true; for (auto* uvHandle : this->uvHandles) { uv_close(reinterpret_cast(uvHandle), static_cast(onCloseSignal)); } } void SignalHandle::OnUvSignal(int signum) { MS_TRACE(); // Notify the listener. this->listener->OnSignal(this, signum); } ================================================ FILE: worker/src/handles/TcpConnectionHandle.cpp ================================================ #define MS_CLASS "TcpConnectionHandle" // #define MS_LOG_DEV_LEVEL 3 #include "handles/TcpConnectionHandle.hpp" #include "DepLibUV.hpp" #ifdef MS_LIBURING_SUPPORTED #include "DepLibUring.hpp" #endif #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include // std::memcpy() /* Static methods for UV callbacks. */ inline static void onAlloc(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) { auto* connection = static_cast(handle->data); if (connection) { connection->OnUvReadAlloc(suggestedSize, buf); } } inline static void onRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) { auto* connection = static_cast(handle->data); if (connection) { connection->OnUvRead(nread, buf); } } inline static void onWrite(uv_write_t* req, int status) { auto* writeData = static_cast(req->data); auto* handle = req->handle; auto* connection = static_cast(handle->data); const auto* cb = writeData->cb; if (connection) { connection->OnUvWrite(status, cb); } // Delete the UvWriteData struct and the cb. delete writeData; } // NOTE: We have different onCloseXxx() callbacks to avoid an ASAN warning by // ensuring that we call `delete xxx` with same type as `new xxx` before. inline static void onCloseTcp(uv_handle_t* handle) { delete reinterpret_cast(handle); } inline static void onShutdown(uv_shutdown_t* req, int /*status*/) { auto* handle = req->handle; delete req; // Now do close the handle. uv_close(reinterpret_cast(handle), static_cast(onCloseTcp)); } /* Instance methods. */ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) TcpConnectionHandle::TcpConnectionHandle(size_t bufferSize) : bufferSize(bufferSize), uvHandle(new uv_tcp_t) { MS_TRACE(); this->uvHandle->data = static_cast(this); // NOTE: Don't allocate the buffer here. Instead wait for the first uv_alloc_cb(). } TcpConnectionHandle::~TcpConnectionHandle() { MS_TRACE(); if (!this->closed) { InternalClose(); } delete[] this->buffer; } void TcpConnectionHandle::TriggerClose() { MS_TRACE(); if (this->closed) { return; } InternalClose(); this->listener->OnTcpConnectionClosed(this); } void TcpConnectionHandle::Dump(int indentation) const { MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " local IP: %s", this->localIp.c_str()); MS_DUMP_CLEAN(indentation, " local port: %" PRIu16, static_cast(this->localPort)); MS_DUMP_CLEAN(indentation, " remote IP: %s", this->peerIp.c_str()); MS_DUMP_CLEAN(indentation, " remote port: %" PRIu16, static_cast(this->peerPort)); MS_DUMP_CLEAN(indentation, " closed: %s", this->closed ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } void TcpConnectionHandle::Setup( Listener* listener, struct sockaddr_storage* localAddr, const std::string& localIp, uint16_t localPort) { MS_TRACE(); // Set the UV handle. const int err = uv_tcp_init(DepLibUV::GetLoop(), this->uvHandle); if (err != 0) { delete this->uvHandle; this->uvHandle = nullptr; MS_THROW_ERROR("uv_tcp_init() failed: %s", uv_strerror(err)); } // Set the listener. this->listener = listener; // Set the local address. this->localAddr = localAddr; this->localIp = localIp; this->localPort = localPort; } void TcpConnectionHandle::Start() { MS_TRACE(); if (this->closed) { return; } // NOLINTNEXTLINE(misc-const-correctness) int err = uv_read_start( reinterpret_cast(this->uvHandle), static_cast(onAlloc), static_cast(onRead)); if (err != 0) { MS_THROW_ERROR("uv_read_start() failed: %s", uv_strerror(err)); } // Get the peer address. if (!SetPeerAddress()) { MS_THROW_ERROR("error setting peer IP and port"); } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { err = uv_fileno(reinterpret_cast(this->uvHandle), std::addressof(this->fd)); if (err != 0) { MS_THROW_ERROR("uv_fileno() failed: %s", uv_strerror(err)); } } #endif } void TcpConnectionHandle::Write( const uint8_t* data1, size_t len1, const uint8_t* data2, size_t len2, TcpConnectionHandle::onSendCallback* cb) { MS_TRACE(); if (this->closed) { if (cb) { (*cb)(false); delete cb; } return; } if (len1 == 0 && len2 == 0) { if (cb) { (*cb)(false); delete cb; } return; } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { if (!DepLibUring::IsActive()) { goto write_libuv; } // Prepare the data to be sent. // NOTE: If all SQEs are currently in use or no UserData entry is available we'll // fall back to libuv. auto prepared = DepLibUring::PrepareWrite(this->fd, data1, len1, data2, len2, cb); if (!prepared) { MS_DEBUG_DEV("cannot write via liburing, fallback to libuv"); goto write_libuv; } return; } write_libuv: #endif // First try uv_try_write(). In case it can not directly write all the given // data then build a uv_req_t and use uv_write(). const size_t totalLen = len1 + len2; uv_buf_t buffers[2]; int written{ 0 }; int err; buffers[0] = uv_buf_init(reinterpret_cast(const_cast(data1)), len1); buffers[1] = uv_buf_init(reinterpret_cast(const_cast(data2)), len2); written = uv_try_write(reinterpret_cast(this->uvHandle), buffers, 2); // All the data was written. Done. if (written == static_cast(totalLen)) { // Update sent bytes. this->sentBytes += written; if (cb) { (*cb)(true); delete cb; } return; } // Cannot write any data at first time. Use uv_write(). else if (written == UV_EAGAIN || written == UV_ENOSYS) { // Set written to 0 so pendingLen can be properly calculated. written = 0; } // Any other error. else if (written < 0) { MS_WARN_DEV("uv_try_write() failed, trying uv_write(): %s", uv_strerror(written)); // Set written to 0 so pendingLen can be properly calculated. written = 0; } const size_t pendingLen = totalLen - written; auto* writeData = new UvWriteData(pendingLen); writeData->req.data = static_cast(writeData); // If the first buffer was not entirely written then splice it. if (static_cast(written) < len1) { std::memcpy( writeData->store, data1 + static_cast(written), len1 - static_cast(written)); std::memcpy(writeData->store + (len1 - static_cast(written)), data2, len2); } // Otherwise just take the pending data in the second buffer. else { std::memcpy( writeData->store, data2 + (static_cast(written) - len1), len2 - (static_cast(written) - len1)); } writeData->cb = cb; const uv_buf_t buffer = uv_buf_init(reinterpret_cast(writeData->store), pendingLen); err = uv_write( &writeData->req, reinterpret_cast(this->uvHandle), &buffer, 1, static_cast(onWrite)); if (err != 0) { MS_WARN_DEV("uv_write() failed: %s", uv_strerror(err)); if (cb) { (*cb)(false); } // Delete the UvWriteData struct (it will delete the store and cb too). delete writeData; } else { // Update sent bytes. this->sentBytes += pendingLen; } } void TcpConnectionHandle::ErrorReceiving() { MS_TRACE(); InternalClose(); this->listener->OnTcpConnectionClosed(this); } void TcpConnectionHandle::InternalClose() { MS_TRACE(); if (this->closed) { return; } int err; this->closed = true; // Tell the UV handle that the TcpConnectionHandle has been closed. this->uvHandle->data = nullptr; // Don't read more. err = uv_read_stop(reinterpret_cast(this->uvHandle)); if (err != 0) { try { MS_ABORT("uv_read_stop() failed: %s", uv_strerror(err)); } catch (const std::exception& e) { MS_ERROR("%s", e.what()); } } // If there is no error and the peer didn't close its connection side then close gracefully. if (!this->hasError && !this->isClosedByPeer) { // Use uv_shutdown() so pending data to be written will be sent to the peer // before closing. auto* req = new uv_shutdown_t; req->data = static_cast(this); err = uv_shutdown( req, reinterpret_cast(this->uvHandle), static_cast(onShutdown)); if (err != 0) { try { MS_ABORT("uv_shutdown() failed: %s", uv_strerror(err)); } catch (const std::exception& e) { MS_ERROR("%s", e.what()); } } } // Otherwise directly close the socket. else { uv_close(reinterpret_cast(this->uvHandle), static_cast(onCloseTcp)); } } bool TcpConnectionHandle::SetPeerAddress() { MS_TRACE(); int err; int len = sizeof(this->peerAddr); err = uv_tcp_getpeername(this->uvHandle, reinterpret_cast(&this->peerAddr), &len); if (err != 0) { MS_ERROR("uv_tcp_getpeername() failed: %s", uv_strerror(err)); return false; } int family; Utils::IP::GetAddressInfo( reinterpret_cast(&this->peerAddr), family, this->peerIp, this->peerPort); return true; } inline void TcpConnectionHandle::OnUvReadAlloc(size_t /*suggestedSize*/, uv_buf_t* buf) { MS_TRACE(); // If this is the first call to onUvReadAlloc() then allocate the receiving // buffer now. if (!this->buffer) { this->buffer = new uint8_t[this->bufferSize]; } // Tell UV to write after the last data byte in the buffer. buf->base = reinterpret_cast(this->buffer + this->bufferDataLen); // Give UV all the remaining space in the buffer. if (this->bufferSize > this->bufferDataLen) { buf->len = this->bufferSize - this->bufferDataLen; } else { buf->len = 0; MS_WARN_DEV("no available space in the buffer"); } } inline void TcpConnectionHandle::OnUvRead(ssize_t nread, const uv_buf_t* /*buf*/) { MS_TRACE(); if (nread == 0) { return; } // Data received. if (nread > 0) { // Update received bytes. this->recvBytes += nread; // Update the buffer data length. this->bufferDataLen += static_cast(nread); // Notify the subclass. UserOnTcpConnectionRead(); } // Client disconnected. else if (nread == UV_EOF || nread == UV_ECONNRESET) { MS_DEBUG_DEV("connection closed by peer, closing server side"); this->isClosedByPeer = true; // Close server side of the connection. InternalClose(); // Notify the listener. this->listener->OnTcpConnectionClosed(this); } // Some error. else { MS_WARN_DEV("read error, closing the connection: %s", uv_strerror(nread)); this->hasError = true; // Close server side of the connection. InternalClose(); // Notify the listener. this->listener->OnTcpConnectionClosed(this); } } inline void TcpConnectionHandle::OnUvWrite(int status, TcpConnectionHandle::onSendCallback* cb) { MS_TRACE(); // NOTE: Do not delete cb here since it will be delete in onWrite() above. if (status == 0) { if (cb) { (*cb)(true); } } else { if (status != UV_EPIPE && status != UV_ENOTCONN) { this->hasError = true; } MS_WARN_DEV("write error, closing the connection: %s", uv_strerror(status)); if (cb) { (*cb)(false); } InternalClose(); this->listener->OnTcpConnectionClosed(this); } } ================================================ FILE: worker/src/handles/TcpServerHandle.cpp ================================================ #define MS_CLASS "TcpServerHandle" // #define MS_LOG_DEV_LEVEL 3 #include "handles/TcpServerHandle.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" /* Static. */ static constexpr int ListenBacklog{ 512 }; /* Static methods for UV callbacks. */ inline static void onConnection(uv_stream_t* handle, int status) { auto* server = static_cast(handle->data); if (server) { server->OnUvConnection(status); } } inline static void onCloseTcp(uv_handle_t* handle) { delete reinterpret_cast(handle); } /* Instance methods. */ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) TcpServerHandle::TcpServerHandle(uv_tcp_t* uvHandle) : uvHandle(uvHandle) { MS_TRACE(); int err; this->uvHandle->data = static_cast(this); err = uv_listen( reinterpret_cast(this->uvHandle), ListenBacklog, static_cast(onConnection)); if (err != 0) { uv_close(reinterpret_cast(this->uvHandle), static_cast(onCloseTcp)); MS_THROW_ERROR("uv_listen() failed: %s", uv_strerror(err)); } // Set local address. if (!SetLocalAddress()) { uv_close(reinterpret_cast(this->uvHandle), static_cast(onCloseTcp)); MS_THROW_ERROR("error setting local IP and port"); } } TcpServerHandle::~TcpServerHandle() { MS_TRACE(); if (!this->closed) { InternalClose(); } } void TcpServerHandle::Dump(int indentation) const { MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " local IP: %s", this->localIp.c_str()); MS_DUMP_CLEAN(indentation, " local port: %" PRIu16, static_cast(this->localPort)); MS_DUMP_CLEAN(indentation, " num connections: %zu", this->connections.size()); MS_DUMP_CLEAN(indentation, " closed: %s", this->closed ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } uint32_t TcpServerHandle::GetSendBufferSize() const { MS_TRACE(); int size{ 0 }; const int err = uv_send_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(size)); if (err) { MS_THROW_ERROR("uv_send_buffer_size() failed: %s", uv_strerror(err)); } return static_cast(size); } void TcpServerHandle::SetSendBufferSize(uint32_t size) { MS_TRACE(); auto sizeInt = static_cast(size); if (sizeInt <= 0) { MS_THROW_TYPE_ERROR("invalid size: %d", sizeInt); } const int err = uv_send_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(sizeInt)); if (err) { MS_THROW_ERROR("uv_send_buffer_size() failed: %s", uv_strerror(err)); } } uint32_t TcpServerHandle::GetRecvBufferSize() const { MS_TRACE(); int size{ 0 }; const int err = uv_recv_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(size)); if (err) { MS_THROW_ERROR("uv_recv_buffer_size() failed: %s", uv_strerror(err)); } return static_cast(size); } void TcpServerHandle::SetRecvBufferSize(uint32_t size) { MS_TRACE(); auto sizeInt = static_cast(size); if (sizeInt <= 0) { MS_THROW_TYPE_ERROR("invalid size: %d", sizeInt); } const int err = uv_recv_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(sizeInt)); if (err) { MS_THROW_ERROR("uv_recv_buffer_size() failed: %s", uv_strerror(err)); } } void TcpServerHandle::AcceptTcpConnection(TcpConnectionHandle* connection) { MS_TRACE(); MS_ASSERT(connection != nullptr, "TcpConnectionHandle pointer was not allocated by the user"); try { connection->Setup(this, &(this->localAddr), this->localIp, this->localPort); } catch (const MediaSoupError& error) { delete connection; return; } // Accept the connection. const int err = uv_accept( reinterpret_cast(this->uvHandle), reinterpret_cast(connection->GetUvHandle())); if (err != 0) { MS_ABORT("uv_accept() failed: %s", uv_strerror(err)); } // Start receiving data. try { // NOTE: This may throw. connection->Start(); } catch (const MediaSoupError& error) { delete connection; return; } // Store it. this->connections.insert(connection); } void TcpServerHandle::InternalClose() { MS_TRACE(); if (this->closed) { return; } this->closed = true; // Tell the UV handle that the TcpServerHandle has been closed. this->uvHandle->data = nullptr; MS_DEBUG_DEV("closing %zu active connections", this->connections.size()); for (auto* connection : this->connections) { delete connection; } uv_close(reinterpret_cast(this->uvHandle), static_cast(onCloseTcp)); } bool TcpServerHandle::SetLocalAddress() { MS_TRACE(); int err; int len = sizeof(this->localAddr); err = uv_tcp_getsockname(this->uvHandle, reinterpret_cast(&this->localAddr), &len); if (err != 0) { MS_ERROR("uv_tcp_getsockname() failed: %s", uv_strerror(err)); return false; } int family; Utils::IP::GetAddressInfo( reinterpret_cast(&this->localAddr), family, this->localIp, this->localPort); return true; } inline void TcpServerHandle::OnUvConnection(int status) { MS_TRACE(); if (this->closed) { return; } if (status != 0) { MS_ERROR("error while receiving a new TCP connection: %s", uv_strerror(status)); return; } // Notify the subclass about a new TCP connection attempt. UserOnTcpConnectionAlloc(); } inline void TcpServerHandle::OnTcpConnectionClosed(TcpConnectionHandle* connection) { MS_TRACE(); MS_DEBUG_DEV("TCP connection closed"); // Remove the TcpConnectionHandle from the set. this->connections.erase(connection); // Notify the subclass. UserOnTcpConnectionClosed(connection); // Delete it. delete connection; } ================================================ FILE: worker/src/handles/TimerHandle.cpp ================================================ #define MS_CLASS "TimerHandle" // #define MS_LOG_DEV_LEVEL 3 #include "handles/TimerHandle.hpp" #include "DepLibUV.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" /* Static methods for UV callbacks. */ static void onTimer(uv_timer_t* handle) { static_cast(handle->data)->OnUvTimer(); } static void onCloseTimer(uv_handle_t* handle) { delete reinterpret_cast(handle); } /* Instance methods. */ TimerHandle::TimerHandle(TimerHandleInterface::Listener* listener) : listener(listener), uvHandle(new uv_timer_t) { MS_TRACE(); this->uvHandle->data = static_cast(this); const int err = uv_timer_init(DepLibUV::GetLoop(), this->uvHandle); if (err != 0) { delete this->uvHandle; this->uvHandle = nullptr; MS_THROW_ERROR("uv_timer_init() failed: %s", uv_strerror(err)); } } TimerHandle::~TimerHandle() { MS_TRACE(); if (!this->closed) { InternalClose(); } } void TimerHandle::Start(uint64_t timeout, uint64_t repeat) { MS_TRACE(); if (this->closed) { MS_THROW_ERROR("closed"); } this->timeout = timeout; this->repeat = repeat; int err; if (uv_is_active(reinterpret_cast(this->uvHandle)) != 0) { err = uv_timer_stop(this->uvHandle); if (err != 0) { MS_THROW_ERROR("uv_timer_stop() failed: %s", uv_strerror(err)); } } err = uv_timer_start(this->uvHandle, static_cast(onTimer), this->timeout, this->repeat); if (err != 0) { MS_THROW_ERROR("uv_timer_start() failed: %s", uv_strerror(err)); } } void TimerHandle::Stop() { MS_TRACE(); if (this->closed) { MS_THROW_ERROR("closed"); } const int err = uv_timer_stop(this->uvHandle); if (err != 0) { MS_THROW_ERROR("uv_timer_stop() failed: %s", uv_strerror(err)); } } void TimerHandle::Restart() { MS_TRACE(); if (this->closed) { MS_THROW_ERROR("closed"); } int err; if (uv_is_active(reinterpret_cast(this->uvHandle)) != 0) { err = uv_timer_stop(this->uvHandle); if (err != 0) { MS_THROW_ERROR("uv_timer_stop() failed: %s", uv_strerror(err)); } } err = uv_timer_start(this->uvHandle, static_cast(onTimer), this->timeout, this->repeat); if (err != 0) { MS_THROW_ERROR("uv_timer_start() failed: %s", uv_strerror(err)); } } void TimerHandle::Restart(uint64_t timeout, uint64_t repeat) { MS_TRACE(); if (this->closed) { MS_THROW_ERROR("closed"); } this->timeout = timeout; this->repeat = repeat; int err; if (uv_is_active(reinterpret_cast(this->uvHandle)) != 0) { err = uv_timer_stop(this->uvHandle); if (err != 0) { MS_THROW_ERROR("uv_timer_stop() failed: %s", uv_strerror(err)); } } err = uv_timer_start(this->uvHandle, static_cast(onTimer), this->timeout, this->repeat); if (err != 0) { MS_THROW_ERROR("uv_timer_start() failed: %s", uv_strerror(err)); } } void TimerHandle::InternalClose() { MS_TRACE(); if (this->closed) { return; } this->closed = true; uv_close(reinterpret_cast(this->uvHandle), static_cast(onCloseTimer)); } void TimerHandle::OnUvTimer() { MS_TRACE(); // Notify the listener. this->listener->OnTimer(this); } ================================================ FILE: worker/src/handles/UdpSocketHandle.cpp ================================================ #define MS_CLASS "UdpSocketHandle" // #define MS_LOG_DEV_LEVEL 3 #include "handles/UdpSocketHandle.hpp" #ifdef MS_LIBURING_SUPPORTED #include "DepLibUring.hpp" #endif #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include // std::memcpy() /* Static. */ static constexpr size_t ReadBufferSize{ 65536 }; // NOTE: Buffer must be 4-byte aligned since RTP/RTCP/STUN packet parsing casts // it to structs (e.g. RTP::Packet::FixedHeader) that require 4-byte alignment. // Without this, accessing multi-byte fields would be undefined behavior on // strict-alignment architectures. alignas(4) static thread_local uint8_t ReadBuffer[ReadBufferSize]; /* Static methods for UV callbacks. */ inline static void onAlloc(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) { auto* socket = static_cast(handle->data); if (socket) { socket->OnUvRecvAlloc(suggestedSize, buf); } } inline static void onRecv( uv_udp_t* handle, ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags) { auto* socket = static_cast(handle->data); if (socket) { socket->OnUvRecv(nread, buf, addr, flags); } } inline static void onSend(uv_udp_send_t* req, int status) { auto* sendData = static_cast(req->data); auto* handle = req->handle; auto* socket = static_cast(handle->data); const auto* cb = sendData->cb; if (socket) { socket->OnUvSend(status, cb); } // Delete the UvSendData struct (it will delete the store and cb too). delete sendData; } inline static void onCloseUdp(uv_handle_t* handle) { delete reinterpret_cast(handle); } /* Instance methods. */ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) UdpSocketHandle::UdpSocketHandle(uv_udp_t* uvHandle) : uvHandle(uvHandle) { MS_TRACE(); this->uvHandle->data = static_cast(this); // NOLINTNEXTLINE(misc-const-correctness) int err = uv_udp_recv_start( this->uvHandle, static_cast(onAlloc), static_cast(onRecv)); if (err != 0) { uv_close(reinterpret_cast(this->uvHandle), static_cast(onCloseUdp)); MS_THROW_ERROR("uv_udp_recv_start() failed: %s", uv_strerror(err)); } // Set local address. if (!SetLocalAddress()) { uv_close(reinterpret_cast(this->uvHandle), static_cast(onCloseUdp)); MS_THROW_ERROR("error setting local IP and port"); } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { err = uv_fileno(reinterpret_cast(this->uvHandle), std::addressof(this->fd)); if (err != 0) { MS_THROW_ERROR("uv_fileno() failed: %s", uv_strerror(err)); } } #endif } UdpSocketHandle::~UdpSocketHandle() { MS_TRACE(); if (!this->closed) { try { InternalClose(); } catch (const std::exception& e) { MS_ERROR("error closing UDP socket: %s", e.what()); } } } void UdpSocketHandle::Dump(int indentation) const { MS_DUMP_CLEAN(indentation, ""); MS_DUMP_CLEAN(indentation, " local IP: %s", this->localIp.c_str()); MS_DUMP_CLEAN(indentation, " local port: %" PRIu16, static_cast(this->localPort)); MS_DUMP_CLEAN(indentation, " closed: %s", this->closed ? "yes" : "no"); MS_DUMP_CLEAN(indentation, ""); } void UdpSocketHandle::Send( const uint8_t* data, size_t len, const struct sockaddr* addr, UdpSocketHandle::onSendCallback* cb) { MS_TRACE(); if (this->closed) { if (cb) { (*cb)(false); delete cb; } return; } if (len == 0) { if (cb) { (*cb)(false); delete cb; } return; } #ifdef MS_LIBURING_SUPPORTED if (DepLibUring::IsEnabled()) { if (!DepLibUring::IsActive()) { goto send_libuv; } // Prepare the data to be sent. // NOTE: If all SQEs are currently in use or no UserData entry is available we'll // fall back to libuv. auto prepared = DepLibUring::PrepareSend(this->fd, data, len, addr, cb); if (!prepared) { MS_DEBUG_DEV("cannot send via liburing, fallback to libuv"); goto send_libuv; } return; } send_libuv: #endif // First try uv_udp_try_send(). In case it can not directly send the datagram // then build a uv_req_t and use uv_udp_send(). uv_buf_t buffer = uv_buf_init(reinterpret_cast(const_cast(data)), len); const int sent = uv_udp_try_send(this->uvHandle, &buffer, 1, addr); // Entire datagram was sent. Done. if (sent == static_cast(len)) { // Update sent bytes. this->sentBytes += sent; if (cb) { (*cb)(true); delete cb; } return; } else if (sent >= 0) { MS_WARN_DEV("datagram truncated (just %d of %zu bytes were sent)", sent, len); // Update sent bytes. this->sentBytes += sent; if (cb) { (*cb)(false); delete cb; } return; } // Any error but legit EAGAIN. Use uv_udp_send(). else if (sent != UV_EAGAIN) { MS_WARN_DEV("uv_udp_try_send() failed, trying uv_udp_send(): %s", uv_strerror(sent)); } auto* sendData = new UvSendData(len); sendData->req.data = static_cast(sendData); std::memcpy(sendData->store, data, len); sendData->cb = cb; buffer = uv_buf_init(reinterpret_cast(sendData->store), len); const int err = uv_udp_send( &sendData->req, this->uvHandle, &buffer, 1, addr, static_cast(onSend)); if (err != 0) { // NOTE: uv_udp_send() returns error if a wrong INET family is given // (IPv6 destination on a IPv4 binded socket), so be ready. MS_WARN_DEV("uv_udp_send() failed: %s", uv_strerror(err)); if (cb) { (*cb)(false); } // Delete the UvSendData struct (it will delete the store and cb too). delete sendData; } else { // Update sent bytes. this->sentBytes += len; } } uint32_t UdpSocketHandle::GetSendBufferSize() const { MS_TRACE(); int size{ 0 }; const int err = uv_send_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(size)); if (err) { MS_THROW_ERROR("uv_send_buffer_size() failed: %s", uv_strerror(err)); } return static_cast(size); } void UdpSocketHandle::SetSendBufferSize(uint32_t size) { MS_TRACE(); auto sizeInt = static_cast(size); if (sizeInt <= 0) { MS_THROW_TYPE_ERROR("invalid size: %d", sizeInt); } const int err = uv_send_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(sizeInt)); if (err) { MS_THROW_ERROR("uv_send_buffer_size() failed: %s", uv_strerror(err)); } } uint32_t UdpSocketHandle::GetRecvBufferSize() const { MS_TRACE(); int size{ 0 }; const int err = uv_recv_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(size)); if (err) { MS_THROW_ERROR("uv_recv_buffer_size() failed: %s", uv_strerror(err)); } return static_cast(size); } void UdpSocketHandle::SetRecvBufferSize(uint32_t size) { MS_TRACE(); auto sizeInt = static_cast(size); if (sizeInt <= 0) { MS_THROW_TYPE_ERROR("invalid size: %d", sizeInt); } const int err = uv_recv_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(sizeInt)); if (err) { MS_THROW_ERROR("uv_recv_buffer_size() failed: %s", uv_strerror(err)); } } void UdpSocketHandle::InternalClose() { MS_TRACE(); if (this->closed) { return; } this->closed = true; // Tell the UV handle that the UdpSocketHandle has been closed. this->uvHandle->data = nullptr; // Don't read more. const int err = uv_udp_recv_stop(this->uvHandle); if (err != 0) { MS_ABORT("uv_udp_recv_stop() failed: %s", uv_strerror(err)); } uv_close(reinterpret_cast(this->uvHandle), static_cast(onCloseUdp)); } bool UdpSocketHandle::SetLocalAddress() { MS_TRACE(); int err; int len = sizeof(this->localAddr); err = uv_udp_getsockname(this->uvHandle, reinterpret_cast(&this->localAddr), &len); if (err != 0) { MS_ERROR("uv_udp_getsockname() failed: %s", uv_strerror(err)); return false; } int family; Utils::IP::GetAddressInfo( reinterpret_cast(&this->localAddr), family, this->localIp, this->localPort); return true; } inline void UdpSocketHandle::OnUvRecvAlloc(size_t /*suggestedSize*/, uv_buf_t* buf) { MS_TRACE(); // Tell UV to write into the static buffer. buf->base = reinterpret_cast(ReadBuffer); // Give UV all the buffer space. buf->len = ReadBufferSize; } inline void UdpSocketHandle::OnUvRecv( ssize_t nread, const uv_buf_t* buf, const struct sockaddr* addr, unsigned int flags) { MS_TRACE(); // NOTE: Ignore if there is nothing to read or if it was an empty datagram. if (nread == 0) { return; } // Check flags. if ((flags & UV_UDP_PARTIAL) != 0u) { MS_ERROR("received datagram was truncated due to insufficient buffer, ignoring it"); return; } // Data received. if (nread > 0) { // Update received bytes. this->recvBytes += nread; // Notify the subclass. UserOnUdpDatagramReceived(reinterpret_cast(buf->base), nread, ReadBufferSize, addr); } // Some error. else { MS_DEBUG_DEV("read error: %s", uv_strerror(nread)); } } inline void UdpSocketHandle::OnUvSend(int status, UdpSocketHandle::onSendCallback* cb) { MS_TRACE(); // NOTE: Do not delete cb here since it will be delete in onSend() above. if (status == 0) { if (cb) { (*cb)(true); } } else { #if MS_LOG_DEV_LEVEL == 3 MS_DEBUG_DEV("send error: %s", uv_strerror(status)); #endif if (cb) { (*cb)(false); } } } ================================================ FILE: worker/src/handles/UnixStreamSocketHandle.cpp ================================================ /** * NOTE: This code cannot log to the Channel since this is the base code of the * Channel. */ #define MS_CLASS "UnixStreamSocketHandle" // #define MS_LOG_DEV_LEVEL 3 #include "handles/UnixStreamSocketHandle.hpp" #include "DepLibUV.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include // std::memcpy() /* Static methods for UV callbacks. */ inline static void onAlloc(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) { auto* socket = static_cast(handle->data); if (socket) { socket->OnUvReadAlloc(suggestedSize, buf); } } inline static void onRead(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) { auto* socket = static_cast(handle->data); if (socket) { socket->OnUvRead(nread, buf); } } inline static void onWrite(uv_write_t* req, int status) { auto* writeData = static_cast(req->data); auto* handle = req->handle; auto* socket = static_cast(handle->data); // Just notify the UnixStreamSocketHandle when error. if (socket && status != 0) { socket->OnUvWriteError(status); } // Delete the UvWriteData struct. delete writeData; } // NOTE: We have different onCloseXxx() callbacks to avoid an ASAN warning by // ensuring that we call `delete xxx` with same type as `new xxx` before. inline static void onClosePipe(uv_handle_t* handle) { delete reinterpret_cast(handle); } inline static void onShutdown(uv_shutdown_t* req, int /*status*/) { auto* handle = req->handle; delete req; // Now do close the handle. uv_close(reinterpret_cast(handle), static_cast(onClosePipe)); } /* Instance methods. */ UnixStreamSocketHandle::UnixStreamSocketHandle( int fd, size_t bufferSize, UnixStreamSocketHandle::Role role) : uvHandle(new uv_pipe_t), bufferSize(bufferSize), role(role) { MS_TRACE_STD(); int err; this->uvHandle->data = static_cast(this); err = uv_pipe_init(DepLibUV::GetLoop(), this->uvHandle, 0); if (err != 0) { delete this->uvHandle; this->uvHandle = nullptr; MS_THROW_ERROR_STD("uv_pipe_init() failed: %s", uv_strerror(err)); } err = uv_pipe_open(this->uvHandle, fd); if (err != 0) { uv_close(reinterpret_cast(this->uvHandle), static_cast(onClosePipe)); MS_THROW_ERROR_STD("uv_pipe_open() failed: %s", uv_strerror(err)); } if (this->role == UnixStreamSocketHandle::Role::CONSUMER) { // Start reading. err = uv_read_start( reinterpret_cast(this->uvHandle), static_cast(onAlloc), static_cast(onRead)); if (err != 0) { uv_close(reinterpret_cast(this->uvHandle), static_cast(onClosePipe)); MS_THROW_ERROR_STD("uv_read_start() failed: %s", uv_strerror(err)); } } // NOTE: Don't allocate the buffer here. Instead wait for the first uv_alloc_cb(). } UnixStreamSocketHandle::~UnixStreamSocketHandle() { MS_TRACE_STD(); if (!this->closed) { Close(); } delete[] this->buffer; } // NOTE: In UnixStreamSocketHandle we need a poublic Close() method and cannot // just rely on the destructor plus a private InternalClose() method. void UnixStreamSocketHandle::Close() { MS_TRACE_STD(); if (this->closed) { return; } int err; this->closed = true; // Tell the UV handle that the UnixStreamSocketHandle has been closed. this->uvHandle->data = nullptr; if (this->role == UnixStreamSocketHandle::Role::CONSUMER) { // Don't read more. err = uv_read_stop(reinterpret_cast(this->uvHandle)); if (err != 0) { try { MS_ABORT("uv_read_stop() failed: %s", uv_strerror(err)); } catch (const std::exception& e) { MS_ERROR("%s", e.what()); } } } // If there is no error and the peer didn't close its pipe side then close gracefully. if (this->role == UnixStreamSocketHandle::Role::PRODUCER && !this->hasError && !this->isClosedByPeer) { // Use uv_shutdown() so pending data to be written will be sent to the peer before closing. auto* req = new uv_shutdown_t; req->data = static_cast(this); err = uv_shutdown( req, reinterpret_cast(this->uvHandle), static_cast(onShutdown)); if (err != 0) { try { MS_ABORT("uv_shutdown() failed: %s", uv_strerror(err)); } catch (const std::exception& e) { MS_ERROR("%s", e.what()); } } } // Otherwise directly close the socket. else { uv_close(reinterpret_cast(this->uvHandle), static_cast(onClosePipe)); } } void UnixStreamSocketHandle::Write(const uint8_t* data, size_t len) { MS_TRACE_STD(); if (this->closed) { return; } if (len == 0) { return; } // First try uv_try_write(). In case it can not directly send all the given data // then build a uv_req_t and use uv_write(). uv_buf_t buffer = uv_buf_init(reinterpret_cast(const_cast(data)), len); int written = uv_try_write(reinterpret_cast(this->uvHandle), &buffer, 1); // All the data was written. Done. if (written == static_cast(len)) { return; } // Cannot write any data at first time. Use uv_write(). else if (written == UV_EAGAIN || written == UV_ENOSYS) { // Set written to 0 so pendingLen can be properly calculated. written = 0; } // Any other error. else if (written < 0) { MS_ERROR_STD("uv_try_write() failed, trying uv_write(): %s", uv_strerror(written)); // Set written to 0 so pendingLen can be properly calculated. written = 0; } const size_t pendingLen = len - written; auto* writeData = new UvWriteData(pendingLen); writeData->req.data = static_cast(writeData); std::memcpy(writeData->store, data + written, pendingLen); buffer = uv_buf_init(reinterpret_cast(writeData->store), pendingLen); const int err = uv_write( &writeData->req, reinterpret_cast(this->uvHandle), &buffer, 1, static_cast(onWrite)); if (err != 0) { MS_ERROR_STD("uv_write() failed: %s", uv_strerror(err)); // Delete the UvSendData struct. delete writeData; } } uint32_t UnixStreamSocketHandle::GetSendBufferSize() const { MS_TRACE(); int size{ 0 }; const int err = uv_send_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(size)); if (err) { MS_THROW_ERROR_STD("uv_send_buffer_size() failed: %s", uv_strerror(err)); } return static_cast(size); } void UnixStreamSocketHandle::SetSendBufferSize(uint32_t size) { MS_TRACE(); auto sizeInt = static_cast(size); if (sizeInt <= 0) { MS_THROW_TYPE_ERROR_STD("invalid size: %d", sizeInt); } const int err = uv_send_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(sizeInt)); if (err) { MS_THROW_ERROR_STD("uv_send_buffer_size() failed: %s", uv_strerror(err)); } } uint32_t UnixStreamSocketHandle::GetRecvBufferSize() const { MS_TRACE(); int size{ 0 }; const int err = uv_recv_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(size)); if (err) { MS_THROW_ERROR_STD("uv_recv_buffer_size() failed: %s", uv_strerror(err)); } return static_cast(size); } void UnixStreamSocketHandle::SetRecvBufferSize(uint32_t size) { MS_TRACE(); auto sizeInt = static_cast(size); if (sizeInt <= 0) { MS_THROW_TYPE_ERROR_STD("invalid size: %d", sizeInt); } const int err = uv_recv_buffer_size(reinterpret_cast(this->uvHandle), std::addressof(sizeInt)); if (err) { MS_THROW_ERROR_STD("uv_recv_buffer_size() failed: %s", uv_strerror(err)); } } inline void UnixStreamSocketHandle::OnUvReadAlloc(size_t /*suggestedSize*/, uv_buf_t* buf) { MS_TRACE_STD(); // If this is the first call to onUvReadAlloc() then allocate the receiving buffer now. if (!this->buffer) { this->buffer = new uint8_t[this->bufferSize]; } // Tell UV to write after the last data byte in the buffer. buf->base = reinterpret_cast(this->buffer + this->bufferDataLen); // Give UV all the remaining space in the buffer. if (this->bufferSize > this->bufferDataLen) { buf->len = this->bufferSize - this->bufferDataLen; } else { buf->len = 0; MS_ERROR_STD("no available space in the buffer"); } } inline void UnixStreamSocketHandle::OnUvRead(ssize_t nread, const uv_buf_t* /*buf*/) { MS_TRACE_STD(); if (nread == 0) { return; } // Data received. if (nread > 0) { // Update the buffer data length. this->bufferDataLen += static_cast(nread); // Notify the subclass. UserOnUnixStreamRead(); } // Peer disconnected. else if (nread == UV_EOF || nread == UV_ECONNRESET) { this->isClosedByPeer = true; // Close local side of the pipe. Close(); // Notify the subclass. UserOnUnixStreamSocketClosed(); } // Some error. else { MS_ERROR_STD("read error, closing the pipe: %s", uv_strerror(nread)); this->hasError = true; // Close the socket. Close(); // Notify the subclass. UserOnUnixStreamSocketClosed(); } } inline void UnixStreamSocketHandle::OnUvWriteError(int error) { MS_TRACE_STD(); if (error != UV_EPIPE && error != UV_ENOTCONN) { this->hasError = true; } MS_ERROR_STD("write error, closing the pipe: %s", uv_strerror(error)); Close(); // Notify the subclass. UserOnUnixStreamSocketClosed(); } ================================================ FILE: worker/src/lib.cpp ================================================ #define MS_CLASS "mediasoup-worker" // #define MS_LOG_DEV_LEVEL 3 #include "lib.hpp" #include "common.hpp" #include "DepLibSRTP.hpp" #ifdef MS_LIBURING_SUPPORTED #include "DepLibUring.hpp" #endif #include "DepLibUV.hpp" #include "DepLibWebRTC.hpp" #include "DepOpenSSL.hpp" // TODO: Remove once we only use built-in SCTP stack. #include "DepUsrSCTP.hpp" #include "Logger.hpp" #include "MediaSoupErrors.hpp" #include "Settings.hpp" #include "Shared.hpp" #include "Utils.hpp" #include "Worker.hpp" #include "Channel/ChannelMessageRegistrator.hpp" #include "Channel/ChannelNotifier.hpp" #include "Channel/ChannelSocket.hpp" #include "RTC/DtlsTransport.hpp" #include "RTC/SrtpSession.hpp" #include #include // sigaction() #include static void ignoreSignals(); /** * Initializes everything and creates an instance of Worker class. * * @return * - 0 if the Worker terminated properly. * - 42 if given settings are wrong/invalid. * - 40 if an uncaught MediasoupError happens (only in non executable mode). * - 134 when any other uncaught C++ exception happens (only in non executable * mode). */ // NOLINTNEXTLINE(readability-identifier-naming) extern "C" int mediasoup_worker_run( int argc, char* argv[], const char* version, int consumerChannelFd, int producerChannelFd, ChannelReadFn channelReadFn, ChannelReadCtx channelReadCtx, ChannelWriteFn channelWriteFn, ChannelWriteCtx channelWriteCtx) { // Initialize libuv stuff (we need it for the Channel). DepLibUV::ClassInit(); // Channel socket. If Worker instance runs properly, this socket is closed by // it in its destructor. Otherwise it's closed here by also letting libuv // deallocate its UV handles. std::unique_ptr channel{ nullptr }; #ifndef MS_EXECUTABLE try { #endif if (channelReadFn) { channel.reset( new Channel::ChannelSocket(channelReadFn, channelReadCtx, channelWriteFn, channelWriteCtx)); } else { channel.reset(new Channel::ChannelSocket(consumerChannelFd, producerChannelFd)); } #ifndef MS_EXECUTABLE } catch (const MediaSoupError& error) { MS_ERROR_STD("error creating the Channel: %s", error.what()); DepLibUV::RunLoop(); DepLibUV::ClassDestroy(); // 40 is a custom exit code to notify "unknown error" caller. return 40; } #endif // Create a Shared singleton. std::unique_ptr shared{ new Shared( /*channelMessageRegistrator*/ new Channel::ChannelMessageRegistrator(), /*channelNotifier*/ new Channel::ChannelNotifier(channel.get())) }; // Initialize the Logger. Logger::ClassInit(channel.get()); try { Settings::SetConfiguration(argc, argv); } catch (const MediaSoupTypeError& error) { MS_ERROR_STD("settings error: %s", error.what()); channel->Close(); DepLibUV::RunLoop(); DepLibUV::ClassDestroy(); // 42 is a custom exit code to notify "settings error" caller. return 42; } #ifndef MS_EXECUTABLE catch (const MediaSoupError& error) { MS_ERROR_STD("unexpected settings error: %s", error.what()); channel->Close(); DepLibUV::RunLoop(); DepLibUV::ClassDestroy(); // 40 is a custom exit code to notify "unknown error" caller. return 40; } catch (const std::runtime_error& error) { // 134 is the exit code for SIGABRT. return 134; } #endif MS_DEBUG_TAG(info, "starting mediasoup-worker process [version:%s]", version); #ifdef MS_LITTLE_ENDIAN MS_DEBUG_TAG(info, "little-endian CPU detected"); #elif defined(MS_BIG_ENDIAN) MS_DEBUG_TAG(info, "big-endian CPU detected"); #else MS_WARN_TAG(info, "cannot determine whether little-endian or big-endian"); #endif #if defined(INTPTR_MAX) && defined(INT32_MAX) && (INTPTR_MAX == INT32_MAX) MS_DEBUG_TAG(info, "32 bits architecture detected"); #elif defined(INTPTR_MAX) && defined(INT64_MAX) && (INTPTR_MAX == INT64_MAX) MS_DEBUG_TAG(info, "64 bits architecture detected"); #else MS_WARN_TAG(info, "cannot determine 32 or 64 bits architecture"); #endif Settings::PrintConfiguration(); DepLibUV::PrintVersion(); #ifndef MS_EXECUTABLE try { #endif // Initialize static stuff. DepOpenSSL::ClassInit(); DepLibSRTP::ClassInit(); // TODO: Remove once we only use built-in SCTP stack. if (!Settings::configuration.useBuiltInSctpStack) { DepUsrSCTP::ClassInit(); } #ifdef MS_LIBURING_SUPPORTED DepLibUring::ClassInit(); #endif DepLibWebRTC::ClassInit(); Utils::Crypto::ClassInit(); RTC::DtlsTransport::ClassInit(); RTC::SrtpSession::ClassInit(); // Ignore some signals. ignoreSignals(); MS_DEBUG_TAG(info, "creating Worker instance"); // Run the Worker. const Worker worker(channel.get(), shared.get()); MS_DEBUG_TAG(info, "Worker instance terminated"); // Free static stuff. DepLibSRTP::ClassDestroy(); Utils::Crypto::ClassDestroy(); DepLibWebRTC::ClassDestroy(); #ifdef MS_LIBURING_SUPPORTED DepLibUring::ClassDestroy(); #endif RTC::DtlsTransport::ClassDestroy(); // TODO: Remove once we only use built-in SCTP stack. if (!Settings::configuration.useBuiltInSctpStack) { DepUsrSCTP::ClassDestroy(); } DepLibUV::ClassDestroy(); return 0; #ifndef MS_EXECUTABLE } catch (const MediaSoupError& error) { MS_ERROR_STD("failure exit: %s", error.what()); // 40 is a custom exit code to notify "unknown error" caller. return 40; } catch (const std::runtime_error& error) { // 134 is the exit code for SIGABRT. return 134; } #endif } static void ignoreSignals() { #ifdef MS_EXECUTABLE #ifndef _WIN32 MS_TRACE(); int err; struct sigaction act{}; // NOLINT(cppcoreguidelines-pro-type-member-init) // clang-format off absl::flat_hash_map const ignoredSignals = { { "PIPE", SIGPIPE }, { "HUP", SIGHUP }, { "ALRM", SIGALRM }, { "USR1", SIGUSR1 }, { "USR2", SIGUSR2 } }; // clang-format on act.sa_handler = SIG_IGN; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) act.sa_flags = 0; err = sigfillset(&act.sa_mask); if (err != 0) { MS_THROW_ERROR("sigfillset() failed: %s", std::strerror(errno)); } for (const auto& kv : ignoredSignals) { const auto& sigName = kv.first; const int sigId = kv.second; err = sigaction(sigId, &act, nullptr); if (err != 0) { MS_THROW_ERROR("sigaction() failed for signal %s: %s", sigName.c_str(), std::strerror(errno)); } } #endif #endif } ================================================ FILE: worker/src/lib.rs ================================================ use std::os::raw::{c_char, c_int, c_void}; pub use planus_codegen::*; mod planus_codegen { #![allow(clippy::all)] include!(concat!(env!("OUT_DIR"), "/fbs.rs")); } #[repr(transparent)] #[derive(Copy, Clone)] pub struct UvAsyncT(pub *const c_void); unsafe impl Send for UvAsyncT {} #[repr(transparent)] pub struct ChannelReadCtx(pub *const c_void); pub type ChannelReadFreeFn = Option< unsafe extern "C" fn( /* message: */ *mut u8, /* message_len: */ u32, /* message_ctx: */ usize, ), >; pub type ChannelReadFn = unsafe extern "C" fn( /* message: */ *mut *mut u8, /* message_len: */ *mut u32, /* message_ctx: */ *mut usize, // This is `uv_async_t` handle that can be called later with `uv_async_send()` when there is // more data to read /* handle */ UvAsyncT, /* ctx: */ ChannelReadCtx, ) -> ChannelReadFreeFn; unsafe impl Send for ChannelReadCtx {} #[repr(transparent)] pub struct ChannelWriteCtx(pub *const c_void); pub type ChannelWriteFn = unsafe extern "C" fn( /* message: */ *const u8, /* message_len: */ u32, /* ctx: */ ChannelWriteCtx, ); unsafe impl Send for ChannelWriteCtx {} #[link(name = "mediasoup-worker", kind = "static")] extern "C" { /// Returns `0` on success, or an error code `< 0` on failure pub fn uv_async_send(handle: UvAsyncT) -> c_int; pub fn mediasoup_worker_run( argc: c_int, argv: *const *const c_char, version: *const c_char, consumer_channel_fd: c_int, producer_channel_fd: c_int, channel_read_fn: ChannelReadFn, channel_read_ctx: ChannelReadCtx, channel_write_fn: ChannelWriteFn, channel_write_ctx: ChannelWriteCtx, ) -> c_int; } ================================================ FILE: worker/src/main.cpp ================================================ #define MS_CLASS "mediasoup-worker" // #define MS_LOG_DEV_LEVEL 3 #include "common.hpp" #include "Logger.hpp" #include "lib.hpp" #include // std::_Exit() #include static constexpr int ConsumerChannelFd{ 3 }; static constexpr int ProducerChannelFd{ 4 }; int main(int argc, char* argv[]) { const char* envVersion = std::getenv("MEDIASOUP_VERSION"); // Ensure we are called by our Node library. if (!envVersion) { MS_ERROR_STD("you don't seem to be my real father!"); // 41 is a custom exit code to notify about "missing MEDIASOUP_VERSION" env. std::_Exit(41); } const std::string version{ envVersion }; const auto statusCode = mediasoup_worker_run( /*argc*/ argc, /*argv*/ argv, /*version*/ version.c_str(), /*consumerChannelFd*/ ConsumerChannelFd, /*producerChannelFd*/ ProducerChannelFd, /*channelReadFn*/ nullptr, /*channelReadCtx*/ nullptr, /*channelWriteFn*/ nullptr, /*channelWriteCtx*/ nullptr); std::_Exit(statusCode); } ================================================ FILE: worker/subprojects/.clang-tidy ================================================ # Bad workaround to disable clang-tidy checks in worker/subprojects folder. # Ideally we should use a modern version so worker/.clang-tidy-ignore would be # honored. Here we should also be able to do Checks: '-*' to disable all rules # but if we do it it fails with "Error: no checks enable". --- Checks: 'modernize-*' ... ================================================ FILE: worker/subprojects/abseil-cpp.wrap ================================================ [wrap-file] directory = abseil-cpp-20240722.0 source_url = https://github.com/abseil/abseil-cpp/releases/download/20240722.0/abseil-cpp-20240722.0.tar.gz source_filename = abseil-cpp-20240722.0.tar.gz source_hash = f50e5ac311a81382da7fa75b97310e4b9006474f9560ac46f54a9967f07d4ae3 patch_filename = abseil-cpp_20240722.0-4_patch.zip patch_url = https://wrapdb.mesonbuild.com/v2/abseil-cpp_20240722.0-4/get_patch patch_hash = e39d535c4707f6e342e84e3e616449e1cc98cb7fadda92a09820b0ae67c6d0d6 source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/abseil-cpp_20240722.0-4/abseil-cpp-20240722.0.tar.gz wrapdb_version = 20240722.0-4 [provide] absl_base = absl_base_dep absl_container = absl_container_dep absl_debugging = absl_debugging_dep absl_log = absl_log_dep absl_flags = absl_flags_dep absl_hash = absl_hash_dep absl_crc = absl_crc_dep absl_numeric = absl_numeric_dep absl_profiling = absl_profiling_dep absl_random = absl_random_dep absl_status = absl_status_dep absl_strings = absl_strings_dep absl_synchronization = absl_synchronization_dep absl_time = absl_time_dep absl_types = absl_types_dep absl_algorithm_container = absl_base_dep absl_any_invocable = absl_base_dep absl_bad_any_cast_impl = absl_types_dep absl_bad_optional_access = absl_types_dep absl_bad_variant_access = absl_types_dep absl_bind_front = absl_base_dep absl_city = absl_hash_dep absl_civil_time = absl_time_dep absl_cleanup = absl_base_dep absl_cord = absl_strings_dep absl_cord_internal = absl_strings_dep absl_cordz_functions = absl_strings_dep absl_cordz_handle = absl_strings_dep absl_cordz_info = absl_strings_dep absl_cordz_sample_token = absl_strings_dep absl_core_headers = absl_base_dep absl_crc32c = absl_crc_dep absl_debugging_internal = absl_debugging_dep absl_demangle_internal = absl_debugging_dep absl_die_if_null = absl_log_dep absl_examine_stack = absl_debugging_dep absl_exponential_biased = absl_profiling_dep absl_failure_signal_handler = absl_debugging_dep absl_flags_commandlineflag = absl_flags_dep absl_flags_commandlineflag_internal = absl_flags_dep absl_flags_config = absl_flags_dep absl_flags_internal = absl_flags_dep absl_flags_marshalling = absl_flags_dep absl_flags_parse = absl_flags_dep absl_flags_private_handle_accessor = absl_flags_dep absl_flags_program_name = absl_flags_dep absl_flags_reflection = absl_flags_dep absl_flags_usage = absl_flags_dep absl_flags_usage_internal = absl_flags_dep absl_flat_hash_map = absl_container_dep absl_flat_hash_set = absl_container_dep absl_function_ref = absl_base_dep absl_graphcycles_internal = absl_synchronization_dep absl_hashtablez_sampler = absl_container_dep absl_inlined_vector = absl_container_dep absl_int128 = absl_numeric_dep absl_leak_check = absl_debugging_dep absl_log_initialize = absl_log_dep absl_log_internal_check_op = absl_log_dep absl_log_internal_message = absl_log_dep absl_log_severity = absl_base_dep absl_low_level_hash = absl_hash_dep absl_memory = absl_base_dep absl_optional = absl_types_dep absl_periodic_sampler = absl_profiling_dep absl_random_bit_gen_ref = absl_random_dep absl_random_distributions = absl_random_dep absl_random_internal_distribution_test_util = absl_random_dep absl_random_internal_platform = absl_random_dep absl_random_internal_pool_urbg = absl_random_dep absl_random_internal_randen = absl_random_dep absl_random_internal_randen_hwaes = absl_random_dep absl_random_internal_randen_hwaes_impl = absl_random_dep absl_random_internal_randen_slow = absl_random_dep absl_random_internal_seed_material = absl_random_dep absl_random_random = absl_random_dep absl_random_seed_gen_exception = absl_random_dep absl_random_seed_sequences = absl_random_dep absl_raw_hash_set = absl_container_dep absl_raw_logging_internal = absl_base_dep absl_scoped_set_env = absl_base_dep absl_span = absl_types_dep absl_spinlock_wait = absl_base_dep absl_stacktrace = absl_debugging_dep absl_statusor = absl_status_dep absl_str_format = absl_strings_dep absl_str_format_internal = absl_strings_dep absl_strerror = absl_base_dep absl_string_view = absl_strings_dep absl_strings_internal = absl_strings_dep absl_symbolize = absl_debugging_dep absl_throw_delegate = absl_base_dep absl_time_zone = absl_time_dep absl_type_traits = absl_base_dep absl_utility = absl_base_dep absl_variant = absl_types_dep ================================================ FILE: worker/subprojects/catch2.wrap ================================================ [wrap-file] directory = Catch2-3.14.0 source_url = https://github.com/catchorg/Catch2/archive/v3.14.0.tar.gz source_filename = Catch2-3.14.0.tar.gz source_hash = ba2a939efead3c833c499cf487e185762f419a71d30158cd1b43c6079c586490 source_fallback_url = https://wrapdb.mesonbuild.com/v2/catch2_3.14.0-1/get_source/Catch2-3.14.0.tar.gz wrapdb_version = 3.14.0-1 [provide] catch2 = catch2_dep catch2-with-main = catch2_with_main_dep ================================================ FILE: worker/subprojects/flatbuffers.wrap ================================================ [wrap-file] directory = flatbuffers-24.3.25 source_url = https://github.com/google/flatbuffers/archive/v24.3.25.tar.gz source_filename = flatbuffers-24.3.25.tar.gz source_hash = 4157c5cacdb59737c5d627e47ac26b140e9ee28b1102f812b36068aab728c1ed patch_filename = flatbuffers_24.3.25-1_patch.zip patch_url = https://wrapdb.mesonbuild.com/v2/flatbuffers_24.3.25-1/get_patch patch_hash = 9be75a2053a19e5a59175f2fbbf6e9d40f4243d2786f2661a131d3502ddfa457 source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/flatbuffers_24.3.25-1/flatbuffers-24.3.25.tar.gz wrapdb_version = 24.3.25-1 [provide] flatbuffers = flatbuffers_dep program_names = flatc, flathash ================================================ FILE: worker/subprojects/libsrtp3.wrap ================================================ [wrap-file] directory = libsrtp-3.0.0-beta source_url = https://github.com/versatica/libsrtp/archive/v3.0.0-beta.zip source_filename = libsrtp-3.0.0-beta.zip source_hash = b0cb21aaf27120b1a242f3d9b07c145b4a6c7a02a25da0824539fd95611f690c [provide] libsrtp3 = libsrtp3_dep ================================================ FILE: worker/subprojects/liburing.wrap ================================================ [wrap-file] directory = liburing-liburing-2.14 source_url = https://github.com/axboe/liburing/archive/refs/tags/liburing-2.14.tar.gz source_filename = liburing-2.14.tar.gz source_hash = 5f80964108981c6ad979c735f0b4877d5f49914c2a062f8e88282f26bf61de0c source_fallback_url = https://wrapdb.mesonbuild.com/v2/liburing_2.14-1/get_source/liburing-2.14.tar.gz patch_filename = liburing_2.14-1_patch.zip patch_url = https://wrapdb.mesonbuild.com/v2/liburing_2.14-1/get_patch patch_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/liburing_2.14-1/liburing_2.14-1_patch.zip patch_hash = 4d0ba8f507c25c4fb9a31507a5c06d2a6c253822828084a0101e2bea27dba62a wrapdb_version = 2.14-1 [provide] dependency_names = liburing ================================================ FILE: worker/subprojects/libuv.wrap ================================================ [wrap-file] directory = libuv-v1.51.0 source_url = https://dist.libuv.org/dist/v1.51.0/libuv-v1.51.0.tar.gz source_filename = libuv-v1.51.0.tar.gz source_hash = 5f0557b90b1106de71951a3c3931de5e0430d78da1d9a10287ebc7a3f78ef8eb patch_filename = libuv_1.51.0-1_patch.zip patch_url = https://wrapdb.mesonbuild.com/v2/libuv_1.51.0-1/get_patch patch_hash = 0fb123dee5e74621a767a8f2a29dde7219c65a01a5fe63e3c8ffeed675a2d820 source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/libuv_1.51.0-1/libuv-v1.51.0.tar.gz wrapdb_version = 1.51.0-1 [provide] libuv = libuv_dep ================================================ FILE: worker/subprojects/openssl.wrap ================================================ [wrap-file] directory = openssl-3.0.8 source_url = https://www.openssl.org/source/openssl-3.0.8.tar.gz source_filename = openssl-3.0.8.tar.gz source_hash = 6c13d2bf38fdf31eac3ce2a347073673f5d63263398f1f69d0df4a41253e4b3e patch_filename = openssl_3.0.8-3_patch.zip patch_url = https://wrapdb.mesonbuild.com/v2/openssl_3.0.8-3/get_patch patch_hash = 300da189e106942347d61a4a4295aa2edbcf06184f8d13b4cee0bed9fb936963 source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/openssl_3.0.8-3/openssl-3.0.8.tar.gz wrapdb_version = 3.0.8-3 [provide] libcrypto = libcrypto_dep libssl = libssl_dep openssl = openssl_dep ================================================ FILE: worker/subprojects/usrsctp.wrap ================================================ [wrap-file] directory = usrsctp-fd070e05a7474f38c7fecdf4d4b6005d2547ee00 source_url = https://github.com/sctplab/usrsctp/archive/fd070e05a7474f38c7fecdf4d4b6005d2547ee00.zip source_filename = fd070e05a7474f38c7fecdf4d4b6005d2547ee00.zip source_hash = a0f9255a88fef2e375b590f2823ddbc70ab38142b9931cd3f1c7c0b700bf7043 [provide] usrsctp = usrsctp_dep ================================================ FILE: worker/subprojects/wingetopt.wrap ================================================ [wrap-file] directory = wingetopt-1.00 source_url = https://github.com/alex85k/wingetopt/archive/v1.00.zip source_filename = wingetopt-1.00.zip source_hash = 4454ca03a59702a4ca4d1488ca8fa6168b0c8d77dc739a6fe2825c3dd8609d87 ================================================ FILE: worker/tasks.py ================================================ # Ignore these pylint warnings, conventions and refactoring messages: # - W0301: Unnecessary semicolon (unnecessary-semicolon) # - W0622: Redefining built-in 'format' (redefined-builtin) # - W0702: No exception type(s) specified (bare-except) # - C0114: Missing module docstring (missing-module-docstring) # - C0301: Line too long (line-too-long) # # pylint: disable=W0301,W0622,W0702,C0114,C0301 # # This is a tasks.py file for the Python invoke package: # https://docs.pyinvoke.org/. # # It's a replacement of our Makefile with same tasks. # # Usage: # pip install invoke # invoke --list # import sys; import os; import inspect; import shutil; from contextlib import contextmanager; # We import this from a custom location and pylint doesn't know. from invoke import task, call; # pylint: disable=import-error MEDIASOUP_BUILDTYPE = os.getenv('MEDIASOUP_BUILDTYPE') or 'Release'; WORKER_DIR = os.path.dirname(os.path.abspath( inspect.getframeinfo(inspect.currentframe()).filename )); # NOTE: MEDIASOUP_OUT_DIR is overrided by build.rs. MEDIASOUP_OUT_DIR = os.getenv('MEDIASOUP_OUT_DIR') or f'{WORKER_DIR}/out'; MEDIASOUP_INSTALL_DIR = os.getenv('MEDIASOUP_INSTALL_DIR') or f'{MEDIASOUP_OUT_DIR}/{MEDIASOUP_BUILDTYPE}'; BUILD_DIR = os.getenv('BUILD_DIR') or f'{MEDIASOUP_INSTALL_DIR}/build'; # Custom pip folder for invoke package. # NOTE: We invoke `pip install` always with `--no-user` to make it not complain # about "can not combine --user and --target". PIP_INVOKE_DIR = f'{MEDIASOUP_OUT_DIR}/pip_invoke'; # Custom pip folder for meson and ninja packages. PIP_MESON_NINJA_DIR = f'{MEDIASOUP_OUT_DIR}/pip_meson_ninja'; # Custom pip folder for pylint package. # NOTE: We do this because using --target and --upgrade in `pip install` is not # supported and a latter invokation entirely replaces the bin folder. PIP_PYLINT_DIR = f'{MEDIASOUP_OUT_DIR}/pip_pylint'; # If available (only on some *nix systems), os.sched_getaffinity(0) gets set of # CPUs the calling thread is restricted to. Instead, os.cpu_count() returns the # total number of CPUs in a system (it doesn't take into account how many of them # the calling thread can use). NUM_CORES = len(os.sched_getaffinity(0)) if hasattr(os, 'sched_getaffinity') else os.cpu_count(); PYTHON = os.getenv('PYTHON') or sys.executable; MESON = os.getenv('MESON') or f'{PIP_MESON_NINJA_DIR}/bin/meson'; MESON_VERSION = os.getenv('MESON_VERSION') or '1.9.1'; # MESON_ARGS can be used to provide extra configuration parameters to meson, # such as adding defines or changing optimization options. For instance, use # `MESON_ARGS="-Dms_log_trace=true -Dms_log_file_line=true" npm i` to compile # worker with tracing and enabled. # NOTE: On Windows make sure to add `--vsenv` or have MSVS environment already # active if you override this parameter. MESON_ARGS = os.getenv('MESON_ARGS') if os.getenv('MESON_ARGS') else '--vsenv' if os.name == 'nt' else ''; # Let's use a specific version of ninja to avoid buggy version 1.11.1: # https://mediasoup.discourse.group/t/partly-solved-could-not-detect-ninja-v1-8-2-or-newer/ # https://github.com/ninja-build/ninja/issues/2211 NINJA_VERSION = os.getenv('NINJA_VERSION') or '1.10.2.4'; PYLINT_VERSION = os.getenv('PYLINT_VERSION') or '3.0.2'; NPM = os.getenv('NPM') or 'npm'; DOCKER = os.getenv('DOCKER') or 'docker'; # pty=True in ctx.run() is not available on Windows so if stdout is not a TTY # let's assume PTY is not supported. Related issue in invoke project: # https://github.com/pyinvoke/invoke/issues/561 PTY_SUPPORTED = os.name != 'nt' and sys.stdout.isatty(); # Use sh (widely supported, more than bash) if not in Windows. SHELL = '/bin/sh' if not os.name == 'nt' else None; # Disable `*.pyc` files creation. os.environ['PYTHONDONTWRITEBYTECODE'] = 'true'; # Instruct meson where to look for ninja binary. if os.name == 'nt': # Windows is, of course, special. os.environ['NINJA'] = f'{PIP_MESON_NINJA_DIR}/bin/ninja.exe'; else: os.environ['NINJA'] = f'{PIP_MESON_NINJA_DIR}/bin/ninja'; # Instruct Python where to look for modules it needs, such that meson actually # runs from installed location. # NOTE: On Windows we must use ; instead of : to separate paths. PYTHONPATH = os.getenv('PYTHONPATH') or ''; if os.name == 'nt': os.environ['PYTHONPATH'] = f'{PIP_INVOKE_DIR};{PIP_MESON_NINJA_DIR};{PIP_PYLINT_DIR};{PYTHONPATH}'; else: os.environ['PYTHONPATH'] = f'{PIP_INVOKE_DIR}:{PIP_MESON_NINJA_DIR}:{PIP_PYLINT_DIR}:{PYTHONPATH}'; @contextmanager def cd_worker(): """ Context manager to change to worker/ folder the safe way """ original_dir = os.getcwd() os.chdir(WORKER_DIR) try: yield finally: os.chdir(original_dir) @task def meson_ninja(ctx): """ Install meson and ninja (also update Python pip and setuptools packages) """ if os.path.isfile(MESON): return; # Updated pip and setuptools are needed for meson. # `--system` is not present everywhere and is only needed as workaround for # Debian-specific issue (copied from https://github.com/gluster/gstatus/pull/33), # fallback to command without `--system` if the first one fails. try: ctx.run( f'"{PYTHON}" -m pip install --system --upgrade --no-user --target "{PIP_MESON_NINJA_DIR}" pip setuptools', echo=True, hide=True, shell=SHELL ); except: ctx.run( f'"{PYTHON}" -m pip install --upgrade --no-user --target "{PIP_MESON_NINJA_DIR}" pip setuptools', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); # Workaround for NixOS and Guix that don't work with pre-built binaries, see: # https://github.com/NixOS/nixpkgs/issues/142383. pip_build_binaries = '--no-binary :all:' if os.path.isfile('/etc/NIXOS') or os.path.isdir('/etc/guix') else ''; # Install meson and ninja using pip into our custom location, so we don't # depend on system-wide installation. ctx.run( f'"{PYTHON}" -m pip install --upgrade --no-user --target "{PIP_MESON_NINJA_DIR}" {pip_build_binaries} meson=={MESON_VERSION} ninja=={NINJA_VERSION}', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task(pre=[meson_ninja]) def setup(ctx, meson_args=MESON_ARGS): """ Run meson setup """ if MEDIASOUP_BUILDTYPE == 'Release': with cd_worker(): ctx.run( f'"{MESON}" setup --prefix "{MEDIASOUP_INSTALL_DIR}" --bindir "" --libdir "" --buildtype release -Db_ndebug=true {meson_args} "{BUILD_DIR}"', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); elif MEDIASOUP_BUILDTYPE == 'Debug': with cd_worker(): ctx.run( f'"{MESON}" setup --prefix "{MEDIASOUP_INSTALL_DIR}" --bindir "" --libdir "" --buildtype debug {meson_args} "{BUILD_DIR}"', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); else: with cd_worker(): ctx.run( f'"{MESON}" setup --prefix "{MEDIASOUP_INSTALL_DIR}" --bindir "" --libdir "" --buildtype {MEDIASOUP_BUILDTYPE} -Db_ndebug=if-release {meson_args} "{BUILD_DIR}"', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task def clean(ctx): # pylint: disable=unused-argument """ Clean the installation directory """ shutil.rmtree(MEDIASOUP_INSTALL_DIR, ignore_errors=True); @task def clean_build(ctx): # pylint: disable=unused-argument """ Clean the build directory """ shutil.rmtree(BUILD_DIR, ignore_errors=True); @task def clean_pip(ctx): # pylint: disable=unused-argument """ Clean the local pip setup """ shutil.rmtree(PIP_MESON_NINJA_DIR, ignore_errors=True); shutil.rmtree(PIP_PYLINT_DIR, ignore_errors=True); @task(pre=[meson_ninja]) def clean_subprojects(ctx): """ Clean meson subprojects """ with cd_worker(): ctx.run( f'"{MESON}" subprojects purge --include-cache --confirm', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task def clean_all(ctx): """ Clean meson subprojects and all installed/built artificats """ with cd_worker(): try: ctx.run( f'"{MESON}" subprojects purge --include-cache --confirm', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); except: pass; shutil.rmtree(MEDIASOUP_OUT_DIR, ignore_errors=True); shutil.rmtree('include/FBS', ignore_errors=True); @task(pre=[meson_ninja]) def update_wrap_file(ctx, subproject): """ Update the wrap file of a subproject """ with cd_worker(): ctx.run( f'"{MESON}" subprojects update --reset {subproject}', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task(pre=[setup]) def flatc(ctx): """ Compile FlatBuffers FBS files """ with cd_worker(): ctx.run( f'"{MESON}" compile -C "{BUILD_DIR}" flatbuffers-generator', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task(pre=[setup, flatc], default=True) def mediasoup_worker(ctx): """ Compile mediasoup-worker binary """ if os.getenv('MEDIASOUP_WORKER_BIN'): print('skipping mediasoup-worker compilation due to the existence of the MEDIASOUP_WORKER_BIN environment variable'); return; with cd_worker(): ctx.run( f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); with cd_worker(): ctx.run( f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task(pre=[setup, flatc]) def libmediasoup_worker(ctx): """ Compile libmediasoup-worker library """ with cd_worker(): ctx.run( f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} libmediasoup-worker', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); with cd_worker(): ctx.run( f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags libmediasoup-worker', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task(pre=[setup, flatc]) def xcode(ctx): """ Setup Xcode project """ with cd_worker(): ctx.run( f'"{MESON}" setup --buildtype {MEDIASOUP_BUILDTYPE.lower()} --backend xcode "{MEDIASOUP_OUT_DIR}/xcode"', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task def lint(ctx): """ Lint source code """ with cd_worker(): ctx.run( f'"{NPM}" run lint --prefix scripts/', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); if not os.path.isdir(PIP_PYLINT_DIR): # Install pylint using pip into our custom location. ctx.run( f'"{PYTHON}" -m pip install --upgrade --no-user --target="{PIP_PYLINT_DIR}" pylint=={PYLINT_VERSION}', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); with cd_worker(): ctx.run( f'"{PYTHON}" -m pylint tasks.py', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task def format(ctx): """ Format source code according to lint rules """ with cd_worker(): ctx.run( f'"{NPM}" run format --prefix scripts/', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true'), flatc]) def tidy(ctx): """ Performs C++ code checks according to `worker/.clang-tidy` rules """ with cd_worker(): ctx.run( f'"{NPM}" run tidy --prefix scripts/', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true'), flatc]) def tidy_fix(ctx): """ Performs C++ code checks according to `worker/.clang-tidy` rules and applies fixes """ with cd_worker(): ctx.run( f'"{NPM}" run tidy:fix --prefix scripts/', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true'), flatc]) def test(ctx): """ Run worker tests """ with cd_worker(): ctx.run( f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker-test', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); with cd_worker(): ctx.run( f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker-test', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); mediasoup_worker_test = 'mediasoup-worker-test.exe' if os.name == 'nt' else 'mediasoup-worker-test'; mediasoup_test_tags = os.getenv('MEDIASOUP_TEST_TAGS') or ''; with cd_worker(): ctx.run( f'"{BUILD_DIR}/{mediasoup_worker_test}" --invisibles --colour-mode=ansi {mediasoup_test_tags}', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true -Db_sanitize=address -Db_lundef=false'), flatc]) def test_asan_address(ctx): """ Run worker test with Address Sanitizer with '-fsanitize=address' """ with cd_worker(): ctx.run( f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker-test-asan-address', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); with cd_worker(): ctx.run( f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker-test-asan-address', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); mediasoup_test_tags = os.getenv('MEDIASOUP_TEST_TAGS') or ''; with cd_worker(): ctx.run( f'"{BUILD_DIR}/mediasoup-worker-test-asan-address" --invisibles {mediasoup_test_tags}', echo=True, pty=PTY_SUPPORTED, shell=SHELL, env={**os.environ, 'ASAN_OPTIONS': 'halt_on_error=1:print_stacktrace=1:detect_leaks=1:symbolize=1:detect_stack_use_after_return=1:strict_init_order=1:check_initialization_order=1:detect_container_overflow=1'} ); @task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_tests=true -Db_sanitize=undefined -Db_lundef=false'), flatc]) def test_asan_undefined(ctx): """ Run worker test with undefined Sanitizer with -fsanitize=undefined """ with cd_worker(): ctx.run( f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker-test-asan-undefined', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); with cd_worker(): ctx.run( f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker-test-asan-undefined', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); mediasoup_test_tags = os.getenv('MEDIASOUP_TEST_TAGS') or ''; with cd_worker(): ctx.run( f'"{BUILD_DIR}/mediasoup-worker-test-asan-undefined" --invisibles {mediasoup_test_tags}', echo=True, pty=PTY_SUPPORTED, shell=SHELL, # Exit with error if there are issues. # NOTE: Ignore well known UBSan errors in OpenSSL. env={**os.environ, 'UBSAN_OPTIONS': 'halt_on_error=1:print_stacktrace=1:suppressions=ubsan_suppressions.txt'} ); @task(pre=[call(setup, meson_args=MESON_ARGS + ' -Dms_build_fuzzer=true -Db_sanitize=address -Db_lundef=false'), flatc]) def fuzzer(ctx): """ Build the mediasoup-worker-fuzzer binary (which uses libFuzzer) """ # NOTE: We need to pass '-Db_sanitize=address' to enable fuzzer in all Meson # subprojects, so we pass it to the setup() task. with cd_worker(): ctx.run( f'"{MESON}" compile -C "{BUILD_DIR}" -j {NUM_CORES} mediasoup-worker-fuzzer', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); with cd_worker(): ctx.run( f'"{MESON}" install -C "{BUILD_DIR}" --no-rebuild --tags mediasoup-worker-fuzzer', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task def fuzzer_run_all(ctx): """ Run all fuzzer cases """ with cd_worker(): ctx.run( f'LSAN_OPTIONS=verbosity=1:log_threads=1 "{BUILD_DIR}/mediasoup-worker-fuzzer" -artifact_prefix=fuzzer/reports/ -max_len=1400 fuzzer/new-corpus deps/webrtc-fuzzer-corpora/corpora/stun-corpus deps/webrtc-fuzzer-corpora/corpora/rtp-corpus deps/webrtc-fuzzer-corpora/corpora/rtcp-corpus', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task def docker(ctx): """ Build a Linux Ubuntu Docker image with fuzzer capable clang++ """ if os.getenv('DOCKER_NO_CACHE') == 'true': with cd_worker(): ctx.run( f'"{DOCKER}" build -f Dockerfile --no-cache --tag mediasoup/docker:latest .', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); else: with cd_worker(): ctx.run( f'"{DOCKER}" build -f Dockerfile --tag mediasoup/docker:latest .', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task def docker_run(ctx): """ Run a container of the Ubuntu Docker image created in the docker task """ with cd_worker(): ctx.run( f'"{DOCKER}" run --name=mediasoupDocker -it --rm --privileged --cap-add SYS_PTRACE -v "{WORKER_DIR}/../:/foo bar/mediasoup" mediasoup/docker:latest', echo=True, pty=True, # NOTE: Needed to enter the terminal of the Docker image. shell=SHELL ); @task def docker_alpine(ctx): """ Build a Linux Alpine Docker image """ if os.getenv('DOCKER_NO_CACHE') == 'true': with cd_worker(): ctx.run( f'"{DOCKER}" build -f Dockerfile.alpine --no-cache --tag mediasoup/docker-alpine:latest .', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); else: with cd_worker(): ctx.run( f'"{DOCKER}" build -f Dockerfile.alpine --tag mediasoup/docker-alpine:latest .', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task def docker_alpine_run(ctx): """ Run a container of the Alpine Docker image created in the docker_alpine task """ with cd_worker(): ctx.run( f'"{DOCKER}" run --name=mediasoupDockerAlpine -it --rm --privileged --cap-add SYS_PTRACE -v "{WORKER_DIR}/../:/foo bar/mediasoup" mediasoup/docker-alpine:latest', echo=True, pty=True, # NOTE: Needed to enter the terminal of the Docker image. shell=SHELL ); @task def docker_386(ctx): """ Build a 386 Linux Debian (32 bits arch) Docker image """ if os.getenv('DOCKER_NO_CACHE') == 'true': with cd_worker(): ctx.run( f'"{DOCKER}" build --platform linux/386 -f Dockerfile.386 --no-cache --tag mediasoup/docker-386:latest .', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); else: with cd_worker(): ctx.run( f'"{DOCKER}" build --platform linux/386 -f Dockerfile.386 --tag mediasoup/docker-386:latest .', echo=True, pty=PTY_SUPPORTED, shell=SHELL ); @task def docker_386_run(ctx): """ Run a container of the 386 Linux Debian (32 bits arch) Docker image created in the docker_386 task """ with cd_worker(): ctx.run( f'"{DOCKER}" run --name=mediasoupDocker386 -it --rm --privileged --cap-add SYS_PTRACE -v "{WORKER_DIR}/../:/foo bar/mediasoup" mediasoup/docker-386:latest', echo=True, pty=True, # NOTE: Needed to enter the terminal of the Docker image. shell=SHELL ); ================================================ FILE: worker/test/data/H264_SVC/naluInfo/naluInfo.csv ================================================ type,length,SId,TID,isIDR,firstSliceID,lastSliceID 7,13,-1,-1,-1,-1,-1 15,16,-1,-1,-1,-1,-1 8,10,-1,-1,-1,-1,-1 8,11,-1,-1,-1,-1,-1 14,9,-1,-1,-1,-1,-1 5,2023,0,0,1,0,0 20,4747,1,0,1,0,0 14,9,-1,-1,-1,-1,-1 1,144,0,1,0,0,0 20,514,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,148,0,2,0,0,0 20,716,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,310,0,0,0,0,0 20,1071,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,154,0,1,0,0,0 20,579,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,144,0,2,0,0,0 20,719,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,320,0,0,0,0,0 20,1039,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,148,0,1,0,0,0 20,594,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,179,0,2,0,0,0 20,707,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,252,0,0,0,0,0 20,1073,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,146,0,1,0,0,0 20,632,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,177,0,2,0,0,0 20,722,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,312,0,0,0,0,0 20,1157,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,179,0,1,0,0,0 20,716,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,207,0,2,0,0,0 20,857,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,368,0,0,0,0,0 20,1261,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,175,0,1,0,0,0 20,776,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,183,0,2,0,0,0 20,824,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,326,0,0,0,0,0 20,1258,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,188,0,1,0,0,0 20,676,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,189,0,2,0,0,0 20,828,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,333,0,0,0,0,0 20,1371,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,158,0,1,0,0,0 20,755,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,250,0,2,0,0,0 20,884,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,492,0,0,0,0,0 20,1744,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,274,0,1,0,0,0 20,1004,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,330,0,2,0,0,0 20,1122,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,580,0,0,0,0,0 20,1807,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,404,0,1,0,0,0 20,1397,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,462,0,2,0,0,0 20,1605,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,682,0,0,0,0,0 20,2205,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,439,0,1,0,0,0 20,1380,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,464,0,2,0,0,0 20,1525,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,694,0,0,0,0,0 20,2232,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,503,0,1,0,0,0 20,1488,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,548,0,2,0,0,0 20,1647,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,718,0,0,0,0,0 20,2207,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,624,0,1,0,0,0 20,1811,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,631,0,2,0,0,0 20,1739,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,790,0,0,0,0,0 20,2487,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,523,0,1,0,0,0 20,1684,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,563,0,2,0,0,0 20,1660,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,784,0,0,0,0,0 20,2331,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,535,0,1,0,0,0 20,1516,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,480,0,2,0,0,0 20,1579,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,679,0,0,0,0,0 20,2205,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,379,0,1,0,0,0 20,1363,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,393,0,2,0,0,0 20,1342,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,596,0,0,0,0,0 20,1902,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,351,0,1,0,0,0 20,1081,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,256,0,2,0,0,0 20,963,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,451,0,0,0,0,0 20,1590,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,185,0,1,0,0,0 20,704,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,182,0,2,0,0,0 20,687,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,415,0,0,0,0,0 20,1421,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,182,0,1,0,0,0 20,705,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,217,0,2,0,0,0 20,981,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,426,0,0,0,0,0 20,1702,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,162,0,1,0,0,0 20,588,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,155,0,2,0,0,0 20,710,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,284,0,0,0,0,0 20,1115,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,151,0,1,0,0,0 20,541,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,147,0,2,0,0,0 20,527,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,267,0,0,0,0,0 20,925,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,130,0,1,0,0,0 20,563,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,132,0,2,0,0,0 20,603,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,275,0,0,0,0,0 20,1089,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,111,0,1,0,0,0 20,594,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,162,0,2,0,0,0 20,620,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,265,0,0,0,0,0 20,1105,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,149,0,1,0,0,0 20,577,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,150,0,2,0,0,0 20,586,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,296,0,0,0,0,0 20,1009,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,86,0,1,0,0,0 20,474,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,160,0,2,0,0,0 20,481,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,278,0,0,0,0,0 20,934,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,117,0,1,0,0,0 20,500,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,155,0,2,0,0,0 20,559,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,233,0,0,0,0,0 20,932,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,107,0,1,0,0,0 20,470,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,91,0,2,0,0,0 20,574,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,202,0,0,0,0,0 20,982,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,132,0,1,0,0,0 20,558,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,132,0,2,0,0,0 20,564,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,255,0,0,0,0,0 20,1092,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,117,0,1,0,0,0 20,528,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,144,0,2,0,0,0 20,592,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,321,0,0,0,0,0 20,1253,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,164,0,1,0,0,0 20,694,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,174,0,2,0,0,0 20,680,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,317,0,0,0,0,0 20,1205,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,91,0,1,0,0,0 20,487,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,114,0,2,0,0,0 20,554,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,206,0,0,0,0,0 20,895,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,94,0,1,0,0,0 20,431,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,105,0,2,0,0,0 20,481,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,183,0,0,0,0,0 20,807,1,0,0,0,0 14,9,-1,-1,-1,-1,-1 1,86,0,1,0,0,0 20,424,1,1,0,0,0 14,9,-1,-1,-1,-1,-1 1,104,0,2,0,0,0 20,488,1,2,0,0,0 14,9,-1,-1,-1,-1,-1 1,191,0,0,0,0,0 20,790,1,0,0,0,0 ================================================ FILE: worker/test/data/packet1.info ================================================ Real-Time Transport Protocol 10.. .... = Version: RFC 1889 Version (2) ..0. .... = Padding: False ...1 .... = Extension: True .... 0000 = Contributing source identifiers count: 0 0... .... = Marker: False Payload type: DynamicRTP-Type-111 (111) Sequence number: 23617 Timestamp: 1660241882 Synchronization Source identifier: 0x9f7108e2 (2674985186) Defined by profile: Unknown (0xbede) Extension length: 1 Header extensions RFC 5285 Header Extension (One-Byte Header) Identifier: 1 Length: 1 Extension Data: ff Payload: bae21a9da27876098db8e277d041d9c0f1e78a699af0ee8f... ================================================ FILE: worker/test/data/packet2.info ================================================ Real-Time Transport Protocol [Stream setup by HEUR RT (frame 125)] 10.. .... = Version: RFC 1889 Version (2) ..1. .... = Padding: True ...0 .... = Extension: False .... 0000 = Contributing source identifiers count: 0 0... .... = Marker: False Payload type: DynamicRTP-Type-100 (100) Sequence number: 28478 [Extended sequence number: 94014] Timestamp: 172320136 Synchronization Source identifier: 0xc5abdf5a (3316375386) Padding data: b8ece4baca4f2ca92f22bb99b859e8bd7ce122e4fa8bcb97... Padding count: 149 ================================================ FILE: worker/test/data/packet2.raw ================================================ do> EeūZO,/"Y|"˗p$Olk~3% i&ܽ>=2 H٦M`&6*=1 #include // std::malloc(), std::free() #include // std::memcpy() #include namespace iceCommon { // NOTE: We need to declare them here with `extern` and then define them in // iceCommon.cpp. // NOTE: Random size buffers because anyway we use sizeof(XxxxBuffer). extern thread_local uint8_t FactoryBuffer[66661]; extern thread_local uint8_t ResponseFactoryBuffer[66661]; extern thread_local uint8_t SerializeBuffer[66662]; extern thread_local uint8_t CloneBuffer[66663]; extern thread_local uint8_t DataBuffer[66664]; extern thread_local uint8_t ThrowBuffer[66665]; void ResetBuffers(); } // namespace iceCommon // NOLINTNEXTLINE (cppcoreguidelines-macro-usage) #define CHECK_STUN_PACKET( \ /*const RTC::ICE::StunPacket**/ packet, \ /*const uint8_t**/ buffer, \ /*size_t*/ bufferLength, \ /*size_t*/ length, \ /*RTC::ICE::StunPacket::Class*/ klass, \ /*RTC::ICE::StunPacket::Method*/ method, \ /*bool*/ hasUsername, \ /*std::string_view*/ username, \ /*bool*/ hasPriority, \ /*uint32_t*/ priority, \ /*bool*/ hasIceControlling, \ /*uint64_t*/ iceControlling, \ /*bool*/ hasIceControlled, \ /*uint64_t*/ iceControlled, \ /*bool*/ hasUseCandidate, \ /*bool*/ hasNomination, \ /*uint32_t*/ nomination, \ /*bool*/ hasSoftware, \ /*std::string_view*/ software, \ /*bool*/ hasXorMappedAddress, \ /*bool*/ hasErrorCode, \ /*uint16_t*/ errorCode, \ /*std::string_view*/ errorReasonPhrase, \ /*bool*/ hasMessageIntegrity, \ /*bool*/ hasFingerprint) \ do \ { \ uint8_t* originalBuffer = static_cast(std::malloc(bufferLength)); \ std::memcpy(originalBuffer, buffer, bufferLength); \ REQUIRE(RTC::ICE::StunPacket::IsStun(buffer, bufferLength) == true); \ REQUIRE(packet); \ REQUIRE(packet->GetBuffer() != nullptr); \ REQUIRE(packet->GetBuffer() == buffer); \ REQUIRE(packet->GetBufferLength() != 0); \ REQUIRE(packet->GetBufferLength() == bufferLength); \ REQUIRE(packet->GetLength() != 0); \ REQUIRE(packet->GetLength() == length); \ REQUIRE(packet->GetAvailableLength() == packet->GetBufferLength() - packet->GetLength()); \ REQUIRE(packet->GetClass() == klass); \ REQUIRE(packet->GetMethod() == method); \ REQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::USERNAME) == hasUsername); \ REQUIRE(packet->GetUsername() == username); \ REQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::PRIORITY) == hasPriority); \ REQUIRE(packet->GetPriority() == priority); \ REQUIRE( \ packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::ICE_CONTROLLING) == \ hasIceControlling); \ REQUIRE(packet->GetIceControlling() == iceControlling); \ REQUIRE( \ packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::ICE_CONTROLLED) == hasIceControlled); \ REQUIRE(packet->GetIceControlled() == iceControlled); \ REQUIRE( \ packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::USE_CANDIDATE) == hasUseCandidate); \ REQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::NOMINATION) == hasNomination); \ REQUIRE(packet->GetNomination() == nomination); \ REQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::SOFTWARE) == hasSoftware); \ REQUIRE(packet->GetSoftware() == software); \ REQUIRE( \ packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::XOR_MAPPED_ADDRESS) == \ hasXorMappedAddress); \ REQUIRE(packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::ERROR_CODE) == hasErrorCode); \ std::string_view obtainedErrorReasonPhrase; \ REQUIRE(packet->GetErrorCode(obtainedErrorReasonPhrase) == errorCode); \ REQUIRE(obtainedErrorReasonPhrase == errorReasonPhrase); \ REQUIRE( \ packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::MESSAGE_INTEGRITY) == \ hasMessageIntegrity); \ REQUIRE( \ packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::FINGERPRINT) == hasFingerprint); \ if (hasMessageIntegrity || hasFingerprint) \ { \ REQUIRE(packet->IsProtected()); \ REQUIRE_THROWS_AS(packet->Protect(), MediaSoupError); \ REQUIRE_THROWS_AS(packet->AddUsername("foo"), MediaSoupError); \ } \ REQUIRE( \ std::any_of( \ packet->GetTransactionId(), \ packet->GetTransactionId() + RTC::ICE::StunPacket::TransactionIdLength, \ [](uint8_t b) \ { \ return b != 0; \ })); \ REQUIRE(packet->Validate(/*storeAttributes*/ false)); \ REQUIRE( \ packet->CheckAuthentication("lalala-fooo-œæ€œæ€", "∫∂ƒ3487345345Ω∑©™ƒ™œ") != \ RTC::ICE::StunPacket::AuthenticationResult::OK); \ REQUIRE( \ packet->CheckAuthentication("kasjdhaksjhd") != RTC::ICE::StunPacket::AuthenticationResult::OK); \ if ( \ packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::MESSAGE_INTEGRITY) || \ packet->HasAttribute(RTC::ICE::StunPacket::AttributeType::FINGERPRINT)) \ { \ REQUIRE_THROWS_AS(packet->Protect("isoiulkajdlkja"), MediaSoupError); \ REQUIRE_THROWS_AS(packet->Protect(), MediaSoupError); \ } \ REQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true); \ REQUIRE_THROWS_AS( \ const_cast(packet)->Serialize(iceCommon::ThrowBuffer, length - 1), \ MediaSoupError); \ REQUIRE_THROWS_AS(packet->Clone(iceCommon::ThrowBuffer, length - 1), MediaSoupError); \ REQUIRE(packet->Validate(/*storeAttributes*/ false)); \ std::free(originalBuffer); \ } while (false) #endif ================================================ FILE: worker/test/include/RTC/RTP/rtpCommon.hpp ================================================ #ifndef MS_TEST_RTC_RTP_COMMON_HPP #define MS_TEST_RTC_RTP_COMMON_HPP #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/testHelpers.hpp" #include "RTC/RTP/Packet.hpp" #include #include // std::malloc(), std::free() #include // std::memcpy() namespace rtpCommon { // NOTE: We need to declare them here with `extern` and then define them in // rtpCommon.cpp. // NOTE: Random size buffers because anyway we use sizeof(XxxxBuffer). extern thread_local uint8_t FactoryBuffer[66661]; extern thread_local uint8_t SerializeBuffer[66662]; extern thread_local uint8_t CloneBuffer[66663]; extern thread_local uint8_t DataBuffer[66664]; extern thread_local uint8_t ThrowBuffer[66665]; void ResetBuffers(); } // namespace rtpCommon // NOLINTNEXTLINE (cppcoreguidelines-macro-usage) #define CHECK_RTP_PACKET( \ /*const RTC::RTP::Packet**/ packet, \ /*const uint8_t**/ buffer, \ /*size_t*/ bufferLength, \ /*size_t*/ length, \ /*uint8_t*/ payloadType, \ /*bool*/ hasMarker, \ /*uint16_t*/ seqNumber, \ /*uint32_t*/ timestamp, \ /*uint32_t*/ ssrc, \ /*bool*/ hasCsrcs, \ /*bool*/ hasHeaderExtension, \ /*size_t*/ headerExtensionValueLength, \ /*bool*/ hasOneByteExtensions, \ /*bool*/ hasTwoBytesExtensions, \ /*bool*/ hasPayload, \ /*size_t*/ payloadLength, \ /*bool*/ hasPadding, \ /*uint8_t*/ paddingLength) \ do \ { \ uint8_t* originalBuffer = static_cast(std::malloc(bufferLength)); \ std::memcpy(originalBuffer, buffer, bufferLength); \ REQUIRE(RTC::RTP::Packet::IsRtp(buffer, bufferLength) == true); \ REQUIRE(packet); \ REQUIRE(packet->GetBuffer() != nullptr); \ REQUIRE(packet->GetBuffer() == buffer); \ REQUIRE(packet->GetBufferLength() != 0); \ REQUIRE(packet->GetBufferLength() == bufferLength); \ REQUIRE(packet->GetLength() != 0); \ REQUIRE(packet->GetLength() == length); \ REQUIRE(packet->GetAvailableLength() == packet->GetBufferLength() - packet->GetLength()); \ REQUIRE(static_cast(packet->GetVersion()) == 2); \ REQUIRE(static_cast(packet->GetPayloadType()) == payloadType); \ REQUIRE(packet->HasMarker() == hasMarker); \ REQUIRE(packet->GetSequenceNumber() == seqNumber); \ REQUIRE(packet->GetTimestamp() == timestamp); \ REQUIRE(packet->GetSsrc() == ssrc); \ REQUIRE(packet->HasCsrcs() == hasCsrcs); \ REQUIRE(packet->HasHeaderExtension() == hasHeaderExtension); \ REQUIRE(packet->GetHeaderExtensionValueLength() == headerExtensionValueLength); \ REQUIRE(packet->HasExtensions() == (hasOneByteExtensions || hasTwoBytesExtensions)); \ REQUIRE(packet->HasOneByteExtensions() == hasOneByteExtensions); \ REQUIRE(packet->HasTwoBytesExtensions() == hasTwoBytesExtensions); \ if (!packet->HasHeaderExtension()) \ { \ REQUIRE(packet->GetHeaderExtensionValueLength() == 0); \ REQUIRE(packet->HasExtensions() == false); \ REQUIRE(packet->HasOneByteExtensions() == false); \ REQUIRE(packet->HasTwoBytesExtensions() == false); \ } \ REQUIRE(packet->HasPayload() == hasPayload); \ REQUIRE(packet->GetPayloadLength() == payloadLength); \ size_t realPayloadLength; \ packet->GetPayload(realPayloadLength); \ REQUIRE(realPayloadLength == payloadLength); \ if (!packet->HasPayload()) \ { \ REQUIRE(packet->GetPayload() == nullptr); \ REQUIRE(packet->GetPayloadLength() == 0); \ size_t realPayloadLength; \ REQUIRE(packet->GetPayload(realPayloadLength) == nullptr); \ REQUIRE(realPayloadLength == 0); \ } \ REQUIRE(packet->HasPadding() == hasPadding); \ REQUIRE(static_cast(packet->GetPaddingLength()) == paddingLength); \ if (!packet->HasPadding()) \ { \ REQUIRE(static_cast(packet->GetPaddingLength()) == 0); \ } \ REQUIRE(packet->Validate(/*storeExtensions*/ false)); \ REQUIRE_NOTHROW(packet->SetPayloadType(packet->GetPayloadType())); \ REQUIRE_NOTHROW(packet->SetMarker(packet->HasMarker())); \ REQUIRE_NOTHROW(packet->SetSequenceNumber(packet->GetSequenceNumber())); \ REQUIRE_NOTHROW(packet->SetTimestamp(packet->GetTimestamp())); \ REQUIRE_NOTHROW(packet->SetSsrc(packet->GetSsrc())); \ if (!packet->HasHeaderExtension()) \ { \ REQUIRE_NOTHROW(packet->RemoveHeaderExtension()); \ } \ if (!hasPadding) \ { \ REQUIRE_NOTHROW(packet->SetPayload(packet->GetPayload(), packet->GetPayloadLength())); \ REQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true); \ REQUIRE_NOTHROW(packet->SetPayloadLength(packet->GetPayloadLength())); \ REQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true); \ } \ REQUIRE_THROWS_AS( \ packet->SetPayload(rtpCommon::DataBuffer, packet->GetBufferLength()), MediaSoupError); \ REQUIRE_THROWS_AS(packet->SetPayloadLength(packet->GetBufferLength()), MediaSoupError); \ REQUIRE_NOTHROW(packet->SetPaddingLength(packet->GetPaddingLength())); \ if (packet->IsPaddedTo4Bytes() && packet->GetPaddingLength() < 4) \ { \ REQUIRE_NOTHROW(packet->PadTo4Bytes()); \ REQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true); \ } \ REQUIRE(helpers::areBuffersEqual(buffer, bufferLength, originalBuffer, bufferLength) == true); \ REQUIRE_THROWS_AS( \ const_cast(packet)->Serialize(rtpCommon::ThrowBuffer, length - 1), \ MediaSoupError); \ REQUIRE_THROWS_AS(packet->Clone(rtpCommon::ThrowBuffer, length - 1), MediaSoupError); \ REQUIRE(packet->Validate(/*storeExtensions*/ false)); \ std::free(originalBuffer); \ } while (false) #endif ================================================ FILE: worker/test/include/RTC/SCTP/sctpCommon.hpp ================================================ #ifndef MS_TEST_RTC_SCTP_COMMON_HPP #define MS_TEST_RTC_SCTP_COMMON_HPP #include "common.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include "test/include/testHelpers.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp" #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp" #include namespace sctpCommon { // NOTE: We need to declare them here with `extern` and then define them in // common.cpp. // NOTE: Random size buffers because anyway we use sizeof(XxxxBuffer). extern thread_local uint8_t FactoryBuffer[66661]; extern thread_local uint8_t SerializeBuffer[66662]; extern thread_local uint8_t CloneBuffer[66663]; extern thread_local uint8_t DataBuffer[66664]; extern thread_local uint8_t ThrowBuffer[66665]; void ResetBuffers(); } // namespace sctpCommon // NOLINTNEXTLINE (cppcoreguidelines-macro-usage) #define CHECK_SCTP_PACKET( \ /*const RTC::SCTP::Packet**/ packet, \ /*const uint8_t**/ buffer, \ /*size_t*/ bufferLength, \ /*size_t*/ length, \ /*uint16_t*/ sourcePort, \ /*uint16_t*/ destinationPort, \ /*uint32_t*/ verificationTag, \ /*uint32_t*/ checksum, \ /*bool*/ hasValidCrc32cChecksum, \ /*size_t*/ chunksCount) \ do \ { \ REQUIRE(RTC::SCTP::Packet::IsSctp(buffer, length) == true); \ REQUIRE(packet); \ REQUIRE(packet->GetBuffer() != nullptr); \ REQUIRE(packet->GetBuffer() == buffer); \ REQUIRE(packet->GetBufferLength() != 0); \ REQUIRE(packet->GetBufferLength() == bufferLength); \ REQUIRE(packet->GetLength() != 0); \ REQUIRE(packet->GetLength() == length); \ REQUIRE(packet->GetAvailableLength() == packet->GetBufferLength() - packet->GetLength()); \ REQUIRE(Utils::Byte::IsPaddedTo4Bytes(packet->GetLength()) == true); \ REQUIRE(packet->GetSourcePort() == sourcePort); \ REQUIRE(packet->GetDestinationPort() == destinationPort); \ REQUIRE(packet->GetVerificationTag() == verificationTag); \ REQUIRE(packet->GetChecksum() == checksum); \ REQUIRE(packet->ValidateCRC32cChecksum() == hasValidCrc32cChecksum); \ REQUIRE(packet->GetChunksCount() == chunksCount); \ REQUIRE(packet->HasChunks() == (chunksCount > 0)); \ REQUIRE(packet->GetChunkAt(chunksCount) == nullptr); \ REQUIRE( \ helpers::areBuffersEqual(packet->GetBuffer(), packet->GetLength(), buffer, length) == true); \ REQUIRE_THROWS_AS( \ const_cast(packet)->Serialize(sctpCommon::ThrowBuffer, length - 1), \ MediaSoupError); \ REQUIRE_THROWS_AS(packet->Clone(sctpCommon::ThrowBuffer, length - 1), MediaSoupError); \ } while (false) // NOLINTNEXTLINE (cppcoreguidelines-macro-usage) #define CHECK_SCTP_CHUNK( \ /*const RTC::SCTP::Chunk**/ chunk, \ /*uint8_t**/ buffer, \ /*size_t*/ bufferLength, \ /*size_t*/ length, \ /*RTC::SCTP::Chunk::ChunkType*/ chunkType, \ /*bool*/ unknownType, \ /*RTC::SCTP::Chunk::ActionForUnknownChunkType*/ actionForUnknownChunkType, \ /*uint8_t*/ flags, \ /*bool*/ canHaveParameters, \ /*size_t*/ parametersCount, \ /*bool*/ canHaveErrorCauses, \ /*size_t*/ errorCausesCount) \ do \ { \ REQUIRE(chunk); \ REQUIRE(chunk->GetBuffer() != nullptr); \ if (buffer) \ { \ REQUIRE(chunk->GetBuffer() == buffer); \ } \ REQUIRE(chunk->GetBufferLength() != 0); \ REQUIRE(chunk->GetBufferLength() == bufferLength); \ REQUIRE(chunk->GetLength() != 0); \ REQUIRE(chunk->GetLength() == length); \ REQUIRE(chunk->GetAvailableLength() == chunk->GetBufferLength() - chunk->GetLength()); \ REQUIRE(Utils::Byte::IsPaddedTo4Bytes(chunk->GetLength()) == true); \ REQUIRE(chunk->GetType() == chunkType); \ REQUIRE(chunk->HasUnknownType() == unknownType); \ REQUIRE(chunk->GetActionForUnknownChunkType() == actionForUnknownChunkType); \ REQUIRE(chunk->GetFlags() == flags); \ REQUIRE(chunk->CanHaveParameters() == canHaveParameters); \ if (!canHaveParameters) \ { \ REQUIRE_THROWS_AS( \ const_cast(reinterpret_cast(chunk)) \ ->BuildParameterInPlace(), \ MediaSoupError); \ } \ REQUIRE(chunk->GetParametersCount() == parametersCount); \ REQUIRE(chunk->HasParameters() == (parametersCount > 0)); \ REQUIRE(chunk->GetParameterAt(parametersCount) == nullptr); \ REQUIRE(chunk->CanHaveErrorCauses() == canHaveErrorCauses); \ if (!canHaveErrorCauses) \ { \ REQUIRE_THROWS_AS( \ const_cast(reinterpret_cast(chunk)) \ ->BuildErrorCauseInPlace(), \ MediaSoupError); \ } \ REQUIRE(chunk->GetErrorCausesCount() == errorCausesCount); \ REQUIRE(chunk->HasErrorCauses() == (errorCausesCount > 0)); \ REQUIRE(chunk->GetErrorCauseAt(errorCausesCount) == nullptr); \ if (buffer) \ { \ REQUIRE( \ helpers::areBuffersEqual(chunk->GetBuffer(), chunk->GetLength(), buffer, length) == true); \ } \ REQUIRE_THROWS_AS( \ const_cast(reinterpret_cast(chunk)) \ ->Serialize(sctpCommon::ThrowBuffer, length - 1), \ MediaSoupError); \ REQUIRE_THROWS_AS(chunk->Clone(sctpCommon::ThrowBuffer, length - 1), MediaSoupError); \ } while (false) // NOLINTNEXTLINE (cppcoreguidelines-macro-usage) #define CHECK_SCTP_PARAMETER( \ /*const RTC::SCTP::Parameter**/ parameter, \ /*const uint8_t**/ buffer, \ /*size_t*/ bufferLength, \ /*size_t*/ length, \ /*RTC::SCTP::Parameter::ParameterType*/ parameterType, \ /*bool*/ unknownType, \ /*RTC::SCTP::Parameter::ActionForUnknownParameterType*/ actionForUnknownParameterType) \ do \ { \ REQUIRE(parameter); \ REQUIRE(parameter->GetBuffer() != nullptr); \ if (buffer) \ { \ REQUIRE(parameter->GetBuffer() == buffer); \ } \ REQUIRE(parameter->GetBufferLength() != 0); \ REQUIRE(parameter->GetBufferLength() == bufferLength); \ REQUIRE(parameter->GetLength() != 0); \ REQUIRE(parameter->GetLength() == length); \ REQUIRE( \ parameter->GetAvailableLength() == parameter->GetBufferLength() - parameter->GetLength()); \ REQUIRE(Utils::Byte::IsPaddedTo4Bytes(parameter->GetLength()) == true); \ REQUIRE(parameter->GetType() == parameterType); \ REQUIRE(parameter->HasUnknownType() == unknownType); \ REQUIRE(parameter->GetActionForUnknownParameterType() == actionForUnknownParameterType); \ if (buffer) \ { \ REQUIRE( \ helpers::areBuffersEqual(parameter->GetBuffer(), parameter->GetLength(), buffer, length) == \ true); \ } \ REQUIRE_THROWS_AS( \ const_cast(reinterpret_cast(parameter)) \ ->Serialize(sctpCommon::ThrowBuffer, length - 1), \ MediaSoupError); \ REQUIRE_THROWS_AS(parameter->Clone(sctpCommon::ThrowBuffer, length - 1), MediaSoupError); \ } while (false) // NOLINTNEXTLINE (cppcoreguidelines-macro-usage) #define CHECK_SCTP_ERROR_CAUSE( \ /*const RTC::SCTP::ErrorCause**/ errorCause, \ /*const uint8_t**/ buffer, \ /*size_t*/ bufferLength, \ /*size_t*/ length, \ /*RTC::SCTP::ErrorCause::ErrorCauseCode*/ causeCode, \ /*bool*/ unknownCode) \ do \ { \ REQUIRE(errorCause); \ REQUIRE(errorCause->GetBuffer() != nullptr); \ if (buffer) \ { \ REQUIRE(errorCause->GetBuffer() == buffer); \ } \ REQUIRE(errorCause->GetBufferLength() != 0); \ REQUIRE(errorCause->GetBufferLength() == bufferLength); \ REQUIRE(errorCause->GetLength() != 0); \ REQUIRE(errorCause->GetLength() == length); \ REQUIRE( \ errorCause->GetAvailableLength() == errorCause->GetBufferLength() - errorCause->GetLength()); \ REQUIRE(Utils::Byte::IsPaddedTo4Bytes(errorCause->GetLength()) == true); \ REQUIRE(errorCause->GetCode() == causeCode); \ REQUIRE(errorCause->HasUnknownCode() == unknownCode); \ if (buffer) \ { \ REQUIRE( \ helpers::areBuffersEqual( \ errorCause->GetBuffer(), errorCause->GetLength(), buffer, length) == true); \ } \ REQUIRE_THROWS_AS( \ const_cast(reinterpret_cast(errorCause)) \ ->Serialize(sctpCommon::ThrowBuffer, length - 1), \ MediaSoupError); \ REQUIRE_THROWS_AS(errorCause->Clone(sctpCommon::ThrowBuffer, length - 1), MediaSoupError); \ } while (false) #endif ================================================ FILE: worker/test/include/catch2Macros.hpp ================================================ #ifndef MS_CATCH2_MACROS_HPP #define MS_CATCH2_MACROS_HPP #include /** * `VerificationResult` struct is defined in mocks/include/mockTypes.hpp. */ // clang-format off #define REQUIRE_VERIFICATION_RESULT(verificationResult) \ do \ { \ const auto& result = (verificationResult); \ \ if (!(result.ok)) \ { \ FAIL(result.errorMessage); \ } \ } while (false) // clang-format off #define REQUIRE_WITH_MESSAGE(condition, errorMessage) \ do \ { \ if (!(condition)) \ { \ FAIL(errorMessage); \ } \ } while (false) #endif ================================================ FILE: worker/test/include/testHelpers.hpp ================================================ #ifndef MS_TEST_HELPERS_HPP #define MS_TEST_HELPERS_HPP #include "common.hpp" namespace helpers { bool readBinaryFile(const char* file, uint8_t* buffer, size_t* len); bool areBuffersEqual(const uint8_t* data1, size_t size1, const uint8_t* data2, size_t size2); } // namespace helpers #endif ================================================ FILE: worker/test/src/RTC/ICE/TestStunPacket.cpp ================================================ #include "common.hpp" #include "Utils.hpp" #include "test/include/RTC/ICE/iceCommon.hpp" #include "test/include/testHelpers.hpp" #include "RTC/ICE/StunPacket.hpp" #include #include #include // std::memset() #include SCENARIO("ICE StunPacket", "[serializable][ice][stunpacket]") { iceCommon::ResetBuffers(); SECTION("StunPacket::Parse() a STUN request with message integrity and fingerprint succeeds") { // Binding Request // - buffer length: 128 bytes // - transaction id: 0x4A31775941764E5470644B33 // - username: "78tal5pc6dkyv1rpg56vuay5je13cewm:s3Jg" // - priority: 1853693695 // - ice controlling: 15897499370457501716 // - use candidate: yes // - message integrity: f39a23b3a6054e75b39df2177100182da76834f8 // - fingerprint: 1782005644 // // clang-format off alignas(4) uint8_t buffer[] = { 0x00, 0x01, 0x00, 0x6C, 0x21, 0x12, 0xA4, 0x42, 0x4A, 0x31, 0x77, 0x59, 0x41, 0x76, 0x4E, 0x54, 0x70, 0x64, 0x4B, 0x33, 0x00, 0x06, 0x00, 0x25, 0x37, 0x38, 0x74, 0x61, 0x6C, 0x35, 0x70, 0x63, 0x36, 0x64, 0x6B, 0x79, 0x76, 0x31, 0x72, 0x70, 0x67, 0x35, 0x36, 0x76, 0x75, 0x61, 0x79, 0x35, 0x6A, 0x65, 0x31, 0x33, 0x63, 0x65, 0x77, 0x6D, 0x3A, 0x73, 0x33, 0x4A, 0x67, 0x00, 0x00, 0x00, 0xC0, 0x57, 0x00, 0x04, 0x00, 0x03, 0x00, 0x0A, 0x80, 0x2A, 0x00, 0x08, 0xDC, 0x9F, 0x43, 0x72, 0xE9, 0x1D, 0x90, 0x14, 0x00, 0x25, 0x00, 0x00, 0x00, 0x24, 0x00, 0x04, 0x6E, 0x7D, 0x1E, 0xFF, 0x00, 0x08, 0x00, 0x14, 0xF3, 0x9A, 0x23, 0xB3, 0xA6, 0x05, 0x4E, 0x75, 0xB3, 0x9D, 0xF2, 0x17, 0x71, 0x00, 0x18, 0x2D, 0xA7, 0x68, 0x34, 0xF8, 0x80, 0x28, 0x00, 0x04, 0x6A, 0x37, 0x3F, 0x8C }; // clang-format on std::unique_ptr request{ RTC::ICE::StunPacket::Parse( buffer, sizeof(buffer)) }; CHECK_STUN_PACKET(/*packet*/ request.get(), /*buffert*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ sizeof(buffer), /*klass*/ RTC::ICE::StunPacket::Class::REQUEST, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ "78tal5pc6dkyv1rpg56vuay5je13cewm:s3Jg", /*hasPriority*/ true, /*priority*/ 1853693695, /*hasIceControlling*/ true, /*iceControlling*/ 15897499370457501716u, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ true, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ true, /*hasFingerprint*/ true); const std::string usernameFragment1 = "78tal5pc6dkyv1rpg56vuay5je13cewm"; const std::string password = "1ezk7fni4jeo5bt7ibcdk4wjl8712suw"; REQUIRE( request->CheckAuthentication(usernameFragment1, password) == RTC::ICE::StunPacket::AuthenticationResult::OK); // Trying to modify a STUN Packet once protected must throw. REQUIRE_THROWS_AS(request->Protect("qweqwe"), MediaSoupError); REQUIRE_THROWS_AS(request->Protect(), MediaSoupError); /* Serialize it. */ request->Serialize(iceCommon::SerializeBuffer, sizeof(iceCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_STUN_PACKET(/*packet*/ request.get(), /*buffer*/ iceCommon::SerializeBuffer, /*bufferLength*/ sizeof(iceCommon::SerializeBuffer), /*length*/ sizeof(buffer), /*klass*/ RTC::ICE::StunPacket::Class::REQUEST, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ "78tal5pc6dkyv1rpg56vuay5je13cewm:s3Jg", /*hasPriority*/ true, /*priority*/ 1853693695, /*hasIceControlling*/ true, /*iceControlling*/ 15897499370457501716u, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ true, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ true, /*hasFingerprint*/ true); REQUIRE( request->CheckAuthentication(usernameFragment1, password) == RTC::ICE::StunPacket::AuthenticationResult::OK); // Trying to modify a STUN Packet once protected must throw. REQUIRE_THROWS_AS(request->Protect("qweqwe"), MediaSoupError); REQUIRE_THROWS_AS(request->Protect(), MediaSoupError); /* Clone it. */ request.reset(request->Clone(iceCommon::CloneBuffer, sizeof(iceCommon::CloneBuffer))); std::memset(iceCommon::SerializeBuffer, 0x00, sizeof(iceCommon::SerializeBuffer)); CHECK_STUN_PACKET(/*packet*/ request.get(), /*buffer*/ iceCommon::CloneBuffer, /*bufferLength*/ sizeof(iceCommon::CloneBuffer), /*length*/ sizeof(buffer), /*klass*/ RTC::ICE::StunPacket::Class::REQUEST, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ "78tal5pc6dkyv1rpg56vuay5je13cewm:s3Jg", /*hasPriority*/ true, /*priority*/ 1853693695, /*hasIceControlling*/ true, /*iceControlling*/ 15897499370457501716u, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ true, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ true, /*hasFingerprint*/ true); REQUIRE( request->CheckAuthentication(usernameFragment1, password) == RTC::ICE::StunPacket::AuthenticationResult::OK); // Trying to modify a STUN Packet once protected must throw. REQUIRE_THROWS_AS(request->Protect("qweqwe"), MediaSoupError); REQUIRE_THROWS_AS(request->Protect(), MediaSoupError); } SECTION( "StunPacket::Parse() a STUN success response without message integrity or fingerprint succeeds") { // Binding Success Response // - buffer length: 44 bytes // - transaction id: 0x0102030405060708090A0B0C // - xor-mapped-address: ip 2001:db8:85a3:0:0:8a2e:370:7334, port 1234 // // clang-format off alignas(4) uint8_t buffer[] = { 0x01, 0x01, 0x00, 0x18, 0x21, 0x12, 0xA4, 0x42, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x00, 0x20, 0x00, 0x14, 0x00, 0x02, 0x25, 0xC0, 0x01, 0x13, 0xA9, 0xFA, 0x84, 0xA1, 0x03, 0x04, 0x05, 0x06, 0x8D, 0x26, 0x0A, 0x7A, 0x78, 0x38 }; // clang-format on std::unique_ptr successResponse{ RTC::ICE::StunPacket::Parse( buffer, sizeof(buffer)) }; CHECK_STUN_PACKET(/*packet*/ successResponse.get(), /*buffert*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ sizeof(buffer), /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ true, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); struct sockaddr_storage obtainedXorMappedAddressStorage{}; REQUIRE(successResponse->GetXorMappedAddress(std::addressof(obtainedXorMappedAddressStorage))); int family; uint16_t port; std::string ip; Utils::IP::GetAddressInfo( reinterpret_cast(std::addressof(obtainedXorMappedAddressStorage)), family, ip, port); REQUIRE(family == AF_INET6); std::string expectedIp = "2001:db8:85a3:0:0:8a2e:370:7334"; REQUIRE(ip == Utils::IP::NormalizeIp(expectedIp)); REQUIRE(port == 1234); /* Serialize it. */ successResponse->Serialize(iceCommon::SerializeBuffer, sizeof(iceCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_STUN_PACKET(/*packet*/ successResponse.get(), /*buffer*/ iceCommon::SerializeBuffer, /*bufferLength*/ sizeof(iceCommon::SerializeBuffer), /*length*/ sizeof(buffer), /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ true, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); /* Clone it. */ successResponse.reset( successResponse->Clone(iceCommon::CloneBuffer, sizeof(iceCommon::CloneBuffer))); std::memset(iceCommon::SerializeBuffer, 0x00, sizeof(iceCommon::SerializeBuffer)); CHECK_STUN_PACKET(/*packet*/ successResponse.get(), /*buffer*/ iceCommon::CloneBuffer, /*bufferLength*/ sizeof(iceCommon::CloneBuffer), /*length*/ sizeof(buffer), /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ true, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); } SECTION("StunPacket::Parse() a STUN error response without message integrity or fingerprint succeeds") { // Binding Error Response // - buffer length: 108 bytes // - transaction id: 0x0102030405060708090A0B0C // - username: "œæ€å∫∂" // - ice controlled: 12345678 // - software: "mediasoup test" // - error code: 456 // - error reason phrase: "Something failed Ω∑© :)" // // clang-format off alignas(4) uint8_t buffer[] = { 0x01, 0x11, 0x00, 0x58, 0x21, 0x12, 0xA4, 0x42, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x00, 0x06, 0x00, 0x0F, 0xC5, 0x93, 0xC3, 0xA6, 0xE2, 0x82, 0xAC, 0xC3, 0xA5, 0xE2, 0x88, 0xAB, 0xE2, 0x88, 0x82, 0x00, 0x80, 0x29, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0x61, 0x4E, 0x80, 0x22, 0x00, 0x0E, 0x6D, 0x65, 0x64, 0x69, 0x61, 0x73, 0x6F, 0x75, 0x70, 0x20, 0x74, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x09, 0x00, 0x1F, 0x00, 0x00, 0x04, 0x38, 0x53, 0x6F, 0x6D, 0x65, 0x74, 0x68, 0x69, 0x6E, 0x67, 0x20, 0x66, 0x61, 0x69, 0x6C, 0x65, 0x64, 0x20, 0xCE, 0xA9, 0xE2, 0x88, 0x91, 0xC2, 0xA9, 0x20, 0x3A, 0x29, 0x00 }; // clang-format on std::unique_ptr errorResponse{ RTC::ICE::StunPacket::Parse( buffer, sizeof(buffer)) }; CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffert*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ sizeof(buffer), /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ "œæ€å∫∂", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ true, /*iceControlled*/ 12345678, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ true, /*software*/ "mediasoup test", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ 456, /*errorReasonPhrase*/ "Something failed Ω∑© :)", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); /* Serialize it. */ errorResponse->Serialize(iceCommon::SerializeBuffer, sizeof(iceCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffer*/ iceCommon::SerializeBuffer, /*bufferLength*/ sizeof(iceCommon::SerializeBuffer), /*length*/ sizeof(buffer), /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ "œæ€å∫∂", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ true, /*iceControlled*/ 12345678, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ true, /*software*/ "mediasoup test", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ 456, /*errorReasonPhrase*/ "Something failed Ω∑© :)", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); /* Clone it. */ errorResponse.reset(errorResponse->Clone(iceCommon::CloneBuffer, sizeof(iceCommon::CloneBuffer))); std::memset(iceCommon::SerializeBuffer, 0x00, sizeof(iceCommon::SerializeBuffer)); CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffer*/ iceCommon::CloneBuffer, /*bufferLength*/ sizeof(iceCommon::CloneBuffer), /*length*/ sizeof(buffer), /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ "œæ€å∫∂", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ true, /*iceControlled*/ 12345678, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ true, /*software*/ "mediasoup test", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ 456, /*errorReasonPhrase*/ "Something failed Ω∑© :)", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); } SECTION("StunPacket::Factory() creating a request succeeds") { // clang-format off alignas(4) uint8_t transactionId[RTC::ICE::StunPacket::TransactionIdLength] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC }; // clang-format on std::unique_ptr request{ RTC::ICE::StunPacket::Factory( iceCommon::FactoryBuffer, sizeof(iceCommon::FactoryBuffer), RTC::ICE::StunPacket::Class::REQUEST, RTC::ICE::StunPacket::Method::BINDING, transactionId) }; CHECK_STUN_PACKET(/*packet*/ request.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength, /*klass*/ RTC::ICE::StunPacket::Class::REQUEST, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); // Byte length: 27 (1 byte of padding needed). std::string username = "œæ€å∫∂:¢∞¬÷12"; std::string usernameFragment1 = "œæ€å∫∂"; // Byte length: 4. uint32_t priority = 999888777u; // Byte length: 8. uint64_t iceControlling = 15697499370457501716u; // Byte length of USE_CANDIDATE: 0. // // Byte length: 4. uint32_t nomination = 12345678u; // Byte length: 18 (2 byte of padding needed). std::string software = "mediasoup x.y.z :)"; // Byte length: 4 + 23 (1 byte of padding needed). uint16_t errorCode = 666; std::string errorReasonPhrase = "UPPS UNKNOWN ERROR 😊"; // Total length of the Attributes. size_t attributesLen = (4 + 27 + 1) + (4 + 4) + (4 + 8) + (4) + (4 + 4) + (4 + 18 + 2) + (4 + 4 + 23 + 1); request->AddUsername(username); request->AddPriority(priority); request->AddIceControlling(iceControlling); request->AddUseCandidate(); request->AddNomination(nomination); request->AddSoftware(software); request->AddErrorCode(errorCode, errorReasonPhrase); // It should fail if we try to add a duplicated Attribute. REQUIRE_THROWS_AS(request->AddUsername(username), MediaSoupError); REQUIRE_THROWS_AS(request->AddPriority(priority), MediaSoupError); REQUIRE_THROWS_AS(request->AddIceControlling(iceControlling), MediaSoupError); REQUIRE_THROWS_AS(request->AddUseCandidate(), MediaSoupError); REQUIRE_THROWS_AS(request->AddNomination(nomination), MediaSoupError); REQUIRE_THROWS_AS(request->AddSoftware(software), MediaSoupError); REQUIRE_THROWS_AS(request->AddErrorCode(errorCode, errorReasonPhrase), MediaSoupError); CHECK_STUN_PACKET(/*packet*/ request.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen, /*klass*/ RTC::ICE::StunPacket::Class::REQUEST, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ username, /*hasPriority*/ true, /*priority*/ priority, /*hasIceControlling*/ true, /*iceControlling*/ iceControlling, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ true, /*hasNomination*/ true, /*nomination*/ nomination, /*hasSoftware*/ true, /*software*/ software, /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ errorCode, /*errorReasonPhrase*/ errorReasonPhrase, /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); REQUIRE( helpers::areBuffersEqual( request->GetTransactionId(), RTC::ICE::StunPacket::TransactionIdLength, transactionId, RTC::ICE::StunPacket::TransactionIdLength)); /* Serialize it. */ request->Serialize(iceCommon::SerializeBuffer, sizeof(iceCommon::SerializeBuffer)); CHECK_STUN_PACKET(/*packet*/ request.get(), /*buffer*/ iceCommon::SerializeBuffer, /*bufferLength*/ sizeof(iceCommon::SerializeBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen, /*klass*/ RTC::ICE::StunPacket::Class::REQUEST, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ username, /*hasPriority*/ true, /*priority*/ priority, /*hasIceControlling*/ true, /*iceControlling*/ iceControlling, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ true, /*hasNomination*/ true, /*nomination*/ nomination, /*hasSoftware*/ true, /*software*/ software, /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ errorCode, /*errorReasonPhrase*/ errorReasonPhrase, /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); REQUIRE( helpers::areBuffersEqual( request->GetTransactionId(), RTC::ICE::StunPacket::TransactionIdLength, transactionId, RTC::ICE::StunPacket::TransactionIdLength)); /* Clone it. */ request.reset(request->Clone(iceCommon::CloneBuffer, sizeof(iceCommon::CloneBuffer))); std::memset(iceCommon::SerializeBuffer, 0x00, sizeof(iceCommon::SerializeBuffer)); CHECK_STUN_PACKET(/*packet*/ request.get(), /*buffer*/ iceCommon::CloneBuffer, /*bufferLength*/ sizeof(iceCommon::CloneBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen, /*klass*/ RTC::ICE::StunPacket::Class::REQUEST, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ username, /*hasPriority*/ true, /*priority*/ priority, /*hasIceControlling*/ true, /*iceControlling*/ iceControlling, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ true, /*hasNomination*/ true, /*nomination*/ nomination, /*hasSoftware*/ true, /*software*/ software, /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ errorCode, /*errorReasonPhrase*/ errorReasonPhrase, /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); REQUIRE( helpers::areBuffersEqual( request->GetTransactionId(), RTC::ICE::StunPacket::TransactionIdLength, transactionId, RTC::ICE::StunPacket::TransactionIdLength)); /* Protect the STUN Packet. */ std::string password = "asjhdkjhkasd"; request->Protect(password); CHECK_STUN_PACKET(/*packet*/ request.get(), /*buffer*/ iceCommon::CloneBuffer, /*bufferLength*/ sizeof(iceCommon::CloneBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 + RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4, /*klass*/ RTC::ICE::StunPacket::Class::REQUEST, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ true, /*username*/ username, /*hasPriority*/ true, /*priority*/ priority, /*hasIceControlling*/ true, /*iceControlling*/ iceControlling, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ true, /*hasNomination*/ true, /*nomination*/ nomination, /*hasSoftware*/ true, /*software*/ software, /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ errorCode, /*errorReasonPhrase*/ errorReasonPhrase, /*hasMessageIntegrity*/ true, /*hasFingerprint*/ true); REQUIRE( request->CheckAuthentication(usernameFragment1, password) == RTC::ICE::StunPacket::AuthenticationResult::OK); // Trying to modify a STUN Packet once protected must throw. REQUIRE_THROWS_AS(request->Protect("qweqwe"), MediaSoupError); REQUIRE_THROWS_AS(request->Protect(), MediaSoupError); } SECTION("StunPacket::Factory() creating a success response succeeds") { std::unique_ptr successResponse{ RTC::ICE::StunPacket::Factory( iceCommon::FactoryBuffer, sizeof(iceCommon::FactoryBuffer), RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, RTC::ICE::StunPacket::Method::BINDING) }; CHECK_STUN_PACKET(/*packet*/ successResponse.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength, /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); struct sockaddr_storage xorMappedAddressStorage{}; // Byte length: 8. auto* xorMappedAddressIn = reinterpret_cast(std::addressof(xorMappedAddressStorage)); auto* xorMappedAddress = reinterpret_cast(std::addressof(xorMappedAddressStorage)); uv_ip4_addr("22.33.0.125", 5678, xorMappedAddressIn); // Total length of the Attributes. size_t attributesLen = (4 + 8); successResponse->AddXorMappedAddress(xorMappedAddress); CHECK_STUN_PACKET(/*packet*/ successResponse.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen, /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ true, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); struct sockaddr_storage obtainedXorMappedAddressStorage{}; REQUIRE(successResponse->GetXorMappedAddress(std::addressof(obtainedXorMappedAddressStorage))); int family; uint16_t port; std::string ip; Utils::IP::GetAddressInfo( reinterpret_cast(std::addressof(obtainedXorMappedAddressStorage)), family, ip, port); REQUIRE(family == AF_INET); std::string expectedIp = "22.33.0.125"; REQUIRE(ip == Utils::IP::NormalizeIp(expectedIp)); REQUIRE(port == 5678); std::memset(iceCommon::FactoryBuffer, 0x00, sizeof(iceCommon::FactoryBuffer)); /* Create a new fresh success response. */ successResponse.reset( RTC::ICE::StunPacket::Factory( iceCommon::FactoryBuffer, sizeof(iceCommon::FactoryBuffer), RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, RTC::ICE::StunPacket::Method::BINDING)); // Byte length: 20. auto* xorMappedAddressIn6 = reinterpret_cast(std::addressof(xorMappedAddressStorage)); xorMappedAddress = reinterpret_cast(std::addressof(xorMappedAddressStorage)); uv_ip6_addr("2001:db8::1234", 20002, xorMappedAddressIn6); // Total length of the Attributes. attributesLen = (4 + 20); successResponse->AddXorMappedAddress(xorMappedAddress); CHECK_STUN_PACKET(/*packet*/ successResponse.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen, /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ true, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); REQUIRE(successResponse->GetXorMappedAddress(std::addressof(obtainedXorMappedAddressStorage))); Utils::IP::GetAddressInfo( reinterpret_cast(std::addressof(obtainedXorMappedAddressStorage)), family, ip, port); REQUIRE(family == AF_INET6); expectedIp = "2001:db8::1234"; REQUIRE(ip == Utils::IP::NormalizeIp(expectedIp)); REQUIRE(port == 20002); /* Protect the STUN Packet. */ std::string password = "asjhdkjhkasd"; successResponse->Protect(password); CHECK_STUN_PACKET(/*packet*/ successResponse.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 + RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4, /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ true, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ true, /*hasFingerprint*/ true); REQUIRE( successResponse->CheckAuthentication(password) == RTC::ICE::StunPacket::AuthenticationResult::OK); // Trying to modify a STUN Packet once protected must throw. REQUIRE_THROWS_AS(successResponse->Protect("qweqwe"), MediaSoupError); REQUIRE_THROWS_AS(successResponse->Protect(), MediaSoupError); } SECTION("StunPacket::Factory() creating an error response succeeds") { std::unique_ptr errorResponse{ RTC::ICE::StunPacket::Factory( iceCommon::FactoryBuffer, sizeof(iceCommon::FactoryBuffer), RTC::ICE::StunPacket::Class::ERROR_RESPONSE, RTC::ICE::StunPacket::Method::BINDING) }; CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength, /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); // Byte length: 4 + 23 (1 byte of padding needed). uint16_t errorCode = 666; std::string errorReasonPhrase = "UPPS UNKNOWN ERROR 😊"; // Total length of the Attributes. size_t attributesLen = (4 + 4 + 23 + 1); errorResponse->AddErrorCode(errorCode, errorReasonPhrase); CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen, /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ errorCode, /*errorReasonPhrase*/ errorReasonPhrase, /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); /* Protect the STUN Packet. */ std::string password = "23786asdas123"; errorResponse->Protect(password); CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 + RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4, /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ errorCode, /*errorReasonPhrase*/ errorReasonPhrase, /*hasMessageIntegrity*/ true, /*hasFingerprint*/ true); REQUIRE( errorResponse->CheckAuthentication(password) == RTC::ICE::StunPacket::AuthenticationResult::OK); // Trying to modify a STUN Packet once protected must throw. REQUIRE_THROWS_AS(errorResponse->Protect("qweqwe"), MediaSoupError); REQUIRE_THROWS_AS(errorResponse->Protect(), MediaSoupError); /* Create a new fresh error response. */ errorResponse.reset( RTC::ICE::StunPacket::Factory( iceCommon::FactoryBuffer, sizeof(iceCommon::FactoryBuffer), RTC::ICE::StunPacket::Class::ERROR_RESPONSE, RTC::ICE::StunPacket::Method::BINDING)); // Byte length: 4 + 11 (1 byte of padding needed). errorCode = 400; errorReasonPhrase = "Bad Request"; // Total length of the Attributes. attributesLen = (4 + 4 + 11 + 1); errorResponse->AddErrorCode(errorCode, errorReasonPhrase); CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen, /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ errorCode, /*errorReasonPhrase*/ errorReasonPhrase, /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); /* Protect the STUN Packet (without password). */ // Protect() without password only adds FINGERPRINT Attribute. errorResponse->Protect(); CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffer*/ iceCommon::FactoryBuffer, /*bufferLength*/ sizeof(iceCommon::FactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 + 4, /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ errorCode, /*errorReasonPhrase*/ errorReasonPhrase, /*hasMessageIntegrity*/ false, /*hasFingerprint*/ true); // Cannot check authentication in a STUN Packet without MESSAGE-INTEGRITY. REQUIRE( errorResponse->CheckAuthentication(password) == RTC::ICE::StunPacket::AuthenticationResult::BAD_MESSAGE); // Trying to modify a STUN Packet once protected must throw. REQUIRE_THROWS_AS(errorResponse->Protect("qweqwe"), MediaSoupError); REQUIRE_THROWS_AS(errorResponse->Protect(), MediaSoupError); } SECTION("StunPacket::CreateSuccessResponse() succeeds") { std::unique_ptr request{ RTC::ICE::StunPacket::Factory( iceCommon::FactoryBuffer, sizeof(iceCommon::FactoryBuffer), RTC::ICE::StunPacket::Class::REQUEST, RTC::ICE::StunPacket::Method::BINDING) }; std::unique_ptr successResponse{ request->CreateSuccessResponse( iceCommon::ResponseFactoryBuffer, sizeof(iceCommon::ResponseFactoryBuffer)) }; CHECK_STUN_PACKET(/*packet*/ successResponse.get(), /*buffer*/ iceCommon::ResponseFactoryBuffer, /*bufferLength*/ sizeof(iceCommon::ResponseFactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength, /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); REQUIRE( helpers::areBuffersEqual( successResponse->GetTransactionId(), RTC::ICE::StunPacket::TransactionIdLength, request->GetTransactionId(), RTC::ICE::StunPacket::TransactionIdLength) == true); successResponse->Protect("qwekqjhwekjahsd"); CHECK_STUN_PACKET(/*packet*/ successResponse.get(), /*buffer*/ iceCommon::ResponseFactoryBuffer, /*bufferLength*/ sizeof(iceCommon::ResponseFactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + 4 + RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4, /*klass*/ RTC::ICE::StunPacket::Class::SUCCESS_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ false, /*errorCode*/ 0, /*errorReasonPhrase*/ "", /*hasMessageIntegrity*/ true, /*hasFingerprint*/ true); REQUIRE( successResponse->CheckAuthentication("qwekqjhwekjahsd") == RTC::ICE::StunPacket::AuthenticationResult::OK); // Trying to modify a STUN Packet once protected must throw. REQUIRE_THROWS_AS(successResponse->Protect("qweqwe"), MediaSoupError); REQUIRE_THROWS_AS(successResponse->Protect(), MediaSoupError); } SECTION("StunPacket::CreateErrorResponse() succeeds") { std::unique_ptr request{ RTC::ICE::StunPacket::Factory( iceCommon::FactoryBuffer, sizeof(iceCommon::FactoryBuffer), RTC::ICE::StunPacket::Class::REQUEST, RTC::ICE::StunPacket::Method::BINDING) }; std::unique_ptr errorResponse{ request->CreateErrorResponse( iceCommon::ResponseFactoryBuffer, sizeof(iceCommon::ResponseFactoryBuffer), 666, "BAD STUFF") }; // Total length of the Attributes (ERROR-CODE). const size_t attributesLen = (4 + 4 + 9 + 3); CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffer*/ iceCommon::ResponseFactoryBuffer, /*bufferLength*/ sizeof(iceCommon::ResponseFactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen, /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ 666, /*errorReasonPhrase*/ "BAD STUFF", /*hasMessageIntegrity*/ false, /*hasFingerprint*/ false); REQUIRE( helpers::areBuffersEqual( errorResponse->GetTransactionId(), RTC::ICE::StunPacket::TransactionIdLength, request->GetTransactionId(), RTC::ICE::StunPacket::TransactionIdLength) == true); errorResponse->Protect("qwekqjhwekjahsd"); CHECK_STUN_PACKET(/*packet*/ errorResponse.get(), /*buffer*/ iceCommon::ResponseFactoryBuffer, /*bufferLength*/ sizeof(iceCommon::ResponseFactoryBuffer), /*length*/ RTC::ICE::StunPacket::FixedHeaderLength + attributesLen + 4 + RTC::ICE::StunPacket::MessageIntegrityAttributeLength + 4 + 4, /*klass*/ RTC::ICE::StunPacket::Class::ERROR_RESPONSE, /*method*/ RTC::ICE::StunPacket::Method::BINDING, /*hasUsername*/ false, /*username*/ "", /*hasPriority*/ false, /*priority*/ 0, /*hasIceControlling*/ false, /*iceControlling*/ 0, /*hasIceControlled*/ false, /*iceControlled*/ 0, /*hasUseCandidate*/ false, /*hasNomination*/ false, /*nomination*/ 0, /*hasSoftware*/ false, /*software*/ "", /*hasXorMappedAddress*/ false, /*hasErrorCode*/ true, /*errorCode*/ 666, /*errorReasonPhrase*/ "BAD STUFF", /*hasMessageIntegrity*/ true, /*hasFingerprint*/ true); REQUIRE( errorResponse->CheckAuthentication("qwekqjhwekjahsd") == RTC::ICE::StunPacket::AuthenticationResult::OK); // Trying to modify a STUN Packet once protected must throw. REQUIRE_THROWS_AS(errorResponse->Protect("qweqwe"), MediaSoupError); REQUIRE_THROWS_AS(errorResponse->Protect(), MediaSoupError); } } ================================================ FILE: worker/test/src/RTC/ICE/iceCommon.cpp ================================================ #include "test/include/RTC/ICE/iceCommon.hpp" // in worker/test/include/ #include // std::memset namespace iceCommon { // NOTE: We don't need `alignas(4)` for STUN Packet parsing. However we do it // for consistency with rtpCommon.cpp and sctpCommon.cpp. alignas(4) thread_local uint8_t FactoryBuffer[]; alignas(4) thread_local uint8_t ResponseFactoryBuffer[]; alignas(4) thread_local uint8_t SerializeBuffer[]; alignas(4) thread_local uint8_t CloneBuffer[]; alignas(4) thread_local uint8_t DataBuffer[]; alignas(4) thread_local uint8_t ThrowBuffer[]; void ResetBuffers() { std::memset(FactoryBuffer, 0xAA, sizeof(FactoryBuffer)); std::memset(ResponseFactoryBuffer, 0xAA, sizeof(ResponseFactoryBuffer)); std::memset(SerializeBuffer, 0xBB, sizeof(SerializeBuffer)); std::memset(CloneBuffer, 0xCC, sizeof(CloneBuffer)); std::memset(DataBuffer, 0xDD, sizeof(DataBuffer)); std::memset(ThrowBuffer, 0xEE, sizeof(ThrowBuffer)); for (size_t i = 0; i < 256; ++i) { DataBuffer[i] = static_cast(i); } } } // namespace iceCommon ================================================ FILE: worker/test/src/RTC/RTCP/TestBye.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/Bye.hpp" #include #include // std::memcmp() #include SCENARIO("RTCP BYE", "[rtcp][bye]") { // RCTP BYE packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x82, 0xcb, 0x00, 0x06, // Type: 203 (Bye), Count: 2, length: 2 0x62, 0x42, 0x76, 0xe0, // SSRC: 0x624276e0 0x26, 0x24, 0x67, 0x0e, // SSRC: 0x2624670e 0x0e, 0x48, 0x61, 0x73, // Length: 14, Text: "Hasta la vista" 0x74, 0x61, 0x20, 0x6c, 0x61, 0x20, 0x76, 0x69, 0x73, 0x74, 0x61, 0x00 }; // clang-format on const uint32_t ssrc1{ 0x624276e0 }; const uint32_t ssrc2{ 0x2624670e }; const std::string reason("Hasta la vista"); // NOTE: No need to pass const integers to the lambda. // NOTE: If we pass const integers then clang-tidy complains with // 'clang-diagnostic-unused-lambda-capture'. auto verify = [&reason](RTC::RTCP::ByePacket* packet) { REQUIRE(packet->GetReason() == reason); auto it = packet->Begin(); REQUIRE(*it == ssrc1); ++it; REQUIRE(*it == ssrc2); }; SECTION("parse ByePacket") { std::unique_ptr packet{ RTC::RTCP::ByePacket::Parse(buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized instance with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("create ByePacket") { // Create local Bye packet and check content. RTC::RTCP::ByePacket packet; packet.AddSsrc(ssrc1); packet.AddSsrc(ssrc2); packet.SetReason(reason); verify(&packet); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet.Serialize(serialized); SECTION("compare serialized instance with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackPsAfb.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPsAfb.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback PS AFB", "[rtcp][feedback-ps][afb]") { // RTCP AFB packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x8f, 0xce, 0x00, 0x03, // Type: 206 (Payload Specific), Count: 15 (AFB) Length: 3 0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17 0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000 0x00, 0x00, 0x00, 0x01 // Data }; // clang-format on // AFB values. const uint32_t senderSsrc{ 0xfa17fa17 }; const uint32_t mediaSsrc{ 0 }; auto verify = [](RTC::RTCP::FeedbackPsAfbPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); REQUIRE(packet->GetApplication() == RTC::RTCP::FeedbackPsAfbPacket::Application::UNKNOWN); }; SECTION("parse FeedbackPsAfbPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackPsAfbPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackPsFir.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPsFir.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback PS FIR", "[rtcp][feedback-ps][fir]") { // RTCP FIR packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x84, 0xce, 0x00, 0x04, // Type: 206 (Payload Specific), Count: 4 (FIR), Length: 4 0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17 0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000 0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702 0x04, 0x00, 0x00, 0x00 // Seq: 0x04 }; // clang-format on // FIR values. const uint32_t senderSsrc{ 0xfa17fa17 }; const uint32_t mediaSsrc{ 0 }; const uint32_t ssrc{ 0x02d03702 }; const uint8_t seq{ 4 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackPsFirPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); const RTC::RTCP::FeedbackPsFirItem* item = *(packet->Begin()); REQUIRE(item->GetSsrc() == ssrc); REQUIRE(item->GetSequenceNumber() == seq); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::FeedbackPsFirItem::Header) == 4); } SECTION("parse FeedbackPsFirPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackPsFirPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("create FeedbackPsFirPacket") { RTC::RTCP::FeedbackPsFirPacket packet(senderSsrc, mediaSsrc); auto* item = new RTC::RTCP::FeedbackPsFirItem(ssrc, seq); packet.AddItem(item); verify(&packet); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackPsLei.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPsLei.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback PS LEI", "[rtcp][feedback-ps][lei]") { // RTCP LEI packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x88, 0xce, 0x00, 0x03, // Type: 206 (Payload Specific), Count: 8 (LEI), Length: 3 0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17 0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000 0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702 }; // clang-format on // LEI values. const uint32_t senderSsrc{ 0xfa17fa17 }; const uint32_t mediaSsrc{ 0 }; const uint32_t ssrc{ 0x02d03702 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackPsLeiPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); const RTC::RTCP::FeedbackPsLeiItem* item = *(packet->Begin()); REQUIRE(item->GetSsrc() == ssrc); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::FeedbackPsLeiItem::Header) == 4); } SECTION("parse FeedbackPsLeiPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackPsLeiPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("create FeedbackPsLeiPacket") { RTC::RTCP::FeedbackPsLeiPacket packet(senderSsrc, mediaSsrc); auto* item = new RTC::RTCP::FeedbackPsLeiItem(ssrc); packet.AddItem(item); verify(&packet); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackPsPli.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPsPli.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback RTP PLI", "[rtcp][feedback-ps][pli]") { // RTCP PLI packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x81, 0xce, 0x00, 0x02, // Type: 206 (Payload Specific), Count: 1 (PLI), Length: 2 0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001 0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee }; // clang-format on // PLI values. const uint32_t senderSsrc{ 0x00000001 }; const uint32_t mediaSsrc{ 0x0330bdee }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackPsPliPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); }; SECTION("parse FeedbackPsPliPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackPsPliPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("create FeedbackPsPliPacket") { RTC::RTCP::FeedbackPsPliPacket packet(senderSsrc, mediaSsrc); verify(&packet); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackPsRemb.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPsRemb.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback PS REMB", "[rtcp][feedback-ps][remb]") { // RTCP REMB packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x8f, 0xce, 0x00, 0x06, // Type: 206 (Payload Specific), Count: 15 (AFB), Length: 6 0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17 0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000 0x52, 0x45, 0x4d, 0x42, // Unique Identifier: REMB 0x02, 0x01, 0xdf, 0x82, // SSRCs: 2, BR exp: 0, Mantissa: 122754 0x02, 0xd0, 0x37, 0x02, // SSRC1: 0x02d03702 0x04, 0xa7, 0x67, 0x47 // SSRC2: 0x04a76747 }; // clang-format on // REMB values. const uint32_t senderSsrc{ 0xfa17fa17 }; const uint32_t mediaSsrc{ 0u }; const uint64_t bitrate{ 122754u }; const std::vector ssrcs{ 0x02d03702, 0x04a76747 }; // NOTE: No need to pass const integers to the lambda. auto verify = [&ssrcs](RTC::RTCP::FeedbackPsRembPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); REQUIRE(packet->GetBitrate() == bitrate); REQUIRE(packet->GetSsrcs() == ssrcs); }; SECTION("parse FeedbackPsRembPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackPsRembPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("create FeedbackPsRembPacket") { RTC::RTCP::FeedbackPsRembPacket packet(senderSsrc, mediaSsrc); packet.SetSsrcs(ssrcs); packet.SetBitrate(bitrate); verify(&packet); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackPsRpsi.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPsRpsi.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback PS RPSI", "[rtcp][feedback-ps][rpsi]") { // RTCP RPSI packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x83, 0xce, 0x00, 0x04, // Type: 206 (Payload Specific), Count: 3 (RPSI), Length: 4 0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17 0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000 0x08, // Padding Bits 0x02, // Zero | Payload Type 0x00, 0x00, // Native RPSI bit string 0x00, 0x00, 0x01, 0x00 }; // clang-format on // RPSI values. const uint32_t senderSsrc{ 0xfa17fa17 }; const uint32_t mediaSsrc{ 0 }; const uint8_t payloadType{ 2 }; const uint8_t payloadMask{ 1 }; const size_t length{ 5 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackPsRpsiPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); const RTC::RTCP::FeedbackPsRpsiItem* item = *(packet->Begin()); REQUIRE(item); REQUIRE(item->GetPayloadType() == payloadType); REQUIRE(item->GetLength() == length); REQUIRE((item->GetBitString()[item->GetLength() - 1] & 1) == payloadMask); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::FeedbackPsRpsiItem::Header) == 1); } SECTION("parse FeedbackPsRpsiPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackPsRpsiPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackPsSli.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPsSli.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback PS SLI", "[rtcp][feedback-ps][sli]") { // RTCP SLI packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x82, 0xce, 0x00, 0x03, // Type: 206 (Payload Specific), Count: 2 (SLI), Length: 3 0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17 0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000 0x00, 0x08, 0x01, 0x01 // First: 1, Number: 4, PictureId: 1 }; // clang-format on // SLI values. const uint32_t senderSsrc{ 0xfa17fa17 }; const uint32_t mediaSsrc{ 0 }; const uint16_t first{ 1 }; const uint16_t number{ 4 }; const uint8_t pictureId{ 1 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackPsSliPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); const RTC::RTCP::FeedbackPsSliItem* item = *(packet->Begin()); REQUIRE(item); REQUIRE(item->GetFirst() == first); REQUIRE(item->GetNumber() == number); REQUIRE(item->GetPictureId() == pictureId); }; SECTION("parse FeedbackPsSliPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackPsSliPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackPsTst.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPsTst.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback PS TSTN", "[rtcp][feedback-ps][tstn]") { // RTCP TSTN packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x84, 0xce, 0x00, 0x04, // Type: 206 (Payload Specific), Count: 4 (TST), Length: 4 0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17 0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000 0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702 0x08, 0x00, 0x00, 0x01 // Seq: 8, Reserved, Index: 1 }; // clang-format on // TSTN values. const uint32_t senderSsrc{ 0xfa17fa17 }; const uint32_t mediaSsrc{ 0 }; const uint32_t ssrc{ 0x02d03702 }; const uint8_t seq{ 8 }; const uint8_t index{ 1 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackPsTstnPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); const RTC::RTCP::FeedbackPsTstnItem* item = *(packet->Begin()); REQUIRE(item); REQUIRE(item->GetSsrc() == ssrc); REQUIRE(item->GetSequenceNumber() == seq); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::FeedbackPsTstrItem::Header) == 1); REQUIRE(alignof(RTC::RTCP::FeedbackPsTstnItem::Header) == 1); } SECTION("parse FeedbackPsTstnPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackPsTstnPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("create FeedbackPsTstnPacket") { RTC::RTCP::FeedbackPsTstnPacket packet(senderSsrc, mediaSsrc); auto* item = new RTC::RTCP::FeedbackPsTstnItem(ssrc, seq, index); packet.AddItem(item); verify(&packet); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackPsVbcm.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPsVbcm.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback PS VBCM", "[rtcp][feedback-ps][vbcm]") { // RTCP VBCM packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x84, 0xce, 0x00, 0x05, // Type: 206 (Payload Specific), Count: 4 (VBCM), Length: 5 0xfa, 0x17, 0xfa, 0x17, // Sender SSRC: 0xfa17fa17 0x00, 0x00, 0x00, 0x00, // Media source SSRC: 0x00000000 0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702 0x08, // Seq: 8 0x02, // Zero | Payload Vbcm 0x00, 0x01, // Length 0x01, // VBCM Octet String 0x00, 0x00, 0x00 // Padding }; // clang-format on // VBCM values. const uint32_t senderSsrc{ 0xfa17fa17 }; const uint32_t mediaSsrc{ 0 }; const uint32_t ssrc{ 0x02d03702 }; const uint8_t seq{ 8 }; const uint8_t payloadType{ 2 }; const uint16_t length{ 1 }; const uint8_t valueMask{ 1 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackPsVbcmPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); const RTC::RTCP::FeedbackPsVbcmItem* item = *(packet->Begin()); REQUIRE(item); REQUIRE(item->GetSsrc() == ssrc); REQUIRE(item->GetSequenceNumber() == seq); REQUIRE(item->GetPayloadType() == payloadType); REQUIRE(item->GetLength() == length); REQUIRE((item->GetValue()[item->GetLength() - 1] & 1) == valueMask); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::FeedbackPsVbcmItem::Header) == 4); } SECTION("parse FeedbackPsVbcmPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackPsVbcmPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackRtpEcn.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackRtpEcn.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback RTP ECN", "[rtcp][feedback-rtp][ecn]") { // clang-format off alignas(4) uint8_t buffer[] = { 0x88, 0xcd, 0x00, 0x07, // Type: 205 (Generic RTP Feedback), Count: 8 (ECN) Length: 7 0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001 0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee 0x00, 0x00, 0x00, 0x01, // Extended Highest Sequence Number 0x00, 0x00, 0x00, 0x01, // ECT (0) Counter 0x00, 0x00, 0x00, 0x01, // ECT (1) Counter 0x00, 0x01, // ECN-CE Counter 0x00, 0x01, // not-ECT Counter 0x00, 0x01, // Lost Packets Counter 0x00, 0x01 // Duplication Counter }; // clang-format on // ECN values. const uint32_t senderSsrc{ 0x00000001 }; const uint32_t mediaSsrc{ 0x0330bdee }; const uint32_t sequenceNumber{ 1 }; const uint32_t ect0Counter{ 1 }; const uint32_t ect1Counter{ 1 }; const uint16_t ecnCeCounter{ 1 }; const uint16_t notEctCounter{ 1 }; const uint16_t lostPackets{ 1 }; const uint16_t duplicatedPackets{ 1 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackRtpEcnPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); auto it = packet->Begin(); const auto* item = *it; REQUIRE(item); REQUIRE(item->GetSequenceNumber() == sequenceNumber); REQUIRE(item->GetEct0Counter() == ect0Counter); REQUIRE(item->GetEct1Counter() == ect1Counter); REQUIRE(item->GetEcnCeCounter() == ecnCeCounter); REQUIRE(item->GetNotEctCounter() == notEctCounter); REQUIRE(item->GetLostPackets() == lostPackets); REQUIRE(item->GetDuplicatedPackets() == duplicatedPackets); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::FeedbackRtpEcnItem::Header) == 4); } SECTION("parse FeedbackRtpEcnPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackRtpEcnPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackRtpNack.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackRtpNack.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback RTP NACK", "[rtcp][feedback-rtp][nack]") { // RTCP NACK packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x81, 0xcd, 0x00, 0x03, // Type: 205 (Generic RTP Feedback), Length: 3 0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001 0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee 0x0b, 0x8f, 0x00, 0x03 // NACK PID: 2959, NACK BLP: 0x0003 }; // clang-format on // NACK values. const uint32_t senderSsrc{ 0x00000001 }; const uint32_t mediaSsrc{ 0x0330bdee }; const uint16_t pid{ 2959 }; const uint16_t lostPacketBitmask{ 0x0003 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackRtpNackPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); auto it = packet->Begin(); const auto* item = *it; REQUIRE(item->GetPacketId() == pid); REQUIRE(item->GetLostPacketBitmask() == lostPacketBitmask); REQUIRE(item->CountRequestedPackets() == 3); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::FeedbackRtpNackItem::Header) == 2); } SECTION("parse FeedbackRtpNackItem") { std::unique_ptr packet{ RTC::RTCP::FeedbackRtpNackPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("create FeedbackRtpNackPacket") { RTC::RTCP::FeedbackRtpNackPacket packet(senderSsrc, mediaSsrc); auto* item = new RTC::RTCP::FeedbackRtpNackItem(pid, lostPacketBitmask); packet.AddItem(item); verify(&packet); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackRtpSrReq.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackRtpSrReq.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback RTP SR-REQ", "[rtcp][feedback-rtp][sr-req]") { // RTCP SR-REQ packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x85, 0xcd, 0x00, 0x02, // Type: 205 (Generic RTP Feedback), Count: 5 (SR-REQ) Length: 3 0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001 0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee }; // clang-format on // SR-REQ values. const uint32_t senderSsrc{ 0x00000001 }; const uint32_t mediaSsrc{ 0x0330bdee }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackRtpSrReqPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); }; SECTION("parse FeedbackRtpSrReqPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackRtpSrReqPacket::Parse(buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("create FeedbackRtpSrReqPacket") { RTC::RTCP::FeedbackRtpSrReqPacket packet(senderSsrc, mediaSsrc); verify(&packet); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackRtpTllei.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackRtpTllei.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback RTP TLLEI", "[rtcp][feedback-rtp][tllei]") { // RTCP TLLEI packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x87, 0xcd, 0x00, 0x03, // Type: 205 (Generic RTP Feedback), Count: 7 (TLLEI) Length: 3 0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001 0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee 0x00, 0x01, 0xaa, 0x55 }; // clang-format on // TLLEI values. const uint32_t senderSsrc{ 0x00000001 }; const uint32_t mediaSsrc{ 0x0330bdee }; const uint16_t packetId{ 1 }; const uint16_t lostPacketBitmask{ 0b1010101001010101 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackRtpTlleiPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); auto it = packet->Begin(); const auto* item = *it; REQUIRE(item); REQUIRE(item->GetPacketId() == packetId); REQUIRE(item->GetLostPacketBitmask() == lostPacketBitmask); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::FeedbackRtpTlleiItem::Header) == 2); } SECTION("parse RTC::RTCP::FeedbackRtpTlleiPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackRtpTlleiPacket::Parse(buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackRtpTmmb.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackRtpTmmb.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback RTP TMMBR", "[rtcp][feedback-rtp][tmmb]") { // RTCP TMMBR packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x83, 0xcd, 0x00, 0x04, // Type: 205 (Generic RTP Feedback), Count: 8 (TMMBR) Length: 7 0x00, 0x00, 0x00, 0x01, // Sender SSRC: 0x00000001 0x03, 0x30, 0xbd, 0xee, // Media source SSRC: 0x0330bdee 0x02, 0xd0, 0x37, 0x02, // SSRC: 0x02d03702 0x18, 0x2c, 0x9e, 0x00 }; // clang-format on // TMMBR values. const uint32_t senderSsrc{ 0x00000001 }; const uint32_t mediaSsrc{ 0x0330bdee }; const uint32_t ssrc{ 0x02d03702 }; const uint64_t bitrate{ 365504 }; const uint16_t overhead{ 0 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::FeedbackRtpTmmbrPacket* packet) { REQUIRE(packet->GetSenderSsrc() == senderSsrc); REQUIRE(packet->GetMediaSsrc() == mediaSsrc); auto it = packet->Begin(); const auto* item = *it; REQUIRE(item); REQUIRE(item->GetSsrc() == ssrc); REQUIRE(item->GetBitrate() == bitrate); REQUIRE(item->GetOverhead() == overhead); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::FeedbackRtpTmmbrItem::Header) == 4); REQUIRE(alignof(RTC::RTCP::FeedbackRtpTmmbnItem::Header) == 4); } SECTION("parse FeedbackRtpTmmbrPacket") { std::unique_ptr packet{ RTC::RTCP::FeedbackRtpTmmbrPacket::Parse(buffer, sizeof(buffer)) }; REQUIRE(packet); verify(packet.get()); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); // NOTE: Do not compare byte by byte since different binary values can // represent the same content. SECTION("create a packet out of the serialized buffer") { const std::unique_ptr packet{ RTC::RTCP::FeedbackRtpTmmbrPacket::Parse(buffer, sizeof(buffer)) }; verify(packet.get()); } } } SECTION("create FeedbackRtpTmmbrPacket") { RTC::RTCP::FeedbackRtpTmmbrPacket packet(senderSsrc, mediaSsrc); auto* item = new RTC::RTCP::FeedbackRtpTmmbrItem(); item->SetSsrc(ssrc); item->SetBitrate(bitrate); item->SetOverhead(overhead); packet.AddItem(item); verify(&packet); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestFeedbackRtpTransport.cpp ================================================ #include "common.hpp" #include "Logger.hpp" #include "RTC/RTCP/FeedbackRtpTransport.hpp" #include #include // std::memcmp() SCENARIO("RTCP Feedback RTP Transport", "[rtcp][feedback-rtp][transport]") { struct TestFeedbackRtpTransportInput { TestFeedbackRtpTransportInput(uint16_t sequenceNumber, uint64_t timestamp, size_t maxPacketSize) : sequenceNumber(sequenceNumber), timestamp(timestamp), maxPacketSize(maxPacketSize) { } uint16_t sequenceNumber{ 0u }; uint64_t timestamp{ 0u }; size_t maxPacketSize{ 0u }; }; static constexpr size_t RtcpMtu{ 1200u }; const uint32_t senderSsrc{ 1111u }; const uint32_t mediaSsrc{ 2222u }; auto verify = []( const std::vector& inputs, std::vector packetResults) { auto inputsIterator = inputs.begin(); auto packetResultsIterator = packetResults.begin(); auto lastInput = *inputsIterator; for (++inputsIterator; inputsIterator != inputs.end(); ++inputsIterator, ++packetResultsIterator) { const auto& input = *inputsIterator; auto& packetResult = *packetResultsIterator; const uint16_t missingPackets = input.sequenceNumber - lastInput.sequenceNumber - 1; if (missingPackets > 0) { // All missing packets must be represented in packetResults. for (uint16_t i{ 0u }; i < missingPackets; ++i) { packetResult = *packetResultsIterator; REQUIRE(packetResult.sequenceNumber == lastInput.sequenceNumber + i + 1); REQUIRE(packetResult.received == false); packetResultsIterator++; } } else { REQUIRE(packetResult.sequenceNumber == lastInput.sequenceNumber + 1); REQUIRE(packetResult.sequenceNumber == input.sequenceNumber); REQUIRE(packetResult.received == true); REQUIRE( static_cast(packetResult.receivedAtMs & 0x1FFFFFC0) / 64 == static_cast(input.timestamp & 0x1FFFFFC0) / 64); } lastInput = input; } }; SECTION( "create FeedbackRtpTransportPacket, small delta run length chunk and single large delta status packet") { auto packet = std::make_unique(senderSsrc, mediaSsrc); REQUIRE(packet); /* clang-format off */ std::vector inputs = { { 999, 1000000000, RtcpMtu }, // Pre base. { 1000, 1000000000, RtcpMtu }, // Base. { 1001, 1000000001, RtcpMtu }, { 1002, 1000000012, RtcpMtu }, { 1003, 1000000015, RtcpMtu }, { 1004, 1000000017, RtcpMtu }, { 1005, 1000000018, RtcpMtu }, { 1006, 1000000018, RtcpMtu }, { 1007, 1000000018, RtcpMtu }, { 1008, 1000000018, RtcpMtu }, { 1009, 1000000019, RtcpMtu }, { 1010, 1000000010, RtcpMtu }, { 1011, 1000000011, RtcpMtu }, { 1012, 1000000011, RtcpMtu }, { 1013, 1000000013, RtcpMtu } }; /* clang-format on */ packet->SetFeedbackPacketCount(1); for (auto& input : inputs) { if (std::addressof(input) == std::addressof(inputs.front())) { packet->SetBase(input.sequenceNumber + 1, input.timestamp); } else { packet->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize); } } REQUIRE(packet->GetLatestSequenceNumber() == 1013); REQUIRE(packet->GetLatestTimestamp() == 1000000013); // Add a packet with greater seq number but older timestamp. packet->AddPacket(1014, 1000000013 - 128, RtcpMtu); inputs.emplace_back(1014, 1000000013 - 128, RtcpMtu); REQUIRE(packet->GetLatestSequenceNumber() == 1014); REQUIRE(packet->GetLatestTimestamp() == 1000000013 - 128); packet->AddPacket(1015, 1000000015, RtcpMtu); inputs.emplace_back(1015, 1000000015, RtcpMtu); REQUIRE(packet->GetLatestSequenceNumber() == 1015); REQUIRE(packet->GetLatestTimestamp() == 1000000015); packet->Finish(); verify(inputs, packet->GetPacketResults()); REQUIRE(packet->GetBaseSequenceNumber() == 1000); REQUIRE(packet->GetPacketStatusCount() == 16); REQUIRE(packet->GetFeedbackPacketCount() == 1); REQUIRE(packet->GetPacketFractionLost() == 0); SECTION("serialize packet instance") { alignas(4) uint8_t buffer[1024]; auto len = packet->Serialize(buffer); REQUIRE(packet->GetSize() == len); SECTION("parse serialized buffer") { std::unique_ptr packet2{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len) }; REQUIRE(packet2); REQUIRE(packet2->GetBaseSequenceNumber() == 1000); REQUIRE(packet2->GetPacketStatusCount() == 16); REQUIRE(packet2->GetFeedbackPacketCount() == 1); REQUIRE(packet2->GetPacketFractionLost() == 0); alignas(4) uint8_t buffer2[1024]; auto len2 = packet2->Serialize(buffer2); REQUIRE(len == len2); REQUIRE(std::memcmp(buffer, buffer2, len) == 0); REQUIRE(packet2->GetSize() == len2); } } } SECTION("create FeedbackRtpTransportPacket, run length chunk (2)") { auto packet = std::make_unique(senderSsrc, mediaSsrc); /* clang-format off */ std::vector inputs = { { 999, 1000000000, RtcpMtu }, // Pre base. { 1000, 1000000000, RtcpMtu }, // Base. { 1050, 1000000216, RtcpMtu } }; /* clang-format on */ packet->SetFeedbackPacketCount(10); for (auto& input : inputs) { if (std::addressof(input) == std::addressof(inputs.front())) { packet->SetBase(input.sequenceNumber + 1, input.timestamp); } else { packet->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize); } } packet->Finish(); verify(inputs, packet->GetPacketResults()); REQUIRE(packet->GetBaseSequenceNumber() == 1000); REQUIRE(packet->GetPacketStatusCount() == 51); REQUIRE(packet->GetFeedbackPacketCount() == 10); REQUIRE(packet->GetPacketFractionLost() > 0); REQUIRE(packet->GetLatestSequenceNumber() == 1050); REQUIRE(packet->GetLatestTimestamp() == 1000000216); SECTION("serialize packet instance") { alignas(4) uint8_t buffer[1024]; auto len = packet->Serialize(buffer); REQUIRE(packet->GetSize() == len); SECTION("parse serialized buffer") { std::unique_ptr packet2{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len) }; REQUIRE(packet2); REQUIRE(packet2->GetBaseSequenceNumber() == 1000); REQUIRE(packet2->GetPacketStatusCount() == 51); REQUIRE(packet2->GetFeedbackPacketCount() == 10); REQUIRE(packet2->GetPacketFractionLost() > 0); alignas(4) uint8_t buffer2[1024]; auto len2 = packet2->Serialize(buffer2); REQUIRE(len == len2); REQUIRE(std::memcmp(buffer, buffer2, len) == 0); REQUIRE(packet2->GetSize() == len2); } } } SECTION("create FeedbackRtpTransportPacket, mixed chunks") { /* clang-format off */ std::vector inputs = { { 999, 1000000000, RtcpMtu }, // Pre base. { 1000, 1000000000, RtcpMtu }, // Base. { 1001, 1000000100, RtcpMtu }, { 1002, 1000000200, RtcpMtu }, { 1015, 1000000300, RtcpMtu }, { 1016, 1000000400, RtcpMtu }, { 1017, 1000000500, RtcpMtu } }; /* clang-format on */ auto packet = std::make_unique(senderSsrc, mediaSsrc); packet->SetFeedbackPacketCount(1); for (auto& input : inputs) { if (std::addressof(input) == std::addressof(inputs.front())) { packet->SetBase(input.sequenceNumber + 1, input.timestamp); } else { packet->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize); } } packet->Finish(); verify(inputs, packet->GetPacketResults()); REQUIRE(packet->GetBaseSequenceNumber() == 1000); REQUIRE(packet->GetPacketStatusCount() == 18); REQUIRE(packet->GetFeedbackPacketCount() == 1); REQUIRE(packet->GetPacketFractionLost() > 0); REQUIRE(packet->GetLatestSequenceNumber() == 1017); REQUIRE(packet->GetLatestTimestamp() == 1000000500); SECTION("serialize packet instance") { alignas(4) uint8_t buffer[1024]; auto len = packet->Serialize(buffer); REQUIRE(packet->GetSize() == len); SECTION("parse serialized buffer") { std::unique_ptr packet2{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len) }; REQUIRE(packet2); REQUIRE(packet2->GetBaseSequenceNumber() == 1000); REQUIRE(packet2->GetPacketStatusCount() == 18); REQUIRE(packet2->GetFeedbackPacketCount() == 1); REQUIRE(packet2->GetPacketFractionLost() > 0); alignas(4) uint8_t buffer2[1024]; auto len2 = packet2->Serialize(buffer2); REQUIRE(len == len2); REQUIRE(std::memcmp(buffer, buffer2, len) == 0); REQUIRE(packet2->GetSize() == len2); } } } SECTION("create FeedbackRtpTransportPacket, incomplete two bit vector chunk") { std::vector inputs = { { 999, 1000000000, RtcpMtu }, // Pre base. { 1000, 1000000100, RtcpMtu }, // Base. { 1001, 1000000700, RtcpMtu }, }; auto packet = std::make_unique(senderSsrc, mediaSsrc); packet->SetFeedbackPacketCount(1); for (auto& input : inputs) { if (std::addressof(input) == std::addressof(inputs.front())) { packet->SetBase(input.sequenceNumber + 1, input.timestamp); } else { packet->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize); } } packet->Finish(); verify(inputs, packet->GetPacketResults()); REQUIRE(packet->GetBaseSequenceNumber() == 1000); REQUIRE(packet->GetPacketStatusCount() == 2); REQUIRE(packet->GetFeedbackPacketCount() == 1); REQUIRE(packet->GetPacketFractionLost() == 0); REQUIRE(packet->GetLatestSequenceNumber() == 1001); REQUIRE(packet->GetLatestTimestamp() == 1000000700); SECTION("serialize packet instance") { alignas(4) uint8_t buffer[1024]; auto len = packet->Serialize(buffer); REQUIRE(packet->GetSize() == len); SECTION("parse serialized buffer") { std::unique_ptr packet2{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len) }; REQUIRE(packet2); REQUIRE(packet2->GetBaseSequenceNumber() == 1000); REQUIRE(packet2->GetPacketStatusCount() == 2); REQUIRE(packet2->GetFeedbackPacketCount() == 1); REQUIRE(packet2->GetPacketFractionLost() == 0); alignas(4) uint8_t buffer2[1024]; auto len2 = packet2->Serialize(buffer2); REQUIRE(len == len2); REQUIRE(std::memcmp(buffer, buffer2, len) == 0); REQUIRE(packet2->GetSize() == len2); } } } SECTION("create two sequential FeedbackRtpTransportPackets") { /* clang-format off */ std::vector inputs = { { 999, 1000000000, RtcpMtu }, // Pre base. { 1000, 1000000000, RtcpMtu }, // Base. { 1001, 1000000003, RtcpMtu }, { 1002, 1000000003, RtcpMtu }, { 1003, 1000000003, RtcpMtu }, { 1004, 1000000004, RtcpMtu }, { 1005, 1000000005, RtcpMtu }, { 1006, 1000000005, RtcpMtu }, { 1007, 1000000007, RtcpMtu } }; /* clang-format on */ auto packet = std::make_unique(senderSsrc, mediaSsrc); packet->SetFeedbackPacketCount(1); for (auto& input : inputs) { if (std::addressof(input) == std::addressof(inputs.front())) { packet->SetBase(input.sequenceNumber + 1, input.timestamp); } else { packet->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize); } } packet->Finish(); verify(inputs, packet->GetPacketResults()); REQUIRE(packet->GetBaseSequenceNumber() == 1000); REQUIRE(packet->GetPacketStatusCount() == 8); REQUIRE(packet->GetFeedbackPacketCount() == 1); REQUIRE(packet->GetPacketFractionLost() == 0); REQUIRE(packet->GetLatestSequenceNumber() == 1007); REQUIRE(packet->GetLatestTimestamp() == 1000000007); alignas(4) uint8_t buffer[1024]; auto len = packet->Serialize(buffer); REQUIRE(packet->GetSize() == len); SECTION("parse serialized buffer") { std::unique_ptr packet2{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len) }; REQUIRE(packet2); REQUIRE(packet2->GetBaseSequenceNumber() == 1000); REQUIRE(packet2->GetPacketStatusCount() == 8); REQUIRE(packet2->GetFeedbackPacketCount() == 1); REQUIRE(packet2->GetPacketFractionLost() == 0); alignas(4) uint8_t buffer2[1024]; auto len2 = packet2->Serialize(buffer2); REQUIRE(len == len2); REQUIRE(std::memcmp(buffer, buffer2, len) == 0); REQUIRE(packet2->GetSize() == len2); } auto latestWideSeqNumber = packet->GetLatestSequenceNumber(); auto latestTimestamp = packet->GetLatestTimestamp(); /* clang-format off */ std::vector inputs2 = { { latestWideSeqNumber, latestTimestamp, RtcpMtu }, { 1008, 1000000008, RtcpMtu }, { 1009, 1000000009, RtcpMtu }, { 1010, 1000000010, RtcpMtu }, { 1011, 1000000010, RtcpMtu }, { 1012, 1000000010, RtcpMtu }, { 1013, 1000000014, RtcpMtu }, { 1014, 1000000014, RtcpMtu } }; /* clang-format on */ auto packet2 = std::make_unique(senderSsrc, mediaSsrc); packet2->SetFeedbackPacketCount(2); for (auto& input : inputs2) { if (std::addressof(input) == std::addressof(inputs2.front())) { packet2->SetBase(input.sequenceNumber + 1, input.timestamp); } else { packet2->AddPacket(input.sequenceNumber, input.timestamp, input.maxPacketSize); } } packet2->Finish(); verify(inputs2, packet2->GetPacketResults()); REQUIRE(packet2->GetBaseSequenceNumber() == 1008); REQUIRE(packet2->GetPacketStatusCount() == 7); REQUIRE(packet2->GetFeedbackPacketCount() == 2); REQUIRE(packet2->GetPacketFractionLost() == 0); REQUIRE(packet2->GetLatestSequenceNumber() == 1014); REQUIRE(packet2->GetLatestTimestamp() == 1000000014); len = packet2->Serialize(buffer); REQUIRE(packet2->GetSize() == len); SECTION("parse serialized buffer") { std::unique_ptr packet3{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer, len) }; REQUIRE(packet3); REQUIRE(packet3->GetBaseSequenceNumber() == 1008); REQUIRE(packet3->GetPacketStatusCount() == 7); REQUIRE(packet3->GetFeedbackPacketCount() == 2); REQUIRE(packet3->GetPacketFractionLost() == 0); alignas(4) uint8_t buffer2[1024]; auto len2 = packet3->Serialize(buffer2); REQUIRE(len == len2); REQUIRE(std::memcmp(buffer, buffer2, len) == 0); REQUIRE(packet3->GetSize() == len2); } } SECTION("parse FeedbackRtpTransportPacket, one bit vector chunk") { // clang-format off alignas(4) uint8_t data[] = { 0x8F, 0xCD, 0x00, 0x07, 0xFA, 0x17, 0xFA, 0x17, 0x09, 0xFA, 0xFF, 0x67, 0x00, 0x27, 0x00, 0x0D, 0x5F, 0xC2, 0xF1, 0x03, 0xBF, 0x8E, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x04, 0x00 }; // clang-format on std::unique_ptr packet{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(data, sizeof(data)) }; REQUIRE(packet); REQUIRE(packet->GetSize() == sizeof(data)); REQUIRE(packet->GetBaseSequenceNumber() == 39); REQUIRE(packet->GetPacketStatusCount() == 13); REQUIRE(packet->GetReferenceTime() == 6275825); // 0x5FC2F1 (signed 24 bits) REQUIRE( packet->GetReferenceTimestamp() == RTC::RTCP::FeedbackRtpTransportPacket::TimeWrapPeriod + (static_cast(6275825) * RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick)); REQUIRE(packet->GetFeedbackPacketCount() == 3); SECTION("serialize packet") { alignas(4) uint8_t buffer[1024]; auto len = packet->Serialize(buffer); REQUIRE(len == sizeof(data)); REQUIRE(std::memcmp(data, buffer, len) == 0); } } SECTION("parse FeedbackRtpTransportPacket with negative reference time") { // clang-format off alignas(4) uint8_t data[] = { 0x8F, 0xCD, 0x00, 0x04, 0xFA, 0x17, 0xFA, 0x17, 0x09, 0xFA, 0xFF, 0x67, 0x00, 0x27, 0x00, 0x00, 0xFF, 0xFF, 0xFE, 0x01 }; // clang-format on std::unique_ptr packet{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(data, sizeof(data)) }; REQUIRE(packet); REQUIRE(packet->GetSize() == sizeof(data)); REQUIRE(packet->GetBaseSequenceNumber() == 39); REQUIRE(packet->GetPacketStatusCount() == 0); REQUIRE(packet->GetReferenceTime() == -2); // 0xFFFFFE = -2 (signed 24 bits) REQUIRE( packet->GetReferenceTimestamp() == RTC::RTCP::FeedbackRtpTransportPacket::TimeWrapPeriod + (static_cast(-2) * RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick)); REQUIRE(packet->GetFeedbackPacketCount() == 1); SECTION("serialize packet") { alignas(4) uint8_t buffer[1024]; auto len = packet->Serialize(buffer); REQUIRE(len == sizeof(data)); REQUIRE(std::memcmp(data, buffer, len) == 0); } } SECTION("parse FeedbackRtpTransportPacket generated by Chrome") { // clang-format off alignas(4) uint8_t data[] = { 0x8F, 0xCD, 0x00, 0x05, 0xFA, 0x17, 0xFA, 0x17, 0x39, 0xE9, 0x42, 0x38, 0x00, 0x01, 0x00, 0x02, 0xBD, 0x57, 0xAA, 0x00, 0x20, 0x02, 0x8C, 0x44 }; // clang-format on std::unique_ptr packet{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(data, sizeof(data)) }; REQUIRE(packet); REQUIRE(packet->GetSize() == sizeof(data)); REQUIRE(packet->GetBaseSequenceNumber() == 1); REQUIRE(packet->GetPacketStatusCount() == 2); REQUIRE(packet->GetReferenceTime() == -4368470); REQUIRE( packet->GetReferenceTimestamp() == RTC::RTCP::FeedbackRtpTransportPacket::TimeWrapPeriod + (static_cast(-4368470) * RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick)); REQUIRE(packet->GetFeedbackPacketCount() == 0); SECTION("serialize packet") { alignas(4) uint8_t buffer[1024]; auto len = packet->Serialize(buffer); REQUIRE(len == sizeof(data)); REQUIRE(std::memcmp(data, buffer, len) == 0); } } SECTION("parse FeedbackRtpTransportPacket generated by Chrome with libwebrtc as a reference") { using FeedbackPacketsMeta = struct { int32_t baseTimeRaw; int64_t baseTimeMs; uint16_t baseSequence; size_t packetStatusCount; std::vector deltas; std::vector buffer; }; // Metadata collected by parsing buffers with libwebrtc, buffers itself. // were generated by chrome in direction of mediasoup. const std::vector feedbackPacketsMeta = { { .baseTimeRaw = 35504, .baseTimeMs = 1076014080, .baseSequence = 13, .packetStatusCount = 1, .deltas = std::vector{ 57 }, .buffer = std::vector{ 0xaf, 0xcd, 0x00, 0x05, 0xfa, 0x17, 0xfa, 0x17, 0x00, 0x00, 0x04, 0xd2, 0x00, 0x0d, 0x00, 0x01, 0x00, 0x8A, 0xB0, 0x00, 0x20, 0x01, 0xE4, 0x01 } }, { .baseTimeRaw = 35504, .baseTimeMs = 1076014080, .baseSequence = 14, .packetStatusCount = 4, .deltas = std::vector{ 58, 2, 3, 55 }, .buffer = std::vector{ 0xaf, 0xcd, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x1C, 0xB7, 0xDA, 0xF3, 0x00, 0x0E, 0x00, 0x04, 0x00, 0x8A, 0xB0, 0x01, 0x20, 0x04, 0xE8, 0x08, 0x0C, 0xDC, 0x00, 0x02 } }, { .baseTimeRaw = 35505, .baseTimeMs = 1076014144, .baseSequence = 18, .packetStatusCount = 5, .deltas = std::vector{ 60, 6, 5, 9, 22 }, .buffer = std::vector{ 0xAF, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x1C, 0xB7, 0xDA, 0xF3, 0x00, 0x12, 0x00, 0x05, 0x00, 0x8A, 0xB1, 0x02, 0x20, 0x05, 0xF0, 0x18, 0x14, 0x24, 0x58, 0x01 } }, { .baseTimeRaw = 617873, .baseTimeMs = 1113285696, .baseSequence = 2924, .packetStatusCount = 22, .deltas = std::vector{ 3, 5, 5, 0, 10, 0, 0, 4, 0, 1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 1, 0, 4 }, .buffer = std::vector{ 0x8F, 0xCD, 0x00, 0x0A, 0xFA, 0x17, 0xFA, 0x17, 0x06, 0xF5, 0x11, 0x4C, 0x0B, 0x6C, 0x00, 0x16, 0x09, 0x6D, 0x91, 0xEE, 0x20, 0x16, 0x0C, 0x14, 0x14, 0x00, 0x28, 0x00, 0x00, 0x10, 0x00, 0x04, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x04, 0x00, 0x10 } }, { .baseTimeRaw = -4368470, .baseTimeMs = 794159744, .baseSequence = 1, .packetStatusCount = 2, .deltas = std::vector{ 35, 17 }, .buffer = std::vector{ 0x8F, 0xCD, 0x00, 0x05, 0xFA, 0x17, 0xFA, 0x17, 0x39, 0xE9, 0x42, 0x38, 0x00, 0x01, 0x00, 0x02, 0xBD, 0x57, 0xAA, 0x00, 0x20, 0x02, 0x8C, 0x44 } }, { .baseTimeRaw = 818995, .baseTimeMs = 1126157504, .baseSequence = 930, .packetStatusCount = 5, .deltas = std::vector{ 62, 18, 5, 6, 19 }, .buffer = std::vector{ 0xAF, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x26, 0x9E, 0x8E, 0x50, 0x03, 0xA2, 0x00, 0x05, 0x0C, 0x7F, 0x33, 0x9F, 0x20, 0x05, 0xF8, 0x48, 0x14, 0x18, 0x4C, 0x01 } }, { .baseTimeRaw = 818996, .baseTimeMs = 1126157568, .baseSequence = 921, .packetStatusCount = 7, .deltas = std::vector{ 14, 5, 6, 6, 7, 14, 5 }, .buffer = std::vector{ 0xAF, 0xCD, 0x00, 0x07, 0xFA, 0x17, 0xFA, 0x17, 0x33, 0xB0, 0x4A, 0xE8, 0x03, 0x99, 0x00, 0x07, 0x0C, 0x7F, 0x34, 0x9F, 0x20, 0x07, 0x38, 0x14, 0x18, 0x18, 0x1C, 0x38, 0x14, 0x00, 0x00, 0x03 } }, { .baseTimeRaw = 818996, .baseTimeMs = 1126157568, .baseSequence = 935, .packetStatusCount = 7, .deltas = std::vector{ 57, 0, 6, 5, 5, 24, 0 }, .buffer = std::vector{ 0xAF, 0xCD, 0x00, 0x07, 0xFA, 0x17, 0xFA, 0x17, 0x26, 0x9E, 0x8E, 0x50, 0x03, 0xA7, 0x00, 0x07, 0x0C, 0x7F, 0x34, 0xA0, 0x20, 0x07, 0xE4, 0x00, 0x18, 0x14, 0x14, 0x60, 0x00, 0x00, 0x00, 0x03 } }, { .baseTimeRaw = 818996, .baseTimeMs = 1126157568, .baseSequence = 928, .packetStatusCount = 5, .deltas = std::vector{ 63, 11, 21, 6, 0 }, .buffer = std::vector{ 0xAF, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x33, 0xB0, 0x4A, 0xE8, 0x03, 0xA0, 0x00, 0x05, 0x0C, 0x7F, 0x34, 0xA0, 0x20, 0x05, 0xFC, 0x2C, 0x54, 0x18, 0x00, 0x01 } }, { .baseTimeRaw = 818997, .baseTimeMs = 1126157632, .baseSequence = 942, .packetStatusCount = 6, .deltas = std::vector{ 39, 13, 9, 5, 4, 13 }, .buffer = std::vector{ 0x8F, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x26, 0x9E, 0x8E, 0x50, 0x03, 0xAE, 0x00, 0x06, 0x0C, 0x7F, 0x35, 0xA1, 0x20, 0x06, 0x9C, 0x34, 0x24, 0x14, 0x10, 0x34 } }, { .baseTimeRaw = 821523, .baseTimeMs = 1126319296, .baseSequence = 10, .packetStatusCount = 7, .deltas = std::vector{ 25, 2, 2, 3, 1, 1, 3 }, .buffer = std::vector{ 0xAF, 0xCD, 0x00, 0x07, 0xFA, 0x17, 0xFA, 0x17, 0x00, 0x00, 0x04, 0xD2, 0x00, 0x0A, 0x00, 0x07, 0x0C, 0x89, 0x13, 0x00, 0x20, 0x07, 0x64, 0x08, 0x08, 0x0C, 0x04, 0x04, 0x0C, 0x00, 0x00, 0x03 } }, { .baseTimeRaw = 821524, .baseTimeMs = 1126319360, .baseSequence = 17, .packetStatusCount = 2, .deltas = std::vector{ 44, 18 }, .buffer = std::vector{ 0x8F, 0xCD, 0x00, 0x05, 0xFA, 0x17, 0xFA, 0x17, 0x08, 0xEB, 0x06, 0xD7, 0x00, 0x11, 0x00, 0x02, 0x0C, 0x89, 0x14, 0x01, 0x20, 0x02, 0xB0, 0x48 } }, { .baseTimeRaw = 821524, .baseTimeMs = 1126319360, .baseSequence = 17, .packetStatusCount = 1, .deltas = std::vector{ 62 }, .buffer = std::vector{ 0xAF, 0xCD, 0x00, 0x05, 0xFA, 0x17, 0xFA, 0x17, 0x20, 0x92, 0x5E, 0xB7, 0x00, 0x11, 0x00, 0x01, 0x0C, 0x89, 0x14, 0x00, 0x20, 0x01, 0xF8, 0x01 } }, { .baseTimeRaw = 821526, .baseTimeMs = 1126319488, .baseSequence = 19, .packetStatusCount = 4, .deltas = std::vector{ 4, 0, 4, 0 }, .buffer = std::vector{ 0xAF, 0xCD, 0x00, 0x06, 0xFA, 0x17, 0xFA, 0x17, 0x08, 0xEB, 0x06, 0xD7, 0x00, 0x13, 0x00, 0x04, 0x0C, 0x89, 0x16, 0x02, 0x20, 0x04, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02 } } }; for (const auto& packetMeta : feedbackPacketsMeta) { auto buffer = packetMeta.buffer; std::unique_ptr feedback{ RTC::RTCP::FeedbackRtpTransportPacket::Parse(buffer.data(), buffer.size()) }; REQUIRE(feedback->GetReferenceTime() == packetMeta.baseTimeRaw); REQUIRE(feedback->GetReferenceTimestamp() == packetMeta.baseTimeMs); REQUIRE(feedback->GetBaseSequenceNumber() == packetMeta.baseSequence); REQUIRE(feedback->GetPacketStatusCount() == packetMeta.packetStatusCount); auto packetsResults = feedback->GetPacketResults(); int deltasIt = 0; for (const auto& delta : packetMeta.deltas) { auto resultDelta = packetsResults[deltasIt].delta; REQUIRE(static_cast(resultDelta / 4) == delta); deltasIt++; } } } SECTION("check GetBaseDelta() wraparound") { static const auto MaxBaseTime = RTC::RTCP::FeedbackRtpTransportPacket::TimeWrapPeriod - RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick; auto packet1 = std::make_unique(senderSsrc, mediaSsrc); auto packet2 = std::make_unique(senderSsrc, mediaSsrc); auto packet3 = std::make_unique(senderSsrc, mediaSsrc); packet1->SetReferenceTime(MaxBaseTime); packet2->SetReferenceTime(MaxBaseTime + RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick); packet3->SetReferenceTime( MaxBaseTime + RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick + RTC::RTCP::FeedbackRtpTransportPacket::BaseTimeTick); REQUIRE(packet1->GetReferenceTime() == 16777215); REQUIRE(packet2->GetReferenceTime() == 0); REQUIRE(packet3->GetReferenceTime() == 1); REQUIRE(packet1->GetReferenceTimestamp() == 2147483584); REQUIRE(packet2->GetReferenceTimestamp() == 1073741824); REQUIRE(packet3->GetReferenceTimestamp() == 1073741888); REQUIRE(packet1->GetBaseDelta(packet1->GetReferenceTimestamp()) == 0); REQUIRE(packet2->GetBaseDelta(packet1->GetReferenceTimestamp()) == 64); REQUIRE(packet3->GetBaseDelta(packet2->GetReferenceTimestamp()) == 64); REQUIRE(packet3->GetBaseDelta(packet1->GetReferenceTimestamp()) == 128); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestPacket.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/FeedbackPs.hpp" #include "RTC/RTCP/FeedbackRtp.hpp" #include "RTC/RTCP/Packet.hpp" #include SCENARIO("RTCP Packet", "[rtcp][packet]") { // RTCP common header // Version:2, Padding:false, Count:0, Type:200(SR), Lengh:0 // clang-format off alignas(4) uint8_t buffer[] = { 0x80, 0xc8, 0x00, 0x00 }; // clang-format on SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::Packet::CommonHeader) == 2); REQUIRE(alignof(RTC::RTCP::FeedbackRtpPacket::Header) == 4); REQUIRE(alignof(RTC::RTCP::FeedbackPsPacket::Header) == 4); } SECTION("a RTCP packet may only contain the RTCP common header") { const std::unique_ptr packet{ RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)) }; REQUIRE(packet); } SECTION("a too small RTCP packet should fail") { // Provide a wrong packet length. const size_t length = sizeof(buffer) - 1; const std::unique_ptr packet{ RTC::RTCP::Packet::Parse(buffer, length) }; REQUIRE(!packet); } SECTION("a RTCP packet with incorrect version should fail") { // Set an incorrect version value (0). buffer[0] &= 0b00111111; const std::unique_ptr packet{ RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)) }; REQUIRE(!packet); } SECTION("a RTCP packet with incorrect length should fail") { // Set the packet length to zero. buffer[3] = 1; const std::unique_ptr packet{ RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)) }; REQUIRE(!packet); } SECTION("a RTCP packet with unknown type should fail") { // Set and unknown packet type (0). buffer[1] = 0; const std::unique_ptr packet{ RTC::RTCP::Packet::Parse(buffer, sizeof(buffer)) }; REQUIRE(!packet); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestReceiverReport.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/ReceiverReport.hpp" #include SCENARIO("RTCP ReceiverReport", "[rtcp][receiver-report]") { // RTCP Receiver Report Packet. // clang-format off alignas(4) uint8_t buffer[] = { 0x81, 0xc9, 0x00, 0x07, // Type: 201 (Receiver Report), Count: 1, Length: 7 0x5d, 0x93, 0x15, 0x34, // Sender SSRC: 0x5d931534 // Receiver Report 0x01, 0x93, 0x2d, 0xb4, // SSRC. 0x01932db4 0x00, 0xFF, 0xFF, 0xFF, // Fraction lost: 0, Total lost: -1 0x00, 0x00, 0x00, 0x00, // Extended highest sequence number: 0 0x00, 0x00, 0x00, 0x00, // Jitter: 0 0x00, 0x00, 0x00, 0x00, // Last SR: 0 0x00, 0x00, 0x00, 0x05 // DLSR: 0 }; // clang-format on // Receiver Report buffer start point. const uint8_t* rrBuffer = buffer + RTC::RTCP::Packet::CommonHeaderSize + sizeof(uint32_t); // Sender SSRC. const uint32_t ssrc{ 0x01932db4 }; const uint8_t fractionLost{ 0 }; const int32_t totalLost{ -1 }; const uint32_t lastSeq{ 0 }; const uint32_t jitter{ 0 }; const uint32_t lastSenderReport{ 0 }; const uint32_t delaySinceLastSenderReport{ 5 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::ReceiverReport* report) { REQUIRE(report->GetSsrc() == ssrc); REQUIRE(report->GetFractionLost() == fractionLost); REQUIRE(report->GetTotalLost() == totalLost); REQUIRE(report->GetLastSeq() == lastSeq); REQUIRE(report->GetJitter() == jitter); REQUIRE(report->GetLastSenderReport() == lastSenderReport); REQUIRE(report->GetDelaySinceLastSenderReport() == delaySinceLastSenderReport); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::ReceiverReport::Header) == 4); } SECTION("parse RR packet with a single report") { std::unique_ptr packet{ RTC::RTCP::ReceiverReportPacket::Parse( buffer, sizeof(buffer)) }; REQUIRE(packet->GetCount() == 1); auto* report = *(packet->Begin()); verify(report); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); std::unique_ptr packet2{ RTC::RTCP::ReceiverReportPacket::Parse(serialized, sizeof(buffer)) }; REQUIRE(packet2->GetType() == RTC::RTCP::Type::RR); REQUIRE(packet2->GetCount() == 1); REQUIRE(packet2->GetSize() == 32); auto* buf = reinterpret_cast(buffer); REQUIRE(ntohs(buf->length) == 7); report = *(packet2->Begin()); verify(report); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("parse RR") { std::unique_ptr report{ RTC::RTCP::ReceiverReport::Parse( rrBuffer, RTC::RTCP::ReceiverReport::HeaderSize) }; REQUIRE(report); verify(report.get()); } SECTION("create RR packet with more than 31 reports") { const size_t count = 33; RTC::RTCP::ReceiverReportPacket packet; for (size_t i = 1; i <= count; i++) { // Create report and add to packet. auto* report = new RTC::RTCP::ReceiverReport(); report->SetSsrc(i); report->SetFractionLost(i); report->SetTotalLost(i); report->SetLastSeq(i); report->SetJitter(i); report->SetLastSenderReport(i); report->SetDelaySinceLastSenderReport(i); packet.AddReport(report); } REQUIRE(packet.GetCount() == count); alignas(4) uint8_t buffer[1500] = { 0 }; // Serialization must contain 2 RR packets since report count exceeds 31. packet.Serialize(buffer); std::unique_ptr packet2{ static_cast(RTC::RTCP::Packet::Parse(buffer, sizeof(buffer))) }; REQUIRE(packet2 != nullptr); REQUIRE(packet2->GetCount() == 31); auto reportIt = packet2->Begin(); for (size_t i = 1; i <= 31; ++i, ++reportIt) { auto* report = *reportIt; REQUIRE(report->GetSsrc() == i); REQUIRE(report->GetFractionLost() == i); REQUIRE(report->GetTotalLost() == static_cast(i)); REQUIRE(report->GetLastSeq() == i); REQUIRE(report->GetJitter() == i); REQUIRE(report->GetLastSenderReport() == i); REQUIRE(report->GetDelaySinceLastSenderReport() == i); } auto* packet3 = static_cast(packet2->GetNext()); REQUIRE(packet3 != nullptr); REQUIRE(packet3->GetCount() == 2); reportIt = packet3->Begin(); for (size_t i = 1; i <= 2; ++i, ++reportIt) { auto* report = *reportIt; REQUIRE(report->GetSsrc() == 31 + i); REQUIRE(report->GetFractionLost() == 31 + i); REQUIRE(report->GetTotalLost() == static_cast(31 + i)); REQUIRE(report->GetLastSeq() == 31 + i); REQUIRE(report->GetJitter() == 31 + i); REQUIRE(report->GetLastSenderReport() == 31 + i); REQUIRE(report->GetDelaySinceLastSenderReport() == 31 + i); } delete packet3; } SECTION("create RR report") { // Create local report and check content. RTC::RTCP::ReceiverReport report1; report1.SetSsrc(ssrc); report1.SetFractionLost(fractionLost); report1.SetTotalLost(totalLost); report1.SetLastSeq(lastSeq); report1.SetJitter(jitter); report1.SetLastSenderReport(lastSenderReport); report1.SetDelaySinceLastSenderReport(delaySinceLastSenderReport); verify(&report1); SECTION("create a report out of the existing one") { RTC::RTCP::ReceiverReport report2(&report1); verify(&report2); } } } ================================================ FILE: worker/test/src/RTC/RTCP/TestSdes.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/Packet.hpp" #include "RTC/RTCP/Sdes.hpp" #include #include // std::memcmp() #include SCENARIO("RTCP SDES", "[rtcp][sdes]") { // RTCP Sdes Packet. // clang-format off alignas(4) uint8_t buffer1[] = { 0x81, 0xca, 0x00, 0x06, // Type: 202 (SDES), Count: 1, Length: 6 0x9f, 0x65, 0xe7, 0x42, // SSRC: 0x9f65e742 // Chunk 1 0x01, 0x10, 0x74, 0x37, // Item Type: 1 (CNAME), Length: 16, Value: t7mkYnCm46OcINy/ 0x6d, 0x6b, 0x59, 0x6e, 0x43, 0x6d, 0x34, 0x36, 0x4f, 0x63, 0x49, 0x4e, 0x79, 0x2f, 0x00, 0x00 // 2 null octets }; // clang-format on // First chunk (chunk 1). const uint32_t ssrc1{ 0x9f65e742 }; // First item (item 1). const RTC::RTCP::SdesItem::Type item1Type{ RTC::RTCP::SdesItem::Type::CNAME }; const std::string item1Value{ "t7mkYnCm46OcINy/" }; const size_t item1Length{ 16u }; // clang-format off alignas(4) uint8_t buffer2[] = { 0xa2, 0xca, 0x00, 0x0d, // Padding, Type: 202 (SDES), Count: 2, Length: 13 // Chunk 2 0x00, 0x00, 0x04, 0xd2, // SSRC: 1234 0x01, 0x06, 0x71, 0x77, // Item Type: 1 (CNAME), Length: 6, Text: "qwerty" 0x65, 0x72, 0x74, 0x79, 0x06, 0x06, 0x69, 0xc3, // Item Type: 6 (TOOL), Length: 6, Text: "iñaki" 0xb1, 0x61, 0x6b, 0x69, 0x00, 0x00, 0x00, 0x00, // 4 null octets // Chunk 3 0x00, 0x00, 0x16, 0x2e, // SSRC: 5678 0x05, 0x11, 0x73, 0x6f, // Item Type: 5 (LOC), Length: 17, Text: "somewhere œæ€" 0x6d, 0x65, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0xc5, 0x93, 0xc3, 0xa6, 0xe2, 0x82, 0xac, 0x00, // 1 null octet 0x00, 0x00, 0x00, 0x00 // Pading (4 bytes) }; // clang-format on // First chunk (chunk 2). const uint32_t ssrc2{ 1234 }; // First item (item 2). const RTC::RTCP::SdesItem::Type item2Type{ RTC::RTCP::SdesItem::Type::CNAME }; const std::string item2Value{ "qwerty" }; const size_t item2Length{ 6u }; // First item (item 3). const RTC::RTCP::SdesItem::Type item3Type{ RTC::RTCP::SdesItem::Type::TOOL }; const std::string item3Value{ "iñaki" }; const size_t item3Length{ 6u }; // Second chunk (chunk 3). const uint32_t ssrc3{ 5678 }; // First item (item 4). const RTC::RTCP::SdesItem::Type item4Type{ RTC::RTCP::SdesItem::Type::LOC }; const std::string item4Value{ "somewhere œæ€" }; const size_t item4Length{ 17u }; // clang-format off alignas(4) uint8_t buffer3[] = { 0x81, 0xca, 0x00, 0x03, // Type: 202 (SDES), Count: 1, Length: 3 // Chunk 0x11, 0x22, 0x33, 0x44, // SSRC: 0x11223344 0x05, 0x02, 0x61, 0x62, // Item Type: 5 (LOC), Length: 2, Text: "ab" 0x00, 0x00, 0x00, 0x00 // 4 null octets }; // clang-format on // First chunk (chunk 4). const uint32_t ssrc4{ 0x11223344 }; // First item (item 5). const RTC::RTCP::SdesItem::Type item5Type{ RTC::RTCP::SdesItem::Type::LOC }; const std::string item5Value{ "ab" }; const size_t item5Length{ 2u }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::SdesItem::Header) == 1); } SECTION("parse packet 1") { std::unique_ptr packet{ RTC::RTCP::SdesPacket::Parse( buffer1, sizeof(buffer1)) }; auto* header = reinterpret_cast(buffer1); REQUIRE(packet); REQUIRE(ntohs(header->length) == 6); REQUIRE(packet->GetSize() == 28); REQUIRE(packet->GetCount() == 1); size_t chunkIdx{ 0u }; for (auto it = packet->Begin(); it != packet->End(); ++it, ++chunkIdx) { auto* chunk = *it; // NOLINTNEXTLINE(hicpp-multiway-paths-covered) switch (chunkIdx) { /* First chunk (chunk 1). */ case 0: { // Chunk size must be 24 bytes (including 4 null octets). REQUIRE(chunk->GetSize() == 24); REQUIRE(chunk->GetSsrc() == ssrc1); size_t itemIdx{ 0u }; for (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2, ++itemIdx) { auto* item = *it2; // NOLINTNEXTLINE(hicpp-multiway-paths-covered) switch (itemIdx) { /* First item (item 1). */ case 0: { REQUIRE(item->GetType() == item1Type); REQUIRE(item->GetLength() == item1Length); REQUIRE(std::string(item->GetValue(), item1Length) == item1Value); break; } default:; } } // There is 1 item. REQUIRE(itemIdx == 1); break; } default:; } } // There is 1 chunk. REQUIRE(chunkIdx == 1); SECTION("serialize SdesChunk instance") { auto it = packet->Begin(); auto* chunk1 = *it; const uint8_t* chunk1Buffer = buffer1 + RTC::RTCP::Packet::CommonHeaderSize; // NOTE: Length of first chunk (including null octets) is 24. alignas(4) uint8_t serialized1[24] = { 0 }; chunk1->Serialize(serialized1); REQUIRE(std::memcmp(chunk1Buffer, serialized1, 24) == 0); } } SECTION("parse packet 2") { std::unique_ptr packet{ RTC::RTCP::SdesPacket::Parse( buffer2, sizeof(buffer2)) }; auto* header = reinterpret_cast(buffer2); REQUIRE(packet); REQUIRE(ntohs(header->length) == 13); // Despite total buffer size is 56 bytes, our GetSize() method doesn't not // consider RTCP padding (4 bytes in this case). REQUIRE(packet->GetSize() == 52); REQUIRE(packet->GetCount() == 2); size_t chunkIdx{ 0u }; for (auto it = packet->Begin(); it != packet->End(); ++it, ++chunkIdx) { auto* chunk = *it; switch (chunkIdx) { /* First chunk (chunk 2). */ case 0: { // Chunk size must be 24 bytes (including 4 null octets). REQUIRE(chunk->GetSize() == 24); REQUIRE(chunk->GetSsrc() == ssrc2); size_t itemIdx{ 0u }; for (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2, ++itemIdx) { auto* item = *it2; switch (itemIdx) { /* First item (item 2). */ case 0: { REQUIRE(item->GetType() == item2Type); REQUIRE(item->GetLength() == item2Length); REQUIRE(std::string(item->GetValue(), item2Length) == item2Value); break; } /* Second item (item 3). */ case 1: { REQUIRE(item->GetType() == item3Type); REQUIRE(item->GetLength() == item3Length); REQUIRE(std::string(item->GetValue(), item3Length) == item3Value); break; } default:; } } // There are 2 items. REQUIRE(itemIdx == 2); break; } /* Second chunk (chunk 3). */ case 1: { // Chunk size must be 24 bytes (including 1 null octet). REQUIRE(chunk->GetSize() == 24); REQUIRE(chunk->GetSsrc() == ssrc3); size_t itemIdx{ 0u }; for (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2, ++itemIdx) { auto* item = *it2; // NOLINTNEXTLINE(hicpp-multiway-paths-covered) switch (itemIdx) { /* First item (item 4). */ case 0: { REQUIRE(item->GetType() == item4Type); REQUIRE(item->GetLength() == item4Length); REQUIRE(std::string(item->GetValue(), item4Length) == item4Value); break; } default:; } } // There is 1 item. REQUIRE(itemIdx == 1); break; } default:; } } // There are 2 chunks. REQUIRE(chunkIdx == 2); SECTION("serialize SdesChunk instances") { auto it = packet->Begin(); auto* chunk1 = *it; const uint8_t* chunk1Buffer = buffer2 + RTC::RTCP::Packet::CommonHeaderSize; // NOTE: Length of first chunk (including null octets) is 24. alignas(4) uint8_t serialized1[24] = { 0 }; chunk1->Serialize(serialized1); REQUIRE(std::memcmp(chunk1Buffer, serialized1, 24) == 0); auto* chunk2 = *(++it); const uint8_t* chunk2Buffer = buffer2 + RTC::RTCP::Packet::CommonHeaderSize + 24; // NOTE: Length of second chunk (including null octets) is 24. alignas(4) uint8_t serialized2[24] = { 0 }; chunk2->Serialize(serialized2); REQUIRE(std::memcmp(chunk2Buffer, serialized2, 24) == 0); } } SECTION("parse packet 3") { std::unique_ptr packet{ RTC::RTCP::SdesPacket::Parse( buffer3, sizeof(buffer3)) }; auto* header = reinterpret_cast(buffer3); REQUIRE(packet); REQUIRE(ntohs(header->length) == 3); REQUIRE(packet->GetSize() == 16); REQUIRE(packet->GetCount() == 1); size_t chunkIdx{ 0u }; for (auto it = packet->Begin(); it != packet->End(); ++it, ++chunkIdx) { auto* chunk = *it; // NOLINTNEXTLINE(hicpp-multiway-paths-covered) switch (chunkIdx) { /* First chunk (chunk 4). */ case 0: { REQUIRE(chunk->GetSize() == 12); REQUIRE(chunk->GetSsrc() == ssrc4); size_t itemIdx{ 0u }; for (auto it2 = chunk->Begin(); it2 != chunk->End(); ++it2, ++itemIdx) { auto* item = *it2; // NOLINTNEXTLINE(hicpp-multiway-paths-covered) switch (itemIdx) { /* First item (item 5). */ case 0: { REQUIRE(item->GetType() == item5Type); REQUIRE(item->GetLength() == item5Length); REQUIRE(std::string(item->GetValue(), item5Length) == item5Value); break; } default:; } } // There is 1 item. REQUIRE(itemIdx == 1); break; } default:; } } // There is 1 chunk. REQUIRE(chunkIdx == 1); SECTION("serialize SdesChunk instance") { auto it = packet->Begin(); auto* chunk1 = *it; const uint8_t* chunk1Buffer = buffer3 + RTC::RTCP::Packet::CommonHeaderSize; // NOTE: Length of first chunk (including null octets) is 12. alignas(4) uint8_t serialized1[12] = { 0 }; chunk1->Serialize(serialized1); REQUIRE(std::memcmp(chunk1Buffer, serialized1, 12) == 0); } } SECTION("parsing a packet with missing null octects fails") { // clang-format off alignas(4) uint8_t buffer[] = { 0x81, 0xca, 0x00, 0x02, // Type: 202 (SDES), Count: 1, Length: 2 // Chunk 0x11, 0x22, 0x33, 0x44, // SSRC: 0x11223344 0x08, 0x02, 0x61, 0x62 // Item Type: 8 (PRIV), Length: 2, Text: "ab" }; const auto* packet = RTC::RTCP::SdesPacket::Parse(buffer, sizeof(buffer)); REQUIRE(!packet); } SECTION("create SDES packet with 31 chunks") { const size_t count = 31; RTC::RTCP::SdesPacket packet; // Create a chunk and an item to obtain their size. auto chunk = std::make_unique(1234 /*ssrc*/); auto* item1 = new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, item1Value.size(), item1Value.c_str()); chunk->AddItem(item1); auto chunkSize = chunk->GetSize(); for (size_t i{ 1 }; i <= count; ++i) { // Create chunk and add to packet. auto* chunk = new RTC::RTCP::SdesChunk(i /*ssrc*/); auto* item1 = new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, item1Value.size(), item1Value.c_str()); chunk->AddItem(item1); packet.AddChunk(chunk); } REQUIRE(packet.GetCount() == count); REQUIRE(packet.GetSize() == RTC::RTCP::Packet::CommonHeaderSize + (count * chunkSize)); alignas(4) uint8_t buffer1[1500] = { 0 }; // Serialization must contain 1 SDES packet since report count doesn't // exceed 31. packet.Serialize(buffer1); std::unique_ptr packet2{static_cast(RTC::RTCP::Packet::Parse(buffer1, sizeof(buffer1)))}; REQUIRE(packet2 != nullptr); REQUIRE(packet2->GetCount() == count); REQUIRE(packet2->GetSize() == RTC::RTCP::Packet::CommonHeaderSize + (count * chunkSize)); auto reportIt = packet2->Begin(); for (size_t i{ 1 }; i <= 31; ++i, ++reportIt) { auto* chunk = *reportIt; REQUIRE(chunk->GetSsrc() == i); auto* item = *(chunk->Begin()); REQUIRE(item->GetType() == RTC::RTCP::SdesItem::Type::CNAME); REQUIRE(item->GetSize() == 2 + item1Value.size()); REQUIRE(std::string(item->GetValue()) == item1Value); } std::unique_ptr packet3{static_cast(packet2->GetNext())}; REQUIRE(packet3 == nullptr); } SECTION("create SDES packet with more than 31 chunks") { const size_t count = 33; RTC::RTCP::SdesPacket packet; // Create a chunk and an item to obtain their size. auto chunk = std::make_unique(1234 /*ssrc*/); auto* item1 = new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, item1Value.size(), item1Value.c_str()); chunk->AddItem(item1); auto chunkSize = chunk->GetSize(); for (size_t i{ 1 }; i <= count; ++i) { // Create chunk and add to packet. auto* chunk = new RTC::RTCP::SdesChunk(i /*ssrc*/); auto* item1 = new RTC::RTCP::SdesItem(RTC::RTCP::SdesItem::Type::CNAME, item1Value.size(), item1Value.c_str()); chunk->AddItem(item1); packet.AddChunk(chunk); } REQUIRE(packet.GetCount() == count); REQUIRE(packet.GetSize() == RTC::RTCP::Packet::CommonHeaderSize + (31 * chunkSize) + RTC::RTCP::Packet::CommonHeaderSize + ((count - 31) * chunkSize)); alignas(4) uint8_t buffer1[1500] = { 0 }; // Serialization must contain 2 SDES packets since report count exceeds 31. packet.Serialize(buffer1); std::unique_ptr packet2 {static_cast(RTC::RTCP::Packet::Parse(buffer1, sizeof(buffer1)))}; REQUIRE(packet2 != nullptr); REQUIRE(packet2->GetCount() == 31); REQUIRE(packet2->GetSize() == RTC::RTCP::Packet::CommonHeaderSize + (31 * chunkSize)); auto reportIt = packet2->Begin(); for (size_t i{ 1 }; i <= 31; ++i, ++reportIt) { auto* chunk = *reportIt; REQUIRE(chunk->GetSsrc() == i); auto* item = *(chunk->Begin()); REQUIRE(item->GetType() == RTC::RTCP::SdesItem::Type::CNAME); REQUIRE(item->GetSize() == 2 + item1Value.size()); REQUIRE(std::string(item->GetValue()) == item1Value); } auto* packet3 = static_cast(packet2->GetNext()); REQUIRE(packet3 != nullptr); REQUIRE(packet3->GetCount() == count - 31); REQUIRE(packet3->GetSize() == RTC::RTCP::Packet::CommonHeaderSize + ((count - 31) * chunkSize)); reportIt = packet3->Begin(); for (size_t i{ 1 }; i <= 2; ++i, ++reportIt) { auto* chunk = *reportIt; REQUIRE(chunk->GetSsrc() == 31 + i); auto* item = *(chunk->Begin()); REQUIRE(item->GetType() == RTC::RTCP::SdesItem::Type::CNAME); REQUIRE(item->GetSize() == 2 + item1Value.size()); REQUIRE(std::string(item->GetValue()) == item1Value); } delete packet3; } SECTION("create SdesChunk") { auto* item = new RTC::RTCP::SdesItem(item1Type, item1Length, item1Value.c_str()); // Create sdes chunk. RTC::RTCP::SdesChunk chunk(ssrc1); chunk.AddItem(item); REQUIRE(chunk.GetSsrc() == ssrc1); const RTC::RTCP::SdesItem* item1 = *(chunk.Begin()); REQUIRE(item1->GetType() == item1Type); REQUIRE(item1->GetLength() == item1Length); REQUIRE(std::string(item1->GetValue(), item1Length) == item1Value); } } ================================================ FILE: worker/test/src/RTC/RTCP/TestSenderReport.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/SenderReport.hpp" #include #include // std::memcmp() SCENARIO("RTCP SenderReport", "[rtcp][sender-report]") { // RTCP Packet. Sender Report and Receiver Report. // clang-format off alignas(4) uint8_t buffer[] = { 0x80, 0xc8, 0x00, 0x06, // Type: 200 (Sender Report), Count: 0, Length: 6 0x5d, 0x93, 0x15, 0x34, // SSRC: 0x5d931534 0xdd, 0x3a, 0xc1, 0xb4, // NTP Sec: 3711615412 0x76, 0x54, 0x71, 0x71, // NTP Frac: 1985245553 0x00, 0x08, 0xcf, 0x00, // RTP timestamp: 577280 0x00, 0x00, 0x0e, 0x18, // Packet count: 3608 0x00, 0x08, 0xcf, 0x00 // Octet count: 577280 }; // clang-format on // Sender Report buffer start point. const uint8_t* srBuffer = buffer + RTC::RTCP::Packet::CommonHeaderSize; // SR values. const uint32_t ssrc{ 0x5d931534 }; const uint32_t ntpSec{ 3711615412 }; const uint32_t ntpFrac{ 1985245553 }; const uint32_t rtpTs{ 577280 }; const uint32_t packetCount{ 3608 }; const uint32_t octetCount{ 577280 }; // NOTE: No need to pass const integers to the lambda. auto verify = [](RTC::RTCP::SenderReport* report) { REQUIRE(report->GetSsrc() == ssrc); REQUIRE(report->GetNtpSec() == ntpSec); REQUIRE(report->GetNtpFrac() == ntpFrac); REQUIRE(report->GetRtpTs() == rtpTs); REQUIRE(report->GetPacketCount() == packetCount); REQUIRE(report->GetOctetCount() == octetCount); }; SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::SenderReport::Header) == 4); } SECTION("parse SR packet") { std::unique_ptr packet{ RTC::RTCP::SenderReportPacket::Parse( buffer, sizeof(buffer)) }; auto* report = *(packet->Begin()); verify(report); SECTION("serialize packet instance") { alignas(4) uint8_t serialized[sizeof(buffer)] = { 0 }; packet->Serialize(serialized); SECTION("compare serialized packet with original buffer") { REQUIRE(std::memcmp(buffer, serialized, sizeof(buffer)) == 0); } } } SECTION("parse SR") { std::unique_ptr report{ RTC::RTCP::SenderReport::Parse( srBuffer, RTC::RTCP::SenderReport::HeaderSize) }; REQUIRE(report); verify(report.get()); SECTION("serialize SenderReport instance") { alignas(4) uint8_t serialized[RTC::RTCP::SenderReport::HeaderSize] = { 0 }; report->Serialize(serialized); SECTION("compare serialized SenderReport with original buffer") { REQUIRE(std::memcmp(srBuffer, serialized, RTC::RTCP::SenderReport::HeaderSize) == 0); } } } SECTION("create SR packet multiple reports") { const size_t count = 3; RTC::RTCP::SenderReportPacket packet; for (size_t i = 1; i <= count; ++i) { // Create report and add to packet. auto* report = new RTC::RTCP::SenderReport(); report->SetSsrc(i); report->SetNtpSec(i); report->SetNtpFrac(i); report->SetRtpTs(i); report->SetPacketCount(i); report->SetOctetCount(i); packet.AddReport(report); } alignas(4) uint8_t buffer[1500] = { 0 }; // Serialization must contain 3 SR packets. packet.Serialize(buffer); // NOTE: clang-tidy says that this could be `const SenderReport* const reports` // but that's absolutely wrong! // NOLINTNEXTLINE(misc-const-correctness) const RTC::RTCP::SenderReport* reports[count]{ nullptr }; std::unique_ptr packet2{ static_cast(RTC::RTCP::Packet::Parse(buffer, sizeof(buffer))) }; REQUIRE(packet2 != nullptr); reports[0] = *(packet2->Begin()); auto* packet3 = static_cast(packet2->GetNext()); REQUIRE(packet3 != nullptr); reports[1] = *(packet3->Begin()); auto* packet4 = static_cast(packet3->GetNext()); REQUIRE(packet4 != nullptr); reports[2] = *(packet4->Begin()); for (size_t i = 1; i <= count; ++i) { const auto* report = reports[i - 1]; REQUIRE(report != nullptr); REQUIRE(report->GetSsrc() == i); REQUIRE(report->GetNtpSec() == i); REQUIRE(report->GetNtpFrac() == i); REQUIRE(report->GetRtpTs() == i); REQUIRE(report->GetPacketCount() == i); REQUIRE(report->GetOctetCount() == i); } delete packet3; delete packet4; } SECTION("create SR") { // Create local report and check content. RTC::RTCP::SenderReport report1; report1.SetSsrc(ssrc); report1.SetNtpSec(ntpSec); report1.SetNtpFrac(ntpFrac); report1.SetRtpTs(rtpTs); report1.SetPacketCount(packetCount); report1.SetOctetCount(octetCount); verify(&report1); SECTION("create a report out of the existing one") { RTC::RTCP::SenderReport report2(&report1); verify(&report2); } } } ================================================ FILE: worker/test/src/RTC/RTCP/TestXr.cpp ================================================ #include "common.hpp" #include "RTC/RTCP/XR.hpp" #include "RTC/RTCP/XrDelaySinceLastRr.hpp" #include "RTC/RTCP/XrReceiverReferenceTime.hpp" #include #include // std::memcmp(), std::memcpy() SCENARIO("RTCP XR", "[rtcp][xr]") { // clang-format off alignas(4) uint8_t buffer[] = { 0xa0, 0xcf, 0x00, 0x09, // Padding, Type: 207 (XR), Length: 9 0x5d, 0x93, 0x15, 0x34, // Sender SSRC: 0x5d931534 // Extended Report DLRR 0x05, 0x00, 0x00, 0x06, // BT: 5 (DLRR), Block Length: 6 0x11, 0x12, 0x13, 0x14, // SSRC 1 0x00, 0x11, 0x00, 0x11, // LRR 1 0x11, 0x00, 0x11, 0x00, // DLRR 1 0x21, 0x22, 0x23, 0x24, // SSRC 2 0x00, 0x22, 0x00, 0x22, // LRR 2 0x22, 0x00, 0x22, 0x00, // DLRR 2 0x00, 0x00, 0x00, 0x04 // Padding (4 bytes) }; // clang-format on SECTION("alignof() RTCP structs") { REQUIRE(alignof(RTC::RTCP::ExtendedReportBlock::CommonHeader) == 2); REQUIRE(alignof(RTC::RTCP::DelaySinceLastRr::SsrcInfo::Body) == 4); REQUIRE(alignof(RTC::RTCP::ReceiverReferenceTime::Body) == 4); } SECTION("parse XR packet") { std::unique_ptr packet( RTC::RTCP::ExtendedReportPacket::Parse(buffer, sizeof(buffer))); REQUIRE(packet); // Despite total buffer size is 40 bytes, our GetSize() method doesn't not // consider RTCP padding (4 bytes in this case). // https://github.com/versatica/mediasoup/issues/1233 REQUIRE(packet->GetSize() == 36); REQUIRE(packet->GetCount() == 0); REQUIRE(packet->GetSsrc() == 0x5d931534); size_t blockIdx{ 0u }; for (auto it = packet->Begin(); it != packet->End(); ++it, ++blockIdx) { auto* block = *it; // NOLINTNEXTLINE(hicpp-multiway-paths-covered) switch (blockIdx) { case 0: { REQUIRE(block->GetSize() == 28); size_t ssrcInfoIdx{ 0u }; auto* dlrrBlock = reinterpret_cast(block); for (auto it2 = dlrrBlock->Begin(); it2 != dlrrBlock->End(); ++it2, ++ssrcInfoIdx) { auto* ssrcInfo = *it2; switch (ssrcInfoIdx) { case 0: { REQUIRE(ssrcInfo->GetSsrc() == 0x11121314); REQUIRE(ssrcInfo->GetLastReceiverReport() == 0x00110011); REQUIRE(ssrcInfo->GetDelaySinceLastReceiverReport() == 0x11001100); break; } case 1: { REQUIRE(ssrcInfo->GetSsrc() == 0x21222324); REQUIRE(ssrcInfo->GetLastReceiverReport() == 0x00220022); REQUIRE(ssrcInfo->GetDelaySinceLastReceiverReport() == 0x22002200); break; } default:; } } // There are 2 SSRC infos. REQUIRE(ssrcInfoIdx == 2); break; } default:; } } // There are 1 block (the DLRR block). REQUIRE(blockIdx == 1); SECTION("serialize packet instance") { // NOTE: Padding in RTCP is removed (if not needed) when serializing the // packet, so we must mangle the buffer content (padding bit) and the // buffer length before comparing the serialized packet with and original // buffer. const size_t paddingBytes{ 4 }; const size_t serializedBufferLength = sizeof(buffer) - paddingBytes; alignas(4) uint8_t serialized[serializedBufferLength] = { 0 }; // Clone the original buffer into a new buffer without padding. alignas(4) uint8_t clonedBuffer[serializedBufferLength] = { 0 }; std::memcpy(clonedBuffer, buffer, serializedBufferLength); // Remove the padding bit in the first byte of the cloned buffer. clonedBuffer[0] = 0x80; // Change RTCP length field in the cloned buffer. clonedBuffer[3] = clonedBuffer[3] - 1; packet->Serialize(serialized); std::unique_ptr packet2( RTC::RTCP::ExtendedReportPacket::Parse(serialized, serializedBufferLength)); REQUIRE(packet2->GetType() == RTC::RTCP::Type::XR); REQUIRE(packet2->GetCount() == 0); REQUIRE(packet2->GetSize() == 36); REQUIRE(std::memcmp(clonedBuffer, serialized, serializedBufferLength) == 0); } } } SCENARIO("RTCP XrDelaySinceLastRt", "[rtcp][xr]") { SECTION("create RRT") { // Create local report and check content. // NOTE: We cannot use unique_ptr here since the instance lifecycle will be // managed by the packet. auto* report1 = new RTC::RTCP::ReceiverReferenceTime(); report1->SetNtpSec(11111111); report1->SetNtpFrac(22222222); REQUIRE(report1->GetType() == RTC::RTCP::ExtendedReportBlock::Type::RRT); REQUIRE(report1->GetNtpSec() == 11111111); REQUIRE(report1->GetNtpFrac() == 22222222); // Serialize the report into an external buffer. alignas(4) uint8_t bufferReport1[256]{ 0 }; report1->Serialize(bufferReport1); // Create a new report out of the external buffer. // NOTE: We cannot use unique_ptr here since the instance lifecycle will be // managed by the packet. auto* report2 = RTC::RTCP::ReceiverReferenceTime::Parse(bufferReport1, report1->GetSize()); REQUIRE(report1->GetType() == report2->GetType()); REQUIRE(report1->GetNtpSec() == report2->GetNtpSec()); REQUIRE(report1->GetNtpFrac() == report2->GetNtpFrac()); // Create a local packet. std::unique_ptr packet1(new RTC::RTCP::ExtendedReportPacket()); packet1->SetSsrc(2222); packet1->AddReport(report1); packet1->AddReport(report2); REQUIRE(packet1->GetType() == RTC::RTCP::Type::XR); REQUIRE(packet1->GetCount() == 0); REQUIRE(packet1->GetSsrc() == 2222); // Total size: // - RTCP common header // - SSRC // - block 1 // - block 2 REQUIRE(packet1->GetSize() == 4 + 4 + 12 + 12); // Serialize the packet into an external buffer. alignas(4) uint8_t bufferPacket1[256]{ 0 }; alignas(4) uint8_t bufferPacket2[256]{ 0 }; packet1->Serialize(bufferPacket1); // Create a new packet out of the external buffer. std::unique_ptr packet2( RTC::RTCP::ExtendedReportPacket::Parse(bufferPacket1, packet1->GetSize())); REQUIRE(packet2->GetType() == packet1->GetType()); REQUIRE(packet2->GetCount() == packet1->GetCount()); REQUIRE(packet2->GetSsrc() == packet1->GetSsrc()); REQUIRE(packet2->GetSize() == packet1->GetSize()); packet2->Serialize(bufferPacket2); REQUIRE(std::memcmp(bufferPacket1, bufferPacket2, packet1->GetSize()) == 0); } SECTION("create DLRR") { // Create local report and check content. // NOTE: We cannot use unique_ptr here since the instance lifecycle will be // managed by the packet. auto* report1 = new RTC::RTCP::DelaySinceLastRr(); // NOTE: We cannot use unique_ptr here since the instance lifecycle will be // managed by the report. auto* ssrcInfo1 = new RTC::RTCP::DelaySinceLastRr::SsrcInfo(); ssrcInfo1->SetSsrc(1234); ssrcInfo1->SetLastReceiverReport(11111111); ssrcInfo1->SetDelaySinceLastReceiverReport(22222222); REQUIRE(ssrcInfo1->GetSsrc() == 1234); REQUIRE(ssrcInfo1->GetLastReceiverReport() == 11111111); REQUIRE(ssrcInfo1->GetDelaySinceLastReceiverReport() == 22222222); REQUIRE(ssrcInfo1->GetSize() == sizeof(RTC::RTCP::DelaySinceLastRr::SsrcInfo::Body)); report1->AddSsrcInfo(ssrcInfo1); // Serialize the report into an external buffer. alignas(4) uint8_t bufferReport1[256]{ 0 }; report1->Serialize(bufferReport1); // Create a new report out of the external buffer. // NOTE: We cannot use unique_ptr here since the instance lifecycle will be // managed by the packet. // NOLINTNEXTLINE(llvm-qualified-auto,readability-qualified-auto) auto* report2 = RTC::RTCP::DelaySinceLastRr::Parse(bufferReport1, report1->GetSize()); REQUIRE(report1->GetType() == report2->GetType()); auto ssrcInfoIt = report2->Begin(); auto* ssrcInfo2 = *ssrcInfoIt; REQUIRE(ssrcInfo1->GetSsrc() == ssrcInfo2->GetSsrc()); REQUIRE(ssrcInfo1->GetLastReceiverReport() == ssrcInfo2->GetLastReceiverReport()); REQUIRE( ssrcInfo1->GetDelaySinceLastReceiverReport() == ssrcInfo2->GetDelaySinceLastReceiverReport()); REQUIRE(ssrcInfo1->GetSize() == ssrcInfo2->GetSize()); // Create a local packet. std::unique_ptr packet1(new RTC::RTCP::ExtendedReportPacket()); packet1->SetSsrc(2222); packet1->AddReport(report1); packet1->AddReport(report2); REQUIRE(packet1->GetType() == RTC::RTCP::Type::XR); REQUIRE(packet1->GetCount() == 0); REQUIRE(packet1->GetSsrc() == 2222); // Total size: // - RTCP common header // - SSRC // - block 1 // - block 2 REQUIRE(packet1->GetSize() == 4 + 4 + 16 + 16); // Serialize the packet into an external buffer. alignas(4) uint8_t bufferPacket1[256]{ 0 }; alignas(4) uint8_t bufferPacket2[256]{ 0 }; packet1->Serialize(bufferPacket1); // Create a new packet out of the external buffer. std::unique_ptr packet2( RTC::RTCP::ExtendedReportPacket::Parse(bufferPacket1, packet1->GetSize())); REQUIRE(packet2->GetType() == packet1->GetType()); REQUIRE(packet2->GetCount() == packet1->GetCount()); REQUIRE(packet2->GetSsrc() == packet1->GetSsrc()); REQUIRE(packet2->GetSize() == packet1->GetSize()); packet2->Serialize(bufferPacket2); REQUIRE(std::memcmp(bufferPacket1, bufferPacket2, packet1->GetSize()) == 0); } } ================================================ FILE: worker/test/src/RTC/RTP/Codecs/TestDependencyDescriptor.cpp ================================================ #include "common.hpp" #include "RTC/RTP/Codecs/DependencyDescriptor.hpp" #include SCENARIO("Dependency Descriptor", "[rtp][codecs][dependency-descriptor]") { class Listener : public RTC::RTP::Codecs::DependencyDescriptor::Listener { public: void OnDependencyDescriptorUpdated(const uint8_t* data, size_t len) override { } }; SECTION("parse") { /** * Taken from https://issues.webrtc.org/issues/42225660. * * { * "startOfFrame" : true, * "endOfFrame" : false, * "frameDependencyTemplateId" : 0, * "frameNumber" : 303, * "templateStructure": { * "templateIdOffset" : 0, * "templateInfo" : { * "0" : { * "spatialId" : 0, * "temporalId" : 0, * "dti" : [ "SWITCH", "SWITCH", "SWITCH" ], * "fdiff" : [], * "chains" : [0] * }, * "1" : { * "spatialId" : 0, * "temporalId" : 0, * "dti" : [ "SWITCH", "SWITCH", "SWITCH" ], * "fdiff" : [4], * "chains" : [4] * }, * "2" : { * "spatialId" : 0, * "temporalId" : 1, * "dti" : [ "NOT_PRESENT", "DISCARDABLE", "SWITCH" ], * "fdiff" : [2], * "chains" : [2] * }, * "3" : { * "spatialId" : 0, * "temporalId" : 2, * "dti" : [ "NOT_PRESENT", "NOT_PRESENT", "DISCARDABLE" ], * "fdiff" : [1], * "chains" : [1] * }, * "4" : { * "spatialId" : 0, * "temporalId" : 2, * "dti" : [ "NOT_PRESENT", "NOT_PRESENT", "DISCARDABLE" ], * "fdiff" : [1], * "chains" : [3] * } * }, * "decodeTargetInfo" : { * "0" : { "protectedBy" : 0, "spatialId" : 0, "temporalId" : 0 }, * "1" : { "protectedBy" : 0, "spatialId" : 0, "temporalId" : 1 }, * "2" : { "protectedBy" : 0, "spatialId" : 0, "temporalId" : 2 } * }, * "maxSpatialId" : 0, * "maxTemporalId" : 2 * } * } */ // clang-format off uint8_t data[] = { 0x80, 0x01, 0x2F, 0x80, 0x02, 0x14, 0xEA, 0xA8, 0x60, 0x41, 0x4D, 0x14, 0x10, 0x20, 0x84, 0x26 }; Listener listener; // clang-format on std::unique_ptr templateDependencyStructure; auto dependencyDescriptor = std::unique_ptr( RTC::RTP::Codecs::DependencyDescriptor::Parse( data, sizeof(data), std::addressof(listener), templateDependencyStructure)); REQUIRE(dependencyDescriptor); REQUIRE(dependencyDescriptor->startOfFrame == true); REQUIRE(dependencyDescriptor->endOfFrame == false); REQUIRE(dependencyDescriptor->frameDependencyTemplateId == 0); REQUIRE(dependencyDescriptor->frameNumber == 303); auto* templateStructure = dependencyDescriptor->templateDependencyStructure; std::vector dtis{}; std::vector fdiffs{}; std::vector fdiffChains{}; REQUIRE(templateStructure->templateLayers.size() == 5); REQUIRE(templateStructure->templateLayers[0].spatialLayer == 0); REQUIRE(templateStructure->templateLayers[0].temporalLayer == 0); dtis = { RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH, }; REQUIRE(templateStructure->templateLayers[0].decodeTargetIndications == dtis); fdiffs = {}; REQUIRE(templateStructure->templateLayers[0].frameDiffs == fdiffs); fdiffChains = { 0 }; REQUIRE(templateStructure->templateLayers[0].frameDiffChains == fdiffChains); REQUIRE(templateStructure->templateLayers[1].spatialLayer == 0); REQUIRE(templateStructure->templateLayers[1].temporalLayer == 0); dtis = { RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH, }; REQUIRE(templateStructure->templateLayers[1].decodeTargetIndications == dtis); fdiffs = { 4 }; REQUIRE(templateStructure->templateLayers[1].frameDiffs == fdiffs); fdiffChains = { 4 }; REQUIRE(templateStructure->templateLayers[1].frameDiffChains == fdiffChains); REQUIRE(templateStructure->templateLayers[2].spatialLayer == 0); REQUIRE(templateStructure->templateLayers[2].temporalLayer == 1); dtis = { RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::DISCARDABLE, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::SWITCH, }; REQUIRE(templateStructure->templateLayers[2].decodeTargetIndications == dtis); fdiffs = { 2 }; REQUIRE(templateStructure->templateLayers[2].frameDiffs == fdiffs); fdiffChains = { 2 }; REQUIRE(templateStructure->templateLayers[2].frameDiffChains == fdiffChains); REQUIRE(templateStructure->templateLayers[3].spatialLayer == 0); REQUIRE(templateStructure->templateLayers[3].temporalLayer == 2); dtis = { RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::DISCARDABLE, }; REQUIRE(templateStructure->templateLayers[3].decodeTargetIndications == dtis); fdiffs = { 1 }; REQUIRE(templateStructure->templateLayers[3].frameDiffs == fdiffs); fdiffChains = { 1 }; REQUIRE(templateStructure->templateLayers[3].frameDiffChains == fdiffChains); REQUIRE(templateStructure->templateLayers[4].spatialLayer == 0); REQUIRE(templateStructure->templateLayers[4].temporalLayer == 2); dtis = { RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::NOT_PRESENT, RTC::RTP::Codecs::DependencyDescriptor::DecodeTargetIndication::DISCARDABLE, }; REQUIRE(templateStructure->templateLayers[4].decodeTargetIndications == dtis); fdiffs = { 1 }; REQUIRE(templateStructure->templateLayers[4].frameDiffs == fdiffs); fdiffChains = { 3 }; REQUIRE(templateStructure->templateLayers[4].frameDiffChains == fdiffChains); } SECTION("serialize") { /** * * startOfFrame: true * endOfFrame: false * frameDependencyTemplateId: 0 * frameNumber: 232 * templateId: 0 * temporalLayer: 0 * spatialLayer: 0 * * spatialLayers: 0 * temporalLayers: 1 * templateIdOffset: 0 * decodeTargetCount: 2 * * * spatialLayerId: 0 * temporalLayerId: 0 * SS * * 0 * * * spatialLayerId: 0 * temporalLayerId: 0 * SS * 2 * 2 * * * spatialLayerId: 0 * temporalLayerId: 1 * -D * 1 * 1 * * * * */ // clang-format off uint8_t data1[] = { 0x80, 0x00, 0xE8, 0x80, 0x01, 0x1E, 0xA8, 0x51, 0x41, 0x01, 0x0C, 0x13, 0xFC, 0x0B, 0x3C, }; // clang-format on Listener listener; std::unique_ptr templateDependencyStructure; auto dependencyDescriptor = std::unique_ptr( RTC::RTP::Codecs::DependencyDescriptor::Parse( data1, sizeof(data1), std::addressof(listener), templateDependencyStructure)); REQUIRE(dependencyDescriptor); REQUIRE(dependencyDescriptor->frameNumber == 232); // clang-format off // 000000 4D 00 D8 uint8_t data2[] = { 0x00, 0x00, 0xE8, }; // clang-format on dependencyDescriptor = std::unique_ptr( RTC::RTP::Codecs::DependencyDescriptor::Parse( data2, sizeof(data2), std::addressof(listener), templateDependencyStructure)); REQUIRE(dependencyDescriptor); REQUIRE(dependencyDescriptor->frameNumber == 232); uint8_t len; const auto* data = dependencyDescriptor->Serialize(len); // clang-format on dependencyDescriptor = std::unique_ptr( RTC::RTP::Codecs::DependencyDescriptor::Parse( data, sizeof(data), std::addressof(listener), templateDependencyStructure)); REQUIRE(dependencyDescriptor); REQUIRE(dependencyDescriptor->frameNumber == 232); dependencyDescriptor->UpdateActiveDecodeTargets(0, 1); data = dependencyDescriptor->Serialize(len); // clang-format on dependencyDescriptor = std::unique_ptr( RTC::RTP::Codecs::DependencyDescriptor::Parse( data, sizeof(data), std::addressof(listener), templateDependencyStructure)); REQUIRE(dependencyDescriptor); REQUIRE(dependencyDescriptor->frameNumber == 232); // activeDecodeTargetsBitmask == 00000011 REQUIRE(dependencyDescriptor->activeDecodeTargetsBitmask == 3); } } ================================================ FILE: worker/test/src/RTC/RTP/Codecs/TestH264.cpp ================================================ #include "common.hpp" #include "RTC/RTP/Codecs/H264.hpp" #include SCENARIO("H264 payload descriptor", "[rtp][codecs][h264]") { SECTION("parse payload descriptor") { // clang-format off uint8_t originalBuffer[] = { 0x07, 0x80, 0x11, 0x00 }; // clang-format on // // Keep a copy of the original buffer for comparing. uint8_t buffer[4] = { 0 }; std::memcpy(buffer, originalBuffer, sizeof(buffer)); RTC::RTP::Codecs::DependencyDescriptor* dependencyDescriptor{ nullptr }; std::unique_ptr payloadDescriptor{ RTC::RTP::Codecs::H264::Parse(buffer, sizeof(buffer), dependencyDescriptor) }; REQUIRE(payloadDescriptor); } } ================================================ FILE: worker/test/src/RTC/RTP/Codecs/TestVP8.cpp ================================================ #include "common.hpp" #include "test/include/RTC/RTP/rtpCommon.hpp" #include "RTC/RTP/Codecs/VP8.hpp" #include "RTC/RTP/Packet.hpp" #include #include // std::memcmp(), std::memcpy() namespace { RTC::RTP::Codecs::VP8::PayloadDescriptor* createVP8PayloadDescriptor( uint8_t* buffer, size_t bufferLen, uint16_t pictureId, uint8_t tl0PictureIndex, uint8_t tlIndex, bool layerSync = true) { uint16_t netPictureId = htons(pictureId); std::memcpy(buffer + 2, &netPictureId, 2); buffer[2] |= 0x80; buffer[4] = tl0PictureIndex; buffer[5] = tlIndex << 6; if (layerSync) { buffer[5] |= 0x20; // y bit } auto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(buffer, bufferLen); REQUIRE(payloadDescriptor); return payloadDescriptor; } std::unique_ptr processVP8Packet( RTC::RTP::Codecs::VP8::EncodingContext& context, uint16_t pictureId, uint8_t tl0PictureIndex, uint8_t tlIndex, bool layerSync = true) { // clang-format off uint8_t payload[] = { 0x90, 0xe0, 0x80, 0x00, 0x00, 0x00 }; // clang-format on std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; packet->SetPayload(payload, sizeof(payload)); bool marker; auto* payloadDescriptor = createVP8PayloadDescriptor( packet->GetPayload(), packet->GetPayloadLength(), pictureId, tl0PictureIndex, tlIndex, layerSync); std::unique_ptr payloadDescriptorHandler( new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor)); if (payloadDescriptorHandler->Process(&context, packet.get(), marker)) { return std::unique_ptr( RTC::RTP::Codecs::VP8::Parse(packet->GetPayload(), packet->GetPayloadLength())); } return nullptr; } } // namespace SCENARIO("VP8 payload descriptor", "[rtp][codecs][vp8]") { SECTION("parse payload descriptor") { /** * VP8 Payload Descriptor * * 1 = X bit: Extended control bits present (I L T K) * 1 = R bit: Reserved for future use (Error should be zero) * 0 = N bit: Reference frame * 1 = S bit: Start of VP8 partition * Part Id: 0 * 1 = I bit: Picture ID byte present * 0 = L bit: TL0PICIDX byte not present * 0 = T bit: TID (temporal layer index) byte not present * 0 = K bit: TID/KEYIDX byte not present * 0000 = Reserved A: 0 * 0001 0001 = Picture Id: 17 */ // clang-format off uint8_t originalBuffer[] = { 0xd0, 0x80, 0x11, 0x00 }; // clang-format on // Keep a copy of the original buffer for comparing. uint8_t buffer[4] = { 0 }; std::memcpy(buffer, originalBuffer, sizeof(buffer)); std::unique_ptr payloadDescriptor{ RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer)) }; REQUIRE(payloadDescriptor); REQUIRE(payloadDescriptor->extended == 1); REQUIRE(payloadDescriptor->nonReference == 0); REQUIRE(payloadDescriptor->start == 1); REQUIRE(payloadDescriptor->partitionIndex == 0); // Optional field flags. REQUIRE(payloadDescriptor->i == 1); REQUIRE(payloadDescriptor->l == 0); REQUIRE(payloadDescriptor->t == 0); REQUIRE(payloadDescriptor->k == 0); // Optional fields. REQUIRE(payloadDescriptor->pictureId == 17); REQUIRE(payloadDescriptor->tl0PictureIndex == 0); REQUIRE(payloadDescriptor->tlIndex == 0); REQUIRE(payloadDescriptor->y == 0); REQUIRE(payloadDescriptor->keyIndex == 0); REQUIRE(payloadDescriptor->isKeyFrame == true); REQUIRE(payloadDescriptor->hasPictureId == true); REQUIRE(payloadDescriptor->hasOneBytePictureId == true); REQUIRE(payloadDescriptor->hasTwoBytesPictureId == false); REQUIRE(payloadDescriptor->hasTl0PictureIndex == false); REQUIRE(payloadDescriptor->hasTlIndex == false); SECTION("encode payload descriptor") { payloadDescriptor->Encode( buffer, payloadDescriptor->pictureId, payloadDescriptor->tl0PictureIndex); SECTION("compare encoded payload descriptor with original buffer") { REQUIRE(std::memcmp(buffer, originalBuffer, sizeof(buffer)) == 0); } } } SECTION("parse payload descriptor 2") { /** * VP8 Payload Descriptor * * 1 = X bit: Extended control bits present (I L T K) * 0 = R bit: Reserved for future use * 0 = N bit: Reference frame * 0 = S bit: Continuation of VP8 partition * 000 = Part Id: 0 * 0 = I bit: No Picture byte ID * 0 = L bit: TL0PICIDX byte not present * 1 = T bit: TID (temporal layer index) byte present * 1 = K bit: TID/KEYIDX byte present * 1110 = Reserved A: 14 * 11 = Temporal layer Index (TID): 3 * 1 = 1 Lay Sync Bit (Y): True * ...0 0100 = Temporal Key Frame Index (KEYIDX): 4 */ // clang-format off uint8_t originalBuffer[] = { 0x88, 0x3e, 0xe4, 0x00 }; // clang-format on // Keep a copy of the original buffer for comparing. uint8_t buffer[4] = { 0 }; std::memcpy(buffer, originalBuffer, sizeof(buffer)); // Parse the buffer. std::unique_ptr payloadDescriptor{ RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer)) }; REQUIRE(payloadDescriptor); REQUIRE(payloadDescriptor->extended == 1); REQUIRE(payloadDescriptor->nonReference == 0); REQUIRE(payloadDescriptor->start == 0); REQUIRE(payloadDescriptor->partitionIndex == 0); // Optional field flags. REQUIRE(payloadDescriptor->i == 0); REQUIRE(payloadDescriptor->l == 0); REQUIRE(payloadDescriptor->t == 1); REQUIRE(payloadDescriptor->k == 1); // Optional fields. REQUIRE(payloadDescriptor->pictureId == 0); REQUIRE(payloadDescriptor->tl0PictureIndex == 0); REQUIRE(payloadDescriptor->tlIndex == 3); REQUIRE(payloadDescriptor->y == 1); REQUIRE(payloadDescriptor->keyIndex == 4); REQUIRE(payloadDescriptor->isKeyFrame == false); REQUIRE(payloadDescriptor->hasPictureId == false); REQUIRE(payloadDescriptor->hasOneBytePictureId == false); REQUIRE(payloadDescriptor->hasTwoBytesPictureId == false); REQUIRE(payloadDescriptor->hasTl0PictureIndex == false); REQUIRE(payloadDescriptor->hasTlIndex == true); SECTION("encode payload descriptor") { payloadDescriptor->Encode( buffer, payloadDescriptor->pictureId, payloadDescriptor->tl0PictureIndex); SECTION("compare encoded payloadDescriptor with original buffer") { REQUIRE(std::memcmp(buffer, originalBuffer, sizeof(buffer)) == 0); } } }; SECTION("parse payload descriptor, encode") { /** * VP8 Payload Descriptor * * 1 = X bit: Extended control bits present (I L T K) * 1 = R bit: Reserved for future use (Error should be zero) * 0 = N bit: Reference frame * 1 = S bit: Start of VP8 partition * Part Id: 0 * 1 = I bit: Picture ID byte present * 1 = L bit: TL0PICIDX byte present * 0 = T bit: TID (temporal layer index) byte not present * 0 = K bit: TID/KEYIDX byte not present * 0000 = Reserved A: 0 * 0001 0001 = Picture Id: 17 * 0000 0011 = TL0PICIDX: 3 */ // clang-format off uint8_t originalBuffer[] = { 0xd0, 0xc0, 0x11, 0x03 }; // clang-format on // Keep a copy of the original buffer for comparing. uint8_t buffer[4] = { 0 }; std::memcpy(buffer, originalBuffer, sizeof(buffer)); std::unique_ptr payloadDescriptor{ RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer)) }; REQUIRE(payloadDescriptor); REQUIRE(payloadDescriptor->pictureId == 17); REQUIRE(payloadDescriptor->tl0PictureIndex == 3); SECTION("encode payload descriptor") { payloadDescriptor->Encode(buffer, 20, 1); std::unique_ptr payloadDescriptor{ RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer)) }; REQUIRE(payloadDescriptor->pictureId == 20); REQUIRE(payloadDescriptor->tl0PictureIndex == 1); } SECTION("restore payload descriptor") { payloadDescriptor->Restore(buffer); std::unique_ptr payloadDescriptor{ RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer)) }; REQUIRE(payloadDescriptor->pictureId == 17); REQUIRE(payloadDescriptor->tl0PictureIndex == 3); } } SECTION("parse payload descriptor, I flag set but no space for pictureId") { /** * VP8 Payload Descriptor * * 1 = X bit: Extended control bits present (I L T K) * 1 = R bit: Reserved for future use (Error should be zero) * 0 = N bit: Reference frame * 1 = S bit: Start of VP8 partition * Part Id: 0 * 1 = I bit: Picture ID byte present * 0 = L bit: TL0PICIDX byte not present * 0 = T bit: TID (temporal layer index) byte not present * 0 = K bit: TID/KEYIDX byte not present * 0000 = Reserved A: 0 */ // clang-format off uint8_t buffer[] = { 0xd0, 0x80 }; // clang-format on const auto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer)); REQUIRE(!payloadDescriptor); } SECTION("parse payload descriptor, X flag is not set, no keyframe") { /** * VP8 Payload Descriptor * * 0 = X bit: Extended control bits present (I L T K) * 1 = R bit: Reserved for future use (Error should be zero) * 0 = N bit: Reference frame * 1 = S bit: Start of VP8 partition * Part Id: 0 * 000000 = Size0 | H | VER * 1 = P bit: Inverse Keyframe */ // clang-format off uint8_t buffer[] = { 0x50, 0x01 }; // clang-format on auto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer)); REQUIRE(payloadDescriptor); REQUIRE(payloadDescriptor->extended == 0); REQUIRE(payloadDescriptor->nonReference == 0); REQUIRE(payloadDescriptor->start == 1); REQUIRE(payloadDescriptor->partitionIndex == 0); // Optional field flags. REQUIRE(payloadDescriptor->i == 0); REQUIRE(payloadDescriptor->l == 0); REQUIRE(payloadDescriptor->t == 0); REQUIRE(payloadDescriptor->k == 0); // Optional fields. REQUIRE(payloadDescriptor->pictureId == 0); REQUIRE(payloadDescriptor->tl0PictureIndex == 0); REQUIRE(payloadDescriptor->tlIndex == 0); REQUIRE(payloadDescriptor->y == 0); REQUIRE(payloadDescriptor->keyIndex == 0); REQUIRE(payloadDescriptor->isKeyFrame == false); REQUIRE(payloadDescriptor->hasPictureId == false); REQUIRE(payloadDescriptor->hasOneBytePictureId == false); REQUIRE(payloadDescriptor->hasTwoBytesPictureId == false); REQUIRE(payloadDescriptor->hasTl0PictureIndex == false); REQUIRE(payloadDescriptor->hasTlIndex == false); delete payloadDescriptor; } SECTION("parse payload descriptor, X flag is not set, keyframe") { /** * VP8 Payload Descriptor * * 0 = X bit: Extended control bits present (I L T K) * 1 = R bit: Reserved for future use (Error should be zero) * 0 = N bit: Reference frame * 1 = S bit: Start of VP8 partition * Part Id: 0 * 000000 = Size0 | H | VER * 0 = P bit: Inverse Keyframe */ // clang-format off uint8_t buffer[] = { 0x50, 0x00 }; // clang-format on auto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(buffer, sizeof(buffer)); REQUIRE(payloadDescriptor); REQUIRE(payloadDescriptor->extended == 0); REQUIRE(payloadDescriptor->nonReference == 0); REQUIRE(payloadDescriptor->start == 1); REQUIRE(payloadDescriptor->partitionIndex == 0); // Optional field flags. REQUIRE(payloadDescriptor->i == 0); REQUIRE(payloadDescriptor->l == 0); REQUIRE(payloadDescriptor->t == 0); REQUIRE(payloadDescriptor->k == 0); // Optional fields. REQUIRE(payloadDescriptor->pictureId == 0); REQUIRE(payloadDescriptor->tl0PictureIndex == 0); REQUIRE(payloadDescriptor->tlIndex == 0); REQUIRE(payloadDescriptor->y == 0); REQUIRE(payloadDescriptor->keyIndex == 0); REQUIRE(payloadDescriptor->isKeyFrame == true); REQUIRE(payloadDescriptor->hasPictureId == false); REQUIRE(payloadDescriptor->hasOneBytePictureId == false); REQUIRE(payloadDescriptor->hasTwoBytesPictureId == false); REQUIRE(payloadDescriptor->hasTl0PictureIndex == false); REQUIRE(payloadDescriptor->hasTlIndex == false); delete payloadDescriptor; } } SCENARIO("process VP8 payload descriptor", "[rtp][codecs][vp8]") { constexpr uint16_t MaxPictureId = (1 << 15) - 1; SECTION("do not drop TL0PICIDX from temporal layers higher than 0") { RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 0; params.temporalLayers = 2; RTC::RTP::Codecs::VP8::EncodingContext context(params); context.SetCurrentTemporalLayer(0); context.SetTargetTemporalLayer(0); // Frame 1. auto forwarded = processVP8Packet(context, 0, 0, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == 0); REQUIRE(forwarded->tl0PictureIndex == 0); // Frame 2 gets lost. // Frame 3. forwarded = processVP8Packet(context, 2, 1, 1); REQUIRE(!forwarded); // Frame 2 retransmitted. forwarded = processVP8Packet(context, 1, 1, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == 1); REQUIRE(forwarded->tl0PictureIndex == 1); } SECTION("drop packets that belong to other temporal layers after rolling over pictureID") { RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 0; params.temporalLayers = 2; RTC::RTP::Codecs::VP8::EncodingContext context(params); context.SyncRequired(); context.SetCurrentTemporalLayer(0); context.SetTargetTemporalLayer(0); // Frame 1. auto forwarded = processVP8Packet(context, MaxPictureId, 0, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == 1); REQUIRE(forwarded->tl0PictureIndex == 1); // Frame 2. forwarded = processVP8Packet(context, 0, 0, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == 2); REQUIRE(forwarded->tl0PictureIndex == 1); // Frame 3. forwarded = processVP8Packet(context, 1, 0, 1); REQUIRE(!forwarded); } SECTION("old packets with higher temporal layer than current are dropped") { RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 0; params.temporalLayers = 2; RTC::RTP::Codecs::VP8::EncodingContext context(params); context.SyncRequired(); context.SetCurrentTemporalLayer(0); context.SetTargetTemporalLayer(0); // Frame 1. auto forwarded = processVP8Packet(context, 1, 0, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == 1); REQUIRE(forwarded->tlIndex == 0); REQUIRE(forwarded->tl0PictureIndex == 1); // Frame 2. forwarded = processVP8Packet(context, 2, 0, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == 2); REQUIRE(forwarded->tlIndex == 0); REQUIRE(forwarded->tl0PictureIndex == 1); // Frame 3. Old packet with higher temporal layer than current. forwarded = processVP8Packet(context, 0, 0, 1); REQUIRE(!forwarded); REQUIRE(context.GetCurrentTemporalLayer() == 0); } SECTION("packets with higher temporal layer than current are dropped") { RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 0; params.temporalLayers = 2; RTC::RTP::Codecs::VP8::EncodingContext context(params); context.SyncRequired(); context.SetCurrentTemporalLayer(0); context.SetTargetTemporalLayer(0); // Frame 1. auto forwarded = processVP8Packet(context, 1, 0, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == 1); REQUIRE(forwarded->tlIndex == 0); REQUIRE(forwarded->tl0PictureIndex == 1); // Frame 2. forwarded = processVP8Packet(context, 2, 0, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == 2); REQUIRE(forwarded->tlIndex == 0); REQUIRE(forwarded->tl0PictureIndex == 1); context.SetTargetTemporalLayer(2); // Frame 3. Old packet with higher temporal layer than current. forwarded = processVP8Packet(context, 3, 0, 1); REQUIRE(!forwarded); REQUIRE(context.GetCurrentTemporalLayer() == 0); } } SCENARIO("encode VP8 payload descriptor", "[rtp][codecs][vp8]") { /** * VP8 Payload Descriptor * * 1 = X bit: Extended control bits present (I L T K) * 0 = R bit: Reserved for future use * 0 = N bit: Reference frame * 0 = S bit: Continuation of VP8 partition * 000 = Part Id: 0 * 1 = I bit: Picture byte ID * 1 = L bit: TL0PICIDX byte present * 1 = T bit: TID (temporal layer index) byte present * 0 = K bit: TID/KEYIDX byte present * 0000 = Reserved A: 14 * 0000000000000001 = PictureId */ // clang-format off uint8_t payload[] = { 0x80, 0xe0, 0x01, 0x01, 0xe8, 0x40, 0x7a, 0xd8 }; // clang-format on bool marker; SECTION("encode based on specific encoder") { auto* payloadDescriptor = RTC::RTP::Codecs::VP8::Parse(payload, sizeof(payload)); REQUIRE(payloadDescriptor); RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 0; params.temporalLayers = 3; RTC::RTP::Codecs::VP8::EncodingContext context(params); context.SetCurrentTemporalLayer(3); context.SetTargetTemporalLayer(3); REQUIRE(payloadDescriptor->pictureId == 1); auto* payloadDescriptorHandler = new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor); std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; packet->SetPayload(payload, sizeof(payload)); auto forwarded = payloadDescriptorHandler->Process(&context, packet.get(), marker); REQUIRE(forwarded); auto encoder1 = payloadDescriptorHandler->GetEncoder(); REQUIRE(encoder1); // Update pictureId. payloadDescriptor->pictureId = 2; packet.reset( RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer))); packet->SetPayload(payload, sizeof(payload)); forwarded = payloadDescriptorHandler->Process(&context, packet.get(), marker); REQUIRE(forwarded); REQUIRE(payloadDescriptor->pictureId == 2); // encoder2 contains the pictureId value 2. auto encoder2 = payloadDescriptorHandler->GetEncoder(); REQUIRE(encoder2); // Encode with encoder1. packet.reset( RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer))); packet->SetPayload(payload, sizeof(payload)); payloadDescriptorHandler->Encode(packet.get(), encoder1.get()); // Parse the payload. auto* payloadDescriptor2 = RTC::RTP::Codecs::VP8::Parse(payload, sizeof(payload)); REQUIRE(payloadDescriptor2); REQUIRE(payloadDescriptor2->pictureId == 1); // Encode with encoder2. packet.reset( RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer))); packet->SetPayload(payload, sizeof(payload)); payloadDescriptorHandler->Encode(packet.get(), encoder2.get()); // Parse the payload. auto* payloadDescriptor3 = RTC::RTP::Codecs::VP8::Parse(packet->GetPayload(), packet->GetPayloadLength()); REQUIRE(payloadDescriptor3); REQUIRE(payloadDescriptor3->pictureId == 2); delete payloadDescriptor3; delete payloadDescriptor2; delete payloadDescriptorHandler; } } ================================================ FILE: worker/test/src/RTC/RTP/Codecs/TestVP9.cpp ================================================ #include "common.hpp" #include "test/include/RTC/RTP/rtpCommon.hpp" #include "RTC/RTP/Codecs/VP9.hpp" #include "RTC/RTP/Packet.hpp" #include #include // std::memcmp() namespace { RTC::RTP::Codecs::VP9::PayloadDescriptor* createVP9PayloadDescriptor( uint8_t* buffer, size_t bufferLen, uint16_t pictureId, uint8_t tlIndex) { buffer[0] = 0xAD; // I, L, B, E bits uint16_t netPictureId = htons(pictureId); std::memcpy(buffer + 1, &netPictureId, 2); buffer[1] |= 0x80; buffer[3] = (tlIndex << 5) | (1 << 4); // tlIndex, switchingUpPoint auto* payloadDescriptor = RTC::RTP::Codecs::VP9::Parse(buffer, bufferLen); REQUIRE(payloadDescriptor); return payloadDescriptor; } std::unique_ptr processVP9Packet( RTC::RTP::Codecs::VP9::EncodingContext& context, uint16_t pictureId, uint8_t tlIndex) { // clang-format off uint8_t payload[] = { 0xAD, 0x80, 0x00, 0x00, 0x00, 0x00 }; // clang-format on bool marker; auto* payloadDescriptor = createVP9PayloadDescriptor(payload, sizeof(payload), pictureId, tlIndex); std::unique_ptr payloadDescriptorHandler( new RTC::RTP::Codecs::VP9::PayloadDescriptorHandler(payloadDescriptor)); std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; packet->SetPayload(payload, sizeof(payload)); if (payloadDescriptorHandler->Process(&context, packet.get(), marker)) { return std::unique_ptr( RTC::RTP::Codecs::VP9::Parse(payload, sizeof(payload))); } return nullptr; } } // namespace SCENARIO("process VP9 payload descriptor", "[rtp][codecs][vp9]") { constexpr uint16_t MaxPictureId = (1 << 15) - 1; SECTION("drop packets that belong to other temporal layers after rolling over pictureID") { RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 1; params.temporalLayers = 3; RTC::RTP::Codecs::VP9::EncodingContext context(params); context.SyncRequired(); context.SetCurrentTemporalLayer(0); context.SetTargetTemporalLayer(0); context.SetCurrentSpatialLayer(0); context.SetTargetSpatialLayer(0); // Frame 1. auto forwarded = processVP9Packet(context, MaxPictureId, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == MaxPictureId); // Frame 2. forwarded = processVP9Packet(context, 0, 0); REQUIRE(forwarded); REQUIRE(forwarded->pictureId == 0); // Frame 3. forwarded = processVP9Packet(context, 1, 1); REQUIRE(!forwarded); } SECTION("PayloadDescriptorHandler") { RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 1; params.temporalLayers = 3; RTC::RTP::Codecs::VP9::EncodingContext context(params); const uint16_t start = MaxPictureId - 2000; context.SetCurrentTemporalLayer(0, start + 0); context.SetCurrentTemporalLayer(1, start + 1); context.SetCurrentTemporalLayer(2, start + 5); context.SetCurrentTemporalLayer(0, start + 6); REQUIRE(context.GetTemporalLayerForPictureId(start + 0) == 0); REQUIRE(context.GetTemporalLayerForPictureId(start + 1) == 1); REQUIRE(context.GetTemporalLayerForPictureId(start + 2) == 1); REQUIRE(context.GetTemporalLayerForPictureId(start + 5) == 2); REQUIRE(context.GetTemporalLayerForPictureId(start + 6) == 0); context.SetCurrentTemporalLayer(1, start + 1000); context.SetCurrentTemporalLayer(2, start + 1001); // This will drop the first item. REQUIRE(context.GetTemporalLayerForPictureId(start + 1000) == 1); REQUIRE(context.GetTemporalLayerForPictureId(start + 0) == 1); // It will get the item at start+1. context.SetCurrentTemporalLayer(0, 0); // This will drop items from start to start+999. REQUIRE(context.GetTemporalLayerForPictureId(0) == 0); REQUIRE( context.GetTemporalLayerForPictureId(start + 0) == 1); // It will get the item at start+1000. } SECTION("drop packets that belong to other temporal layers with unordered pictureID") { RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 1; params.temporalLayers = 3; RTC::RTP::Codecs::VP9::EncodingContext context(params); context.SyncRequired(); context.SetCurrentSpatialLayer(0, 0); context.SetTargetSpatialLayer(0); const uint16_t start = MaxPictureId - 20; const std::vector> packets = { // targetTemporalLayer=0 { start, 0, 0, true }, { start, 1, -1, false }, { start + 1, 0, -1, true }, { start + 1, 1, -1, false }, { start + 2, 0, -1, true }, { start + 2, 1, -1, false }, // targetTemporalLayer=1 { start + 10, 0, 1, true }, { start + 10, 1, -1, true }, { start + 11, 0, -1, true }, { start + 11, 1, -1, true }, { start + 3, 0, -1, true }, // old packet { start + 3, 1, -1, false }, { start + 12, 0, -1, true }, { start + 12, 1, -1, true }, // targetTemporalLayer=0 { start + 14, 0, 0, true }, { start + 14, 1, -1, false }, { start + 13, 0, -1, true }, // old packet { start + 13, 1, -1, true }, // targetTemporalLayer=1 { start + 15, 0, 1, true }, { start + 15, 1, -1, true }, // targetTemporalLayer=0 { 0, 0, 0, true }, { 0, 1, -1, false }, { 1, 0, -1, true }, { 1, 1, -1, false }, { start + 16, 0, -1, true }, // old packet { start + 16, 1, -1, true }, }; for (const auto& packet : packets) { auto pictureId = std::get<0>(packet); auto tlIndex = std::get<1>(packet); auto targetTemporalLayer = std::get<2>(packet); auto shouldForward = std::get<3>(packet); if (targetTemporalLayer >= 0) { context.SetTargetTemporalLayer(targetTemporalLayer); } auto forwarded = processVP9Packet(context, pictureId, tlIndex); if (shouldForward) { REQUIRE(forwarded); REQUIRE(forwarded->pictureId == pictureId); } else { REQUIRE(!forwarded); } } } } ================================================ FILE: worker/test/src/RTC/RTP/TestPacket.cpp ================================================ #include "common.hpp" #include "Utils.hpp" #include "test/include/RTC/RTP/rtpCommon.hpp" #include "test/include/testHelpers.hpp" #include "RTC/RTP/HeaderExtensionIds.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RtpDictionaries.hpp" #include #include // std::memset() #include SCENARIO("RTP Packet", "[serializable][rtp][packet]") { rtpCommon::ResetBuffers(); SECTION("alignof() RTP structs") { REQUIRE(alignof(RTC::RTP::Packet::FixedHeader) == 4); REQUIRE(alignof(RTC::RTP::Packet::HeaderExtension) == 2); REQUIRE(alignof(RTC::RTP::Packet::OneByteExtension) == 1); REQUIRE(alignof(RTC::RTP::Packet::TwoBytesExtension) == 1); } SECTION("Packet::Parse() packet1.raw succeeds") { alignas(4) uint8_t buffer[65536]; size_t bufferLength; if (!helpers::readBinaryFile("data/packet1.raw", buffer, std::addressof(bufferLength))) { FAIL("cannot open file"); } std::unique_ptr packet{ RTC::RTP::Packet::Parse(buffer, bufferLength) }; CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ buffer, /*bufferLength*/ bufferLength, /*length*/ bufferLength, /*payloadType*/ 111, /*hasMarker*/ false, /*seqNumber*/ 23617, /*timestamp*/ 1660241882, /*ssrc*/ 2674985186, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 4, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 33, /*hasPadding*/ false, /*paddingLength*/ 0); /* Serialize it. */ packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::SerializeBuffer, /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer), /*length*/ bufferLength, /*payloadType*/ 111, /*hasMarker*/ false, /*seqNumber*/ 23617, /*timestamp*/ 1660241882, /*ssrc*/ 2674985186, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 4, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 33, /*hasPadding*/ false, /*paddingLength*/ 0); /* Clone it. */ packet.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer))); std::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ bufferLength, /*payloadType*/ 111, /*hasMarker*/ false, /*seqNumber*/ 23617, /*timestamp*/ 1660241882, /*ssrc*/ 2674985186, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 4, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 33, /*hasPadding*/ false, /*paddingLength*/ 0); /* Set payload. */ packet->SetPayload(rtpCommon::DataBuffer, 16); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ bufferLength - 33 + 16, /*payloadType*/ 111, /*hasMarker*/ false, /*seqNumber*/ 23617, /*timestamp*/ 1660241882, /*ssrc*/ 2674985186, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 4, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 16, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true); } SECTION("Packet::Parse() packet2.raw succeeds") { alignas(4) uint8_t buffer[65536]; size_t bufferLength; if (!helpers::readBinaryFile("data/packet2.raw", buffer, std::addressof(bufferLength))) { FAIL("cannot open file"); } std::unique_ptr packet{ RTC::RTP::Packet::Parse(buffer, bufferLength) }; CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ buffer, /*bufferLength*/ bufferLength, /*length*/ bufferLength, /*payloadType*/ 100, /*hasMarker*/ false, /*seqNumber*/ 28478, /*timestamp*/ 172320136, /*ssrc*/ 3316375386, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 78, /*hasPadding*/ true, /*paddingLength*/ 149); /* Serialize it. */ packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::SerializeBuffer, /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer), /*length*/ bufferLength, /*payloadType*/ 100, /*hasMarker*/ false, /*seqNumber*/ 28478, /*timestamp*/ 172320136, /*ssrc*/ 3316375386, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 78, /*hasPadding*/ true, /*paddingLength*/ 149); /* Clone it. */ packet.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer))); std::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ bufferLength, /*payloadType*/ 100, /*hasMarker*/ false, /*seqNumber*/ 28478, /*timestamp*/ 172320136, /*ssrc*/ 3316375386, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 78, /*hasPadding*/ true, /*paddingLength*/ 149); /* Set payload. */ packet->SetPayload(rtpCommon::DataBuffer, 16); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ bufferLength - 78 + 16 - 149, /*payloadType*/ 100, /*hasMarker*/ false, /*seqNumber*/ 28478, /*timestamp*/ 172320136, /*ssrc*/ 3316375386, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 16, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true); } SECTION("Packet::Parse() packet3.raw succeeds") { alignas(4) uint8_t buffer[65536]; size_t bufferLength; if (!helpers::readBinaryFile("data/packet3.raw", buffer, std::addressof(bufferLength))) { FAIL("cannot open file"); } std::unique_ptr packet{ RTC::RTP::Packet::Parse(buffer, bufferLength) }; CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ buffer, /*bufferLength*/ bufferLength, /*length*/ bufferLength, /*payloadType*/ 111, /*hasMarker*/ false, /*seqNumber*/ 19354, /*timestamp*/ 863466045, /*ssrc*/ 235797202, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 8, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 77, /*hasPadding*/ false, /*paddingLength*/ 0); /* Serialize it. */ packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::SerializeBuffer, /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer), /*length*/ bufferLength, /*payloadType*/ 111, /*hasMarker*/ false, /*seqNumber*/ 19354, /*timestamp*/ 863466045, /*ssrc*/ 235797202, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 8, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 77, /*hasPadding*/ false, /*paddingLength*/ 0); /* Clone it. */ packet.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer))); std::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ bufferLength, /*payloadType*/ 111, /*hasMarker*/ false, /*seqNumber*/ 19354, /*timestamp*/ 863466045, /*ssrc*/ 235797202, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 8, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 77, /*hasPadding*/ false, /*paddingLength*/ 0); /* Set payload. */ packet->SetPayload(rtpCommon::DataBuffer, 16); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ bufferLength - 77 + 16, /*payloadType*/ 111, /*hasMarker*/ false, /*seqNumber*/ 19354, /*timestamp*/ 863466045, /*ssrc*/ 235797202, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 8, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 16, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true); } SECTION("Packet::Parse() without extensions or payload succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { 0x80, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05 }; // clang-format on std::unique_ptr packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) }; CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Serialize it. */ packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::SerializeBuffer, /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Clone it. */ packet.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer))); std::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Set payload. */ packet->SetPayload(rtpCommon::DataBuffer, 16); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ sizeof(buffer) + 16, /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 16, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true); REQUIRE(packet->IsPaddedTo4Bytes() == true); } SECTION("Packet::Parse() with One-Byte extensions succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { 0x90, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0xbe, 0xde, 0x00, 0x03, // Header Extension 0x10, 0xaa, 0x21, 0xbb, // - id: 1, len: 1 0xff, 0x00, 0x00, 0x33, // - id: 2, len: 2 0xff, 0xff, 0xff, 0xff, // - id: 3, len: 4 0x12, 0x23 }; // clang-format on std::unique_ptr packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) }; CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 2, /*hasPadding*/ false, /*paddingLength*/ 0); uint8_t* extensionValue; uint8_t extensionLen; REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionLen == 1); REQUIRE(helpers::areBuffersEqual(extensionValue, 1, buffer + 17, 1) == true); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionLen == 2); REQUIRE(helpers::areBuffersEqual(extensionValue, 2, buffer + 19, 2) == true); REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(extensionLen == 4); REQUIRE(helpers::areBuffersEqual(extensionValue, 4, buffer + 24, 4) == true); REQUIRE(packet->HasExtension(4) == false); REQUIRE(packet->GetExtensionValue(4, extensionLen) == nullptr); REQUIRE(packet->IsPaddedTo4Bytes() == false); /* Serialize it. */ packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::SerializeBuffer, /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 2, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionLen == 1); REQUIRE(helpers::areBuffersEqual(extensionValue, 1, rtpCommon::SerializeBuffer + 17, 1) == true); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionLen == 2); REQUIRE(helpers::areBuffersEqual(extensionValue, 2, rtpCommon::SerializeBuffer + 19, 2) == true); REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(extensionLen == 4); REQUIRE(helpers::areBuffersEqual(extensionValue, 4, rtpCommon::SerializeBuffer + 24, 4) == true); REQUIRE(packet->HasExtension(4) == false); REQUIRE(packet->GetExtensionValue(4, extensionLen) == nullptr); REQUIRE(packet->IsPaddedTo4Bytes() == false); /* Clone it. */ packet.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer))); std::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 2, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionLen == 1); REQUIRE(helpers::areBuffersEqual(extensionValue, 1, rtpCommon::CloneBuffer + 17, 1) == true); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionLen == 2); REQUIRE(helpers::areBuffersEqual(extensionValue, 2, rtpCommon::CloneBuffer + 19, 2) == true); REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(extensionLen == 4); REQUIRE(helpers::areBuffersEqual(extensionValue, 4, rtpCommon::CloneBuffer + 24, 4) == true); REQUIRE(packet->HasExtension(4) == false); REQUIRE(packet->GetExtensionValue(4, extensionLen) == nullptr); REQUIRE(packet->IsPaddedTo4Bytes() == false); /* Set payload. */ packet->SetPayload(rtpCommon::DataBuffer, 16); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ sizeof(buffer) - 2 + 16, /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 16, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 16) == true); REQUIRE(packet->IsPaddedTo4Bytes() == true); } SECTION("Packet::Parse() with Two-Bytes extensions succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { 0x90, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x10, 0x00, 0x00, 0x04, // Header Extension 0x00, 0x00, 0x01, 0x00, // - id: 1, len: 0 0x02, 0x01, 0x42, 0x00, // - id: 2, len: 1 0x03, 0x02, 0x11, 0x22, // - id: 3, len: 2 0x00, 0x00, 0x04, 0x00 // - id: 4, len: 0 }; // clang-format on std::unique_ptr packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) }; CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 16, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ true, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ false, /*paddingLength*/ 0); uint8_t* extensionValue; uint8_t extensionLen; REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionLen == 0); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionLen == 1); REQUIRE(helpers::areBuffersEqual(extensionValue, 1, buffer + 22, 1) == true); REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(extensionLen == 2); REQUIRE(helpers::areBuffersEqual(extensionValue, 2, buffer + 26, 2) == true); REQUIRE(packet->HasExtension(4) == true); extensionValue = packet->GetExtensionValue(4, extensionLen); REQUIRE(extensionLen == 0); REQUIRE(packet->HasExtension(5) == false); REQUIRE(packet->GetExtensionValue(5, extensionLen) == nullptr); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Serialize it. */ packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::SerializeBuffer, /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 16, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ true, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionLen == 0); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionLen == 1); REQUIRE(helpers::areBuffersEqual(extensionValue, 1, rtpCommon::SerializeBuffer + 22, 1) == true); REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(extensionLen == 2); REQUIRE(helpers::areBuffersEqual(extensionValue, 2, rtpCommon::SerializeBuffer + 26, 2) == true); REQUIRE(packet->HasExtension(4) == true); extensionValue = packet->GetExtensionValue(4, extensionLen); REQUIRE(extensionLen == 0); REQUIRE(packet->HasExtension(5) == false); REQUIRE(packet->GetExtensionValue(5, extensionLen) == nullptr); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Clone it. */ packet.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer))); std::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 16, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ true, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionLen == 0); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionLen == 1); REQUIRE(helpers::areBuffersEqual(extensionValue, 1, rtpCommon::CloneBuffer + 22, 1) == true); REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(extensionLen == 2); REQUIRE(helpers::areBuffersEqual(extensionValue, 2, rtpCommon::CloneBuffer + 26, 2) == true); REQUIRE(packet->HasExtension(4) == true); extensionValue = packet->GetExtensionValue(4, extensionLen); REQUIRE(extensionLen == 0); REQUIRE(packet->HasExtension(5) == false); REQUIRE(packet->GetExtensionValue(5, extensionLen) == nullptr); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Set payload. */ packet->SetPayload(rtpCommon::DataBuffer, 15); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ sizeof(buffer) + 15, /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 8, /*timestamp*/ 4, /*ssrc*/ 5, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 16, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ true, /*hasPayload*/ true, /*payloadLength*/ 15, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 15) == true); REQUIRE(packet->IsPaddedTo4Bytes() == false); } SECTION("Packet::Parse() padding-only packet succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { 0xA0, 0x01, 0x00, 0x09, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, // Padding (8 bytes) 0x00, 0x00, 0x00, 0x08 }; // clang-format on std::unique_ptr packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) }; CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 9, /*timestamp*/ 5, /*ssrc*/ 6, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ true, /*paddingLength*/ 8); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Serialize it. */ packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::SerializeBuffer, /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 9, /*timestamp*/ 5, /*ssrc*/ 6, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ true, /*paddingLength*/ 8); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Clone it. */ packet.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer))); std::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ sizeof(buffer), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 9, /*timestamp*/ 5, /*ssrc*/ 6, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ true, /*paddingLength*/ 8); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Set payload. */ packet->SetPayload(rtpCommon::DataBuffer, 1); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ packet->GetLength(), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 9, /*timestamp*/ 5, /*ssrc*/ 6, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 1, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true); REQUIRE(packet->IsPaddedTo4Bytes() == false); /* Pad to 4 bytes. */ packet->PadTo4Bytes(); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ packet->GetLength(), /*payloadType*/ 1, /*hasMarker*/ false, /*seqNumber*/ 9, /*timestamp*/ 5, /*ssrc*/ 6, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 1, /*hasPadding*/ true, /*paddingLength*/ 3); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true); REQUIRE(packet->IsPaddedTo4Bytes() == true); } SECTION("Packet::Parse() with wrong arguments fails") { // clang-format off alignas(4) uint8_t buffer[] = { 0x90, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0xbe, 0xde, 0x00, 0x03, // Header Extension 0x10, 0xaa, 0x21, 0xbb, // - id: 1, len: 1 0xff, 0x00, 0x00, 0x33, // - id: 2, len: 2 0xff, 0xff, 0xff, 0xff, // - id: 3, len: 4 0x12, 0x23 }; // clang-format on std::unique_ptr packet{ nullptr }; // bufferLength is lower than packetLen. REQUIRE_THROWS_AS( packet.reset(RTC::RTP::Packet::Parse(buffer, sizeof(buffer), sizeof(buffer) - 1)), MediaSoupTypeError); REQUIRE(!packet); } SECTION("Packet::Factory() succeeds") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength, /*payloadType*/ 0, /*hasMarker*/ false, /*seqNumber*/ 0, /*timestamp*/ 0, /*ssrc*/ 0, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->IsPaddedTo4Bytes() == true); packet->SetPayloadType(100); packet->SetMarker(true); packet->SetSequenceNumber(12345); packet->SetTimestamp(987654321); packet->SetSsrc(1234567890); std::vector extensions; // Extensions: // // Using One-Byte Extensions: // - Header Extension value length: 1 + 1 + 1 + 2 + 1 + 3 = 9 => 12 (padded) // - Header Extension length: 4 + 12 = 16 // // Using Two-Bytes Extensions: // - Header Extension value length: 2 + 1 + 2 + 2 + 2 + 3 = 12 // - Header Extension length: 4 + 12 = 16 // // Extension id 1. rtpCommon::DataBuffer[0] = 11; // Extension id 2. rtpCommon::DataBuffer[1] = 22; rtpCommon::DataBuffer[2] = 0xAA; // Extension id 14. rtpCommon::DataBuffer[3] = 14; rtpCommon::DataBuffer[4] = 0xBB; rtpCommon::DataBuffer[5] = 0xCC; extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::MID, /*id*/ 1, /*len*/ 1, /*value*/ rtpCommon::DataBuffer + 0); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, /*id*/ 2, /*len*/ 2, /*value*/ rtpCommon::DataBuffer + 1); extensions.emplace_back( /*type*/ RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID, /*id*/ 14, /*len*/ 3, /*value*/ rtpCommon::DataBuffer + 3); // Add One-Byte Extensions. packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions); packet->SetPayload(rtpCommon::DataBuffer, 10); packet->PadTo4Bytes(); // payload + padding = 12. CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 2, /*payloadType*/ 100, /*hasMarker*/ true, /*seqNumber*/ 12345, /*timestamp*/ 987654321, /*ssrc*/ 1234567890, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 10, /*hasPadding*/ true, /*paddingLength*/ 2); const uint8_t* extensionValue; uint8_t extensionLen; REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 1); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]); REQUIRE(extensionLen == 2); REQUIRE(packet->HasExtension(3) == false); REQUIRE(packet->HasExtension(14) == true); extensionValue = packet->GetExtensionValue(14, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]); REQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]); REQUIRE(extensionLen == 3); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Serialize it. */ packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::SerializeBuffer, /*bufferLength*/ sizeof(rtpCommon::SerializeBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 2, /*payloadType*/ 100, /*hasMarker*/ true, /*seqNumber*/ 12345, /*timestamp*/ 987654321, /*ssrc*/ 1234567890, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 10, /*hasPadding*/ true, /*paddingLength*/ 2); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 1); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]); REQUIRE(extensionLen == 2); REQUIRE(packet->HasExtension(3) == false); REQUIRE(packet->HasExtension(14) == true); extensionValue = packet->GetExtensionValue(14, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]); REQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]); REQUIRE(extensionLen == 3); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Clone it. */ packet.reset(packet->Clone(rtpCommon::CloneBuffer, sizeof(rtpCommon::CloneBuffer))); std::memset(rtpCommon::SerializeBuffer, 0x00, sizeof(rtpCommon::SerializeBuffer)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 2, /*payloadType*/ 100, /*hasMarker*/ true, /*seqNumber*/ 12345, /*timestamp*/ 987654321, /*ssrc*/ 1234567890, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 10, /*hasPadding*/ true, /*paddingLength*/ 2); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 1); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]); REQUIRE(extensionLen == 2); REQUIRE(packet->HasExtension(3) == false); REQUIRE(packet->HasExtension(14) == true); extensionValue = packet->GetExtensionValue(14, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]); REQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]); REQUIRE(extensionLen == 3); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Set payload. */ packet->SetPayload(rtpCommon::DataBuffer, 1); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 1, /*payloadType*/ 100, /*hasMarker*/ true, /*seqNumber*/ 12345, /*timestamp*/ 987654321, /*ssrc*/ 1234567890, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 1, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 1); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]); REQUIRE(extensionLen == 2); REQUIRE(packet->HasExtension(3) == false); REQUIRE(packet->HasExtension(14) == true); extensionValue = packet->GetExtensionValue(14, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]); REQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]); REQUIRE(extensionLen == 3); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true); REQUIRE(packet->IsPaddedTo4Bytes() == false); /* Pad to 4 bytes. */ packet->PadTo4Bytes(); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 1 + 3, /*payloadType*/ 100, /*hasMarker*/ true, /*seqNumber*/ 12345, /*timestamp*/ 987654321, /*ssrc*/ 1234567890, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 1, /*hasPadding*/ true, /*paddingLength*/ 3); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 1); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]); REQUIRE(extensionLen == 2); REQUIRE(packet->HasExtension(3) == false); REQUIRE(packet->HasExtension(14) == true); extensionValue = packet->GetExtensionValue(14, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]); REQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]); REQUIRE(extensionLen == 3); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Remove Header Extension. */ packet->RemoveHeaderExtension(); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 1 + 3, /*payloadType*/ 100, /*hasMarker*/ true, /*seqNumber*/ 12345, /*timestamp*/ 987654321, /*ssrc*/ 1234567890, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 1, /*hasPadding*/ true, /*paddingLength*/ 3); REQUIRE(packet->HasExtension(1) == false); REQUIRE(packet->HasExtension(2) == false); REQUIRE(packet->HasExtension(3) == false); REQUIRE(packet->HasExtension(14) == false); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true); REQUIRE(packet->IsPaddedTo4Bytes() == true); // Add Two-Bytes Extensions. packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::CloneBuffer, /*bufferLength*/ sizeof(rtpCommon::CloneBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 1 + 3, /*payloadType*/ 100, /*hasMarker*/ true, /*seqNumber*/ 12345, /*timestamp*/ 987654321, /*ssrc*/ 1234567890, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ true, /*hasPayload*/ true, /*payloadLength*/ 1, /*hasPadding*/ true, /*paddingLength*/ 3); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 1); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[1]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[2]); REQUIRE(extensionLen == 2); REQUIRE(packet->HasExtension(3) == false); REQUIRE(packet->HasExtension(14) == true); extensionValue = packet->GetExtensionValue(14, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[3]); REQUIRE(extensionValue[1] == rtpCommon::DataBuffer[4]); REQUIRE(extensionValue[2] == rtpCommon::DataBuffer[5]); REQUIRE(extensionLen == 3); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 1) == true); REQUIRE(packet->IsPaddedTo4Bytes() == true); } SECTION("Packet::SetExtensions() with ExtensionsType::Auto selects best type") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; std::vector extensions; // Can fit into One-Byte type Extensions. extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::MID, 1, 1, rtpCommon::DataBuffer }, { RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, 14, 16, rtpCommon::DataBuffer } }); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions); REQUIRE(packet->HasOneByteExtensions()); // Requires Two-Bytes type Extensions due to id > 14. extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME, 15, 2, rtpCommon::DataBuffer } }); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions); REQUIRE(packet->HasTwoBytesExtensions()); // Requires Two-Bytes type Extensions due to length 0. extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID, 1, 0, rtpCommon::DataBuffer } }); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions); REQUIRE(packet->HasTwoBytesExtensions()); // Requires Two-Bytes type Extensions due to length > 16. extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::TIME_OFFSET, 1, 17, rtpCommon::DataBuffer } }); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::Auto, extensions); REQUIRE(packet->HasTwoBytesExtensions()); } SECTION("Packet::SetExtensions() with supported extensions succeeds") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; std::vector extensions; std::string mid{ "mid-€1" }; std::string rid{ "r1-ß" }; uint32_t absSendtime{ 12345678 }; uint16_t wideSeqNumber{ 5555 }; uint8_t absSendtimeValue[100]{}; uint8_t wideSeqNumberValue[100]{}; Utils::Byte::Set3Bytes(absSendtimeValue, 0, absSendtime); Utils::Byte::Set2Bytes(wideSeqNumberValue, 0, wideSeqNumber); // clang-format off extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::MID, 1, static_cast(mid.size()), reinterpret_cast(mid.data()) }, { RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, 2, static_cast(rid.size()), reinterpret_cast(rid.data()) }, { RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME, 3, 3, absSendtimeValue }, { RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01, 4, 2, wideSeqNumberValue } } ); // clang-format on packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions); REQUIRE(packet->HasOneByteExtensions()); std::string readMid; std::string readRid; uint32_t readAbsSendtime; uint16_t readWideSeqNumber; REQUIRE(packet->ReadMid(readMid)); REQUIRE(readMid == mid); REQUIRE(packet->ReadRid(readRid)); REQUIRE(readRid == rid); REQUIRE(packet->ReadAbsSendTime(readAbsSendtime)); REQUIRE(readAbsSendtime == absSendtime); REQUIRE(packet->ReadTransportWideCc01(readWideSeqNumber)); REQUIRE(readWideSeqNumber == wideSeqNumber); std::string newMid{ "mid-®2" }; uint64_t newAbsSendtimeMs{ 999999 }; uint16_t newWideSeqNumber{ 5556 }; REQUIRE(packet->UpdateMid(newMid)); REQUIRE(packet->UpdateAbsSendTime(newAbsSendtimeMs)); REQUIRE(packet->UpdateTransportWideCc01(newWideSeqNumber)); REQUIRE(packet->ReadMid(readMid)); REQUIRE(readMid == newMid); REQUIRE(packet->ReadRid(readRid)); REQUIRE(readRid == rid); REQUIRE(packet->ReadAbsSendTime(readAbsSendtime)); REQUIRE(readAbsSendtime == Utils::Time::TimeMsToAbsSendTime(newAbsSendtimeMs)); REQUIRE(packet->ReadTransportWideCc01(readWideSeqNumber)); REQUIRE(readWideSeqNumber == newWideSeqNumber); std::unique_ptr packet2{ RTC::RTP::Packet::Parse( packet->GetBuffer(), packet->GetLength()) }; REQUIRE(packet2); REQUIRE(packet2->Validate(/*storeExtensions*/ false)); packet2->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); RTC::RTP::HeaderExtensionIds headerExtensionIds{}; headerExtensionIds.mid = 1; headerExtensionIds.rid = 2; headerExtensionIds.absSendTime = 3; headerExtensionIds.transportWideCc01 = 4; packet2->AssignExtensionIds(headerExtensionIds); REQUIRE(packet2->HasOneByteExtensions()); REQUIRE(packet2->ReadMid(readMid)); REQUIRE(readMid == newMid); REQUIRE(packet2->ReadRid(readRid)); REQUIRE(readRid == rid); REQUIRE(packet2->ReadAbsSendTime(readAbsSendtime)); REQUIRE(readAbsSendtime == Utils::Time::TimeMsToAbsSendTime(newAbsSendtimeMs)); REQUIRE(packet2->ReadTransportWideCc01(readWideSeqNumber)); REQUIRE(readWideSeqNumber == newWideSeqNumber); } SECTION("Packet::SetExtensions() fails if wrong extensions are given") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; packet->SetPayload(rtpCommon::DataBuffer, 10); packet->PadTo4Bytes(); std::vector extensions; auto* d = rtpCommon::DataBuffer; // Invalid Extension id 0. extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::MID, 0, 4, d }, { RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, 1, 1, d } }); REQUIRE_THROWS_AS( packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions), MediaSoupTypeError); REQUIRE_THROWS_AS( packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions), MediaSoupTypeError); // Invalid Extension id > 14 in One-Byte. extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION, 15, 2, d }, { RTC::RtpHeaderExtensionUri::Type::MID, 6, 6, d }, { RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL, 7, 7, d } }); REQUIRE_THROWS_AS( packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions), MediaSoupTypeError); REQUIRE_NOTHROW(packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions)); // Invalid Extension length 0 in One-Byte. extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::MID, 3, 0, d }, { RTC::RtpHeaderExtensionUri::Type::REPAIRED_RTP_STREAM_ID, 6, 6, d }, { RTC::RtpHeaderExtensionUri::Type::RTP_STREAM_ID, 7, 7, d }, { RTC::RtpHeaderExtensionUri::Type::SSRC_AUDIO_LEVEL, 8, 8, d } }); REQUIRE_THROWS_AS( packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions), MediaSoupTypeError); REQUIRE_NOTHROW(packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions)); // Invalid Extension length > 16 in One-Byte. extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::MEDIASOUP_PACKET_ID, 3, 17, d }, { RTC::RtpHeaderExtensionUri::Type::MID, 6, 6, d }, { RTC::RtpHeaderExtensionUri::Type::VIDEO_ORIENTATION, 7, 7, d }, { RTC::RtpHeaderExtensionUri::Type::DEPENDENCY_DESCRIPTOR, 8, 8, d }, { RTC::RtpHeaderExtensionUri::Type::PLAYOUT_DELAY, 9, 9, d }, { RTC::RtpHeaderExtensionUri::Type::ABS_CAPTURE_TIME, 100, 10, d } }); REQUIRE_THROWS_AS( packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions), MediaSoupTypeError); REQUIRE_NOTHROW(packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::TwoBytes, extensions)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 4 + 72 + 10 + 2, /*payloadType*/ 0, /*hasMarker*/ false, /*seqNumber*/ 0, /*timestamp*/ 0, /*ssrc*/ 0, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 72, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ true, /*hasPayload*/ true, /*payloadLength*/ 10, /*hasPadding*/ true, /*paddingLength*/ 2); const uint8_t* extensionValue; uint8_t extensionLen; REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 17); REQUIRE(packet->HasExtension(1) == false); REQUIRE(packet->HasExtension(6) == true); extensionValue = packet->GetExtensionValue(6, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 6); REQUIRE(packet->HasExtension(7) == true); extensionValue = packet->GetExtensionValue(7, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 7); REQUIRE(packet->HasExtension(8) == true); extensionValue = packet->GetExtensionValue(8, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 8); REQUIRE(packet->HasExtension(9) == true); extensionValue = packet->GetExtensionValue(9, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 9); REQUIRE(packet->HasExtension(100) == true); extensionValue = packet->GetExtensionValue(100, extensionLen); REQUIRE(extensionValue[0] == rtpCommon::DataBuffer[0]); REQUIRE(extensionLen == 10); REQUIRE(packet->HasExtension(101) == false); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), rtpCommon::DataBuffer, 10) == true); REQUIRE(packet->IsPaddedTo4Bytes() == true); } SECTION("Packet::SetPayload(), SetPayloadLength() and packet::RemovePayload() succeed") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; // clang-format off uint8_t payload[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA }; // clang-format on /* Set payload. */ packet->SetPayload(payload, 10); packet->PadTo4Bytes(); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 10 + 2, /*payloadType*/ 0, /*hasMarker*/ false, /*seqNumber*/ 0, /*timestamp*/ 0, /*ssrc*/ 0, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 10, /*hasPadding*/ true, /*paddingLength*/ 2); /* Set payload length. */ packet->SetPayloadLength(501); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 501, /*payloadType*/ 0, /*hasMarker*/ false, /*seqNumber*/ 0, /*timestamp*/ 0, /*ssrc*/ 0, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 501, /*hasPadding*/ false, /*paddingLength*/ 0); /* Remove payload. */ // This method removes padding. packet->RemovePayload(); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength, /*payloadType*/ 0, /*hasMarker*/ false, /*seqNumber*/ 0, /*timestamp*/ 0, /*ssrc*/ 0, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ false, /*payloadLength*/ 0, /*hasPadding*/ false, /*paddingLength*/ 0); // Invalid arguments. REQUIRE_THROWS_AS(packet->SetPayload(nullptr, 2), MediaSoupTypeError); } SECTION("Packet::ShiftPayload() succeeds") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; packet->SetSsrc(12344321); // clang-format off uint8_t payload[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA }; // clang-format on packet->SetPayload(payload, 10); packet->PadTo4Bytes(); std::vector extensions; // One-Byte Extensions: // - Header Extension value length: 1 + 1 + 1 + 2 + 1 + 3 = 9 => 12 (padded) // - Header Extension length: 4 + 12 = 16 // // clang-format off uint8_t extension1[] = { 0x12 }; uint8_t extension2[] = { 0x34, 0x56 }; uint8_t extension3[] = { 0x78, 0x9A, 0xBC }; // clang-format on extensions.assign( { { RTC::RtpHeaderExtensionUri::Type::MID, 1, 1, extension1 }, { RTC::RtpHeaderExtensionUri::Type::ABS_SEND_TIME, 2, 2, extension2 }, { RTC::RtpHeaderExtensionUri::Type::TRANSPORT_WIDE_CC_01, 3, 3, extension3 } }); packet->SetExtensions(RTC::RTP::Packet::ExtensionsType::OneByte, extensions); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 2, /*payloadType*/ 0, /*hasMarker*/ false, /*seqNumber*/ 0, /*timestamp*/ 0, /*ssrc*/ 12344321, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 10, /*hasPadding*/ true, /*paddingLength*/ 2); const uint8_t* extensionValue; uint8_t extensionLen; REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension1, 1) == true); REQUIRE(extensionLen == 1); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension2, 2) == true); REQUIRE(extensionLen == 2); REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension3, 3) == true); REQUIRE(extensionLen == 3); REQUIRE( helpers::areBuffersEqual(packet->GetPayload(), packet->GetPayloadLength(), payload, 10) == true); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* Shift payload. */ // This method removes padding. packet->ShiftPayload(/*payloadOffset*/ 2, /*delta*/ 1); // Fill the new byte in the payload with 0XFF. packet->GetPayload()[2] = 0xFF; // clang-format off uint8_t shiftedPayload[] = { 0x11, 0x22, 0xFF, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA }; // clang-format on REQUIRE(packet->Validate(/*storeExtensions*/ false)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 + 1, /*payloadType*/ 0, /*hasMarker*/ false, /*seqNumber*/ 0, /*timestamp*/ 0, /*ssrc*/ 12344321, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 11, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension1, 1) == true); REQUIRE(extensionLen == 1); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension2, 2) == true); REQUIRE(extensionLen == 2); REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension3, 3) == true); REQUIRE(extensionLen == 3); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), shiftedPayload, 11) == true); REQUIRE(packet->IsPaddedTo4Bytes() == false); // Reset the payload and padding. packet->SetPayload(payload, 10); packet->PadTo4Bytes(); /* Unshift payload. */ // This method removes padding. packet->ShiftPayload(/*payloadOffset*/ 4, /*delta*/ -2); // clang-format off uint8_t unshiftedPayload[] = { 0x11, 0x22, 0x33, 0x44, 0x77, 0x88, 0x99, 0xAA }; // clang-format on REQUIRE(packet->Validate(/*storeExtensions*/ false)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 16 + 10 - 2, /*payloadType*/ 0, /*hasMarker*/ false, /*seqNumber*/ 0, /*timestamp*/ 0, /*ssrc*/ 12344321, /*hasCsrcs*/ false, /*hasHeaderExtension*/ true, /*headerExtensionValueLength*/ 12, /*hasOneByteExtensions*/ true, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 8, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->HasExtension(1) == true); extensionValue = packet->GetExtensionValue(1, extensionLen); REQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension1, 1) == true); REQUIRE(extensionLen == 1); REQUIRE(packet->HasExtension(2) == true); extensionValue = packet->GetExtensionValue(2, extensionLen); REQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension2, 2) == true); REQUIRE(extensionLen == 2); REQUIRE(packet->HasExtension(3) == true); extensionValue = packet->GetExtensionValue(3, extensionLen); REQUIRE(helpers::areBuffersEqual(extensionValue, extensionLen, extension3, 3) == true); REQUIRE(extensionLen == 3); REQUIRE( helpers::areBuffersEqual( packet->GetPayload(), packet->GetPayloadLength(), unshiftedPayload, 8) == true); REQUIRE(packet->IsPaddedTo4Bytes() == true); // Reset the payload and padding. packet->SetPayload(payload, 10); packet->PadTo4Bytes(); /* Shitf and unshift (undo). */ packet->ShiftPayload(/*payloadOffset*/ 3, /*delta*/ 5); packet->ShiftPayload(/*payloadOffset*/ 3, /*delta*/ -5); REQUIRE( helpers::areBuffersEqual(packet->GetPayload(), packet->GetPayloadLength(), payload, 10) == true); } SECTION("Packet::ShiftPayload() fails if wrong values are given") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; // clang-format off uint8_t payload[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA }; // clang-format on packet->SetPayload(payload, 10); // payloadOffset higger or equal than payload length. REQUIRE_THROWS_AS(packet->ShiftPayload(/*payloadOffset*/ 10, /*delta*/ 2), MediaSoupTypeError); // delta too big. REQUIRE_THROWS_AS(packet->ShiftPayload(/*payloadOffset*/ 2, /*delta*/ -9), MediaSoupTypeError); // New computed payload length too big. REQUIRE_THROWS_AS( packet->ShiftPayload(/*payloadOffset*/ 2, /*delta*/ packet->GetBufferLength()), MediaSoupTypeError); } SECTION("Packet::RtxEncode() and packet::RtxDecode() succeed") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; packet->SetPayloadType(100); packet->SetSequenceNumber(12345); packet->SetTimestamp(987654321); packet->SetSsrc(1234567890); // clang-format off uint8_t payload[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA }; // clang-format on packet->SetPayload(payload, 10); packet->PadTo4Bytes(); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 10 + 2, /*payloadType*/ 100, /*hasMarker*/ false, /*seqNumber*/ 12345, /*timestamp*/ 987654321, /*ssrc*/ 1234567890, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 10, /*hasPadding*/ true, /*paddingLength*/ 2); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* RTX encode. */ // This method removes padding. packet->RtxEncode(/*payloadType*/ 111, /*ssrc*/ 999999, /*seq*/ 666); REQUIRE(packet->Validate(/*storeExtensions*/ false)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 10 + 2, /*payloadType*/ 111, /*hasMarker*/ false, /*seqNumber*/ 666, /*timestamp*/ 987654321, /*ssrc*/ 999999, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 12, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->IsPaddedTo4Bytes() == true); /* RTX decode. */ // This method removes padding. packet->RtxDecode(/*payloadType*/ 100, /*ssrc*/ 1234567890); REQUIRE(packet->Validate(/*storeExtensions*/ false)); CHECK_RTP_PACKET( /*packet*/ packet.get(), /*buffer*/ rtpCommon::FactoryBuffer, /*bufferLength*/ sizeof(rtpCommon::FactoryBuffer), /*length*/ RTC::RTP::Packet::FixedHeaderMinLength + 10, /*payloadType*/ 100, /*hasMarker*/ false, /*seqNumber*/ 12345, /*timestamp*/ 987654321, /*ssrc*/ 1234567890, /*hasCsrcs*/ false, /*hasHeaderExtension*/ false, /*headerExtensionValueLength*/ 0, /*hasOneByteExtensions*/ false, /*hasTwoBytesExtensions*/ false, /*hasPayload*/ true, /*payloadLength*/ 10, /*hasPadding*/ false, /*paddingLength*/ 0); REQUIRE(packet->IsPaddedTo4Bytes() == false); } SECTION("Packet::SetBufferReleasedListener() when Packet is destroyed succeeds") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; REQUIRE(packet); bool packetBufferReleased{ false }; RTC::Serializable::BufferReleasedListener packetBufferReleasedListener = [&packetBufferReleased](const RTC::Serializable* serializable, const uint8_t* serializableBuffer) { if (serializable->GetBuffer() == rtpCommon::FactoryBuffer && serializable->GetBuffer() == serializableBuffer) { packetBufferReleased = true; } }; packet->SetBufferReleasedListener(std::addressof(packetBufferReleasedListener)); // If we destroy the Packet it should invoke the listener. packet.reset(nullptr); REQUIRE(packetBufferReleased == true); } SECTION("Packet::SetBufferReleasedListener() when Packet is serialized into another buffer succeeds") { std::unique_ptr packet{ RTC::RTP::Packet::Factory( rtpCommon::FactoryBuffer, sizeof(rtpCommon::FactoryBuffer)) }; REQUIRE(packet); bool packetBufferReleased{ false }; RTC::Serializable::BufferReleasedListener packetBufferReleasedListener = [&packetBufferReleased](const RTC::Serializable* serializable, const uint8_t* serializableBuffer) { if (serializable->GetBuffer() == rtpCommon::FactoryBuffer && serializable->GetBuffer() == serializableBuffer) { packetBufferReleased = true; } }; packet->SetBufferReleasedListener(std::addressof(packetBufferReleasedListener)); // If we serialize the Packet into another buffer it should invoke the // listener. packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); REQUIRE(packetBufferReleased == true); // NOTE: We need to unset the buffer released listener because once the // unique_ptr of the Packet gets out of the scope, the Packet will be // deallocated and will invoke the buffer released listener, which at // that time is already out of the scope (it's lifetime ended) so it's // been destroyed. packet->SetBufferReleasedListener(nullptr); } } ================================================ FILE: worker/test/src/RTC/RTP/TestProbationGenerator.cpp ================================================ #include "common.hpp" #include "RTC/RTP/ProbationGenerator.hpp" #include SCENARIO("RTP ProbationGenerator", "[rtp][probation-generator]") { SECTION("ProbationGenerator generates RTP Packets of the requested length") { RTC::RTP::ProbationGenerator probationGenerator; const auto* packet = probationGenerator.GetNextPacket(1000); const auto seq = packet->GetSequenceNumber(); REQUIRE(packet->GetSsrc() == RTC::RTP::ProbationGenerator::Ssrc); REQUIRE(packet->GetPayloadType() == RTC::RTP::ProbationGenerator::PayloadType); REQUIRE(packet->GetLength() == 1000); REQUIRE(packet->IsPaddedTo4Bytes()); // If given length is higher than ProbationGenerator::ProbationPacketMaxLength // then that limit value is used instead. packet = probationGenerator.GetNextPacket(RTC::RTP::ProbationGenerator::ProbationPacketMaxLength + 10); REQUIRE(packet->GetSequenceNumber() == seq + 1); REQUIRE(packet->GetLength() == RTC::RTP::ProbationGenerator::ProbationPacketMaxLength); REQUIRE(packet->IsPaddedTo4Bytes()); // If given length is less than probation packet minimum length, then that // limit value is used instead. packet = probationGenerator.GetNextPacket(probationGenerator.GetProbationPacketMinLength() - 10); REQUIRE(packet->GetSequenceNumber() == seq + 2); REQUIRE(packet->GetLength() == probationGenerator.GetProbationPacketMinLength()); REQUIRE(packet->IsPaddedTo4Bytes()); } } ================================================ FILE: worker/test/src/RTC/RTP/TestRetransmissionBuffer.cpp ================================================ #include "common.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RetransmissionBuffer.hpp" #include "RTC/RTP/SharedPacket.hpp" #include #include SCENARIO("RTP RetransmissionBuffer", "[rtp][rtx]") { // Class inheriting from RtpRetransmissionBuffer so we can access its protected // buffer member. class RtpMyRetransmissionBuffer : public RTC::RTP::RetransmissionBuffer { public: struct VerificationItem { bool isPresent; uint16_t sequenceNumber; uint32_t timestamp; }; public: RtpMyRetransmissionBuffer(uint16_t maxItems, uint32_t maxRetransmissionDelayMs, uint32_t clockRate) : RTC::RTP::RetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate) { } public: void Insert(uint16_t seq, uint32_t timestamp) { // clang-format off uint8_t rtpBuffer[] = { 0b10000000, 0b01111011, 0b01010010, 0b00001110, 0b01011011, 0b01101011, 0b11001010, 0b10110101, 0, 0, 0, 2 }; // clang-format on std::unique_ptr packet{ RTC::RTP::Packet::Parse(rtpBuffer, sizeof(rtpBuffer)) }; packet->SetSequenceNumber(seq); packet->SetTimestamp(timestamp); const RTC::RTP::SharedPacket sharedPacket; RTC::RTP::RetransmissionBuffer::Insert(packet.get(), sharedPacket); } void AssertBuffer(std::vector verificationBuffer) { REQUIRE(verificationBuffer.size() == this->buffer.size()); for (size_t idx{ 0u }; idx < verificationBuffer.size(); ++idx) { auto& verificationItem = verificationBuffer.at(idx); auto* item = this->buffer.at(idx); REQUIRE(verificationItem.isPresent == !!item); if (item) { REQUIRE(verificationItem.sequenceNumber == item->sequenceNumber); REQUIRE(verificationItem.timestamp == item->timestamp); } } } }; SECTION("proper packets received in order") { const uint16_t maxItems{ 4 }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; RtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate); myRetransmissionBuffer.Insert(10001, 1000000000); myRetransmissionBuffer.Insert(10002, 1000000000); myRetransmissionBuffer.Insert(10003, 1000000200); myRetransmissionBuffer.Insert(10004, 1000000200); // clang-format off myRetransmissionBuffer.AssertBuffer( { { true, 10001, 1000000000 }, { true, 10002, 1000000000 }, { true, 10003, 1000000200 }, { true, 10004, 1000000200 } } ); // clang-format on } SECTION("proper packets received out of order") { const uint16_t maxItems{ 4 }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; RtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate); myRetransmissionBuffer.Insert(20004, 2000000200); myRetransmissionBuffer.Insert(20001, 2000000000); myRetransmissionBuffer.Insert(20003, 2000000200); myRetransmissionBuffer.Insert(20002, 2000000000); // clang-format off myRetransmissionBuffer.AssertBuffer( { { true, 20001, 2000000000 }, { true, 20002, 2000000000 }, { true, 20003, 2000000200 }, { true, 20004, 2000000200 } } ); // clang-format on } SECTION("packet with too new sequence number produces buffer emptying") { const uint16_t maxItems{ 4 }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; RtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate); myRetransmissionBuffer.Insert(30001, 3000000000); myRetransmissionBuffer.Insert(30002, 3000000000); myRetransmissionBuffer.Insert(30003, 3000000200); myRetransmissionBuffer.Insert(40000, 3000003000); // clang-format off myRetransmissionBuffer.AssertBuffer( { { true, 40000, 3000003000 } } ); // clang-format on } SECTION("blank slots are properly created") { const uint16_t maxItems{ 10 }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; RtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate); myRetransmissionBuffer.Insert(40002, 4000000002); // Packet must be discarded since its timestamp is lower than in seq 40002. myRetransmissionBuffer.Insert(40003, 4000000001); // Must produce 1 blank slot. myRetransmissionBuffer.Insert(40004, 4000000004); // Discarded (duplicated). myRetransmissionBuffer.Insert(40002, 4000000002); // Must produce 4 blank slot. myRetransmissionBuffer.Insert(40008, 4000000008); myRetransmissionBuffer.Insert(40006, 4000000006); // Must produce 1 blank slot at the front. myRetransmissionBuffer.Insert(40000, 4000000000); // clang-format off myRetransmissionBuffer.AssertBuffer( { { true, 40000, 4000000000 }, { false, 0, 0 }, { true, 40002, 4000000002 }, { false, 0, 0 }, { true, 40004, 4000000004 }, { false, 0, 0 }, { true, 40006, 4000000006 }, { false, 0, 0 }, { true, 40008, 4000000008 } } ); // clang-format on } SECTION("packet with too old sequence number is discarded") { const uint16_t maxItems{ 4 }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; RtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate); myRetransmissionBuffer.Insert(10001, 1000000001); myRetransmissionBuffer.Insert(10002, 1000000002); myRetransmissionBuffer.Insert(10003, 1000000003); // Too old seq. myRetransmissionBuffer.Insert(40000, 1000000000); // clang-format off myRetransmissionBuffer.AssertBuffer( { { true, 10001, 1000000001 }, { true, 10002, 1000000002 }, { true, 10003, 1000000003 } } ); // clang-format on } SECTION("packet with too old timestamp is discarded") { const uint16_t maxItems{ 4 }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; RtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate); auto maxDiffTs = static_cast(maxRetransmissionDelayMs * clockRate / 1000); myRetransmissionBuffer.Insert(10001, 1000000001); myRetransmissionBuffer.Insert(10002, 1000000002); myRetransmissionBuffer.Insert(10003, 1000000003); // Too old timestamp (subtract 100 to avoid math issues). myRetransmissionBuffer.Insert(10000, 1000000003 - maxDiffTs - 100); // clang-format off myRetransmissionBuffer.AssertBuffer( { { true, 10001, 1000000001 }, { true, 10002, 1000000002 }, { true, 10003, 1000000003 } } ); // clang-format on } SECTION("packet with very newest timestamp is inserted as newest item despite its seq is old") { const uint16_t maxItems{ 4 }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; RtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate); // Scenario based on https://github.com/versatica/mediasoup/issues/1037. myRetransmissionBuffer.Insert(24816, 1024930187); myRetransmissionBuffer.Insert(24980, 1025106407); myRetransmissionBuffer.Insert(18365, 1026593387); // clang-format off myRetransmissionBuffer.AssertBuffer( { { true, 18365, 1026593387 } } ); // clang-format on } SECTION( "packet with lower seq than newest packet in the buffer and higher timestamp forces buffer emptying") { const uint16_t maxItems{ 4 }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; RtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate); myRetransmissionBuffer.Insert(33331, 1000000001); myRetransmissionBuffer.Insert(33332, 1000000002); myRetransmissionBuffer.Insert(33330, 1000000003); // clang-format off myRetransmissionBuffer.AssertBuffer( { { true, 33330, 1000000003 } } ); // clang-format on } SECTION("fuzzer generated packets") { const uint16_t maxItems{ 2500u }; const uint32_t maxRetransmissionDelayMs{ 2000u }; const uint32_t clockRate{ 90000 }; RtpMyRetransmissionBuffer myRetransmissionBuffer(maxItems, maxRetransmissionDelayMs, clockRate); // These packets reproduce an already fixed crash reported here: // https://github.com/versatica/mediasoup/issues/1027#issuecomment-1478464584 // I've commented first packets and just left those that produce the crash. // myRetransmissionBuffer.Insert(14906, 976891962); // myRetransmissionBuffer.Insert(14906, 976891962); // myRetransmissionBuffer.Insert(14906, 976892730); // myRetransmissionBuffer.Insert(13157, 862283031); // myRetransmissionBuffer.Insert(13114, 859453491); // myRetransmissionBuffer.Insert(14906, 976892264); // myRetransmissionBuffer.Insert(14906, 976897098); // myRetransmissionBuffer.Insert(13114, 859464290); // myRetransmissionBuffer.Insert(14906, 976889088); // myRetransmissionBuffer.Insert(13056, 855638184); // myRetransmissionBuffer.Insert(14906, 976891950); // myRetransmissionBuffer.Insert(17722, 1161443894); // myRetransmissionBuffer.Insert(12846, 841888049); // myRetransmissionBuffer.Insert(14906, 976905830); // myRetransmissionBuffer.Insert(15677, 1027420485); // myRetransmissionBuffer.Insert(33742, 2211317269); // myRetransmissionBuffer.Insert(14906, 976892672); // myRetransmissionBuffer.Insert(13102, 858665774); // myRetransmissionBuffer.Insert(12850, 842150702); // myRetransmissionBuffer.Insert(14906, 976891941); // myRetransmissionBuffer.Insert(15677, 1027423549); // myRetransmissionBuffer.Insert(12346, 809120580); // myRetransmissionBuffer.Insert(12645, 828715313); myRetransmissionBuffer.Insert(12645, 828702743); myRetransmissionBuffer.Insert(33998, 2228092928); myRetransmissionBuffer.Insert(33998, 2228092928); } } ================================================ FILE: worker/test/src/RTC/RTP/TestRtpStreamRecv.cpp ================================================ #include "common.hpp" #include "mocks/include/MockShared.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtpStream.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" #include #include // 17: 16 bit mask + the initial sequence number. static constexpr size_t MaxRequestedPackets{ 17 }; static constexpr unsigned int SendNackDelay{ 0u }; // In ms. static const bool UseRtpInactivityCheck{ false }; SCENARIO("RtpStreamRecv", "[rtp][rtpstream][rtpstreamrecv]") { class RtpStreamRecvListener : public RTC::RTP::RtpStreamRecv::Listener { public: void OnRtpStreamScore( RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override { } void OnRtpStreamSendRtcpPacket(RTC::RTP::RtpStreamRecv* /*rtpStream*/, RTC::RTCP::Packet* packet) override { switch (packet->GetType()) { case RTC::RTCP::Type::PSFB: { switch (dynamic_cast(packet)->GetMessageType()) { case RTC::RTCP::FeedbackPs::MessageType::PLI: { INFO("PLI required"); REQUIRE(this->shouldTriggerPLI == true); this->shouldTriggerPLI = false; this->nackedSeqNumbers.clear(); break; } case RTC::RTCP::FeedbackPs::MessageType::FIR: { INFO("FIR required"); REQUIRE(this->shouldTriggerFIR == true); this->shouldTriggerFIR = false; this->nackedSeqNumbers.clear(); break; } default:; } break; } case RTC::RTCP::Type::RTPFB: { switch (dynamic_cast(packet)->GetMessageType()) { case RTC::RTCP::FeedbackRtp::MessageType::NACK: { INFO("NACK required"); REQUIRE(this->shouldTriggerNack == true); this->shouldTriggerNack = false; auto* nackPacket = dynamic_cast(packet); for (auto it = nackPacket->Begin(); it != nackPacket->End(); ++it) { const RTC::RTCP::FeedbackRtpNackItem* item = *it; const uint16_t firstSeq = item->GetPacketId(); uint16_t bitmask = item->GetLostPacketBitmask(); this->nackedSeqNumbers.push_back(firstSeq); for (size_t i{ 1 }; i < MaxRequestedPackets; ++i) { if ((bitmask & 1) != 0) { this->nackedSeqNumbers.push_back(firstSeq + i); } bitmask >>= 1; } } break; } default:; } break; } default:; } } void OnRtpStreamNeedWorstRemoteFractionLost( RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override { } public: bool shouldTriggerNack = false; bool shouldTriggerPLI = false; bool shouldTriggerFIR = false; std::vector nackedSeqNumbers; }; mocks::MockShared shared(/*getTimeMs*/ []() { return 1000; }); // clang-format off alignas(4) uint8_t buffer[] = { 0x80, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00 // Extra space for RTX encoding. }; // clang-format on std::unique_ptr packet{ RTC::RTP::Packet::Parse(buffer, 12, 12 + 4) }; if (!packet) { FAIL("not a RTP packet"); } RTC::RTP::RtpStream::Params params; params.ssrc = packet->GetSsrc(); params.rtxSsrc = 1234; params.rtxPayloadType = 96; params.clockRate = 90000; params.useNack = true; params.usePli = true; params.useFir = false; SECTION("NACK one packet") { RtpStreamRecvListener listener; RTC::RTP::RtpStreamRecv rtpStream( std::addressof(listener), std::addressof(shared), params, SendNackDelay, UseRtpInactivityCheck); packet->SetSequenceNumber(1); rtpStream.ReceivePacket(packet.get()); packet->SetSequenceNumber(3); listener.shouldTriggerNack = true; listener.shouldTriggerPLI = false; listener.shouldTriggerFIR = false; rtpStream.ReceivePacket(packet.get()); REQUIRE(listener.nackedSeqNumbers.size() == 1); REQUIRE(listener.nackedSeqNumbers[0] == 2); listener.nackedSeqNumbers.clear(); packet->SetSequenceNumber(2); rtpStream.ReceivePacket(packet.get()); REQUIRE(listener.nackedSeqNumbers.empty()); packet->SetSequenceNumber(4); rtpStream.ReceivePacket(packet.get()); REQUIRE(listener.nackedSeqNumbers.empty()); } SECTION("receive RTX before corresponding RTP") { RtpStreamRecvListener listener; RTC::RTP::RtpStreamRecv rtpStream( std::addressof(listener), std::addressof(shared), params, SendNackDelay, UseRtpInactivityCheck); packet->SetSequenceNumber(1); rtpStream.ReceivePacket(packet.get()); packet->SetSequenceNumber(2); rtpStream.ReceivePacket(packet.get()); packet->SetSequenceNumber(3); rtpStream.ReceivePacket(packet.get()); packet->SetSequenceNumber(4); rtpStream.ReceivePacket(packet.get()); packet->SetSequenceNumber(5); rtpStream.ReceivePacket(packet.get()); // Sequence number 6 arrives via RTX before the original RTP packet. auto originalSsrc = packet->GetSsrc(); auto originalPayloadType = packet->GetPayloadType(); packet->SetSequenceNumber(6); packet->RtxEncode(params.rtxPayloadType, params.rtxSsrc, 1000 /*seq=*/); REQUIRE(rtpStream.ReceiveRtxPacket(packet.get())); packet->RtxDecode(originalPayloadType, originalSsrc); } SECTION("wrapping sequence numbers") { RtpStreamRecvListener listener; RTC::RTP::RtpStreamRecv rtpStream( std::addressof(listener), std::addressof(shared), params, SendNackDelay, UseRtpInactivityCheck); packet->SetSequenceNumber(0xfffe); rtpStream.ReceivePacket(packet.get()); packet->SetSequenceNumber(1); listener.shouldTriggerNack = true; listener.shouldTriggerPLI = false; listener.shouldTriggerFIR = false; rtpStream.ReceivePacket(packet.get()); REQUIRE(listener.nackedSeqNumbers.size() == 2); REQUIRE(listener.nackedSeqNumbers[0] == 0xffff); REQUIRE(listener.nackedSeqNumbers[1] == 0); listener.nackedSeqNumbers.clear(); } SECTION("require key frame") { RtpStreamRecvListener listener; RTC::RTP::RtpStreamRecv rtpStream( std::addressof(listener), std::addressof(shared), params, SendNackDelay, UseRtpInactivityCheck); packet->SetSequenceNumber(1); rtpStream.ReceivePacket(packet.get()); // Seq different is bigger than MaxNackPackets in NackGenerator, so it // triggers a key frame. packet->SetSequenceNumber(1003); listener.shouldTriggerPLI = true; listener.shouldTriggerFIR = false; rtpStream.ReceivePacket(packet.get()); } } ================================================ FILE: worker/test/src/RTC/RTP/TestRtpStreamSend.cpp ================================================ #include "common.hpp" #include "mocks/include/MockShared.hpp" #include "RTC/RTCP/FeedbackRtpNack.hpp" #include "RTC/RTP/Codecs/AV1.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Codecs/VP8.hpp" #include "RTC/RTP/HeaderExtensionIds.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtpStream.hpp" #include "RTC/RTP/RtpStreamSend.hpp" #include "RTC/RTP/SharedPacket.hpp" #include #include // std::memcpy() #include #include // #define PERFORMANCE_TEST 1 SCENARIO("RtpStreamSend", "[rtp][rtcp][nack][rtpstream][rtpstreamsend]") { class TestRtpStreamListener : public RTC::RTP::RtpStreamSend::Listener { public: void OnRtpStreamScore( RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override { } void OnRtpStreamRetransmitRtpPacket( RTC::RTP::RtpStreamSend* /*rtpStream*/, RTC::RTP::Packet* packet) override { this->retransmittedPackets.push_back(packet); } public: std::vector retransmittedPackets; }; auto createRtpPacket = [](uint8_t* buffer, size_t len, uint16_t seq, uint32_t timestamp) { auto* packet = RTC::RTP::Packet::Parse(buffer, len); REQUIRE(packet); packet->SetPayloadType(123); packet->SetSequenceNumber(seq); packet->SetTimestamp(timestamp); return std::unique_ptr(packet); }; auto sendRtpPacket = []( // NOTE: clang-tidy suggests passing `streams` by reference but that's // wrong because we create `streams` in place when calling this function. // NOLINTNEXTLINE(performance-unnecessary-value-param) std::vector> streams, RTC::RTP::Packet* packet) { RTC::RTP::SharedPacket sharedPacket; for (auto& kv : streams) { auto* stream = kv.first; auto ssrc = kv.second; auto origSsrc = packet->GetSsrc(); packet->SetSsrc(ssrc); auto result = stream->ReceivePacket(packet, sharedPacket); packet->SetSsrc(origSsrc); // NOTE: Here we must replicate the behaviour of Consumer::sendRtpPacket() // in which, if the shared packet has been stored and it didn't contain the // packet yet, we fill it with a cloned packet. if ( result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED && !sharedPacket.HasPacket()) { sharedPacket.Assign(packet); } } }; auto checkRtxPacket = [](RTC::RTP::Packet* rtxPacket, RTC::RTP::Packet* origPacket) { REQUIRE(rtxPacket); REQUIRE(rtxPacket->GetSequenceNumber() == origPacket->GetSequenceNumber()); REQUIRE(rtxPacket->GetTimestamp() == origPacket->GetTimestamp()); REQUIRE(rtxPacket->HasMarker() == origPacket->HasMarker()); }; auto parseAV1RtpPacket = []( RTC::RTP::Packet* packet, std::unique_ptr& templateDependencyStructure) { std::unique_ptr dependencyDescriptor; packet->ReadDependencyDescriptor(dependencyDescriptor, templateDependencyStructure); REQUIRE(dependencyDescriptor); auto* payloadDescriptor = RTC::RTP::Codecs::AV1::Parse(dependencyDescriptor); auto* payloadDescriptorHandler = new RTC::RTP::Codecs::AV1::PayloadDescriptorHandler(payloadDescriptor); packet->SetPayloadDescriptorHandler(payloadDescriptorHandler); }; mocks::MockShared shared(/*getTimeMs*/ []() { return 1000; }); // clang-format off uint8_t rtpBuffer1[] = { 0b10000000, 0b01111011, 0b01010010, 0b00001110, 0b01011011, 0b01101011, 0b11001010, 0b10110101, 0, 0, 0, 2 }; // clang-format on uint8_t rtpBuffer2[1500]; uint8_t rtpBuffer3[1500]; uint8_t rtpBuffer4[1500]; uint8_t rtpBuffer5[1500]; std::memcpy(rtpBuffer2, rtpBuffer1, sizeof(rtpBuffer1)); std::memcpy(rtpBuffer3, rtpBuffer1, sizeof(rtpBuffer1)); std::memcpy(rtpBuffer4, rtpBuffer1, sizeof(rtpBuffer1)); std::memcpy(rtpBuffer5, rtpBuffer1, sizeof(rtpBuffer1)); SECTION("receive NACK and get retransmitted packets") { // packet1 [seq:21006, timestamp:1533790901] auto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, 1533790901)); // packet2 [seq:21007, timestamp:1533790901] auto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, 1533790901)); packet2->SetMarker(true); // packet3 [seq:21008, timestamp:1533793871] auto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 21008, 1533793871)); // packet4 [seq:21009, timestamp:1533793871] auto packet4(createRtpPacket(rtpBuffer4, sizeof(rtpBuffer4), 21009, 1533793871)); // packet5 [seq:21010, timestamp:1533796931] auto packet5(createRtpPacket(rtpBuffer5, sizeof(rtpBuffer5), 21010, 1533796931)); packet5->SetMarker(true); // Create a RtpStreamSend instance. TestRtpStreamListener testRtpStreamListener; RTC::RTP::RtpStream::Params params; params.ssrc = 1111; params.clockRate = 90000; params.useNack = true; params.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; auto stream = std::make_unique( std::addressof(testRtpStreamListener), std::addressof(shared), params, mid); // Receive all the packets (some of them not in order and/or duplicated). sendRtpPacket( { { stream.get(), params.ssrc } }, packet1.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet3.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet2.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet3.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet4.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet5.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet5.get()); // Create a NACK item that request for all the packets. RTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params.ssrc); auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000001111); nackPacket.AddItem(nackItem); REQUIRE(nackItem->GetPacketId() == 21006); REQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000001111); stream->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener.retransmittedPackets.size() == 5); auto* rtxPacket1 = testRtpStreamListener.retransmittedPackets[0]; auto* rtxPacket2 = testRtpStreamListener.retransmittedPackets[1]; auto* rtxPacket3 = testRtpStreamListener.retransmittedPackets[2]; auto* rtxPacket4 = testRtpStreamListener.retransmittedPackets[3]; auto* rtxPacket5 = testRtpStreamListener.retransmittedPackets[4]; testRtpStreamListener.retransmittedPackets.clear(); checkRtxPacket(rtxPacket1, packet1.get()); checkRtxPacket(rtxPacket2, packet2.get()); checkRtxPacket(rtxPacket3, packet3.get()); checkRtxPacket(rtxPacket4, packet4.get()); checkRtxPacket(rtxPacket5, packet5.get()); } SECTION("receive NACK and get zero retransmitted packets if useNack is not set") { // packet1 [seq:21006, timestamp:1533790901] auto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, 1533790901)); // packet2 [seq:21007, timestamp:1533790901] auto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, 1533790901)); // packet3 [seq:21008, timestamp:1533793871] auto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 21008, 1533793871)); // packet4 [seq:21009, timestamp:1533793871] auto packet4(createRtpPacket(rtpBuffer4, sizeof(rtpBuffer4), 21009, 1533793871)); // packet5 [seq:21010, timestamp:1533796931] auto packet5(createRtpPacket(rtpBuffer5, sizeof(rtpBuffer5), 21010, 1533796931)); // Create a RtpStreamSend instance. TestRtpStreamListener testRtpStreamListener; RTC::RTP::RtpStream::Params params; params.ssrc = 1111; params.clockRate = 90000; params.useNack = false; params.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; auto stream = std::make_unique( std::addressof(testRtpStreamListener), std::addressof(shared), params, mid); // Receive all the packets (some of them not in order and/or duplicated). sendRtpPacket( { { stream.get(), params.ssrc } }, packet1.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet3.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet2.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet3.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet4.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet5.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet5.get()); // Create a NACK item that request for all the packets. RTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params.ssrc); auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000001111); nackPacket.AddItem(nackItem); REQUIRE(nackItem->GetPacketId() == 21006); REQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000001111); stream->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener.retransmittedPackets.empty()); testRtpStreamListener.retransmittedPackets.clear(); } SECTION("receive NACK and get zero retransmitted packets for audio") { // packet1 [seq:21006, timestamp:1533790901] auto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, 1533790901)); // packet2 [seq:21007, timestamp:1533790901] auto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, 1533790901)); // packet3 [seq:21008, timestamp:1533793871] auto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 21008, 1533793871)); // packet4 [seq:21009, timestamp:1533793871] auto packet4(createRtpPacket(rtpBuffer4, sizeof(rtpBuffer4), 21009, 1533793871)); // packet5 [seq:21010, timestamp:1533796931] auto packet5(createRtpPacket(rtpBuffer5, sizeof(rtpBuffer5), 21010, 1533796931)); // Create a RtpStreamSend instance. TestRtpStreamListener testRtpStreamListener; RTC::RTP::RtpStream::Params params; params.ssrc = 1111; params.clockRate = 90000; params.useNack = false; params.mimeType.type = RTC::RtpCodecMimeType::Type::AUDIO; std::string mid; auto stream = std::make_unique( std::addressof(testRtpStreamListener), std::addressof(shared), params, mid); // Receive all the packets (some of them not in order and/or duplicated). sendRtpPacket( { { stream.get(), params.ssrc } }, packet1.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet3.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet2.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet3.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet4.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet5.get()); sendRtpPacket( { { stream.get(), params.ssrc } }, packet5.get()); // Create a NACK item that request for all the packets. RTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params.ssrc); auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000001111); nackPacket.AddItem(nackItem); REQUIRE(nackItem->GetPacketId() == 21006); REQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000001111); stream->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener.retransmittedPackets.empty()); testRtpStreamListener.retransmittedPackets.clear(); } SECTION("receive NACK in different RtpStreamSend instances and get retransmitted packets") { // packet1 [seq:21006, timestamp:1533790901] auto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, 1533790901)); // packet2 [seq:21007, timestamp:1533790901] auto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, 1533790901)); // Create two RtpStreamSend instances. TestRtpStreamListener testRtpStreamListener1; TestRtpStreamListener testRtpStreamListener2; RTC::RTP::RtpStream::Params params1; params1.ssrc = 1111; params1.clockRate = 90000; params1.useNack = true; params1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; std::unique_ptr stream1(new RTC::RTP::RtpStreamSend( std::addressof(testRtpStreamListener1), std::addressof(shared), params1, mid)); RTC::RTP::RtpStream::Params params2; params2.ssrc = 2222; params2.clockRate = 90000; params2.useNack = true; params2.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::unique_ptr stream2(new RTC::RTP::RtpStreamSend( std::addressof(testRtpStreamListener2), std::addressof(shared), params2, mid)); // Receive all the packets in both streams. sendRtpPacket( { { stream1.get(), params1.ssrc }, { stream2.get(), params2.ssrc } }, packet1.get()); sendRtpPacket( { { stream1.get(), params1.ssrc }, { stream2.get(), params2.ssrc } }, packet2.get()); // Create a NACK item that request for all the packets. RTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc); auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000000001); nackPacket.AddItem(nackItem); REQUIRE(nackItem->GetPacketId() == 21006); REQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000001); // Process the NACK packet on stream1. stream1->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener1.retransmittedPackets.size() == 2); auto* rtxPacket1 = testRtpStreamListener1.retransmittedPackets[0]; auto* rtxPacket2 = testRtpStreamListener1.retransmittedPackets[1]; testRtpStreamListener1.retransmittedPackets.clear(); checkRtxPacket(rtxPacket1, packet1.get()); checkRtxPacket(rtxPacket2, packet2.get()); // Process the NACK packet on stream2. stream2->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener2.retransmittedPackets.size() == 2); rtxPacket1 = testRtpStreamListener2.retransmittedPackets[0]; rtxPacket2 = testRtpStreamListener2.retransmittedPackets[1]; testRtpStreamListener2.retransmittedPackets.clear(); checkRtxPacket(rtxPacket1, packet1.get()); checkRtxPacket(rtxPacket2, packet2.get()); } SECTION("retransmitted packets are correctly encoded [VP8]") { // clang-format off uint8_t rtpBuffer1[] = { 0x80, 0x7b, 0x52, 0x0e, 0x5b, 0x6b, 0xca, 0xb5, 0x00, 0x00, 0x00, 0x02, 0x80, 0xe0, 0x80, 0x01, 0xe8, 0x40, 0x7a, 0xd8 }; uint8_t rtpBuffer2[] = { 0x80, 0x7b, 0x52, 0x0e, 0x5b, 0x6b, 0xca, 0xb5, 0x00, 0x00, 0x00, 0x02, 0x80, 0xe0, 0x80, 0x02, 0xe9, 0x40, 0x7a, 0xd8 }; uint8_t rtpBuffer3[] = { 0x80, 0x7b, 0x52, 0x0e, 0x5b, 0x6b, 0xca, 0xb5, 0x00, 0x00, 0x00, 0x02, 0x80, 0xe0, 0x80, 0x03, 0xea, 0x40, 0x7a, 0xd8 }; // clang-format on // packet1 [seq:1, timestamp:1] auto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 1, 1)); // packet2 [seq:2, timestamp:1] auto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 2, 1)); // packet3 [seq:3, timestamp:1] auto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 3, 1)); // Create two RtpStreamSend instances. TestRtpStreamListener testRtpStreamListener1; TestRtpStreamListener testRtpStreamListener2; RTC::RTP::RtpStream::Params params1; params1.ssrc = 1111; params1.clockRate = 90000; params1.useNack = true; params1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; std::unique_ptr stream1(new RTC::RTP::RtpStreamSend( std::addressof(testRtpStreamListener1), std::addressof(shared), params1, mid)); RTC::RTP::RtpStream::Params params2; params2.ssrc = 2222; params2.clockRate = 90000; params2.useNack = true; params2.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::unique_ptr stream2(new RTC::RTP::RtpStreamSend( std::addressof(testRtpStreamListener2), std::addressof(shared), params2, mid)); // Create two VP8 encoding contexts. RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 0; params.temporalLayers = 3; RTC::RTP::Codecs::VP8::EncodingContext context1(params); context1.SetCurrentTemporalLayer(3); context1.SetTargetTemporalLayer(3); RTC::RTP::Codecs::VP8::EncodingContext context2(params); context2.SetCurrentTemporalLayer(0); context2.SetTargetTemporalLayer(0); // Parse the first packet. auto* payloadDescriptor1 = RTC::RTP::Codecs::VP8::Parse(packet1->GetPayload(), packet1->GetPayloadLength()); REQUIRE(payloadDescriptor1->pictureId == 1); auto* payloadDescriptorHandler1 = new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor1); packet1->SetPayloadDescriptorHandler(payloadDescriptorHandler1); bool marker = false; // Process the first packet with context1. auto forwarded = payloadDescriptorHandler1->Process(&context1, packet1.get(), marker); REQUIRE(forwarded); // Parse the second packet. auto* payloadDescriptor2 = RTC::RTP::Codecs::VP8::Parse(packet2->GetPayload(), packet2->GetPayloadLength()); REQUIRE(payloadDescriptor2->pictureId == 2); auto* payloadDescriptorHandler2 = new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor2); packet2->SetPayloadDescriptorHandler(payloadDescriptorHandler2); // Process the second packet with context1. forwarded = payloadDescriptorHandler2->Process(&context1, packet2.get(), marker); REQUIRE(forwarded); // Process the second packet for context2. forwarded = payloadDescriptorHandler2->Process(&context2, packet2.get(), marker); // It must not forwared because the target temporal layer is 0. REQUIRE(!forwarded); // Parse the third packet auto* payloadDescriptor3 = RTC::RTP::Codecs::VP8::Parse(packet3->GetPayload(), packet3->GetPayloadLength()); REQUIRE(payloadDescriptor3->pictureId == 3); auto* payloadDescriptorHandler3 = new RTC::RTP::Codecs::VP8::PayloadDescriptorHandler(payloadDescriptor3); packet2->SetPayloadDescriptorHandler(payloadDescriptorHandler3); // Process the third packet for context1. forwarded = payloadDescriptorHandler3->Process(&context1, packet3.get(), marker); REQUIRE(forwarded); // Receive the third packet in the first stream. sendRtpPacket( { { stream1.get(), params1.ssrc } }, packet3.get()); // Update current/target temporal layers for context2. context2.SetCurrentTemporalLayer(3); context2.SetTargetTemporalLayer(3); forwarded = payloadDescriptorHandler3->Process(&context2, packet3.get(), marker); REQUIRE(forwarded); // Receive the third packet in the second stream. sendRtpPacket( { { stream2.get(), params2.ssrc } }, packet3.get()); // Create a NACK item that requests the third packet. RTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc); auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(3, 0b0000000000000000); nackPacket.AddItem(nackItem); REQUIRE(nackItem->GetPacketId() == 3); REQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000000); // Process the NACK packet on stream1. stream1->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener1.retransmittedPackets.size() == 1); auto* packet = testRtpStreamListener1.retransmittedPackets[0]; // Parse payload and check pictureId. auto* payloadDescriptor4 = RTC::RTP::Codecs::VP8::Parse(packet->GetPayload(), packet->GetPayloadLength()); REQUIRE(payloadDescriptor4->pictureId == 3); // Process the NACK packet on stream2. stream2->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener2.retransmittedPackets.size() == 1); packet = testRtpStreamListener2.retransmittedPackets[0]; // Parse payload and check pictureId. auto* payloadDescriptor5 = RTC::RTP::Codecs::VP8::Parse(packet->GetPayload(), packet->GetPayloadLength()); REQUIRE(payloadDescriptor5); REQUIRE(payloadDescriptor5->pictureId == 2); delete payloadDescriptor4; delete payloadDescriptor5; } SECTION("retransmitted packets are correctly encoded [AV1]") { /* * * startOfFrame: true * endOfFrame: false * frameDependencyTemplateId: 0 * frameNumber: 1 * templateId: 0 * spatialLayer: 0 * temporalLayer: 0 * * spatialLayers: 0 * temporalLayers: 1 * templateIdOffset: 0 * decodeTargetCount: 2 * * * spatialLayerId: 0 * temporalLayerId: 0 * SS * * 0 * * * spatialLayerId: 0 * temporalLayerId: 0 * SS * 2 * 2 * * * spatialLayerId: 0 * temporalLayerId: 1 * -D * 1 * 1 * * * spatialLayerId: 0 * temporalLayerId: 1 * -D * 1 * 1 * * * * */ // clang-format off uint8_t rtpBuffer1[] = { 0x90, 0x2D, 0x56, 0xA5, 0x8D, 0x76, 0xF5, 0x02, 0xDD, 0xD5, 0x4C, 0xB9, 0xBE, 0xDE, 0x00, 0x07, 0x22, 0x89, 0xDF, 0xFE, 0x31, 0x00, 0x07, 0x40, 0x31, 0xCE, 0x80, 0x00, 0x01, 0x80, 0x01, 0x1E, 0xA8, 0x51, 0x41, 0x01, 0x0C, 0x13, 0xFC, 0x0B, 0x3C, 0x00, 0x00, 0x00 }; /* * * startOfFrame: true * endOfFrame: true * frameDependencyTemplateId: 2 * frameNumber: 2 * templateId: 2 * temporalLayer: 1 * spatialLayer: 0 * */ uint8_t rtpBuffer2[] = { 0x90, 0xAD, 0x56, 0xA9, 0x8D, 0x77, 0x02, 0xB8, 0xDD, 0xD5, 0x4C, 0xB9, 0xBE, 0xDE, 0x00, 0x04, 0x22, 0x8A, 0x07, 0xAB, 0x31, 0x00, 0x18, 0x40, 0x31, 0xC2, 0xC2, 0x00, 0x02, 0x00, 0x00, 0x00 }; /* * * startOfFrame: false * endOfFrame: true * frameDependencyTemplateId: 0 * frameNumber: 1 * templateId: 0 * spatialLayer: 0 * temporalLayer: 0 * */ uint8_t rtpBuffer3[] = { 0x90, 0xAD, 0x56, 0xA8, 0x8D, 0x76, 0xF5, 0x02, 0xDD, 0xD5, 0x4C, 0xB9, 0xBE, 0xDE, 0x00, 0x04, 0x22, 0x8A, 0x03, 0xE5, 0xD0, 0x00, 0x31, 0x00, 0x17, 0xC2, 0x40, 0x00, 0x01, 0x40, 0x31, 0x00 }; // clang-format on RTC::RTP::HeaderExtensionIds headerExtensionIds{}; headerExtensionIds.dependencyDescriptor = 12; // packet1 [seq:1, timestamp:1] auto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 1, 1)); packet1->AssignExtensionIds(headerExtensionIds); // packet2 [seq:2, timestamp:1] auto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 2, 1)); packet2->AssignExtensionIds(headerExtensionIds); // packet3 [seq:3, timestamp:1] auto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 3, 1)); packet3->AssignExtensionIds(headerExtensionIds); // Create two RtpStreamSend instances. TestRtpStreamListener testRtpStreamListener1; TestRtpStreamListener testRtpStreamListener2; RTC::RTP::RtpStream::Params params1; params1.ssrc = 1111; params1.clockRate = 90000; params1.useNack = true; params1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; std::unique_ptr stream1(new RTC::RTP::RtpStreamSend( std::addressof(testRtpStreamListener1), std::addressof(shared), params1, mid)); RTC::RTP::RtpStream::Params params2; params2.ssrc = 2222; params2.clockRate = 90000; params2.useNack = true; params2.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::unique_ptr stream2(new RTC::RTP::RtpStreamSend( std::addressof(testRtpStreamListener2), std::addressof(shared), params2, mid)); // Create two AV1 encoding contexts. RTC::RTP::Codecs::EncodingContext::Params params; params.spatialLayers = 1; params.temporalLayers = 2; RTC::RTP::Codecs::AV1::EncodingContext context1(params); context1.SetCurrentSpatialLayer(0); context1.SetCurrentTemporalLayer(0); context1.SetTargetSpatialLayer(0); context1.SetTargetTemporalLayer(0); RTC::RTP::Codecs::AV1::EncodingContext context2(params); context2.SetCurrentSpatialLayer(0); context2.SetCurrentTemporalLayer(0); context2.SetTargetSpatialLayer(0); context2.SetTargetTemporalLayer(1); std::unique_ptr templateDependencyStructure; // Parse the first packet for the shake of having the template dependency structure. parseAV1RtpPacket(packet1.get(), templateDependencyStructure); // Parse the second packet. parseAV1RtpPacket(packet2.get(), templateDependencyStructure); bool marker = false; bool forwarded = false; // Process the second packet for context1. forwarded = packet2->ProcessPayload(&context1, marker); REQUIRE(!forwarded); // Process the second packet with context2. forwarded = packet2->ProcessPayload(&context2, marker); REQUIRE(forwarded); REQUIRE(context2.GetCurrentSpatialLayer() == 0); REQUIRE(context2.GetCurrentTemporalLayer() == 1); // Parse the third packet parseAV1RtpPacket(packet3.get(), templateDependencyStructure); // Process the third packet with context1 and verify current spatial layers. forwarded = packet3->ProcessPayload(&context1, marker); REQUIRE(forwarded); REQUIRE(context1.GetCurrentSpatialLayer() == 0); REQUIRE(context1.GetCurrentTemporalLayer() == 0); RTC::RTP::SharedPacket sharedPacket; packet3->SetSsrc(params1.ssrc); // Whenever packet3 is Nacked on stream1, it must always be set a // 00000001 (S0_T1) active decode target bitmas. auto result = stream1->ReceivePacket(packet3.get(), sharedPacket); REQUIRE(result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED); sharedPacket.Assign(packet3.get()); // Process the third packet with context2 and verify current spatial layers. forwarded = packet3->ProcessPayload(&context2, marker); REQUIRE(forwarded); REQUIRE(context2.GetCurrentSpatialLayer() == 0); REQUIRE(context2.GetCurrentTemporalLayer() == 1); packet3->SetSsrc(params2.ssrc); // Whenever packet3 is Nacked on stream2, it must always be set a // 00000011 (S0_T1) active decode target bitmas. stream2->ReceivePacket(packet3.get(), sharedPacket); // Create a NACK item that requests the third packet. RTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc); auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(3, 0b0000000000000000); nackPacket.AddItem(nackItem); REQUIRE(nackItem->GetPacketId() == 3); REQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000000); // Process the NACK packet on stream1. stream1->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener1.retransmittedPackets.size() == 1); auto* packet = testRtpStreamListener1.retransmittedPackets[0]; // Parse DD and check bitmask. std::unique_ptr dependencyDescriptor4; packet->ReadDependencyDescriptor(dependencyDescriptor4, templateDependencyStructure); REQUIRE(dependencyDescriptor4); // TODO: Enable once we write DD. // REQUIRE(dependencyDescriptor4->activeDecodeTargetsBitmask == 0b0000000000000001); // Process the NACK packet on stream2. stream2->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener2.retransmittedPackets.size() == 1); packet = testRtpStreamListener2.retransmittedPackets[0]; // Parse DD and check bitmask. std::unique_ptr dependencyDescriptor5; packet->ReadDependencyDescriptor(dependencyDescriptor5, templateDependencyStructure); REQUIRE(dependencyDescriptor5); // TODO: Enable once we write DD. // REQUIRE(dependencyDescriptor5->activeDecodeTargetsBitmask == 0b0000000000000011); } SECTION("packets get retransmitted as long as they don't exceed MaxRetransmissionDelayForVideoMs") { const uint32_t clockRate = 90000; const uint32_t firstTs = 1533790901; const uint32_t diffTs = RTC::RTP::RtpStreamSend::MaxRetransmissionDelayForVideoMs * clockRate / 1000; const uint32_t secondTs = firstTs + diffTs; auto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, firstTs)); auto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, secondTs - 1)); // Create a RtpStreamSend instance. TestRtpStreamListener testRtpStreamListener; RTC::RTP::RtpStream::Params params1; params1.ssrc = 1111; params1.clockRate = clockRate; params1.useNack = true; params1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; auto stream = std::make_unique( std::addressof(testRtpStreamListener), std::addressof(shared), params1, mid); // Receive all the packets. sendRtpPacket( { { stream.get(), params1.ssrc } }, packet1.get()); sendRtpPacket( { { stream.get(), params1.ssrc } }, packet2.get()); // Create a NACK item that request for all the packets. RTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc); auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000000001); nackPacket.AddItem(nackItem); REQUIRE(nackItem->GetPacketId() == 21006); REQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000001); // Process the NACK packet on stream1. stream->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener.retransmittedPackets.size() == 2); auto* rtxPacket1 = testRtpStreamListener.retransmittedPackets[0]; auto* rtxPacket2 = testRtpStreamListener.retransmittedPackets[1]; testRtpStreamListener.retransmittedPackets.clear(); checkRtxPacket(rtxPacket1, packet1.get()); checkRtxPacket(rtxPacket2, packet2.get()); } SECTION("packets don't get retransmitted if MaxRetransmissionDelayForVideoMs is exceeded") { const uint32_t clockRate = 90000; const uint32_t firstTs = 1533790901; const uint32_t diffTs = RTC::RTP::RtpStreamSend::MaxRetransmissionDelayForVideoMs * clockRate / 1000; // Make second packet arrive more than MaxRetransmissionDelayForVideoMs later. const uint32_t secondTs = firstTs + diffTs + 100; // Send a third packet so it will clean old packets from the buffer. const uint32_t thirdTs = firstTs + (2 * diffTs); auto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 21006, firstTs)); auto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 21007, secondTs)); auto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 21008, thirdTs)); // Create a RtpStreamSend instance. TestRtpStreamListener testRtpStreamListener; RTC::RTP::RtpStream::Params params1; params1.ssrc = 1111; params1.clockRate = clockRate; params1.useNack = true; params1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; auto stream = std::make_unique( std::addressof(testRtpStreamListener), std::addressof(shared), params1, mid); // Receive all the packets. sendRtpPacket( { { stream.get(), params1.ssrc } }, packet1.get()); sendRtpPacket( { { stream.get(), params1.ssrc } }, packet2.get()); sendRtpPacket( { { stream.get(), params1.ssrc } }, packet3.get()); // Create a NACK item that requests for all packets. RTC::RTCP::FeedbackRtpNackPacket nackPacket(0, params1.ssrc); auto* nackItem = new RTC::RTCP::FeedbackRtpNackItem(21006, 0b0000000000000001); nackPacket.AddItem(nackItem); REQUIRE(nackItem->GetPacketId() == 21006); REQUIRE(nackItem->GetLostPacketBitmask() == 0b0000000000000001); // Process the NACK packet on stream1. stream->ReceiveNack(&nackPacket); REQUIRE(testRtpStreamListener.retransmittedPackets.size() == 1); auto* rtxPacket2 = testRtpStreamListener.retransmittedPackets[0]; testRtpStreamListener.retransmittedPackets.clear(); checkRtxPacket(rtxPacket2, packet2.get()); } SECTION("packets get removed from the retransmission buffer if seq number of the stream is reset") { // This scenario reproduce the "too bad sequence number" and "bad sequence // number" scenarios in RtpStream::UpdateSeq(). auto packet1(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 50001, 1000001)); auto packet2(createRtpPacket(rtpBuffer2, sizeof(rtpBuffer2), 50002, 1000002)); // Third packet has bad sequence number (its seq is more than MaxDropout=3000 // older than current max seq) and will be dropped. auto packet3(createRtpPacket(rtpBuffer3, sizeof(rtpBuffer3), 40003, 1000003)); // Forth packet has seq=badSeq+1 so will be accepted and will trigger a // stream reset. auto packet4(createRtpPacket(rtpBuffer4, sizeof(rtpBuffer4), 40004, 1000004)); // Create a RtpStreamSend instance. TestRtpStreamListener testRtpStreamListener; RTC::RTP::RtpStream::Params params1; params1.ssrc = 1111; params1.clockRate = 90000; params1.useNack = true; params1.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; auto stream = std::make_unique( std::addressof(testRtpStreamListener), std::addressof(shared), params1, mid); sendRtpPacket( { { stream.get(), params1.ssrc } }, packet1.get()); sendRtpPacket( { { stream.get(), params1.ssrc } }, packet2.get()); sendRtpPacket( { { stream.get(), params1.ssrc } }, packet3.get()); sendRtpPacket( { { stream.get(), params1.ssrc } }, packet4.get()); // Create a NACK item that requests for packets 1 and 2. RTC::RTCP::FeedbackRtpNackPacket nackPacket2(0, params1.ssrc); auto* nackItem2 = new RTC::RTCP::FeedbackRtpNackItem(50001, 0b0000000000000001); nackPacket2.AddItem(nackItem2); // Process the NACK packet on stream1. stream->ReceiveNack(&nackPacket2); REQUIRE(testRtpStreamListener.retransmittedPackets.empty()); } SECTION("duplicated packets are discarded") { auto packet(createRtpPacket(rtpBuffer1, sizeof(rtpBuffer1), 50001, 1000001)); // Create a RtpStreamSend instance. TestRtpStreamListener testRtpStreamListener; RTC::RTP::RtpStream::Params params; params.ssrc = packet->GetSsrc(); params.clockRate = 90000; params.useNack = true; params.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; auto stream = std::make_unique( std::addressof(testRtpStreamListener), std::addressof(shared), params, mid); const RTC::RTP::SharedPacket sharedPacket; auto result = stream->ReceivePacket(packet.get(), sharedPacket); REQUIRE(result == RTC::RTP::RtpStreamSend::ReceivePacketResult::ACCEPTED_AND_STORED); result = stream->ReceivePacket(packet.get(), sharedPacket); REQUIRE(result == RTC::RTP::RtpStreamSend::ReceivePacketResult::DISCARDED); } #ifdef PERFORMANCE_TEST SECTION("Performance") { // Create a RtpStreamSend instance. TestRtpStreamListener testRtpStreamListener; RTC::RTP::RtpStream::Params params; params.ssrc = 1111; params.clockRate = 90000; params.useNack = true; params.mimeType.type = RTC::RtpCodecMimeType::Type::VIDEO; std::string mid; std::unique_ptr stream1(new RTC::RTP::RtpStreamSend( std::addressof(testRtpStreamListener), std::addressof(shared), params, mid)); size_t iterations = 10000000; auto start = std::chrono::system_clock::now(); for (size_t i = 0; i < iterations; i++) { // Create packet. auto* packet = RTC::RTP::Packet::Parse(rtpBuffer1, 1500); packet->SetSsrc(1111); std::shared_ptr sharedPacket(packet); stream1->ReceivePacket(packet, sharedPacket); } std::chrono::duration dur = std::chrono::system_clock::now() - start; std::cout << "nullptr && initialized shared_ptr: \t" << dur.count() << " seconds" << std::endl; params.mimeType.type = RTC::RtpCodecMimeType::Type::AUDIO; std::unique_ptr stream2(new RTC::RTP::RtpStreamSend( std::addressof(testRtpStreamListener), std::addressof(shared), params, mid)); start = std::chrono::system_clock::now(); for (size_t i = 0; i < iterations; i++) { std::shared_ptr sharedPacket; // Create packet. auto* packet = RTC::RTP::Packet::Parse(rtpBuffer1, 1500); packet->SetSsrc(1111); stream2->ReceivePacket(packet, sharedPacket); } dur = std::chrono::system_clock::now() - start; std::cout << "raw && empty shared_ptr duration: \t" << dur.count() << " seconds" << std::endl; } #endif } ================================================ FILE: worker/test/src/RTC/RTP/TestSharedPacket.cpp ================================================ #include "common.hpp" #include "test/include/RTC/RTP/rtpCommon.hpp" // in worker/test/include/ #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/SharedPacket.hpp" #include SCENARIO("RTP SharedPacket", "[rtp][sharedpacket]") { auto compareRtpPackets = [](const RTC::RTP::Packet* packet1, const RTC::RTP::Packet* packet2) { REQUIRE(packet1->GetSsrc() == packet2->GetSsrc()); REQUIRE(packet1->GetSequenceNumber() == packet2->GetSequenceNumber()); REQUIRE(packet1->GetTimestamp() == packet2->GetTimestamp()); REQUIRE(packet1->GetLength() == packet2->GetLength()); }; auto* packetA = RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer, 2000); packetA->SetSequenceNumber(1111); packetA->SetTimestamp(111111); packetA->SetSsrc(11111111); auto* packetB = RTC::RTP::Packet::Factory(rtpCommon::FactoryBuffer + 2000, 2000); packetB->SetSequenceNumber(2222); packetB->SetTimestamp(222222); packetB->SetSsrc(22222222); SECTION("default constructor and assign later") { RTC::RTP::SharedPacket sharedPacket; REQUIRE(!sharedPacket.HasPacket()); REQUIRE(sharedPacket.GetPacket() == nullptr); sharedPacket.Assign(packetA); REQUIRE(sharedPacket.HasPacket()); compareRtpPackets(sharedPacket.GetPacket(), packetA); sharedPacket.Reset(); REQUIRE(!sharedPacket.HasPacket()); REQUIRE(sharedPacket.GetPacket() == nullptr); delete packetA; delete packetB; } SECTION("constructor with packet and copy constructor") { // Create sharedPacket1 using constructor with a Packet. RTC::RTP::SharedPacket sharedPacket1(packetA); REQUIRE(sharedPacket1.HasPacket()); compareRtpPackets(sharedPacket1.GetPacket(), packetA); // Create sharedPacket2 using copy constructor. RTC::RTP::SharedPacket sharedPacket2(sharedPacket1); REQUIRE(sharedPacket2.HasPacket()); compareRtpPackets(sharedPacket2.GetPacket(), packetA); sharedPacket2.Assign(packetB); REQUIRE(sharedPacket1.HasPacket()); compareRtpPackets(sharedPacket1.GetPacket(), packetB); REQUIRE(sharedPacket2.HasPacket()); compareRtpPackets(sharedPacket2.GetPacket(), packetB); REQUIRE(sharedPacket1.GetPacket() == sharedPacket2.GetPacket()); sharedPacket1.AssertSamePacket(sharedPacket1.GetPacket()); sharedPacket1.AssertSamePacket(sharedPacket2.GetPacket()); sharedPacket2.AssertSamePacket(sharedPacket2.GetPacket()); sharedPacket2.AssertSamePacket(sharedPacket1.GetPacket()); sharedPacket1.Reset(); REQUIRE(!sharedPacket1.HasPacket()); REQUIRE(sharedPacket1.GetPacket() == nullptr); REQUIRE(!sharedPacket2.HasPacket()); REQUIRE(sharedPacket2.GetPacket() == nullptr); delete packetA; delete packetB; } SECTION("copy assignment operator") { RTC::RTP::SharedPacket sharedPacket1(packetA); REQUIRE(sharedPacket1.HasPacket()); compareRtpPackets(sharedPacket1.GetPacket(), packetA); RTC::RTP::SharedPacket sharedPacket2; // Fill sharedPacket2 using copy assignment operator. sharedPacket2 = sharedPacket1; REQUIRE(sharedPacket2.HasPacket()); compareRtpPackets(sharedPacket2.GetPacket(), packetA); sharedPacket2.Assign(packetB); REQUIRE(sharedPacket1.HasPacket()); compareRtpPackets(sharedPacket1.GetPacket(), packetB); REQUIRE(sharedPacket2.HasPacket()); compareRtpPackets(sharedPacket2.GetPacket(), packetB); REQUIRE(sharedPacket1.GetPacket() == sharedPacket2.GetPacket()); sharedPacket1.AssertSamePacket(sharedPacket1.GetPacket()); sharedPacket1.AssertSamePacket(sharedPacket2.GetPacket()); sharedPacket2.AssertSamePacket(sharedPacket2.GetPacket()); sharedPacket2.AssertSamePacket(sharedPacket1.GetPacket()); sharedPacket1.Reset(); REQUIRE(!sharedPacket1.HasPacket()); REQUIRE(sharedPacket1.GetPacket() == nullptr); REQUIRE(!sharedPacket2.HasPacket()); REQUIRE(sharedPacket2.GetPacket() == nullptr); delete packetA; delete packetB; } SECTION("assign nullptr") { RTC::RTP::SharedPacket sharedPacket(packetA); REQUIRE(sharedPacket.HasPacket()); compareRtpPackets(sharedPacket.GetPacket(), packetA); sharedPacket.Assign(nullptr); REQUIRE(!sharedPacket.HasPacket()); REQUIRE(sharedPacket.GetPacket() == nullptr); delete packetA; delete packetB; } } ================================================ FILE: worker/test/src/RTC/RTP/rtpCommon.cpp ================================================ #include "test/include/RTC/RTP/rtpCommon.hpp" // in worker/test/include/ #include // std::memset namespace rtpCommon { // NOTE: Buffers must be 4-byte aligned since RTP Packet parsing casts them // to structs (e.g. FixedHeader, HeaderExtension) that require 4-byte // alignment. Without this, accessing multi-byte fields would be undefined // behavior on strict-alignment architectures. alignas(4) thread_local uint8_t FactoryBuffer[]; alignas(4) thread_local uint8_t SerializeBuffer[]; alignas(4) thread_local uint8_t CloneBuffer[]; alignas(4) thread_local uint8_t DataBuffer[]; alignas(4) thread_local uint8_t ThrowBuffer[]; void ResetBuffers() { std::memset(FactoryBuffer, 0xAA, sizeof(FactoryBuffer)); std::memset(SerializeBuffer, 0xBB, sizeof(SerializeBuffer)); std::memset(CloneBuffer, 0xCC, sizeof(CloneBuffer)); std::memset(DataBuffer, 0xDD, sizeof(DataBuffer)); std::memset(ThrowBuffer, 0xEE, sizeof(ThrowBuffer)); for (size_t i = 0; i < 256; ++i) { DataBuffer[i] = static_cast(i); } } } // namespace rtpCommon ================================================ FILE: worker/test/src/RTC/SCTP/association/TestHeartbeatHandler.cpp ================================================ #include "common.hpp" #include "mocks/include/MockShared.hpp" #include "mocks/include/RTC/SCTP/association/MockAssociationListener.hpp" #include "mocks/include/RTC/SCTP/association/MockTransmissionControlBlockContext.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "test/include/catch2Macros.hpp" #include "test/include/testHelpers.hpp" #include "RTC/SCTP/association/HeartbeatHandler.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp" #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp" #include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include #include SCENARIO("SCTP HeartbeatHandler", "[sctp][heartbeathandler]") { constexpr uint64_t InitialNowMs{ 1000000 }; constexpr uint64_t HeartbeatIntervalMs{ 30000 }; class TestHeartbeatHandler { public: explicit TestHeartbeatHandler(uint64_t heartbeatIntervalMs) // NOTE: The order in which these members are initialized is **critical**. : sctpOptions( RTC::SCTP::SctpOptions{ .heartbeatIntervalMs = heartbeatIntervalMs, .heartbeatIntervalIncludeRtt = false, .zeroChecksumAlternateErrorDetectionMethod = RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE }), tcbContext(this->associationListener, this->sctpOptions), shared(/*getTimeMs*/ [this]() { return this->nowMs; }), heartbeatHandler( this->associationListener, this->sctpOptions, std::addressof(this->shared), std::addressof(this->tcbContext)) { // Simulate that the SCTP assiciation is connected. this->tcbContext.SetAssociationEstablished(true); }; public: void AdvanceTimeMs(int64_t incrementMs) { this->nowMs += incrementMs; } private: uint64_t nowMs{ InitialNowMs }; // NOTE: Public members for testing. public: RTC::SCTP::SctpOptions sctpOptions; mocks::RTC::SCTP::MockAssociationListener associationListener; mocks::RTC::SCTP::MockTransmissionControlBlockContext tcbContext; mocks::MockShared shared; RTC::SCTP::HeartbeatHandler heartbeatHandler; }; SECTION("has running heartbeat interval timer") { TestHeartbeatHandler test(HeartbeatIntervalMs); test.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs); auto* heartbeatIntervalTimer = test.shared.GetBackoffTimer("sctp-heartbeat-interval"); REQUIRE(heartbeatIntervalTimer); REQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == true); REQUIRE(test.associationListener.HasSentPackets() == true); const std::vector sentBuffer = test.associationListener.ConsumeFirstSentPacket(); std::unique_ptr sentPacket{ RTC::SCTP::Packet::Parse( sentBuffer.data(), sentBuffer.size()) }; REQUIRE(sentPacket); REQUIRE(sentPacket->GetChunksCount() == 1); const auto* sentHeartbeatRequestChunk = sentPacket->GetFirstChunkOfType(); REQUIRE(sentHeartbeatRequestChunk); const auto* sentHeartbeatInfoParameter = sentHeartbeatRequestChunk->GetFirstParameterOfType(); REQUIRE(sentHeartbeatInfoParameter); REQUIRE(sentHeartbeatInfoParameter->HasInfo()); } SECTION("replies to heartbeat requests") { TestHeartbeatHandler test(HeartbeatIntervalMs); std::unique_ptr receivedHeartbeatRequestChunk{ RTC::SCTP::HeartbeatRequestChunk::Factory(sctpCommon::FactoryBuffer, test.sctpOptions.mtu) }; auto* receivedHeartbeatInfoParameter = receivedHeartbeatRequestChunk->BuildParameterInPlace(); receivedHeartbeatInfoParameter->SetInfo(sctpCommon::DataBuffer, 10); receivedHeartbeatInfoParameter->Consolidate(); test.heartbeatHandler.HandleReceivedHeartbeatRequestChunk(receivedHeartbeatRequestChunk.get()); const std::vector sentBuffer = test.associationListener.ConsumeFirstSentPacket(); std::unique_ptr sentPacket{ RTC::SCTP::Packet::Parse( sentBuffer.data(), sentBuffer.size()) }; REQUIRE(sentPacket); REQUIRE(sentPacket->GetChunksCount() == 1); const auto* sentHeartbeatAckChunk = sentPacket->GetFirstChunkOfType(); REQUIRE(sentHeartbeatAckChunk); const auto* sentHeartbeatInfoParameter = sentHeartbeatAckChunk->GetFirstParameterOfType(); REQUIRE(sentHeartbeatInfoParameter); REQUIRE(sentHeartbeatInfoParameter->HasInfo()); REQUIRE( helpers::areBuffersEqual( sentHeartbeatInfoParameter->GetBuffer(), sentHeartbeatInfoParameter->GetLength(), receivedHeartbeatInfoParameter->GetBuffer(), receivedHeartbeatInfoParameter->GetLength()) == true); } SECTION("sends heartbeat requests on idle connections") { TestHeartbeatHandler test(HeartbeatIntervalMs); test.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs); auto* heartbeatIntervalTimer = test.shared.GetBackoffTimer("sctp-heartbeat-interval"); REQUIRE(heartbeatIntervalTimer); REQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == true); REQUIRE(test.associationListener.HasSentPackets() == true); const std::vector sentBuffer = test.associationListener.ConsumeFirstSentPacket(); std::unique_ptr sentPacket{ RTC::SCTP::Packet::Parse( sentBuffer.data(), sentBuffer.size()) }; REQUIRE(sentPacket); REQUIRE(sentPacket->GetChunksCount() == 1); const auto* sentHeartbeatRequestChunk = sentPacket->GetFirstChunkOfType(); REQUIRE(sentHeartbeatRequestChunk); const auto* sentHeartbeatInfoParameter = sentHeartbeatRequestChunk->GetFirstParameterOfType(); REQUIRE(sentHeartbeatInfoParameter); REQUIRE(sentHeartbeatInfoParameter->HasInfo()); std::unique_ptr receivedHeartbeatAckChunk{ RTC::SCTP::HeartbeatAckChunk::Factory(sctpCommon::FactoryBuffer, test.sctpOptions.mtu) }; auto* receivedHeartbeatInfoParameter = receivedHeartbeatAckChunk->BuildParameterInPlace(); receivedHeartbeatInfoParameter->SetInfo( sentHeartbeatInfoParameter->GetInfo(), sentHeartbeatInfoParameter->GetInfoLength()); receivedHeartbeatInfoParameter->Consolidate(); // Respond a while later. const uint64_t rttMs{ 313 }; test.tcbContext.ExpectObserveRttMsCalledTimes(1); test.AdvanceTimeMs(rttMs); test.heartbeatHandler.HandleReceivedHeartbeatAckChunk(receivedHeartbeatAckChunk.get()); REQUIRE_VERIFICATION_RESULT(test.tcbContext.VerifyExpectations()); } SECTION("doesn't observe RTT on invalid hearbeats receipt") { TestHeartbeatHandler test(HeartbeatIntervalMs); test.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs); auto* heartbeatIntervalTimer = test.shared.GetBackoffTimer("sctp-heartbeat-interval"); REQUIRE(heartbeatIntervalTimer); REQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == true); REQUIRE(test.associationListener.HasSentPackets() == true); const std::vector sentBuffer = test.associationListener.ConsumeFirstSentPacket(); std::unique_ptr sentPacket{ RTC::SCTP::Packet::Parse( sentBuffer.data(), sentBuffer.size()) }; REQUIRE(sentPacket); REQUIRE(sentPacket->GetChunksCount() == 1); const auto* sentHeartbeatRequestChunk = sentPacket->GetFirstChunkOfType(); REQUIRE(sentHeartbeatRequestChunk); const auto* sentHeartbeatInfoParameter = sentHeartbeatRequestChunk->GetFirstParameterOfType(); REQUIRE(sentHeartbeatInfoParameter); REQUIRE(sentHeartbeatInfoParameter->HasInfo()); std::unique_ptr receivedHeartbeatAckChunk{ RTC::SCTP::HeartbeatAckChunk::Factory(sctpCommon::FactoryBuffer, test.sctpOptions.mtu) }; auto* receivedHeartbeatInfoParameter = receivedHeartbeatAckChunk->BuildParameterInPlace(); receivedHeartbeatInfoParameter->SetInfo( sentHeartbeatInfoParameter->GetInfo(), sentHeartbeatInfoParameter->GetInfoLength()); receivedHeartbeatInfoParameter->Consolidate(); test.tcbContext.ExpectObserveRttMsCalledTimes(0); // Go backwards in time to make the HEARTBEAT-ACK have an invalid timestamp // in it, as it will be in the future. test.AdvanceTimeMs(-100); test.heartbeatHandler.HandleReceivedHeartbeatAckChunk(receivedHeartbeatAckChunk.get()); REQUIRE_VERIFICATION_RESULT(test.tcbContext.VerifyExpectations()); } SECTION("increases error if heartbeat request is not acked in time") { TestHeartbeatHandler test(HeartbeatIntervalMs); const uint64_t rtoMs{ 105 }; test.tcbContext.WillGetCurrentRtoMsOnce( []() { return rtoMs; }); test.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs); auto* heartbeatIntervalTimer = test.shared.GetBackoffTimer("sctp-heartbeat-interval"); REQUIRE(heartbeatIntervalTimer); REQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == true); // Validate that a request was sent. REQUIRE(test.associationListener.HasSentPackets() == true); test.tcbContext.ExpectIncrementTxErrorCounterCalledTimes(1); test.AdvanceTimeMs(rtoMs); auto* heartbeatTimeoutTimer = test.shared.GetBackoffTimer("sctp-heartbeat-timeout"); REQUIRE(heartbeatTimeoutTimer); REQUIRE(heartbeatTimeoutTimer->EvaluateHasExpired() == true); REQUIRE_VERIFICATION_RESULT(test.tcbContext.VerifyExpectations()); } SECTION("doesn't send heartbeat requests when disabled") { TestHeartbeatHandler test(0); test.AdvanceTimeMs(test.sctpOptions.heartbeatIntervalMs); auto* heartbeatIntervalTimer = test.shared.GetBackoffTimer("sctp-heartbeat-interval"); REQUIRE(heartbeatIntervalTimer); REQUIRE(heartbeatIntervalTimer->EvaluateHasExpired() == false); REQUIRE(test.associationListener.HasSentPackets() == false); } } ================================================ FILE: worker/test/src/RTC/SCTP/association/TestNegotiatedCapabilities.cpp ================================================ #include "common.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/association/NegotiatedCapabilities.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/InitChunk.hpp" #include "RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp" #include "RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp" #include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include SCENARIO("SCTP Negotiated Capabilities", "[sctp][negotiatedcapabilities]") { sctpCommon::ResetBuffers(); SECTION("NegotiatedCapabilities::Factory() succeeds (1)") { RTC::SCTP::SctpOptions sctpOptions{}; sctpOptions.announcedMaxOutboundStreams = 8192; sctpOptions.announcedMaxInboundStreams = 2048; sctpOptions.enablePartialReliability = true; sctpOptions.enableMessageInterleaving = true; sctpOptions.zeroChecksumAlternateErrorDetectionMethod = RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS; auto* remoteChunk = RTC::SCTP::InitChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); remoteChunk->SetNumberOfOutboundStreams(4096); remoteChunk->SetNumberOfInboundStreams(1024); auto* remoteSupportedExtensionsParameter = remoteChunk->BuildParameterInPlace(); remoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::FORWARD_TSN); remoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG); remoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA); remoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN); remoteSupportedExtensionsParameter->Consolidate(); auto* remoteZeroChecksumAcceptableParameter = remoteChunk->BuildParameterInPlace(); remoteZeroChecksumAcceptableParameter->SetAlternateErrorDetectionMethod( RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS); remoteZeroChecksumAcceptableParameter->Consolidate(); auto negotiatedCapabilities = RTC::SCTP::NegotiatedCapabilities::Factory(sctpOptions, remoteChunk); delete remoteChunk; REQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 1024); REQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2048); REQUIRE(negotiatedCapabilities.partialReliability == true); REQUIRE(negotiatedCapabilities.messageInterleaving == true); REQUIRE(negotiatedCapabilities.reConfig == true); REQUIRE(negotiatedCapabilities.zeroChecksum == true); } SECTION("NegotiatedCapabilities::Factory() succeeds (2)") { RTC::SCTP::SctpOptions sctpOptions{}; sctpOptions.announcedMaxOutboundStreams = 1000; sctpOptions.announcedMaxInboundStreams = 2000; sctpOptions.enablePartialReliability = true; sctpOptions.enableMessageInterleaving = true; sctpOptions.zeroChecksumAlternateErrorDetectionMethod = RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS; auto* remoteChunk = RTC::SCTP::InitChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); remoteChunk->SetNumberOfOutboundStreams(4000); remoteChunk->SetNumberOfInboundStreams(3000); auto* remoteSupportedExtensionsParameter = remoteChunk->BuildParameterInPlace(); // NOTE: Missing FORWARD_TSN, but peer announced support for it via // Forward-TSN-Supported Parameter negotiation). // NOTE: Missing RE_CONFIG (needed for Partial Reliability Extension // negotiation). // NOTE: Missing I_FORWARD_TSN (needed for Message Interleaving negotiation). remoteSupportedExtensionsParameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA); remoteSupportedExtensionsParameter->Consolidate(); auto* remoteForwardTsnSupportedParameter = remoteChunk->BuildParameterInPlace(); remoteForwardTsnSupportedParameter->Consolidate(); auto* remoteZeroChecksumAcceptableParameter = remoteChunk->BuildParameterInPlace(); remoteZeroChecksumAcceptableParameter->SetAlternateErrorDetectionMethod( // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) static_cast(666)); remoteZeroChecksumAcceptableParameter->Consolidate(); auto negotiatedCapabilities = RTC::SCTP::NegotiatedCapabilities::Factory(sctpOptions, remoteChunk); delete remoteChunk; REQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 1000); REQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2000); REQUIRE(negotiatedCapabilities.partialReliability == true); REQUIRE(negotiatedCapabilities.messageInterleaving == false); REQUIRE(negotiatedCapabilities.reConfig == false); REQUIRE(negotiatedCapabilities.zeroChecksum == false); } } ================================================ FILE: worker/test/src/RTC/SCTP/association/TestStateCookie.cpp ================================================ #include "common.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" // in worker/test/include/ #include "RTC/SCTP/association/NegotiatedCapabilities.hpp" #include "RTC/SCTP/association/StateCookie.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include #include // std::memset() SCENARIO("SCTP State Cookie", "[sctp][statecookie]") { sctpCommon::ResetBuffers(); SECTION("StateCookie::Parse() succeeds") { // clang-format off uint8_t buffer[] = { // Magic 1: 0x6D73776F726B6572 0x6D, 0x73, 0x77, 0x6F, 0x72, 0x6B, 0x65, 0x72, // Local Verification Tag: 11223344 0x00, 0xAB, 0x41, 0x30, // Remote Verification Tag: 55667788 0x03, 0x51, 0x6C, 0x4C, // Local Initial TSN: 12345678 0x00, 0xBC, 0x61, 0x4E, // Remote Initial TSN: 87654321 0x05, 0x39, 0x7F, 0xB1, // Remote Advertised Receiver Window Credit (a_rwnd): 66666666 0x03, 0xF9, 0x40, 0xAA, // Tie-Tag: 0xABCDEF0011223344 0xAB, 0xCD, 0xEF, 0x00, 0x11, 0x22, 0x33, 0x44, // Negotiated Capabilities // - partialReliability: 1 // - messageInterleaving: 0 // - re-config: 1 // - zeroChecksum: 1 // Magic 2: 0xAD81 0x00, 0b00001101, 0xAD, 0x81, // Max Outbound Streams: 15000, Max Inbound Streams: 2500 0x3A, 0x98, 0x09, 0xC4 }; // clang-format on REQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer, sizeof(buffer)) == true); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer, sizeof(buffer)) == RTC::SCTP::Types::SctpImplementation::MEDIASOUP); auto* stateCookie = RTC::SCTP::StateCookie::Parse(buffer, sizeof(buffer)); REQUIRE(stateCookie); REQUIRE(stateCookie->GetBuffer() == buffer); REQUIRE(stateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(stateCookie->GetBufferLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(stateCookie->GetLocalVerificationTag() == 11223344); REQUIRE(stateCookie->GetRemoteVerificationTag() == 55667788); REQUIRE(stateCookie->GetLocalInitialTsn() == 12345678); REQUIRE(stateCookie->GetRemoteInitialTsn() == 87654321); REQUIRE(stateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 66666666); REQUIRE(stateCookie->GetTieTag() == 0xABCDEF0011223344); REQUIRE( RTC::SCTP::StateCookie::IsMediasoupStateCookie( stateCookie->GetBuffer(), stateCookie->GetLength()) == true); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation( stateCookie->GetBuffer(), stateCookie->GetLength()) == RTC::SCTP::Types::SctpImplementation::MEDIASOUP); auto negotiatedCapabilities = stateCookie->GetNegotiatedCapabilities(); REQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 15000); REQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2500); REQUIRE(negotiatedCapabilities.partialReliability == true); REQUIRE(negotiatedCapabilities.messageInterleaving == false); REQUIRE(negotiatedCapabilities.reConfig == true); REQUIRE(negotiatedCapabilities.zeroChecksum == true); /* Serialize it. */ stateCookie->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); REQUIRE(stateCookie); REQUIRE(stateCookie->GetBuffer() == sctpCommon::SerializeBuffer); REQUIRE(stateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(stateCookie->GetBufferLength() == sizeof(sctpCommon::SerializeBuffer)); REQUIRE(stateCookie->GetLocalVerificationTag() == 11223344); REQUIRE(stateCookie->GetRemoteVerificationTag() == 55667788); REQUIRE(stateCookie->GetLocalInitialTsn() == 12345678); REQUIRE(stateCookie->GetRemoteInitialTsn() == 87654321); REQUIRE(stateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 66666666); REQUIRE(stateCookie->GetTieTag() == 0xABCDEF0011223344); REQUIRE( RTC::SCTP::StateCookie::IsMediasoupStateCookie( stateCookie->GetBuffer(), stateCookie->GetLength()) == true); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation( stateCookie->GetBuffer(), stateCookie->GetLength()) == RTC::SCTP::Types::SctpImplementation::MEDIASOUP); negotiatedCapabilities = stateCookie->GetNegotiatedCapabilities(); REQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 15000); REQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2500); REQUIRE(negotiatedCapabilities.partialReliability == true); REQUIRE(negotiatedCapabilities.messageInterleaving == false); REQUIRE(negotiatedCapabilities.reConfig == true); REQUIRE(negotiatedCapabilities.zeroChecksum == true); /* Clone it. */ auto* clonedStateCookie = stateCookie->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete stateCookie; REQUIRE(clonedStateCookie); REQUIRE(clonedStateCookie->GetBuffer() == sctpCommon::CloneBuffer); REQUIRE(clonedStateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(clonedStateCookie->GetBufferLength() == sizeof(sctpCommon::CloneBuffer)); REQUIRE(clonedStateCookie->GetLocalVerificationTag() == 11223344); REQUIRE(clonedStateCookie->GetRemoteVerificationTag() == 55667788); REQUIRE(clonedStateCookie->GetLocalInitialTsn() == 12345678); REQUIRE(clonedStateCookie->GetRemoteInitialTsn() == 87654321); REQUIRE(clonedStateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 66666666); REQUIRE(clonedStateCookie->GetTieTag() == 0xABCDEF0011223344); REQUIRE( RTC::SCTP::StateCookie::IsMediasoupStateCookie( clonedStateCookie->GetBuffer(), clonedStateCookie->GetLength()) == true); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation( clonedStateCookie->GetBuffer(), clonedStateCookie->GetLength()) == RTC::SCTP::Types::SctpImplementation::MEDIASOUP); negotiatedCapabilities = clonedStateCookie->GetNegotiatedCapabilities(); REQUIRE(negotiatedCapabilities.negotiatedMaxOutboundStreams == 15000); REQUIRE(negotiatedCapabilities.negotiatedMaxInboundStreams == 2500); REQUIRE(negotiatedCapabilities.partialReliability == true); REQUIRE(negotiatedCapabilities.messageInterleaving == false); REQUIRE(negotiatedCapabilities.reConfig == true); REQUIRE(negotiatedCapabilities.zeroChecksum == true); delete clonedStateCookie; } SECTION("StateCookie::Parse() fails") { // Wrong Magic 1. // clang-format off uint8_t buffer1[] = { // Magic 1: 0x6D73776F726B6573 (wrong) 0x6D, 0x73, 0x77, 0x6F, 0x72, 0x6B, 0x65, 0x73, // Local Verification Tag: 11223344 0x00, 0xAB, 0x41, 0x30, // Remote Verification Tag: 55667788 0x03, 0x51, 0x6C, 0x4C, // Local Initial TSN: 12345678 0x00, 0xBC, 0x61, 0x4E, // Remote Initial TSN: 87654321 0x05, 0x39, 0x7F, 0xB1, // Remote Advertised Receiver Window Credit (a_rwnd): 66666666 0x03, 0xF9, 0x40, 0xAA, // Tie-Tag: 0xABCDEF0011223344 0xAB, 0xCD, 0xEF, 0x00, 0x11, 0x22, 0x33, 0x44, // Negotiated Capabilities // - partialReliability: 1 // - messageInterleaving: 0 // - re-config: 1 // - zeroChecksum: 1 // Magic 2: 0xAD81 0x00, 0b00001101, 0xAD, 0x81, // Max Outbound Streams: 15000, Max Inbound Streams: 2500 0x3A, 0x98, 0x09, 0xC4 }; // clang-format on REQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer1, sizeof(buffer1)) == false); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer1, sizeof(buffer1)) == RTC::SCTP::Types::SctpImplementation::UNKNOWN); REQUIRE(!RTC::SCTP::StateCookie::Parse(buffer1, sizeof(buffer1))); // Wrong Magic 2. // clang-format off uint8_t buffer2[] = { // Magic 1: 0x6D73776F726B6572 0x6D, 0x73, 0x77, 0x6F, 0x72, 0x6B, 0x65, 0x72, // Local Verification Tag: 11223344 0x00, 0xAB, 0x41, 0x30, // Remote Verification Tag: 55667788 0x03, 0x51, 0x6C, 0x4C, // Local Initial TSN: 12345678 0x00, 0xBC, 0x61, 0x4E, // Remote Initial TSN: 87654321 0x05, 0x39, 0x7F, 0xB1, // Remote Advertised Receiver Window Credit (a_rwnd): 66666666 0x03, 0xF9, 0x40, 0xAA, // Tie-Tag: 0xABCDEF0011223344 0xAB, 0xCD, 0xEF, 0x00, 0x11, 0x22, 0x33, 0x44, // Negotiated Capabilities // - partialReliability: 1 // - messageInterleaving: 0 // - re-config: 1 // - zeroChecksum: 1 // Magic 2: 0xAD82 (instead of 0xAD81) 0x00, 0b00001101, 0xAD, 0x82, // Max Outbound Streams: 15000, Max Inbound Streams: 2500 0x3A, 0x98, 0x09, 0xC4 }; // clang-format on REQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer2, sizeof(buffer2)) == false); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer2, sizeof(buffer2)) == RTC::SCTP::Types::SctpImplementation::MEDIASOUP); REQUIRE(!RTC::SCTP::StateCookie::Parse(buffer2, sizeof(buffer2))); // Buffer too big. // clang-format off uint8_t buffer3[] = { // Magic 1: 0x6D73776F726B6572 0x6D, 0x73, 0x77, 0x6F, 0x72, 0x6B, 0x65, 0x72, // Local Verification Tag: 11223344 0x00, 0xAB, 0x41, 0x30, // Remote Verification Tag: 55667788 0x03, 0x51, 0x6C, 0x4C, // Local Initial TSN: 12345678 0x00, 0xBC, 0x61, 0x4E, // Remote Initial TSN: 87654321 0x05, 0x39, 0x7F, 0xB1, // Remote Advertised Receiver Window Credit (a_rwnd): 66666666 0x03, 0xF9, 0x40, 0xAA, // Tie-Tag: 0xABCDEF0011223344 0xAB, 0xCD, 0xEF, 0x00, 0x11, 0x22, 0x33, 0x44, // Negotiated Capabilities // - partialReliability: 1 // - messageInterleaving: 0 // - re-config: 1 // - zeroChecksum: 1 // Magic 2: 0xAD81 0x00, 0b00001101, 0xAD, 0x81, // Max Outbound Streams: 15000, Max Inbound Streams: 2500 0x3A, 0x98, 0x09, 0xC4, // Extra bytes that shouldn't be here. 0x11, 0x22, 0x33, 0x44 }; // clang-format on REQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer3, sizeof(buffer3)) == false); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer3, sizeof(buffer3)) == RTC::SCTP::Types::SctpImplementation::MEDIASOUP); REQUIRE(!RTC::SCTP::StateCookie::Parse(buffer3, sizeof(buffer3))); } SECTION("StateCookie::Factory() succeeds") { RTC::SCTP::NegotiatedCapabilities negotiatedCapabilities = { .negotiatedMaxOutboundStreams = 62000, .negotiatedMaxInboundStreams = 55555, .partialReliability = true, .messageInterleaving = true, .reConfig = true, .zeroChecksum = false }; auto* stateCookie = RTC::SCTP::StateCookie::Factory( /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*localVerificationTag*/ 6660666, /*remoteVerificationTag*/ 9990999, /*localInitialTsn*/ 1110111, /*remoteInitialTsn*/ 2220222, /*remoteAdvertisedReceiverWindowCredit*/ 999909999, /*tieTag*/ 1111222233334444, negotiatedCapabilities); // Change values of the original NegotiatedCapabilities to assert that it // doesn't affect the internals of StateCookie. negotiatedCapabilities.partialReliability = false; negotiatedCapabilities.negotiatedMaxOutboundStreams = 1024; REQUIRE(stateCookie); REQUIRE(stateCookie->GetBuffer() == sctpCommon::FactoryBuffer); REQUIRE(stateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(stateCookie->GetBufferLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(stateCookie->GetLocalVerificationTag() == 6660666); REQUIRE(stateCookie->GetRemoteVerificationTag() == 9990999); REQUIRE(stateCookie->GetLocalInitialTsn() == 1110111); REQUIRE(stateCookie->GetRemoteInitialTsn() == 2220222); REQUIRE(stateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 999909999); REQUIRE(stateCookie->GetTieTag() == 1111222233334444); REQUIRE( RTC::SCTP::StateCookie::IsMediasoupStateCookie( stateCookie->GetBuffer(), stateCookie->GetLength()) == true); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation( stateCookie->GetBuffer(), stateCookie->GetLength()) == RTC::SCTP::Types::SctpImplementation::MEDIASOUP); const auto retrievedNegotiatedCapabilities = stateCookie->GetNegotiatedCapabilities(); REQUIRE(retrievedNegotiatedCapabilities.negotiatedMaxOutboundStreams == 62000); REQUIRE(retrievedNegotiatedCapabilities.negotiatedMaxInboundStreams == 55555); REQUIRE(retrievedNegotiatedCapabilities.partialReliability == true); REQUIRE(retrievedNegotiatedCapabilities.messageInterleaving == true); REQUIRE(retrievedNegotiatedCapabilities.reConfig == true); REQUIRE(retrievedNegotiatedCapabilities.zeroChecksum == false); /* Parse itself and compare. */ auto* parsedStateCookie = RTC::SCTP::StateCookie::Parse(stateCookie->GetBuffer(), stateCookie->GetLength()); delete stateCookie; REQUIRE(parsedStateCookie); REQUIRE(parsedStateCookie->GetBuffer() == sctpCommon::FactoryBuffer); REQUIRE(parsedStateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(parsedStateCookie->GetBufferLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(parsedStateCookie->GetLocalVerificationTag() == 6660666); REQUIRE(parsedStateCookie->GetRemoteVerificationTag() == 9990999); REQUIRE(parsedStateCookie->GetLocalInitialTsn() == 1110111); REQUIRE(parsedStateCookie->GetRemoteInitialTsn() == 2220222); REQUIRE(parsedStateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 999909999); REQUIRE(parsedStateCookie->GetTieTag() == 1111222233334444); REQUIRE( RTC::SCTP::StateCookie::IsMediasoupStateCookie( parsedStateCookie->GetBuffer(), parsedStateCookie->GetLength()) == true); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation( parsedStateCookie->GetBuffer(), parsedStateCookie->GetLength()) == RTC::SCTP::Types::SctpImplementation::MEDIASOUP); const auto retrievedParsedNegotiatedCapabilities = parsedStateCookie->GetNegotiatedCapabilities(); REQUIRE(retrievedParsedNegotiatedCapabilities.negotiatedMaxOutboundStreams == 62000); REQUIRE(retrievedParsedNegotiatedCapabilities.negotiatedMaxInboundStreams == 55555); REQUIRE(retrievedParsedNegotiatedCapabilities.partialReliability == true); REQUIRE(retrievedParsedNegotiatedCapabilities.messageInterleaving == true); REQUIRE(retrievedParsedNegotiatedCapabilities.reConfig == true); REQUIRE(retrievedParsedNegotiatedCapabilities.zeroChecksum == false); delete parsedStateCookie; } SECTION("StateCookie::Write() succeeds") { RTC::SCTP::NegotiatedCapabilities negotiatedCapabilities = { .negotiatedMaxOutboundStreams = 62000, .negotiatedMaxInboundStreams = 55555, .partialReliability = true, .messageInterleaving = true, .reConfig = true, .zeroChecksum = false }; auto* buffer = sctpCommon::FactoryBuffer; RTC::SCTP::StateCookie::Write( /*buffer*/ buffer, /*bufferLength*/ RTC::SCTP::StateCookie::StateCookieLength, /*localVerificationTag*/ 6660666, /*remoteVerificationTag*/ 9990999, /*localInitialTsn*/ 1110111, /*remoteInitialTsn*/ 2220222, /*remoteAdvertisedReceiverWindowCredit*/ 999909999, /*tieTag*/ 1111222233334444, negotiatedCapabilities); // Change values of the original NegotiatedCapabilities to assert that it // doesn't affect the internals of StateCookie. negotiatedCapabilities.partialReliability = false; negotiatedCapabilities.negotiatedMaxOutboundStreams = 1024; /* Parse the buffer. */ auto* stateCookie = RTC::SCTP::StateCookie::Parse(buffer, RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(stateCookie); REQUIRE(stateCookie->GetBuffer() == buffer); REQUIRE(stateCookie->GetLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(stateCookie->GetBufferLength() == RTC::SCTP::StateCookie::StateCookieLength); REQUIRE(stateCookie->GetLocalVerificationTag() == 6660666); REQUIRE(stateCookie->GetRemoteVerificationTag() == 9990999); REQUIRE(stateCookie->GetLocalInitialTsn() == 1110111); REQUIRE(stateCookie->GetRemoteInitialTsn() == 2220222); REQUIRE(stateCookie->GetRemoteAdvertisedReceiverWindowCredit() == 999909999); REQUIRE(stateCookie->GetTieTag() == 1111222233334444); REQUIRE( RTC::SCTP::StateCookie::IsMediasoupStateCookie( stateCookie->GetBuffer(), stateCookie->GetLength()) == true); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation( stateCookie->GetBuffer(), stateCookie->GetLength()) == RTC::SCTP::Types::SctpImplementation::MEDIASOUP); const auto retrievedNegotiatedCapabilities = stateCookie->GetNegotiatedCapabilities(); REQUIRE(retrievedNegotiatedCapabilities.negotiatedMaxOutboundStreams == 62000); REQUIRE(retrievedNegotiatedCapabilities.negotiatedMaxInboundStreams == 55555); REQUIRE(retrievedNegotiatedCapabilities.partialReliability == true); REQUIRE(retrievedNegotiatedCapabilities.messageInterleaving == true); REQUIRE(retrievedNegotiatedCapabilities.reConfig == true); REQUIRE(retrievedNegotiatedCapabilities.zeroChecksum == false); delete stateCookie; } SECTION("StateCookie::DetermineSctpImplementation() succeeds") { // usrsctp generated State Cookie. // clang-format off uint8_t buffer1[] = { // Magic 1: 0x4B414D452D425344 0x4B, 0x41, 0x4D, 0x45, 0x2D, 0x42, 0x53, 0x44, 0x11, 0x22, 0x33, 0x44, 0x6D, 0x73, 0x77, 0x6F, 0x72, 0x6B, 0x65, 0x72, 0x11, 0x22, 0x33, 0x44, 0x00, 0xAB, 0x41, 0x30, 0x03, 0x51, 0x6C, 0x4C, 0x00, 0xBC, 0x61, 0x4E, 0x11, 0x22, 0x33, 0x44, 0x05, 0x39, 0x7F, 0xB1, 0x03, 0xF9, 0x40, 0xAA, 0xAB, 0xCD, 0xEF, 0x00, 0x11, 0x22, 0x33, 0x44, // etc }; // clang-format on REQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer1, sizeof(buffer1)) == false); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer1, sizeof(buffer1)) == RTC::SCTP::Types::SctpImplementation::USRSCTP); // dcSCTP generated State Cookie. // clang-format off uint8_t buffer2[] = { // Magic 1: 0x6463534354503030 0x64, 0x63, 0x53, 0x43, 0x54, 0x50, 0x30, 0x30, 0x5D, 0x0E, 0x21, 0xE4, 0x0F, 0xA8, 0x44, 0x3F, 0x11, 0x80, 0x89, 0x5D, 0x2F, 0x4E, 0x17, 0x1F, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, }; // clang-format on REQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer2, sizeof(buffer2)) == false); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer2, sizeof(buffer2)) == RTC::SCTP::Types::SctpImplementation::DCSCTP); // State Cookie generated by unknown implementation. // clang-format off uint8_t buffer3[] = { // Magic 1: 0x1122334455667788 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x11, 0x80, 0x89, 0x5D, 0x2F, 0x4E, 0x17, 0x1F, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, }; // clang-format on REQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer3, sizeof(buffer3)) == false); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer3, sizeof(buffer3)) == RTC::SCTP::Types::SctpImplementation::UNKNOWN); // Too short State Cookie so we don't know. // clang-format off uint8_t buffer4[] = { // Magic 1: 0xAABBCCDD 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on REQUIRE(RTC::SCTP::StateCookie::IsMediasoupStateCookie(buffer4, sizeof(buffer4)) == false); REQUIRE( RTC::SCTP::StateCookie::DetermineSctpImplementation(buffer4, sizeof(buffer4)) == RTC::SCTP::Types::SctpImplementation::UNKNOWN); } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/TestChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp" #include "RTC/SCTP/packet/chunks/InitChunk.hpp" #include "RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp" #include "RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp" #include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp" #include SCENARIO("SCTP Chunk", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("alignof() SCTP structs") { REQUIRE(alignof(RTC::SCTP::Chunk::ChunkHeader) == 2); REQUIRE(alignof(RTC::SCTP::Chunk::ChunkFlags) == 1); } SECTION("BuildParameterInPlace() and AddParameter() throw if the Chunk needs consolidation") { std::unique_ptr chunk{ RTC::SCTP::InitChunk::Factory( sctpCommon::FactoryBuffer, 1000) }; REQUIRE(chunk->NeedsConsolidation() == false); const auto* parameter1 = chunk->BuildParameterInPlace(); REQUIRE(chunk->NeedsConsolidation() == true); // We didn't call parameter1->Consolidate() yet so this must throw. REQUIRE_THROWS_AS( chunk->BuildParameterInPlace(), MediaSoupError); const auto* parameter2 = RTC::SCTP::ZeroChecksumAcceptableParameter::Factory( sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer)); // We didn't call parameter1->Consolidate() yet so this must throw. REQUIRE_THROWS_AS(chunk->AddParameter(parameter2), MediaSoupError); delete parameter2; parameter1->Consolidate(); REQUIRE(chunk->NeedsConsolidation() == false); // This shouldn't throw now. const auto* parameter3 = chunk->BuildParameterInPlace(); REQUIRE(chunk->NeedsConsolidation() == true); parameter3->Consolidate(); REQUIRE(chunk->NeedsConsolidation() == false); const auto* parameter4 = RTC::SCTP::ZeroChecksumAcceptableParameter::Factory( sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer)); // This shouldn't throw now. chunk->AddParameter(parameter4); REQUIRE(chunk->NeedsConsolidation() == false); delete parameter4; } SECTION("BuildErrorCauseInPlace() and AddErrorCause() throw if the Chunk needs consolidation") { std::unique_ptr chunk{ RTC::SCTP::AbortAssociationChunk::Factory(sctpCommon::FactoryBuffer, 1000) }; REQUIRE(chunk->NeedsConsolidation() == false); const auto* errorCause1 = chunk->BuildErrorCauseInPlace(); REQUIRE(chunk->NeedsConsolidation() == true); // We didn't call errorCause1->Consolidate() yet so this must throw. REQUIRE_THROWS_AS( chunk->BuildErrorCauseInPlace(), MediaSoupError); const auto* errorCause2 = RTC::SCTP::ProtocolViolationErrorCause::Factory( sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer)); // We didn't call errorCause1->Consolidate() yet so this must throw. REQUIRE_THROWS_AS(chunk->AddErrorCause(errorCause2), MediaSoupError); delete errorCause2; errorCause1->Consolidate(); REQUIRE(chunk->NeedsConsolidation() == false); // This shouldn't throw now. const auto* errorCause3 = chunk->BuildErrorCauseInPlace(); REQUIRE(chunk->NeedsConsolidation() == true); errorCause3->Consolidate(); REQUIRE(chunk->NeedsConsolidation() == false); const auto* errorCause4 = RTC::SCTP::ProtocolViolationErrorCause::Factory( sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer)); // This shouldn't throw now. chunk->AddErrorCause(errorCause4); REQUIRE(chunk->NeedsConsolidation() == false); delete errorCause4; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/TestErrorCause.cpp ================================================ #include "common.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include SCENARIO("SCTP Error Cause", "[serializable][sctp][errorcause]") { SECTION("alignof() SCTP structs") { REQUIRE(alignof(RTC::SCTP::ErrorCause::ErrorCauseHeader) == 2); } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/TestPacket.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/chunks/DataChunk.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp" #include "RTC/SCTP/packet/chunks/InitChunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp" #include "RTC/SCTP/packet/chunks/UnknownChunk.hpp" #include "RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp" #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp" #include "RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp" #include #include // std::memset() SCENARIO("SCTP Packet", "[serializable][sctp][packet]") { sctpCommon::ResetBuffers(); SECTION("alignof() SCTP structs") { REQUIRE(alignof(RTC::SCTP::Packet::CommonHeader) == 4); } SECTION("Parse() without Chunks succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Source Port: 10000, Destination Port: 15999 0x27, 0x10, 0x3E, 0x7F, // Verification Tag: 4294967285 0xFF, 0xFF, 0xFF, 0xF5, // Checksum: 5 0x00, 0x00, 0x00, 0x05 }; // clang-format on std::unique_ptr packet{ RTC::SCTP::Packet::Parse(buffer, sizeof(buffer)) }; // NOTE: Obviously the Checksum CRC32C validation fails since Checksum is // totally random. CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*sourcePort*/ 10000, /*destinationPort*/ 15999, /*verificationTag*/ 4294967285, /*checksum*/ 5, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 0); REQUIRE(packet->GetFirstChunkOfType() == nullptr); /* Serialize it. */ packet->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*sourcePort*/ 10000, /*destinationPort*/ 15999, /*verificationTag*/ 4294967285, /*checksum*/ 5, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 0); REQUIRE(packet->GetFirstChunkOfType() == nullptr); /* Insert CRC32C checksum. */ CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*sourcePort*/ 10000, /*destinationPort*/ 15999, /*verificationTag*/ 4294967285, /*checksum*/ 5, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 0); /* Clone it. */ packet.reset(packet->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer))); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*sourcePort*/ 10000, /*destinationPort*/ 15999, /*verificationTag*/ 4294967285, /*checksum*/ 5, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 0); REQUIRE(packet->GetFirstChunkOfType() == nullptr); } SECTION("Parse() with Chunks succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Source Port: 10000, Destination Port: 15999 0x27, 0x10, 0x3E, 0x7F, // Verification Tag: 4294967285 0xFF, 0xFF, 0xFF, 0xF5, // Checksum: 5 0x00, 0x00, 0x00, 0x05, // Chunk 1: Type:0 (DATA), I:1, U:0, B:1, E:1, Length: 18 0x00, 0b00001011, 0x00, 0x12, // TSN: 0x11223344, 0x11, 0x22, 0x33, 0x44, // Stream Identifier S: 0xFF00, Stream Sequence Number n: 0x6677 0xFF, 0x00, 0x66, 0x77, // Payload Protocol Identifier: 0x12341234 0x12, 0x34, 0x12, 0x34, // User Data (2 bytes): 0xABCD, 2 bytes of padding 0xAB, 0xCD, 0x00, 0x00, // Chunk 2: Type:0xEE (UNKNOWN), Flags: 0b00001100, Length: 7 0xEE, 0b00001100, 0x00, 0x07, // Unknown data: 0xAABBCC, 1 byte of padding 0xAA, 0xBB, 0xCC, 0x00, // Chunk 3: Type:5 (HEARTBEAT_ACK), Flags:0b00000000, Length: 10 // NOTE: Chunk Length field must exclude padding of the last Parameter. 0x05, 0b00000000, 0x00, 0x0A, // Parameter 1: Type:1 (HEARBEAT_INFO), Length: 6 0x00, 0x01, 0x00, 0x06, // Heartbeat Information (2 bytes): 0x1122, 2 bytes of padding 0x11, 0x22, 0x00, 0x00, }; // clang-format on std::unique_ptr packet{ RTC::SCTP::Packet::Parse(buffer, sizeof(buffer)) }; CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 52, /*sourcePort*/ 10000, /*destinationPort*/ 15999, /*verificationTag*/ 4294967285, /*checksum*/ 5, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 3); REQUIRE(packet->GetFirstChunkOfType() != nullptr); REQUIRE(packet->GetFirstChunkOfType() != nullptr); REQUIRE(packet->GetFirstChunkOfType() != nullptr); REQUIRE(packet->GetFirstChunkOfType() == nullptr); REQUIRE(packet->GetFirstChunkOfType() == nullptr); REQUIRE(packet->GetFirstChunkOfType() == nullptr); const auto* chunk1 = reinterpret_cast(packet->GetChunkAt(0)); REQUIRE(packet->GetFirstChunkOfType() == chunk1); CHECK_SCTP_CHUNK( /*chunk*/ chunk1, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00001011, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk1->GetI() == true); REQUIRE(chunk1->GetU() == false); REQUIRE(chunk1->GetB() == true); REQUIRE(chunk1->GetE() == true); REQUIRE(chunk1->GetTsn() == 0x11223344); REQUIRE(chunk1->GetStreamId() == 0xFF00); REQUIRE(chunk1->GetStreamSequenceNumber() == 0x6677); REQUIRE(chunk1->GetPayloadProtocolId() == 0x12341234); REQUIRE(chunk1->HasUserDataPayload() == true); REQUIRE(chunk1->GetUserDataPayloadLength() == 2); REQUIRE(chunk1->GetUserDataPayload()[0] == 0xAB); REQUIRE(chunk1->GetUserDataPayload()[1] == 0xCD); const auto* chunk2 = reinterpret_cast(packet->GetChunkAt(1)); REQUIRE(packet->GetFirstChunkOfType() == chunk2); CHECK_SCTP_CHUNK( /*chunk*/ chunk2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*chunkType*/ static_cast(0xEE), /*unknownType*/ true, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00001100, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk2->HasUnknownValue() == true); REQUIRE(chunk2->GetUnknownValueLength() == 3); REQUIRE(chunk2->GetUnknownValue()[0] == 0xAA); REQUIRE(chunk2->GetUnknownValue()[1] == 0xBB); REQUIRE(chunk2->GetUnknownValue()[2] == 0xCC); // Padding. REQUIRE(chunk2->GetUnknownValue()[3] == 0x00); const auto* chunk3 = reinterpret_cast(packet->GetChunkAt(2)); REQUIRE(packet->GetFirstChunkOfType() == chunk3); CHECK_SCTP_CHUNK( /*chunk*/ chunk3, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 1, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); // NOLINTNEXTLINE (readability-identifier-naming) const auto* parameter3_1 = reinterpret_cast(chunk3->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter3_1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter3_1->HasInfo() == true); REQUIRE(parameter3_1->GetInfoLength() == 2); REQUIRE(parameter3_1->GetInfo()[0] == 0x11); REQUIRE(parameter3_1->GetInfo()[1] == 0x22); // These should be padding. REQUIRE(parameter3_1->GetInfo()[2] == 0x00); REQUIRE(parameter3_1->GetInfo()[3] == 0x00); /* Serialize it. */ packet->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 52, /*sourcePort*/ 10000, /*destinationPort*/ 15999, /*verificationTag*/ 4294967285, /*checksum*/ 5, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 3); REQUIRE(packet->GetFirstChunkOfType() == chunk1); REQUIRE(packet->GetFirstChunkOfType() == chunk2); REQUIRE(packet->GetFirstChunkOfType() == chunk3); REQUIRE(packet->GetFirstChunkOfType() == nullptr); REQUIRE(packet->GetFirstChunkOfType() == nullptr); REQUIRE(packet->GetFirstChunkOfType() == nullptr); chunk1 = reinterpret_cast(packet->GetChunkAt(0)); CHECK_SCTP_CHUNK( /*chunk*/ chunk1, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00001011, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk1->GetI() == true); REQUIRE(chunk1->GetU() == false); REQUIRE(chunk1->GetB() == true); REQUIRE(chunk1->GetE() == true); REQUIRE(chunk1->GetTsn() == 0x11223344); REQUIRE(chunk1->GetStreamId() == 0xFF00); REQUIRE(chunk1->GetStreamSequenceNumber() == 0x6677); REQUIRE(chunk1->GetPayloadProtocolId() == 0x12341234); REQUIRE(chunk1->HasUserDataPayload() == true); REQUIRE(chunk1->GetUserDataPayloadLength() == 2); REQUIRE(chunk1->GetUserDataPayload()[0] == 0xAB); REQUIRE(chunk1->GetUserDataPayload()[1] == 0xCD); chunk2 = reinterpret_cast(packet->GetChunkAt(1)); CHECK_SCTP_CHUNK( /*chunk*/ chunk2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*chunkType*/ static_cast(0xEE), /*unknownType*/ true, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00001100, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk2->HasUnknownValue() == true); REQUIRE(chunk2->GetUnknownValueLength() == 3); REQUIRE(chunk2->GetUnknownValue()[0] == 0xAA); REQUIRE(chunk2->GetUnknownValue()[1] == 0xBB); REQUIRE(chunk2->GetUnknownValue()[2] == 0xCC); // Padding. REQUIRE(chunk2->GetUnknownValue()[3] == 0x00); chunk3 = reinterpret_cast(packet->GetChunkAt(2)); CHECK_SCTP_CHUNK( /*chunk*/ chunk3, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 1, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); parameter3_1 = reinterpret_cast(chunk3->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter3_1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter3_1->HasInfo() == true); REQUIRE(parameter3_1->GetInfoLength() == 2); REQUIRE(parameter3_1->GetInfo()[0] == 0x11); REQUIRE(parameter3_1->GetInfo()[1] == 0x22); // These should be padding. REQUIRE(parameter3_1->GetInfo()[2] == 0x00); REQUIRE(parameter3_1->GetInfo()[3] == 0x00); /* Clone it. */ packet.reset(packet->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer))); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 52, /*sourcePort*/ 10000, /*destinationPort*/ 15999, /*verificationTag*/ 4294967285, /*checksum*/ 5, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 3); REQUIRE(packet->GetFirstChunkOfType() != nullptr); REQUIRE(packet->GetFirstChunkOfType() != nullptr); REQUIRE(packet->GetFirstChunkOfType() != nullptr); REQUIRE(packet->GetFirstChunkOfType() == nullptr); REQUIRE(packet->GetFirstChunkOfType() == nullptr); REQUIRE(packet->GetFirstChunkOfType() == nullptr); chunk1 = reinterpret_cast(packet->GetChunkAt(0)); REQUIRE(packet->GetFirstChunkOfType() == chunk1); CHECK_SCTP_CHUNK( /*chunk*/ chunk1, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00001011, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk1->GetI() == true); REQUIRE(chunk1->GetU() == false); REQUIRE(chunk1->GetB() == true); REQUIRE(chunk1->GetE() == true); REQUIRE(chunk1->GetTsn() == 0x11223344); REQUIRE(chunk1->GetStreamId() == 0xFF00); REQUIRE(chunk1->GetStreamSequenceNumber() == 0x6677); REQUIRE(chunk1->GetPayloadProtocolId() == 0x12341234); REQUIRE(chunk1->HasUserDataPayload() == true); REQUIRE(chunk1->GetUserDataPayloadLength() == 2); REQUIRE(chunk1->GetUserDataPayload()[0] == 0xAB); REQUIRE(chunk1->GetUserDataPayload()[1] == 0xCD); chunk2 = reinterpret_cast(packet->GetChunkAt(1)); REQUIRE(packet->GetFirstChunkOfType() == chunk2); CHECK_SCTP_CHUNK( /*chunk*/ chunk2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*chunkType*/ static_cast(0xEE), /*unknownType*/ true, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00001100, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk2->HasUnknownValue() == true); REQUIRE(chunk2->GetUnknownValueLength() == 3); REQUIRE(chunk2->GetUnknownValue()[0] == 0xAA); REQUIRE(chunk2->GetUnknownValue()[1] == 0xBB); REQUIRE(chunk2->GetUnknownValue()[2] == 0xCC); // Padding. REQUIRE(chunk2->GetUnknownValue()[3] == 0x00); chunk3 = reinterpret_cast(packet->GetChunkAt(2)); REQUIRE(packet->GetFirstChunkOfType() == chunk3); CHECK_SCTP_CHUNK( /*chunk*/ chunk3, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 1, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); parameter3_1 = reinterpret_cast(chunk3->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter3_1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter3_1->HasInfo() == true); REQUIRE(parameter3_1->GetInfoLength() == 2); REQUIRE(parameter3_1->GetInfo()[0] == 0x11); REQUIRE(parameter3_1->GetInfo()[1] == 0x22); // These should be padding. REQUIRE(parameter3_1->GetInfo()[2] == 0x00); REQUIRE(parameter3_1->GetInfo()[3] == 0x00); } SECTION("Factory() with Chunks succeeds") { std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) }; CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*sourcePort*/ 0, /*destinationPort*/ 0, /*verificationTag*/ 0, /*checksum*/ 0, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 0); REQUIRE(packet->GetFirstChunkOfType() == nullptr); /* Modify the Packet and add Chunks. */ packet->SetSourcePort(1000); packet->SetDestinationPort(6000); packet->SetVerificationTag(12345678); packet->SetChecksum(0); // Chunk 1: INIT, length: 20 bytes. auto* chunk1 = packet->BuildChunkInPlace(); chunk1->SetInitiateTag(87654321); chunk1->SetAdvertisedReceiverWindowCredit(12345678); chunk1->SetNumberOfOutboundStreams(11100); chunk1->SetNumberOfInboundStreams(22200); chunk1->SetInitialTsn(14141414); // Parameter 1.1: IPV4_ADDRESS, length: 8 bytes. // NOLINTNEXTLINE (readability-identifier-naming) auto* parameter1_1 = chunk1->BuildParameterInPlace(); // 192.168.0.3 IPv4 in network order. uint8_t ipBuffer[] = { 0xC0, 0xA8, 0x00, 0x03 }; parameter1_1->SetIPv4Address(ipBuffer); parameter1_1->Consolidate(); REQUIRE(chunk1->GetFirstParameterOfType() == parameter1_1); // Parameter 1.2: COOKIE_PRESERVATIVE, length: 8 bytes. // NOLINTNEXTLINE (readability-identifier-naming) auto* parameter1_2 = chunk1->BuildParameterInPlace(); parameter1_2->SetLifeSpanIncrement(987654321); parameter1_2->Consolidate(); REQUIRE(chunk1->GetFirstParameterOfType() == parameter1_2); // Consolidate Chunk 1 after consolidating its Parameters 1.1 and 1.2. chunk1->Consolidate(); REQUIRE(chunk1->GetFirstParameterOfType() == parameter1_1); REQUIRE(chunk1->GetFirstParameterOfType() == parameter1_2); REQUIRE(packet->GetFirstChunkOfType() == chunk1); // Chunk 2: HEARTBEAT_REQUEST, length: 4 bytes. auto* chunk2 = packet->BuildChunkInPlace(); // Parameter 2.1: HEARTBEAT_INFO, length: 4 bytes. // NOLINTNEXTLINE (readability-identifier-naming) auto* parameter2_1 = chunk2->BuildParameterInPlace(); // Parameter 2.1: Add 3 bytes of info + 1 byte of padding. parameter2_1->SetInfo(sctpCommon::DataBuffer, 3); parameter2_1->Consolidate(); REQUIRE(chunk2->GetFirstParameterOfType() == parameter2_1); std::memset(sctpCommon::DataBuffer, 0xFF, 3); // Consolidate the Chunk after consolidating its Parameters. chunk2->Consolidate(); REQUIRE(chunk2->GetFirstParameterOfType() == parameter2_1); REQUIRE(packet->GetFirstChunkOfType() == chunk2); // Insert CRC32C checksum. packet->WriteCRC32cChecksum(); auto crc32cChecksum = packet->GetChecksum(); // Packet length must be: // - Packet header: 12 // - Chunk 1: 20 // - Parameter 1.1: 8 // - Parameter 1.2: 8 // - Chunk 2: 4 // - Parameter 2.1: 4 + 3 + 1 = 8 // - Total: 60 CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 60, /*sourcePort*/ 1000, /*destinationPort*/ 6000, /*verificationTag*/ 12345678, /*checksum*/ crc32cChecksum, /*hasValidCrc32cChecksum*/ true, /*chunksCount*/ 2); /* Serialize the Packet. */ packet->Serialize(sctpCommon::SerializeBuffer, packet->GetLength()); std::memset(sctpCommon::FactoryBuffer, 0xAA, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ 60, /*length*/ 60, /*sourcePort*/ 1000, /*destinationPort*/ 6000, /*verificationTag*/ 12345678, /*checksum*/ crc32cChecksum, /*hasValidCrc32cChecksum*/ true, /*chunksCount*/ 2); REQUIRE(packet->GetFirstChunkOfType() == chunk1); REQUIRE(packet->GetFirstChunkOfType() == chunk2); /* Clone the Packet. */ packet.reset(packet->Clone(sctpCommon::CloneBuffer, packet->GetLength())); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); const auto* obtainedChunk1 = reinterpret_cast(packet->GetChunkAt(0)); // NOLINTNEXTLINE (readability-identifier-naming) const auto* obtainedParameter1_1 = reinterpret_cast(obtainedChunk1->GetParameterAt(0)); // NOLINTNEXTLINE (readability-identifier-naming) const auto* obtainedParameter1_2 = reinterpret_cast( obtainedChunk1->GetParameterAt(1)); const auto* obtainedChunk2 = reinterpret_cast(packet->GetChunkAt(1)); // NOLINTNEXTLINE (readability-identifier-naming) const auto* obtainedParameter2_1 = reinterpret_cast(obtainedChunk2->GetParameterAt(0)); CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ 60, /*length*/ 60, /*sourcePort*/ 1000, /*destinationPort*/ 6000, /*verificationTag*/ 12345678, /*checksum*/ crc32cChecksum, /*hasValidCrc32cChecksum*/ true, /*chunksCount*/ 2); REQUIRE(packet->GetFirstChunkOfType() == obtainedChunk1); REQUIRE(packet->GetFirstChunkOfType() == obtainedChunk2); CHECK_SCTP_CHUNK( /*chunk*/ obtainedChunk1, /*buffer*/ nullptr, /*bufferLength*/ 20 + 8 + 8, /*length*/ 20 + 8 + 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(obtainedChunk1->GetInitiateTag() == 87654321); REQUIRE(obtainedChunk1->GetAdvertisedReceiverWindowCredit() == 12345678); REQUIRE(obtainedChunk1->GetNumberOfOutboundStreams() == 11100); REQUIRE(obtainedChunk1->GetNumberOfInboundStreams() == 22200); REQUIRE(obtainedChunk1->GetInitialTsn() == 14141414); CHECK_SCTP_PARAMETER( /*parameter*/ obtainedParameter1_1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(obtainedParameter1_1->GetIPv4Address()[0] == 0xC0); REQUIRE(obtainedParameter1_1->GetIPv4Address()[1] == 0xA8); REQUIRE(obtainedParameter1_1->GetIPv4Address()[2] == 0x00); REQUIRE(obtainedParameter1_1->GetIPv4Address()[3] == 0x03); CHECK_SCTP_PARAMETER( /*parameter*/ obtainedParameter1_2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(obtainedParameter1_2->GetLifeSpanIncrement() == 987654321); CHECK_SCTP_CHUNK( /*chunk*/ obtainedChunk2, /*buffer*/ nullptr, /*bufferLength*/ 4 + 8, /*length*/ 4 + 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 1, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); CHECK_SCTP_PARAMETER( /*parameter*/ obtainedParameter2_1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(obtainedParameter2_1->HasInfo() == true); REQUIRE(obtainedParameter2_1->GetInfoLength() == 3); REQUIRE(obtainedParameter2_1->GetInfo()[0] == 0x00); REQUIRE(obtainedParameter2_1->GetInfo()[1] == 0x01); REQUIRE(obtainedParameter2_1->GetInfo()[2] == 0x02); } SECTION("Factory() using AddChunk() succeeds") { std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, 1000) }; packet->SetSourcePort(1); packet->SetDestinationPort(2); packet->SetVerificationTag(3); packet->SetChecksum(4); // 4 bytes Chunk. auto* chunk1 = RTC::SCTP::ShutdownCompleteChunk::Factory(sctpCommon::FactoryBuffer + 1000, 1000); chunk1->SetT(true); packet->AddChunk(chunk1); REQUIRE(packet->GetFirstChunkOfType() != nullptr); // NOTE: The stored Chunk is not the same than the given one since it's // internally cloned. REQUIRE(packet->GetFirstChunkOfType() != chunk1); // Once added, we can delete the Chunk. delete chunk1; // Packet length must be: // - Packet header: 12 // - Chunk 1: 4 // - Total: 16 CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 1000, /*length*/ 16, /*sourcePort*/ 1, /*destinationPort*/ 2, /*verificationTag*/ 3, /*checksum*/ 4, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 1); const auto* obtainedChunk1 = reinterpret_cast(packet->GetChunkAt(0)); REQUIRE(packet->GetFirstChunkOfType() == obtainedChunk1); CHECK_SCTP_CHUNK( /*chunk*/ obtainedChunk1, /*buffer*/ nullptr, /*bufferLength*/ 4, /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(obtainedChunk1->GetT() == true); } SECTION("BuildChunkInPlace() throws if given Chunk exceeds Packet buffer length") { std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, 28) }; CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 28, /*length*/ 12, /*sourcePort*/ 0, /*destinationPort*/ 0, /*verificationTag*/ 0, /*checksum*/ 0, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 0); // Chunk 1: DATA, length: 16 bytes. auto* chunk1 = packet->BuildChunkInPlace(); // Adding user data 10 bytes, must throw. REQUIRE_THROWS_AS(chunk1->SetUserDataPayload(sctpCommon::DataBuffer, 10), MediaSoupError); delete chunk1; // Chunk 2: INIT, length: 20 bytes. Must throw. REQUIRE_THROWS_AS(packet->BuildChunkInPlace(), MediaSoupError); CHECK_SCTP_PACKET( /*packet*/ packet.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 28, /*length*/ 12, /*sourcePort*/ 0, /*destinationPort*/ 0, /*verificationTag*/ 0, /*checksum*/ 0, /*hasValidCrc32cChecksum*/ false, /*chunksCount*/ 0); } SECTION("BuildChunkInPlace() and AddChunk() throw if the Packet needs consolidation") { std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, 1000) }; REQUIRE(packet->NeedsConsolidation() == false); const auto* chunk1 = packet->BuildChunkInPlace(); REQUIRE(packet->NeedsConsolidation() == true); // We didn't call chunk1->Consolidate() yet so this must throw. REQUIRE_THROWS_AS(packet->BuildChunkInPlace(), MediaSoupError); const auto* chunk2 = RTC::SCTP::ShutdownCompleteChunk::Factory( sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer)); // We didn't call chunk1->Consolidate() yet so this must throw. REQUIRE_THROWS_AS(packet->AddChunk(chunk2), MediaSoupError); delete chunk2; chunk1->Consolidate(); REQUIRE(packet->NeedsConsolidation() == false); // This shouldn't throw now. const auto* chunk3 = packet->BuildChunkInPlace(); REQUIRE(packet->NeedsConsolidation() == true); chunk3->Consolidate(); REQUIRE(packet->NeedsConsolidation() == false); const auto* chunk4 = RTC::SCTP::ShutdownCompleteChunk::Factory( sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer)); // This shouldn't throw now. packet->AddChunk(chunk4); REQUIRE(packet->NeedsConsolidation() == false); delete chunk4; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/TestParameter.cpp ================================================ #include "common.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include SCENARIO("SCTP Parameter", "[serializable][sctp][parameter]") { SECTION("alignof() SCTP structs") { REQUIRE(alignof(RTC::SCTP::Parameter::ParameterHeader) == 2); } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestAbortAssociationChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/chunks/AbortAssociationChunk.hpp" #include "RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp" #include #include // std::memset() SCENARIO("SCTP Abort Association Chunk (6)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("AbortAssociationChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:6 (ABORT), Flags:0b00000000, Length: 12 0x06, 0b00000001, 0x00, 0x0C, // Error Cause 3: Code:1 (STALE_COOKIE), Length: 8 0x00, 0x03, 0x00, 0x08, // Measure of Staleness: 0x12345678 0x12, 0x34, 0x56, 0x78, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* chunk = RTC::SCTP::AbortAssociationChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 1); REQUIRE(chunk->GetT() == true); const auto* errorCause1 = reinterpret_cast(chunk->GetErrorCauseAt(0)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownType*/ false); REQUIRE(errorCause1->GetMeasureOfStaleness() == 0x12345678); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 1); errorCause1 = reinterpret_cast(chunk->GetErrorCauseAt(0)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownType*/ false); REQUIRE(errorCause1->GetMeasureOfStaleness() == 0x12345678); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 1); errorCause1 = reinterpret_cast(clonedChunk->GetErrorCauseAt(0)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownType*/ false); REQUIRE(errorCause1->GetMeasureOfStaleness() == 0x12345678); delete clonedChunk; } SECTION("AbortAssociationChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::AbortAssociationChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 0); REQUIRE(chunk->GetT() == false); /* Modify it and add Error Causes. */ chunk->SetT(true); auto* errorCause1 = chunk->BuildErrorCauseInPlace(); errorCause1->SetMeasureOfStaleness(666); errorCause1->Consolidate(); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4 + (4 + 4), /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 1); REQUIRE(chunk->GetT() == true); const auto* addedErrorCause1 = reinterpret_cast(chunk->GetErrorCauseAt(0)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ addedErrorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(errorCause1->GetMeasureOfStaleness() == 666); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::AbortAssociationChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4 + (4 + 4), /*length*/ 4 + (4 + 4), /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 1); REQUIRE(parsedChunk->GetT() == true); const auto* parsedErrorCause1 = reinterpret_cast(parsedChunk->GetErrorCauseAt(0)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(parsedErrorCause1->GetMeasureOfStaleness() == 666); delete parsedChunk; } SECTION("AbortAssociationChunk::Factory() with AddErrorCause() succeeds") { auto* chunk = RTC::SCTP::AbortAssociationChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); chunk->SetT(true); // 8 bytes Error Cause. auto* errorCause1 = RTC::SCTP::StaleCookieErrorCause::Factory( sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer)); errorCause1->SetMeasureOfStaleness(666666); chunk->AddErrorCause(errorCause1); // Once added, we can delete the Error Cause. delete errorCause1; // Chunk length must be: // - Chunk header: 4 // - Error Cause 1: 8 // - Total: 12 CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 1); REQUIRE(chunk->GetT() == true); const auto* obtainedErrorCause1 = reinterpret_cast(chunk->GetErrorCauseAt(0)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ obtainedErrorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(obtainedErrorCause1->GetMeasureOfStaleness() == 666666); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::AbortAssociationChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::ABORT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 1); REQUIRE(parsedChunk->GetT() == true); obtainedErrorCause1 = reinterpret_cast(parsedChunk->GetErrorCauseAt(0)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ obtainedErrorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(obtainedErrorCause1->GetMeasureOfStaleness() == 666666); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestCookieAckChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/CookieAckChunk.hpp" #include #include // std::memset() SCENARIO("SCTP Cookie Acknowledgement Chunk (11)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("CookieAckChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:11 (COOKIE_ACK), Flags:0x00000001, T: 1, Length: 4 0x0B, 0b00000101, 0x00, 0x04, // Extra bytes that should be ignored 0xAA }; // clang-format on auto* chunk = RTC::SCTP::CookieAckChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000101, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000101, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000101, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); delete clonedChunk; } SECTION("RTC::SCTP::CookieAckChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::CookieAckChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::CookieAckChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4, /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestCookieEchoChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/CookieEchoChunk.hpp" #include #include // std::memset() SCENARIO("SCTP Cookie Echo Chunk (10)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("CookieEchoChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:A (COOKIE_ECHO), Flags: 0b00000000, Length: 9 0x0A, 0b00000000, 0x00, 0x09, // Cookie: 0x1122334455, 0x11, 0x22, 0x33, 0x44, // 3 bytes of padding 0x55, 0x00, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* chunk = RTC::SCTP::CookieEchoChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->HasCookie() == true); REQUIRE(chunk->GetCookieLength() == 5); REQUIRE(chunk->GetCookie()[0] == 0x11); REQUIRE(chunk->GetCookie()[1] == 0x22); REQUIRE(chunk->GetCookie()[2] == 0x33); REQUIRE(chunk->GetCookie()[3] == 0x44); REQUIRE(chunk->GetCookie()[4] == 0x55); // This should be padding. REQUIRE(chunk->GetCookie()[5] == 0x00); REQUIRE(chunk->GetCookie()[6] == 0x00); REQUIRE(chunk->GetCookie()[7] == 0x00); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->HasCookie() == true); REQUIRE(chunk->GetCookieLength() == 5); REQUIRE(chunk->GetCookie()[0] == 0x11); REQUIRE(chunk->GetCookie()[1] == 0x22); REQUIRE(chunk->GetCookie()[2] == 0x33); REQUIRE(chunk->GetCookie()[3] == 0x44); REQUIRE(chunk->GetCookie()[4] == 0x55); // This should be padding. REQUIRE(chunk->GetCookie()[5] == 0x00); REQUIRE(chunk->GetCookie()[6] == 0x00); REQUIRE(chunk->GetCookie()[7] == 0x00); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(clonedChunk->HasCookie() == true); REQUIRE(clonedChunk->GetCookieLength() == 5); REQUIRE(clonedChunk->GetCookie()[0] == 0x11); REQUIRE(clonedChunk->GetCookie()[1] == 0x22); REQUIRE(clonedChunk->GetCookie()[2] == 0x33); REQUIRE(clonedChunk->GetCookie()[3] == 0x44); REQUIRE(clonedChunk->GetCookie()[4] == 0x55); // This should be padding. REQUIRE(clonedChunk->GetCookie()[5] == 0x00); REQUIRE(clonedChunk->GetCookie()[6] == 0x00); REQUIRE(clonedChunk->GetCookie()[7] == 0x00); delete clonedChunk; } SECTION("CookieEchoChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::CookieEchoChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->HasCookie() == false); REQUIRE(chunk->GetCookieLength() == 0); /* Modify it. */ // Verify that replacing the value works. chunk->SetCookie(sctpCommon::DataBuffer + 1000, 2999); REQUIRE(chunk->GetLength() == 3004); REQUIRE(chunk->HasCookie() == true); REQUIRE(chunk->GetCookieLength() == 2999); chunk->SetCookie(nullptr, 0); REQUIRE(chunk->GetLength() == 4); REQUIRE(chunk->HasCookie() == false); REQUIRE(chunk->GetCookieLength() == 0); // 3 bytes + 1 byte of padding. chunk->SetCookie(sctpCommon::DataBuffer, 3); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->HasCookie() == true); REQUIRE(chunk->GetCookieLength() == 3); REQUIRE(chunk->GetCookie()[0] == 0x00); REQUIRE(chunk->GetCookie()[1] == 0x01); REQUIRE(chunk->GetCookie()[2] == 0x02); // This should be padding. REQUIRE(chunk->GetCookie()[3] == 0x00); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::CookieEchoChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(parsedChunk->HasCookie() == true); REQUIRE(parsedChunk->GetCookieLength() == 3); REQUIRE(parsedChunk->GetCookie()[0] == 0x00); REQUIRE(parsedChunk->GetCookie()[1] == 0x01); REQUIRE(parsedChunk->GetCookie()[2] == 0x02); // This should be padding. REQUIRE(parsedChunk->GetCookie()[3] == 0x00); delete parsedChunk; } SECTION("CookieEchoChunk::SetCookie() throws if userDataLength is too big") { auto* chunk = RTC::SCTP::CookieEchoChunk::Factory(sctpCommon::ThrowBuffer, sizeof(sctpCommon::ThrowBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::ThrowBuffer, /*bufferLength*/ sizeof(sctpCommon::ThrowBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE_THROWS_AS(chunk->SetCookie(sctpCommon::ThrowBuffer, 65535), MediaSoupError); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::ThrowBuffer, /*bufferLength*/ sizeof(sctpCommon::ThrowBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::COOKIE_ECHO, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); delete chunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestDataChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/DataChunk.hpp" #include "RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp" #include #include // std::memset() #include SCENARIO("SCTP Payload Data Chunk (0)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("DataChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:0 (DATA), I:1, U:0, B:1, E:1, Length: 19 0x00, 0b00001011, 0x00, 0x13, // TSN: 0x11223344, 0x11, 0x22, 0x33, 0x44, // Stream Identifier S: 0xFF00, Stream Sequence Number n: 0x6677 0xFF, 0x00, 0x66, 0x77, // Payload Protocol Identifier: 0x12341234 0x12, 0x34, 0x12, 0x34, // User Data (3 bytes): 0xABCDEF, 1 byte of padding 0xAB, 0xCD, 0xEF, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* chunk = RTC::SCTP::DataChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00001011, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == true); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == true); REQUIRE(chunk->GetE() == true); REQUIRE(chunk->GetTsn() == 0x11223344); REQUIRE(chunk->GetStreamId() == 0xFF00); REQUIRE(chunk->GetStreamSequenceNumber() == 0x6677); REQUIRE(chunk->GetPayloadProtocolId() == 0x12341234); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3); REQUIRE(chunk->GetUserDataPayload()[0] == 0xAB); REQUIRE(chunk->GetUserDataPayload()[1] == 0xCD); REQUIRE(chunk->GetUserDataPayload()[2] == 0xEF); // This should be padding. REQUIRE(chunk->GetUserDataPayload()[3] == 0x00); auto userData = chunk->MakeUserData(); std::vector expectedPayload = { 0xAB, 0xCD, 0xEF }; REQUIRE(userData.GetStreamId() == 0xFF00); REQUIRE(userData.GetStreamSequenceNumber() == 0x6677); REQUIRE(userData.GetMessageId() == 0); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 0x12341234); // NOTE: clang-tidy doesn't understand that this is fine. // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00001011, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == true); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == true); REQUIRE(chunk->GetE() == true); REQUIRE(chunk->GetTsn() == 0x11223344); REQUIRE(chunk->GetStreamId() == 0xFF00); REQUIRE(chunk->GetStreamSequenceNumber() == 0x6677); REQUIRE(chunk->GetPayloadProtocolId() == 0x12341234); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3); REQUIRE(chunk->GetUserDataPayload()[0] == 0xAB); REQUIRE(chunk->GetUserDataPayload()[1] == 0xCD); REQUIRE(chunk->GetUserDataPayload()[2] == 0xEF); // This should be padding. REQUIRE(chunk->GetUserDataPayload()[3] == 0x00); userData = chunk->MakeUserData(); REQUIRE(userData.GetStreamId() == 0xFF00); REQUIRE(userData.GetStreamSequenceNumber() == 0x6677); REQUIRE(userData.GetMessageId() == 0); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 0x12341234); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00001011, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(clonedChunk->GetI() == true); REQUIRE(clonedChunk->GetU() == false); REQUIRE(clonedChunk->GetB() == true); REQUIRE(clonedChunk->GetE() == true); REQUIRE(clonedChunk->GetTsn() == 0x11223344); REQUIRE(clonedChunk->GetStreamId() == 0xFF00); REQUIRE(clonedChunk->GetStreamSequenceNumber() == 0x6677); REQUIRE(clonedChunk->GetPayloadProtocolId() == 0x12341234); REQUIRE(clonedChunk->HasUserDataPayload() == true); REQUIRE(clonedChunk->GetUserDataPayloadLength() == 3); REQUIRE(clonedChunk->GetUserDataPayload()[0] == 0xAB); REQUIRE(clonedChunk->GetUserDataPayload()[1] == 0xCD); REQUIRE(clonedChunk->GetUserDataPayload()[2] == 0xEF); // This should be padding. REQUIRE(clonedChunk->GetUserDataPayload()[3] == 0x00); userData = clonedChunk->MakeUserData(); REQUIRE(userData.GetStreamId() == 0xFF00); REQUIRE(userData.GetStreamSequenceNumber() == 0x6677); REQUIRE(userData.GetMessageId() == 0); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 0x12341234); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); delete clonedChunk; } SECTION("DataChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::DataChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == false); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == false); REQUIRE(chunk->GetE() == false); REQUIRE(chunk->GetTsn() == 0); REQUIRE(chunk->GetStreamId() == 0); REQUIRE(chunk->GetStreamSequenceNumber() == 0); REQUIRE(chunk->GetPayloadProtocolId() == 0); REQUIRE(chunk->HasUserDataPayload() == false); REQUIRE(chunk->GetUserDataPayloadLength() == 0); auto userData = chunk->MakeUserData(); std::vector expectedPayload = {}; REQUIRE(userData.GetStreamId() == 0); REQUIRE(userData.GetStreamSequenceNumber() == 0); REQUIRE(userData.GetMessageId() == 0); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 0); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); /* Modify it. */ chunk->SetI(true); chunk->SetE(true); chunk->SetTsn(12345678); chunk->SetStreamId(9988); chunk->SetStreamSequenceNumber(2211); chunk->SetPayloadProtocolId(987654321); // Verify that replacing the value works. chunk->SetUserDataPayload(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(chunk->GetLength() == 3016); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3000); chunk->SetUserDataPayload(nullptr, 0); REQUIRE(chunk->GetLength() == 16); REQUIRE(chunk->HasUserDataPayload() == false); REQUIRE(chunk->GetUserDataPayloadLength() == 0); // 3 bytes + 1 byte of padding. chunk->SetUserDataPayload(sctpCommon::DataBuffer, 3); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16 + 3 + 1, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00001001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == true); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == false); REQUIRE(chunk->GetE() == true); REQUIRE(chunk->GetTsn() == 12345678); REQUIRE(chunk->GetStreamId() == 9988); REQUIRE(chunk->GetStreamSequenceNumber() == 2211); REQUIRE(chunk->GetPayloadProtocolId() == 987654321); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3); REQUIRE(chunk->GetUserDataPayload()[0] == 0x00); REQUIRE(chunk->GetUserDataPayload()[1] == 0x01); REQUIRE(chunk->GetUserDataPayload()[2] == 0x02); // Last byte must be a zero byte padding. REQUIRE(chunk->GetUserDataPayload()[3] == 0x00); userData = chunk->MakeUserData(); expectedPayload = { 0x00, 0x01, 0x02 }; REQUIRE(userData.GetStreamId() == 9988); REQUIRE(userData.GetStreamSequenceNumber() == 2211); REQUIRE(userData.GetMessageId() == 0); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 987654321); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::DataChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 16 + 3 + 1, /*length*/ 16 + 3 + 1, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00001001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(parsedChunk->GetI() == true); REQUIRE(parsedChunk->GetU() == false); REQUIRE(parsedChunk->GetB() == false); REQUIRE(parsedChunk->GetE() == true); REQUIRE(parsedChunk->GetTsn() == 12345678); REQUIRE(parsedChunk->GetStreamId() == 9988); REQUIRE(parsedChunk->GetStreamSequenceNumber() == 2211); REQUIRE(parsedChunk->GetPayloadProtocolId() == 987654321); REQUIRE(parsedChunk->HasUserDataPayload() == true); REQUIRE(parsedChunk->GetUserDataPayloadLength() == 3); REQUIRE(parsedChunk->GetUserDataPayload()[0] == 0x00); REQUIRE(parsedChunk->GetUserDataPayload()[1] == 0x01); REQUIRE(parsedChunk->GetUserDataPayload()[2] == 0x02); // Last byte must be a zero byte padding. REQUIRE(parsedChunk->GetUserDataPayload()[3] == 0x00); userData = parsedChunk->MakeUserData(); REQUIRE(userData.GetStreamId() == 9988); REQUIRE(userData.GetStreamSequenceNumber() == 2211); REQUIRE(userData.GetMessageId() == 0); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 987654321); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); delete parsedChunk; } SECTION("DataChunk::SetUserDataPayload() throws if userDataLength is too big") { auto* chunk = RTC::SCTP::DataChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE_THROWS_AS(chunk->SetUserDataPayload(sctpCommon::DataBuffer, 65535), MediaSoupError); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); delete chunk; } SECTION("DataChunk::SetUserData() succeeds") { auto* chunk = RTC::SCTP::DataChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); RTC::SCTP::UserData userData( /*streamId*/ 123, /*ssn*/ 4444, /*mid*/ 0, // Not in DATA chunks. /*fsn*/ 0, // Not in DATA chunks. /*ppid*/ 56789, /*payload*/ { 1, 2, 3, 4 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ true); REQUIRE(userData.GetStreamId() == 123); REQUIRE(userData.GetStreamSequenceNumber() == 4444); REQUIRE(userData.GetMessageId() == 0); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 56789); REQUIRE(userData.GetPayloadLength() == 4); REQUIRE(userData.GetPayload()[0] == 1); REQUIRE(userData.GetPayload()[1] == 2); REQUIRE(userData.GetPayload()[2] == 3); REQUIRE(userData.GetPayload()[3] == 4); REQUIRE(userData.IsBeginning() == true); REQUIRE(userData.IsEnd() == true); REQUIRE(userData.IsUnordered() == true); chunk->SetUserData(std::move(userData)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16 + 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000111, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); auto gotUserData = chunk->MakeUserData(); std::vector expectedPayload = { 1, 2, 3, 4 }; REQUIRE(gotUserData.GetStreamId() == 123); REQUIRE(gotUserData.GetStreamSequenceNumber() == 4444); REQUIRE(gotUserData.GetMessageId() == 0); REQUIRE(gotUserData.GetFragmentSequenceNumber() == 0); REQUIRE(gotUserData.GetPayloadProtocolId() == 56789); REQUIRE(gotUserData.GetPayloadLength() == 4); REQUIRE(gotUserData.GetPayload()[0] == 1); REQUIRE(gotUserData.GetPayload()[1] == 2); REQUIRE(gotUserData.GetPayload()[2] == 3); REQUIRE(gotUserData.GetPayload()[3] == 4); REQUIRE(gotUserData.IsBeginning() == true); REQUIRE(gotUserData.IsEnd() == true); REQUIRE(gotUserData.IsUnordered() == true); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(gotUserData).ReleasePayload() == expectedPayload); delete chunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestForwardTsnChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp" #include #include // std::memset() #include SCENARIO("Forward Cumulative TSN Chunk (192)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("ForwardTsnChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:192 (FORWARD_TSN), Flags: 0b00000000, Length: 16 0xC0, 0b00000000, 0x00, 0x10, // New Cumulative TSN: 287454020, 0x11, 0x22, 0x33, 0x44, // Stream 1: 4660, Stream Sequence 1: 17185 0x12, 0x34, 0x43, 0x21, // Stream 2: 22136, Stream Sequence 2: 34661 0x56, 0x78, 0x87, 0x65, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* chunk = RTC::SCTP::ForwardTsnChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 16, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetNewCumulativeTsn() == 287454020); REQUIRE(chunk->GetNumberOfSkippedStreams() == 2); REQUIRE( chunk->GetSkippedStreams() == std::vector{ { 4660, 17185 }, { 22136, 34661 } }); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 16, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetNewCumulativeTsn() == 287454020); REQUIRE(chunk->GetNumberOfSkippedStreams() == 2); REQUIRE( chunk->GetSkippedStreams() == std::vector{ { 4660, 17185 }, { 22136, 34661 } }); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 16, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(clonedChunk->GetNewCumulativeTsn() == 287454020); REQUIRE(clonedChunk->GetNumberOfSkippedStreams() == 2); REQUIRE( clonedChunk->GetSkippedStreams() == std::vector{ { 4660, 17185 }, { 22136, 34661 } }); delete clonedChunk; } SECTION("ForwardTsnChunk::Parse() fails") { // Length field is not even. // clang-format off alignas(4) uint8_t buffer1[] = { // Type:192 (FORWARD_TSN), Flags: 0b00000000, Length: 14 (should be 16) 0xC0, 0b00000000, 0x00, 0x0E, // New Cumulative TSN: 287454020, 0x11, 0x22, 0x33, 0x44, // Stream 1: 4660, Stream Sequence 1: 17185 0x12, 0x34, 0x43, 0x21, // Stream 2: 22136, Stream Sequence 2 (missing in Length field) 0x56, 0x78, 0x87, 0x65, }; // clang-format on REQUIRE(!RTC::SCTP::ForwardTsnChunk::Parse(buffer1, sizeof(buffer1))); } SECTION("ForwardTsnChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::ForwardTsnChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetNewCumulativeTsn() == 0); REQUIRE(chunk->GetNumberOfSkippedStreams() == 0); REQUIRE(chunk->GetSkippedStreams().empty()); /* Modify it. */ chunk->SetNewCumulativeTsn(1234); chunk->AddSkippedStream( RTC::SCTP::AnyForwardTsnChunk::SkippedStream{ /*streamId*/ 1111, /*ssn*/ 11110 }); chunk->AddSkippedStream( RTC::SCTP::AnyForwardTsnChunk::SkippedStream{ /*streamId*/ 2222, /*ssn*/ 22220 }); chunk->AddSkippedStream( RTC::SCTP::AnyForwardTsnChunk::SkippedStream{ /*streamId*/ 3333, /*ssn*/ 33330 }); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetNewCumulativeTsn() == 1234); REQUIRE(chunk->GetNumberOfSkippedStreams() == 3); REQUIRE( chunk->GetSkippedStreams() == std::vector{ { 1111, 11110 }, { 2222, 22220 }, { 3333, 33330 }, }); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::ForwardTsnChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 20, /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(parsedChunk->GetNewCumulativeTsn() == 1234); REQUIRE(parsedChunk->GetNumberOfSkippedStreams() == 3); REQUIRE( parsedChunk->GetSkippedStreams() == std::vector{ { 1111, 11110 }, { 2222, 22220 }, { 3333, 33330 }, }); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestHeartbeatAckChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatAckChunk.hpp" #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp" #include "RTC/SCTP/packet/parameters/UnknownParameter.hpp" #include #include // std::memset() SCENARIO("SCTP Hearbeat Acknowledgement Chunk (5)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("HeartbeatAckChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type: 5 (HEARTBEAT_ACK), Flags:0b00000000, Length: 22 // NOTE: Chunk Length field must exclude padding of the last Parameter. 0x05, 0b00000000, 0x00, 0x16, // Parameter 1: Type:1 (HEARBEAT_INFO), Length: 11 0x00, 0x01, 0x00, 0x0B, // Heartbeat Information (7 bytes): 0x11223344556677 0x11, 0x22, 0x33, 0x44, // 1 byte of padding 0x55, 0x66, 0x77, 0x00, // Parameter 2: Type:49159 (UNKNOWN), Length: 6 0xC0, 0x07, 0x00, 0x06, // Unknown data: 0xABCD, 2 bytes of padding 0xAB, 0xCD, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, }; // clang-format on auto* chunk = RTC::SCTP::HeartbeatAckChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->HasInfo() == true); REQUIRE(parameter1->GetInfoLength() == 7); REQUIRE(parameter1->GetInfo()[0] == 0x11); REQUIRE(parameter1->GetInfo()[1] == 0x22); REQUIRE(parameter1->GetInfo()[2] == 0x33); REQUIRE(parameter1->GetInfo()[3] == 0x44); REQUIRE(parameter1->GetInfo()[4] == 0x55); REQUIRE(parameter1->GetInfo()[5] == 0x66); REQUIRE(parameter1->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter1->GetInfo()[7] == 0x00); const auto* parameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter2->GetUnknownValue()[0] == 0xAB); REQUIRE(parameter2->GetUnknownValue()[1] == 0xCD); // These should be padding. REQUIRE(parameter2->GetUnknownValue()[2] == 0x00); REQUIRE(parameter2->GetUnknownValue()[3] == 0x00); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->HasInfo() == true); REQUIRE(parameter1->GetInfoLength() == 7); REQUIRE(parameter1->GetInfo()[0] == 0x11); REQUIRE(parameter1->GetInfo()[1] == 0x22); REQUIRE(parameter1->GetInfo()[2] == 0x33); REQUIRE(parameter1->GetInfo()[3] == 0x44); REQUIRE(parameter1->GetInfo()[4] == 0x55); REQUIRE(parameter1->GetInfo()[5] == 0x66); REQUIRE(parameter1->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter1->GetInfo()[7] == 0x00); parameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter2->GetUnknownValue()[0] == 0xAB); REQUIRE(parameter2->GetUnknownValue()[1] == 0xCD); // These should be padding. REQUIRE(parameter2->GetUnknownValue()[2] == 0x00); REQUIRE(parameter2->GetUnknownValue()[3] == 0x00); /* Clone it. */ const auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); parameter1 = reinterpret_cast(clonedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->HasInfo() == true); REQUIRE(parameter1->GetInfoLength() == 7); REQUIRE(parameter1->GetInfo()[0] == 0x11); REQUIRE(parameter1->GetInfo()[1] == 0x22); REQUIRE(parameter1->GetInfo()[2] == 0x33); REQUIRE(parameter1->GetInfo()[3] == 0x44); REQUIRE(parameter1->GetInfo()[4] == 0x55); REQUIRE(parameter1->GetInfo()[5] == 0x66); REQUIRE(parameter1->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter1->GetInfo()[7] == 0x00); parameter2 = reinterpret_cast(clonedChunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter2->GetUnknownValue()[0] == 0xAB); REQUIRE(parameter2->GetUnknownValue()[1] == 0xCD); // These should be padding. REQUIRE(parameter2->GetUnknownValue()[2] == 0x00); REQUIRE(parameter2->GetUnknownValue()[3] == 0x00); delete clonedChunk; } SECTION("HeartbeatAckChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::HeartbeatAckChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); /* Modify it by adding Parameters. */ auto* parameter1 = chunk->BuildParameterInPlace(); // Info length is 5 so 3 bytes of padding will be added. parameter1->SetInfo(sctpCommon::DataBuffer, 5); parameter1->Consolidate(); // Let's add another RTC::SCTP::HeartbeatInfoParameter (it doesn't make sense but // anyway). auto* parameter2 = chunk->BuildParameterInPlace(); // Info length is 2 so 2 bytes of padding will be added. parameter2->SetInfo(sctpCommon::DataBuffer, 2); parameter2->Consolidate(); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2), /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* addedParameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ addedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(addedParameter1->HasInfo() == true); REQUIRE(addedParameter1->GetInfoLength() == 5); REQUIRE(addedParameter1->GetInfo()[0] == 0x00); REQUIRE(addedParameter1->GetInfo()[1] == 0x01); REQUIRE(addedParameter1->GetInfo()[2] == 0x02); REQUIRE(addedParameter1->GetInfo()[3] == 0x03); REQUIRE(addedParameter1->GetInfo()[4] == 0x04); // These should be padding. REQUIRE(addedParameter1->GetInfo()[5] == 0x00); REQUIRE(addedParameter1->GetInfo()[6] == 0x00); const auto* addedParameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ addedParameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(addedParameter2->HasInfo() == true); REQUIRE(addedParameter2->GetInfoLength() == 2); REQUIRE(addedParameter2->GetInfo()[0] == 0x00); REQUIRE(addedParameter2->GetInfo()[1] == 0x01); // These should be padding. REQUIRE(addedParameter2->GetInfo()[2] == 0x00); REQUIRE(addedParameter2->GetInfo()[3] == 0x00); /* Parse itself and compare. */ const auto* parsedChunk = RTC::SCTP::HeartbeatAckChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4 + (4 + 5 + 3) + (4 + 2 + 2), /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2), /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* parsedParameter1 = reinterpret_cast(parsedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter1->HasInfo() == true); REQUIRE(parsedParameter1->GetInfoLength() == 5); REQUIRE(parsedParameter1->GetInfo()[0] == 0x00); REQUIRE(parsedParameter1->GetInfo()[1] == 0x01); REQUIRE(parsedParameter1->GetInfo()[2] == 0x02); REQUIRE(parsedParameter1->GetInfo()[3] == 0x03); REQUIRE(parsedParameter1->GetInfo()[4] == 0x04); // These should be padding. REQUIRE(parsedParameter1->GetInfo()[5] == 0x00); REQUIRE(parsedParameter1->GetInfo()[6] == 0x00); const auto* parsedParameter2 = reinterpret_cast(parsedChunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter2->HasInfo() == true); REQUIRE(parsedParameter2->GetInfoLength() == 2); REQUIRE(parsedParameter2->GetInfo()[0] == 0x00); REQUIRE(parsedParameter2->GetInfo()[1] == 0x01); // These should be padding. REQUIRE(parsedParameter2->GetInfo()[2] == 0x00); REQUIRE(parsedParameter2->GetInfo()[3] == 0x00); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestHeartbeatRequestChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/chunks/HeartbeatRequestChunk.hpp" #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp" #include "RTC/SCTP/packet/parameters/UnknownParameter.hpp" #include #include // std::memset() SCENARIO("SCTP Hearbeat Request Chunk (4)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("HeartbeatRequestChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:4 (HEARTBEAT_REQUEST), Flags:0b00000000, Length: 22 // NOTE: Length field must exclude the padding of the last Parameter. 0x04, 0b00000000, 0x00, 0x16, // Parameter 1: Type:1 (HEARBEAT_INFO), Length: 11 0x00, 0x01, 0x00, 0x0B, // Heartbeat Information (7 bytes): 0x11223344556677 0x11, 0x22, 0x33, 0x44, // 1 byte of padding 0x55, 0x66, 0x77, 0x00, // Parameter 2: Type:49159 (UNKNOWN), Length: 6 0xC0, 0x07, 0x00, 0x06, // Unknown data: 0xABCD, 2 bytes of padding 0xAB, 0xCD, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* chunk = RTC::SCTP::HeartbeatRequestChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->HasInfo() == true); REQUIRE(parameter1->GetInfoLength() == 7); REQUIRE(parameter1->GetInfo()[0] == 0x11); REQUIRE(parameter1->GetInfo()[1] == 0x22); REQUIRE(parameter1->GetInfo()[2] == 0x33); REQUIRE(parameter1->GetInfo()[3] == 0x44); REQUIRE(parameter1->GetInfo()[4] == 0x55); REQUIRE(parameter1->GetInfo()[5] == 0x66); REQUIRE(parameter1->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter1->GetInfo()[7] == 0x00); const auto* parameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter2->HasUnknownValue() == true); REQUIRE(parameter2->GetUnknownValueLength() == 2); REQUIRE(parameter2->GetUnknownValue()[0] == 0xAB); REQUIRE(parameter2->GetUnknownValue()[1] == 0xCD); // These should be padding. REQUIRE(parameter2->GetUnknownValue()[2] == 0x00); REQUIRE(parameter2->GetUnknownValue()[3] == 0x00); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->HasInfo() == true); REQUIRE(parameter1->GetInfoLength() == 7); REQUIRE(parameter1->GetInfo()[0] == 0x11); REQUIRE(parameter1->GetInfo()[1] == 0x22); REQUIRE(parameter1->GetInfo()[2] == 0x33); REQUIRE(parameter1->GetInfo()[3] == 0x44); REQUIRE(parameter1->GetInfo()[4] == 0x55); REQUIRE(parameter1->GetInfo()[5] == 0x66); REQUIRE(parameter1->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter1->GetInfo()[7] == 0x00); parameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter2->HasUnknownValue() == true); REQUIRE(parameter2->GetUnknownValueLength() == 2); REQUIRE(parameter2->GetUnknownValue()[0] == 0xAB); REQUIRE(parameter2->GetUnknownValue()[1] == 0xCD); // These should be padding. REQUIRE(parameter2->GetUnknownValue()[2] == 0x00); REQUIRE(parameter2->GetUnknownValue()[3] == 0x00); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); parameter1 = reinterpret_cast(clonedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->HasInfo() == true); REQUIRE(parameter1->GetInfoLength() == 7); REQUIRE(parameter1->GetInfo()[0] == 0x11); REQUIRE(parameter1->GetInfo()[1] == 0x22); REQUIRE(parameter1->GetInfo()[2] == 0x33); REQUIRE(parameter1->GetInfo()[3] == 0x44); REQUIRE(parameter1->GetInfo()[4] == 0x55); REQUIRE(parameter1->GetInfo()[5] == 0x66); REQUIRE(parameter1->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter1->GetInfo()[7] == 0x00); parameter2 = reinterpret_cast(clonedChunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter2->HasUnknownValue() == true); REQUIRE(parameter2->GetUnknownValueLength() == 2); REQUIRE(parameter2->GetUnknownValue()[0] == 0xAB); REQUIRE(parameter2->GetUnknownValue()[1] == 0xCD); // These should be padding. REQUIRE(parameter2->GetUnknownValue()[2] == 0x00); REQUIRE(parameter2->GetUnknownValue()[3] == 0x00); delete clonedChunk; } SECTION("HeartbeatRequestChunk::Parse() with incorrect but valid Chunk Length field succeeds") { // Here the chunk has incorrect Chunk Length field with value 24 instead of // 22. It's incorrect because, as per RFC 9260: // // > The Chunk Length field does not count any chunk padding. However, it // > does include any padding of variable-length parameters other than the // > last parameter in the chunk. A robust implementation is expected to // > accept the chunk whether or not the final padding has been included in // > the Chunk Length. // clang-format off alignas(4) uint8_t buffer[] = { // Type:4 (HEARTBEAT_REQUEST), Flags:0b00000000, Length: 24 // NOTE: Length field must exclude the padding of the last Parameter so // Length field should be 22 rather than 24. But anyway it's ok. 0x04, 0b00000000, 0x00, 0x18, // Parameter 1: Type:1 (HEARBEAT_INFO), Length: 11 0x00, 0x01, 0x00, 0x0B, // Heartbeat Information (7 bytes): 0x11223344556677 0x11, 0x22, 0x33, 0x44, // 1 byte of padding 0x55, 0x66, 0x77, 0x00, // Parameter 2: Type:49159 (UNKNOWN), Length: 6 0xC0, 0x07, 0x00, 0x06, // Unknown data: 0xABCD, 2 bytes of padding 0xAB, 0xCD, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* chunk = RTC::SCTP::HeartbeatRequestChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->HasInfo() == true); REQUIRE(parameter1->GetInfoLength() == 7); REQUIRE(parameter1->GetInfo()[0] == 0x11); REQUIRE(parameter1->GetInfo()[1] == 0x22); REQUIRE(parameter1->GetInfo()[2] == 0x33); REQUIRE(parameter1->GetInfo()[3] == 0x44); REQUIRE(parameter1->GetInfo()[4] == 0x55); REQUIRE(parameter1->GetInfo()[5] == 0x66); REQUIRE(parameter1->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter1->GetInfo()[7] == 0x00); const auto* parameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter2->HasUnknownValue() == true); REQUIRE(parameter2->GetUnknownValueLength() == 2); REQUIRE(parameter2->GetUnknownValue()[0] == 0xAB); REQUIRE(parameter2->GetUnknownValue()[1] == 0xCD); // These should be padding. REQUIRE(parameter2->GetUnknownValue()[2] == 0x00); REQUIRE(parameter2->GetUnknownValue()[3] == 0x00); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); parameter1 = reinterpret_cast(clonedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->HasInfo() == true); REQUIRE(parameter1->GetInfoLength() == 7); REQUIRE(parameter1->GetInfo()[0] == 0x11); REQUIRE(parameter1->GetInfo()[1] == 0x22); REQUIRE(parameter1->GetInfo()[2] == 0x33); REQUIRE(parameter1->GetInfo()[3] == 0x44); REQUIRE(parameter1->GetInfo()[4] == 0x55); REQUIRE(parameter1->GetInfo()[5] == 0x66); REQUIRE(parameter1->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter1->GetInfo()[7] == 0x00); parameter2 = reinterpret_cast(clonedChunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter2->HasUnknownValue() == true); REQUIRE(parameter2->GetUnknownValueLength() == 2); REQUIRE(parameter2->GetUnknownValue()[0] == 0xAB); REQUIRE(parameter2->GetUnknownValue()[1] == 0xCD); // These should be padding. REQUIRE(parameter2->GetUnknownValue()[2] == 0x00); REQUIRE(parameter2->GetUnknownValue()[3] == 0x00); delete clonedChunk; } SECTION("HeartbeatRequestChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::HeartbeatRequestChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); /* Modify it by adding Parameters. */ auto* parameter1 = chunk->BuildParameterInPlace(); // Info length is 5 so 3 bytes of padding will be added. parameter1->SetInfo(sctpCommon::DataBuffer, 5); parameter1->Consolidate(); // Let's add another HeartbeatInfoParameter (it doesn't make sense but // anyway). auto* parameter2 = chunk->BuildParameterInPlace(); // Info length is 2 so 2 bytes of padding will be added. parameter2->SetInfo(sctpCommon::DataBuffer, 2); parameter2->Consolidate(); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2), /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* addedParameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ addedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(addedParameter1->HasInfo() == true); REQUIRE(addedParameter1->GetInfoLength() == 5); REQUIRE(addedParameter1->GetInfo()[0] == 0x00); REQUIRE(addedParameter1->GetInfo()[1] == 0x01); REQUIRE(addedParameter1->GetInfo()[2] == 0x02); REQUIRE(addedParameter1->GetInfo()[3] == 0x03); REQUIRE(addedParameter1->GetInfo()[4] == 0x04); // These should be padding. REQUIRE(addedParameter1->GetInfo()[5] == 0x00); REQUIRE(addedParameter1->GetInfo()[6] == 0x00); const auto* addedParameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ addedParameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(addedParameter2->HasInfo() == true); REQUIRE(addedParameter2->GetInfoLength() == 2); REQUIRE(addedParameter2->GetInfo()[0] == 0x00); REQUIRE(addedParameter2->GetInfo()[1] == 0x01); // These should be padding. REQUIRE(addedParameter2->GetInfo()[2] == 0x00); REQUIRE(addedParameter2->GetInfo()[3] == 0x00); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::HeartbeatRequestChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4 + (4 + 5 + 3) + (4 + 2 + 2), /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2), /*chunkType*/ RTC::SCTP::Chunk::ChunkType::HEARTBEAT_REQUEST, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* parsedParameter1 = reinterpret_cast(parsedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter1->HasInfo() == true); REQUIRE(parsedParameter1->GetInfoLength() == 5); REQUIRE(parsedParameter1->GetInfo()[0] == 0x00); REQUIRE(parsedParameter1->GetInfo()[1] == 0x01); REQUIRE(parsedParameter1->GetInfo()[2] == 0x02); REQUIRE(parsedParameter1->GetInfo()[3] == 0x03); REQUIRE(parsedParameter1->GetInfo()[4] == 0x04); // These should be padding. REQUIRE(parsedParameter1->GetInfo()[5] == 0x00); REQUIRE(parsedParameter1->GetInfo()[6] == 0x00); const auto* parsedParameter2 = reinterpret_cast(parsedChunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter2->HasInfo() == true); REQUIRE(parsedParameter2->GetInfoLength() == 2); REQUIRE(parsedParameter2->GetInfo()[0] == 0x00); REQUIRE(parsedParameter2->GetInfo()[1] == 0x01); // These should be padding. REQUIRE(parsedParameter2->GetInfo()[2] == 0x00); REQUIRE(parsedParameter2->GetInfo()[3] == 0x00); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestIDataChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/IDataChunk.hpp" #include #include // std::memset() #include SCENARIO("SCTP I-Data Chunk (64)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("IDataChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:64 (I_DATA), I:1, U:0, B:1, E:0, Length: 23 0x40, 0b00001010, 0x00, 0x17, // TSN: 0x11223344, 0x11, 0x22, 0x33, 0x44, // Stream Identifier: 5001 0x13, 0x89, 0x00, 0x00, // Message Identifier: 1234567890 0x49, 0x96, 0x02, 0xD2, // Payload Protocol Identifier / Fragment Sequence Number: 99887766 (PPID) 0x05, 0xF4, 0x2A, 0x96, // User Data (3 bytes): 0xABCDED, 1 byte of padding 0xAB, 0xCD, 0xEF, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on std::unique_ptr chunk{ RTC::SCTP::IDataChunk::Parse( buffer, sizeof(buffer)) }; CHECK_SCTP_CHUNK( /*chunk*/ chunk.get(), /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT, /*flags*/ 0b00001010, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == true); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == true); REQUIRE(chunk->GetE() == false); REQUIRE(chunk->GetTsn() == 0x11223344); REQUIRE(chunk->GetStreamId() == 5001); REQUIRE(chunk->GetMessageId() == 1234567890); // Bit B is set so we have PPID instead of FSN. REQUIRE(chunk->GetPayloadProtocolId() == 99887766); REQUIRE(chunk->GetFragmentSequenceNumber() == 0); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3); REQUIRE(chunk->GetUserDataPayload()[0] == 0xAB); REQUIRE(chunk->GetUserDataPayload()[1] == 0xCD); REQUIRE(chunk->GetUserDataPayload()[2] == 0xEF); // This should be padding. REQUIRE(chunk->GetUserDataPayload()[3] == 0x00); auto userData = chunk->MakeUserData(); std::vector expectedPayload = { 0xAB, 0xCD, 0xEF }; REQUIRE(userData.GetStreamId() == 5001); REQUIRE(userData.GetStreamSequenceNumber() == 0); REQUIRE(userData.GetMessageId() == 1234567890); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 99887766); // NOTE: clang-tidy doesn't understand that this is fine. // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); // Bit B is not set so cannot set FSN. REQUIRE_THROWS_AS(chunk->SetFragmentSequenceNumber(1234), MediaSoupError); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk.get(), /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT, /*flags*/ 0b00001010, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == true); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == true); REQUIRE(chunk->GetE() == false); REQUIRE(chunk->GetTsn() == 0x11223344); REQUIRE(chunk->GetStreamId() == 5001); REQUIRE(chunk->GetMessageId() == 1234567890); // Bit B is set so we have PPID instead of FSN. REQUIRE(chunk->GetPayloadProtocolId() == 99887766); REQUIRE(chunk->GetFragmentSequenceNumber() == 0); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3); REQUIRE(chunk->GetUserDataPayload()[0] == 0xAB); REQUIRE(chunk->GetUserDataPayload()[1] == 0xCD); REQUIRE(chunk->GetUserDataPayload()[2] == 0xEF); // This should be padding. REQUIRE(chunk->GetUserDataPayload()[3] == 0x00); userData = chunk->MakeUserData(); REQUIRE(userData.GetStreamId() == 5001); REQUIRE(userData.GetStreamSequenceNumber() == 0); REQUIRE(userData.GetMessageId() == 1234567890); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 99887766); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); /* Clone it. */ chunk.reset(chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer))); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk.get(), /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT, /*flags*/ 0b00001010, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == true); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == true); REQUIRE(chunk->GetE() == false); REQUIRE(chunk->GetTsn() == 0x11223344); REQUIRE(chunk->GetStreamId() == 5001); REQUIRE(chunk->GetMessageId() == 1234567890); // Bit B is set so we have PPID instead of FSN. REQUIRE(chunk->GetPayloadProtocolId() == 99887766); REQUIRE(chunk->GetFragmentSequenceNumber() == 0); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3); REQUIRE(chunk->GetUserDataPayload()[0] == 0xAB); REQUIRE(chunk->GetUserDataPayload()[1] == 0xCD); REQUIRE(chunk->GetUserDataPayload()[2] == 0xEF); // This should be padding. REQUIRE(chunk->GetUserDataPayload()[3] == 0x00); userData = chunk->MakeUserData(); REQUIRE(userData.GetStreamId() == 5001); REQUIRE(userData.GetStreamSequenceNumber() == 0); REQUIRE(userData.GetMessageId() == 1234567890); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 99887766); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); } SECTION("IDataChunk::Factory() succeeds") { std::unique_ptr chunk{ RTC::SCTP::IDataChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) }; CHECK_SCTP_CHUNK( /*chunk*/ chunk.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == false); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == false); REQUIRE(chunk->GetE() == false); REQUIRE(chunk->GetTsn() == 0); REQUIRE(chunk->GetStreamId() == 0); REQUIRE(chunk->GetMessageId() == 0); // Bit B is not set so we don't have PPID but FSN. REQUIRE(chunk->GetPayloadProtocolId() == 0); REQUIRE(chunk->GetFragmentSequenceNumber() == 0); REQUIRE(chunk->HasUserDataPayload() == false); REQUIRE(chunk->GetUserDataPayloadLength() == 0); auto userData = chunk->MakeUserData(); std::vector expectedPayload = {}; REQUIRE(userData.GetStreamId() == 0); REQUIRE(userData.GetStreamSequenceNumber() == 0); REQUIRE(userData.GetMessageId() == 0); REQUIRE(userData.GetFragmentSequenceNumber() == 0); REQUIRE(userData.GetPayloadProtocolId() == 0); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); /* Modify it. */ chunk->SetI(true); chunk->SetE(true); chunk->SetTsn(12345678); chunk->SetStreamId(9988); chunk->SetMessageId(1234); chunk->SetFragmentSequenceNumber(987654321); // Bit B is not set so cannot set PPID. REQUIRE_THROWS_AS(chunk->SetPayloadProtocolId(1234), MediaSoupError); // Verify that replacing the value works. chunk->SetUserDataPayload(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(chunk->GetLength() == 3020); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3000); chunk->SetUserDataPayload(nullptr, 0); REQUIRE(chunk->GetLength() == 20); REQUIRE(chunk->HasUserDataPayload() == false); REQUIRE(chunk->GetUserDataPayloadLength() == 0); // 3 bytes + 1 byte of padding. chunk->SetUserDataPayload(sctpCommon::DataBuffer, 3); CHECK_SCTP_CHUNK( /*chunk*/ chunk.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20 + 3 + 1, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT, /*flags*/ 0b00001001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == true); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == false); REQUIRE(chunk->GetE() == true); REQUIRE(chunk->GetTsn() == 12345678); REQUIRE(chunk->GetStreamId() == 9988); REQUIRE(chunk->GetMessageId() == 1234); // Bit B is not set so we don't have PPID but FSN. REQUIRE(chunk->GetPayloadProtocolId() == 0); REQUIRE(chunk->GetFragmentSequenceNumber() == 987654321); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3); REQUIRE(chunk->GetUserDataPayload()[0] == 0x00); REQUIRE(chunk->GetUserDataPayload()[1] == 0x01); REQUIRE(chunk->GetUserDataPayload()[2] == 0x02); // Last byte must be a zero byte padding. REQUIRE(chunk->GetUserDataPayload()[3] == 0x00); userData = chunk->MakeUserData(); expectedPayload = { 0x00, 0x01, 0x02 }; REQUIRE(userData.GetStreamId() == 9988); REQUIRE(userData.GetStreamSequenceNumber() == 0); REQUIRE(userData.GetMessageId() == 1234); REQUIRE(userData.GetFragmentSequenceNumber() == 987654321); REQUIRE(userData.GetPayloadProtocolId() == 0); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); /* Parse itself and compare. */ chunk.reset(RTC::SCTP::IDataChunk::Parse(chunk->GetBuffer(), chunk->GetLength())); CHECK_SCTP_CHUNK( /*chunk*/ chunk.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 20 + 3 + 1, /*length*/ 20 + 3 + 1, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT, /*flags*/ 0b00001001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetI() == true); REQUIRE(chunk->GetU() == false); REQUIRE(chunk->GetB() == false); REQUIRE(chunk->GetE() == true); REQUIRE(chunk->GetTsn() == 12345678); REQUIRE(chunk->GetStreamId() == 9988); REQUIRE(chunk->GetMessageId() == 1234); // Bit B is not set so we don't have PPID but FSN. REQUIRE(chunk->GetPayloadProtocolId() == 0); REQUIRE(chunk->GetFragmentSequenceNumber() == 987654321); REQUIRE(chunk->HasUserDataPayload() == true); REQUIRE(chunk->GetUserDataPayloadLength() == 3); REQUIRE(chunk->GetUserDataPayload()[0] == 0x00); REQUIRE(chunk->GetUserDataPayload()[1] == 0x01); REQUIRE(chunk->GetUserDataPayload()[2] == 0x02); // Last byte must be a zero byte padding. REQUIRE(chunk->GetUserDataPayload()[3] == 0x00); userData = chunk->MakeUserData(); REQUIRE(userData.GetStreamId() == 9988); REQUIRE(userData.GetStreamSequenceNumber() == 0); REQUIRE(userData.GetMessageId() == 1234); REQUIRE(userData.GetFragmentSequenceNumber() == 987654321); REQUIRE(userData.GetPayloadProtocolId() == 0); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(userData).ReleasePayload() == expectedPayload); } SECTION("IDataChunk::SetUserDataPayload() throws if userDataPayloadLength is too big") { std::unique_ptr chunk{ RTC::SCTP::IDataChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) }; CHECK_SCTP_CHUNK( /*chunk*/ chunk.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE_THROWS_AS(chunk->SetUserDataPayload(sctpCommon::DataBuffer, 65535), MediaSoupError); CHECK_SCTP_CHUNK( /*chunk*/ chunk.get(), /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); } SECTION("IDataChunk::SetUserData() succeeds") { auto* chunk = RTC::SCTP::IDataChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); RTC::SCTP::UserData userData( /*streamId*/ 123, /*ssn*/ 0, // Not in I-DATA chunks. /*mid*/ 5555, /*fsn*/ 6666, /*ppid*/ 56789, /*payload*/ { 1, 2, 3, 4 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ true); REQUIRE(userData.GetStreamId() == 123); REQUIRE(userData.GetStreamSequenceNumber() == 0); REQUIRE(userData.GetMessageId() == 5555); REQUIRE(userData.GetFragmentSequenceNumber() == 6666); REQUIRE(userData.GetPayloadProtocolId() == 56789); REQUIRE(userData.GetPayloadLength() == 4); REQUIRE(userData.GetPayload()[0] == 1); REQUIRE(userData.GetPayload()[1] == 2); REQUIRE(userData.GetPayload()[2] == 3); REQUIRE(userData.GetPayload()[3] == 4); REQUIRE(userData.IsBeginning() == true); REQUIRE(userData.IsEnd() == true); REQUIRE(userData.IsUnordered() == true); chunk->SetUserData(std::move(userData)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20 + 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_DATA, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP_AND_REPORT, /*flags*/ 0b00000111, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); auto gotUserData = chunk->MakeUserData(); std::vector expectedPayload = { 1, 2, 3, 4 }; REQUIRE(gotUserData.GetStreamId() == 123); REQUIRE(gotUserData.GetStreamSequenceNumber() == 0); REQUIRE(gotUserData.GetMessageId() == 5555); // Bit B is set in the I_DATA Chunk so this must be 0. REQUIRE(gotUserData.GetFragmentSequenceNumber() == 0); REQUIRE(gotUserData.GetPayloadProtocolId() == 56789); REQUIRE(gotUserData.GetPayloadLength() == 4); REQUIRE(gotUserData.GetPayload()[0] == 1); REQUIRE(gotUserData.GetPayload()[1] == 2); REQUIRE(gotUserData.GetPayload()[2] == 3); REQUIRE(gotUserData.GetPayload()[3] == 4); REQUIRE(gotUserData.IsBeginning() == true); REQUIRE(gotUserData.IsEnd() == true); REQUIRE(gotUserData.IsUnordered() == true); // NOLINTNEXTLINE(bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(std::move(gotUserData).ReleasePayload() == expectedPayload); delete chunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestIForwardTsnChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/IForwardTsnChunk.hpp" #include #include // std::memset() #include SCENARIO("I-Forward Cumulative TSN Chunk (194)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("IForwardTsnChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:194 (I_FORWARD_TSN), Flags: 0b00000000, Length: 32 0xC2, 0b00000000, 0x00, 0x20, // New Cumulative TSN: 287454020, 0x11, 0x22, 0x33, 0x44, // Stream 1: 4097, U: 1 0x10, 0x01, 0x00, 0x01, // Message Identifier: 285212689 0x11, 0x00, 0x00, 0x11, // Stream 2: 8194, U: 0 0x20, 0x02, 0x00, 0x00, // Message Identifier: 570425378 0x22, 0x00, 0x00, 0x22, // Stream 3: 12291, U: 1 0x30, 0x03, 0x00, 0x01, // Message Identifier: 855638067 0x33, 0x00, 0x00, 0x33, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* chunk = RTC::SCTP::IForwardTsnChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 32, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetNewCumulativeTsn() == 287454020); REQUIRE(chunk->GetNumberOfSkippedStreams() == 3); REQUIRE( chunk->GetSkippedStreams() == std::vector{ { true, 4097, 285212689, }, { false, 8194, 570425378, }, { true,12291, 855638067, }, }); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 32, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetNewCumulativeTsn() == 287454020); REQUIRE(chunk->GetNumberOfSkippedStreams() == 3); REQUIRE( chunk->GetSkippedStreams() == std::vector{ { true, 4097, 285212689, }, { false, 8194, 570425378, }, { true,12291, 855638067, }, }); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 32, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(clonedChunk->GetNewCumulativeTsn() == 287454020); REQUIRE(clonedChunk->GetNumberOfSkippedStreams() == 3); REQUIRE( clonedChunk->GetSkippedStreams() == std::vector{ { true, 4097, 285212689, }, { false, 8194, 570425378, }, { true,12291, 855638067, }, }); delete clonedChunk; } SECTION("IForwardTsnChunk::Parse() fails") { // Length field is not multiple of 8. // clang-format off alignas(4) uint8_t buffer1[] = { // Type:194 (I_FORWARD_TSN), Flags: 0b00000000, Length: 20 (should be 24) 0xC2, 0b00000000, 0x00, 0x14, // New Cumulative TSN: 287454020, 0x11, 0x22, 0x33, 0x44, // Stream 1: 4097, U: 1 0x10, 0x01, 0x00, 0x01, // Message Identifier: 285212689 0x11, 0x00, 0x00, 0x11, // Stream 2: 8194, U: 0 0x20, 0x02, 0x00, 0x00, // Message Identifier (missing in Length field) 0x22, 0x00, 0x00, 0x22, }; // clang-format on REQUIRE(!RTC::SCTP::IForwardTsnChunk::Parse(buffer1, sizeof(buffer1))); } SECTION("IForwardTsnChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::IForwardTsnChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetNewCumulativeTsn() == 0); REQUIRE(chunk->GetNumberOfSkippedStreams() == 0); REQUIRE(chunk->GetSkippedStreams().empty()); /* Modify it. */ chunk->SetNewCumulativeTsn(12345678); chunk->AddSkippedStream( RTC::SCTP::AnyForwardTsnChunk::SkippedStream{ /*unordered*/ true, /*streamId*/ 1111, /*mid*/ 11110001 }); chunk->AddSkippedStream( RTC::SCTP::AnyForwardTsnChunk::SkippedStream{ /*unordered*/ false, /*streamId*/ 2222, /*mid*/ 22220002 }); chunk->AddSkippedStream( RTC::SCTP::AnyForwardTsnChunk::SkippedStream{ /*unordered*/ true, /*streamId*/ 3333, /*mid*/ 33330003 }); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 32, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetNewCumulativeTsn() == 12345678); REQUIRE(chunk->GetNumberOfSkippedStreams() == 3); REQUIRE( chunk->GetSkippedStreams() == std::vector{ { true, 1111, 11110001 }, { false, 2222, 22220002 }, { true, 3333, 33330003 }, }); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::IForwardTsnChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 32, /*length*/ 32, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::I_FORWARD_TSN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(parsedChunk->GetNewCumulativeTsn() == 12345678); REQUIRE(parsedChunk->GetNumberOfSkippedStreams() == 3); REQUIRE( parsedChunk->GetSkippedStreams() == std::vector{ { true, 1111, 11110001 }, { false, 2222, 22220002 }, { true, 3333, 33330003 }, }); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestInitAckChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/chunks/InitAckChunk.hpp" #include "RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp" #include #include // std::memset() // NOTE: Simplified since it's similar to InitChunk. SCENARIO("SCTP Init Acknowledgement (2)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("InitAckChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:2 (INIT_ACK), Flags: 0b00000000, Length: 28 0x02, 0b00000000, 0x00, 0x1C, // Initiate Tag: 287454020, 0x11, 0x22, 0x33, 0x44, // Advertised Receiver Window Credit: 4278216311 0xFF, 0x00, 0x66, 0x77, // Number of Outbound Streams: 4660, Number of Inbound Streams: 22136 0x12, 0x34, 0x56, 0x78, // Initial TSN: 2882339074 0xAB, 0xCD, 0x01, 0x02, // Parameter 1: Type:5 (IPV4_ADDRESS), Length: 8 0x00, 0x05, 0x00, 0x08, // IPv4 Address: "2.3.4.5" 0x02, 0x03, 0x04, 0x05, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* chunk = RTC::SCTP::InitAckChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 28, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 1, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetInitiateTag() == 287454020); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311); REQUIRE(chunk->GetNumberOfOutboundStreams() == 4660); REQUIRE(chunk->GetNumberOfInboundStreams() == 22136); REQUIRE(chunk->GetInitialTsn() == 2882339074); const auto* parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->GetIPv4Address()[0] == 0x02); REQUIRE(parameter1->GetIPv4Address()[1] == 0x03); REQUIRE(parameter1->GetIPv4Address()[2] == 0x04); REQUIRE(parameter1->GetIPv4Address()[3] == 0x05); delete chunk; } SECTION("InitAckChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::InitAckChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetInitiateTag() == 0); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 0); REQUIRE(chunk->GetNumberOfOutboundStreams() == 0); REQUIRE(chunk->GetNumberOfInboundStreams() == 0); REQUIRE(chunk->GetInitialTsn() == 0); /* Modify it and add Parameters. */ chunk->SetInitiateTag(1111111110); chunk->SetAdvertisedReceiverWindowCredit(2222222220); chunk->SetNumberOfOutboundStreams(1234); chunk->SetNumberOfInboundStreams(5678); chunk->SetInitialTsn(3333333330); auto* parameter1 = chunk->BuildParameterInPlace(); // 11.22.33.44 IPv4 in network order. uint8_t ipBuffer1[] = { 0x0B, 0x16, 0x21, 0x2C }; parameter1->SetIPv4Address(ipBuffer1); parameter1->Consolidate(); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 28, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 1, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetInitiateTag() == 1111111110); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 2222222220); REQUIRE(chunk->GetNumberOfOutboundStreams() == 1234); REQUIRE(chunk->GetNumberOfInboundStreams() == 5678); REQUIRE(chunk->GetInitialTsn() == 3333333330); const auto* addedParameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ addedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(addedParameter1->GetIPv4Address()[0] == 0x0B); REQUIRE(addedParameter1->GetIPv4Address()[1] == 0x16); REQUIRE(addedParameter1->GetIPv4Address()[2] == 0x21); REQUIRE(addedParameter1->GetIPv4Address()[3] == 0x2C); delete chunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestInitChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/chunks/InitChunk.hpp" #include "RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp" #include "RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp" #include "RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp" #include "RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp" #include #include // std::memset() SCENARIO("SCTP Init Chunk (1)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("InitChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:1 (INIT), Flags: 0b00000000, Length: 56 0x01, 0b00000000, 0x00, 0x38, // Initiate Tag: 287454020, 0x11, 0x22, 0x33, 0x44, // Advertised Receiver Window Credit: 4278216311 0xFF, 0x00, 0x66, 0x77, // Number of Outbound Streams: 4660, Number of Inbound Streams: 22136 0x12, 0x34, 0x56, 0x78, // Initial TSN: 2882339074 0xAB, 0xCD, 0x01, 0x02, // Parameter 1: Type:5 (IPV4_ADDRESS), Length: 8 0x00, 0x05, 0x00, 0x08, // IPv4 Address: "2.3.4.5" 0x02, 0x03, 0x04, 0x05, // Type:6 (IPV6_ADDRESS), Length: 20 0x00, 0x06, 0x00, 0x14, // Parameter 2: IPv6 Address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" 0x20, 0x01, 0x0D, 0xB8, 0x85, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x2E, 0x03, 0x70, 0x73, 0x34, // Parameter 3: Type:9 (COOKIE_PRESERVATIVE), Length: 8 0x00, 0x09, 0x00, 0x08, // Suggested Cookie Life-Span Increment: 556942164 0x21, 0x32, 0x43, 0x54, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD }; // clang-format on auto* chunk = RTC::SCTP::InitChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 56, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 3, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetInitiateTag() == 287454020); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311); REQUIRE(chunk->GetNumberOfOutboundStreams() == 4660); REQUIRE(chunk->GetNumberOfInboundStreams() == 22136); REQUIRE(chunk->GetInitialTsn() == 2882339074); const auto* parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->GetIPv4Address()[0] == 0x02); REQUIRE(parameter1->GetIPv4Address()[1] == 0x03); REQUIRE(parameter1->GetIPv4Address()[2] == 0x04); REQUIRE(parameter1->GetIPv4Address()[3] == 0x05); const auto* parameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter2->GetIPv6Address()[0] == 0x20); REQUIRE(parameter2->GetIPv6Address()[1] == 0x01); REQUIRE(parameter2->GetIPv6Address()[2] == 0x0D); REQUIRE(parameter2->GetIPv6Address()[3] == 0xB8); REQUIRE(parameter2->GetIPv6Address()[15] == 0x34); const auto* parameter3 = reinterpret_cast(chunk->GetParameterAt(2)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter3, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter3->GetLifeSpanIncrement() == 556942164); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 56, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 3, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetInitiateTag() == 287454020); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311); REQUIRE(chunk->GetNumberOfOutboundStreams() == 4660); REQUIRE(chunk->GetNumberOfInboundStreams() == 22136); REQUIRE(chunk->GetInitialTsn() == 2882339074); parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->GetIPv4Address()[0] == 0x02); REQUIRE(parameter1->GetIPv4Address()[1] == 0x03); REQUIRE(parameter1->GetIPv4Address()[2] == 0x04); REQUIRE(parameter1->GetIPv4Address()[3] == 0x05); parameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter2->GetIPv6Address()[0] == 0x20); REQUIRE(parameter2->GetIPv6Address()[1] == 0x01); REQUIRE(parameter2->GetIPv6Address()[2] == 0x0D); REQUIRE(parameter2->GetIPv6Address()[3] == 0xB8); REQUIRE(parameter2->GetIPv6Address()[15] == 0x34); parameter3 = reinterpret_cast(chunk->GetParameterAt(2)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter3, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter3->GetLifeSpanIncrement() == 556942164); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 56, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 3, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(clonedChunk->GetInitiateTag() == 287454020); REQUIRE(clonedChunk->GetAdvertisedReceiverWindowCredit() == 4278216311); REQUIRE(clonedChunk->GetNumberOfOutboundStreams() == 4660); REQUIRE(clonedChunk->GetNumberOfInboundStreams() == 22136); REQUIRE(clonedChunk->GetInitialTsn() == 2882339074); parameter1 = reinterpret_cast(clonedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->GetIPv4Address()[0] == 0x02); REQUIRE(parameter1->GetIPv4Address()[1] == 0x03); REQUIRE(parameter1->GetIPv4Address()[2] == 0x04); REQUIRE(parameter1->GetIPv4Address()[3] == 0x05); parameter2 = reinterpret_cast(clonedChunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter2->GetIPv6Address()[0] == 0x20); REQUIRE(parameter2->GetIPv6Address()[1] == 0x01); REQUIRE(parameter2->GetIPv6Address()[2] == 0x0D); REQUIRE(parameter2->GetIPv6Address()[3] == 0xB8); REQUIRE(parameter2->GetIPv6Address()[15] == 0x34); parameter3 = reinterpret_cast(clonedChunk->GetParameterAt(2)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter3, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter3->GetLifeSpanIncrement() == 556942164); delete clonedChunk; } SECTION("InitChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::InitChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetInitiateTag() == 0); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 0); REQUIRE(chunk->GetNumberOfOutboundStreams() == 0); REQUIRE(chunk->GetNumberOfInboundStreams() == 0); REQUIRE(chunk->GetInitialTsn() == 0); /* Modify it and add Parameters. */ chunk->SetInitiateTag(1111111110); chunk->SetAdvertisedReceiverWindowCredit(2222222220); chunk->SetNumberOfOutboundStreams(1234); chunk->SetNumberOfInboundStreams(5678); chunk->SetInitialTsn(3333333330); auto* parameter1 = chunk->BuildParameterInPlace(); // 11.22.33.44 IPv4 in network order. uint8_t ipBuffer1[] = { 0x0B, 0x16, 0x21, 0x2C }; parameter1->SetIPv4Address(ipBuffer1); parameter1->Consolidate(); auto* parameter2 = chunk->BuildParameterInPlace(); // 2345:0425:2CA1:0000:0000:0567:5673:23b5 IPv6 in network order. uint8_t ipBuffer2[] = { 0x23, 0x45, 0x04, 0x25, 0x2C, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x05, 0x67, 0x56, 0x73, 0x23, 0xB5 }; parameter2->SetIPv6Address(ipBuffer2); parameter2->Consolidate(); auto* parameter3 = chunk->BuildParameterInPlace(); parameter3->SetLifeSpanIncrement(876543210); parameter3->Consolidate(); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 56, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 3, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetInitiateTag() == 1111111110); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 2222222220); REQUIRE(chunk->GetNumberOfOutboundStreams() == 1234); REQUIRE(chunk->GetNumberOfInboundStreams() == 5678); REQUIRE(chunk->GetInitialTsn() == 3333333330); const auto* addedParameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ addedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(addedParameter1->GetIPv4Address()[0] == 0x0B); REQUIRE(addedParameter1->GetIPv4Address()[1] == 0x16); REQUIRE(addedParameter1->GetIPv4Address()[2] == 0x21); REQUIRE(addedParameter1->GetIPv4Address()[3] == 0x2C); const auto* addedParameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ addedParameter2, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(addedParameter2->GetIPv6Address()[0] == 0x23); REQUIRE(addedParameter2->GetIPv6Address()[1] == 0x45); REQUIRE(addedParameter2->GetIPv6Address()[2] == 0x04); REQUIRE(addedParameter2->GetIPv6Address()[3] == 0x25); REQUIRE(addedParameter2->GetIPv6Address()[15] == 0xB5); const auto* addedParameter3 = reinterpret_cast(chunk->GetParameterAt(2)); CHECK_SCTP_PARAMETER( /*parameter*/ addedParameter3, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(addedParameter3->GetLifeSpanIncrement() == 876543210); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::InitChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 56, /*length*/ 56, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 3, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(parsedChunk->GetInitiateTag() == 1111111110); REQUIRE(parsedChunk->GetAdvertisedReceiverWindowCredit() == 2222222220); REQUIRE(parsedChunk->GetNumberOfOutboundStreams() == 1234); REQUIRE(parsedChunk->GetNumberOfInboundStreams() == 5678); REQUIRE(parsedChunk->GetInitialTsn() == 3333333330); const auto* parsedParameter1 = reinterpret_cast(parsedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter1->GetIPv4Address()[0] == 0x0B); REQUIRE(parsedParameter1->GetIPv4Address()[1] == 0x16); REQUIRE(parsedParameter1->GetIPv4Address()[2] == 0x21); REQUIRE(parsedParameter1->GetIPv4Address()[3] == 0x2C); const auto* parsedParameter2 = reinterpret_cast(parsedChunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter2, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter2->GetIPv6Address()[0] == 0x23); REQUIRE(parsedParameter2->GetIPv6Address()[1] == 0x45); REQUIRE(parsedParameter2->GetIPv6Address()[2] == 0x04); REQUIRE(parsedParameter2->GetIPv6Address()[3] == 0x25); REQUIRE(parsedParameter2->GetIPv6Address()[15] == 0xB5); const auto* parsedParameter3 = reinterpret_cast(parsedChunk->GetParameterAt(2)); CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter3, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter3->GetLifeSpanIncrement() == 876543210); delete parsedChunk; } SECTION("InitChunk::Factory() using AddParameter() succeeds") { auto* chunk = RTC::SCTP::InitChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); chunk->SetInitiateTag(1); chunk->SetAdvertisedReceiverWindowCredit(2); chunk->SetNumberOfOutboundStreams(3); chunk->SetNumberOfInboundStreams(4); chunk->SetInitialTsn(5); auto* parameter1 = RTC::SCTP::CookiePreservativeParameter::Factory( sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer)); // 8 bytes Parameter. parameter1->SetLifeSpanIncrement(123456); chunk->AddParameter(parameter1); // Once added, we can delete the Parameter. delete parameter1; // Chunk length must be: // - Chunk header: 20 // - Parameter 1: 8 // - Total: 28 CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 28, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 1, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* obtainedParameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ obtainedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(obtainedParameter1->GetLifeSpanIncrement() == 123456); // 4 bytes Parameter. auto* parameter2 = RTC::SCTP::SupportedAddressTypesParameter::Factory( sctpCommon::FactoryBuffer + 1000, sizeof(sctpCommon::FactoryBuffer)); // Add 6 bytes. parameter2->AddAddressType(1111); parameter2->AddAddressType(2222); parameter2->AddAddressType(3333); chunk->AddParameter(parameter2); // Once added, we can delete the Parameter. delete parameter2; // Chunk length must be: // - Chunk header: 20 // - Parameter 1: 8 // - Parameter 2: 12 // - Total: 40 CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 40, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* obtainedParameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ obtainedParameter2, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(obtainedParameter2->GetNumberOfAddressTypes() == 3); REQUIRE(obtainedParameter2->GetAddressTypeAt(0) == 1111); REQUIRE(obtainedParameter2->GetAddressTypeAt(1) == 2222); REQUIRE(obtainedParameter2->GetAddressTypeAt(2) == 3333); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::InitChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 40, /*length*/ 40, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::INIT, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(parsedChunk->GetInitiateTag() == 1); REQUIRE(parsedChunk->GetAdvertisedReceiverWindowCredit() == 2); REQUIRE(parsedChunk->GetNumberOfOutboundStreams() == 3); REQUIRE(parsedChunk->GetNumberOfInboundStreams() == 4); REQUIRE(parsedChunk->GetInitialTsn() == 5); obtainedParameter1 = reinterpret_cast(parsedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ obtainedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(obtainedParameter1->GetLifeSpanIncrement() == 123456); obtainedParameter2 = reinterpret_cast( parsedChunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ obtainedParameter2, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(obtainedParameter2->GetNumberOfAddressTypes() == 3); REQUIRE(obtainedParameter2->GetAddressTypeAt(0) == 1111); REQUIRE(obtainedParameter2->GetAddressTypeAt(1) == 2222); REQUIRE(obtainedParameter2->GetAddressTypeAt(2) == 3333); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestOperationErrorChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/chunks/OperationErrorChunk.hpp" #include "RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp" #include #include // std::memset() SCENARIO("SCTP Operation Error Chunk (9)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("OperationErrorChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:9 (OPERATION_ERROR), Flags:0b00000000, Length: 21 0x09, 0b00000000, 0x00, 0x15, // Error Cause 1: Code:1 (INVALID_STREAM_IDENTIFIER), Length: 8 0x00, 0x01, 0x00, 0x08, // Stream Identifier: 0x1234 0x12, 0x34, 0x00, 0x00, // Error Cause 2: Code:4 (OUT_OF_RESOURCE), Length: 4 0x00, 0x04, 0x00, 0x04, // Error Cause 3: Type:49159 (UNKNOWN), Length: 5 0xC0, 0x07, 0x00, 0x05, // Unknown data: 0xAB, 3 bytes of padding 0xAB, 0x00, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* chunk = RTC::SCTP::OperationErrorChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 3); const auto* errorCause1 = reinterpret_cast( chunk->GetErrorCauseAt(0)); REQUIRE( chunk->GetFirstErrorCauseOfCode() == errorCause1); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, /*unknownType*/ false); REQUIRE(errorCause1->GetStreamIdentifier() == 0x1234); const auto* errorCause2 = reinterpret_cast(chunk->GetErrorCauseAt(1)); REQUIRE(chunk->GetFirstErrorCauseOfCode() == errorCause2); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause2, /*buffer*/ nullptr, /*bufferLength*/ 4, /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, /*unknownCode*/ false); const auto* errorCause3 = reinterpret_cast(chunk->GetErrorCauseAt(2)); REQUIRE(chunk->GetFirstErrorCauseOfCode() == errorCause3); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause3, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ static_cast(49159), /*unknownCode*/ true); REQUIRE(errorCause3->HasUnknownValue() == true); REQUIRE(errorCause3->GetUnknownValueLength() == 1); REQUIRE(errorCause3->GetUnknownValue()[0] == 0xAB); // These should be padding. REQUIRE(errorCause3->GetUnknownValue()[1] == 0x00); REQUIRE(errorCause3->GetUnknownValue()[2] == 0x00); REQUIRE(errorCause3->GetUnknownValue()[3] == 0x00); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 3); errorCause1 = reinterpret_cast( chunk->GetErrorCauseAt(0)); REQUIRE( chunk->GetFirstErrorCauseOfCode() == errorCause1); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, /*unknownType*/ false); REQUIRE(errorCause1->GetStreamIdentifier() == 0x1234); errorCause2 = reinterpret_cast(chunk->GetErrorCauseAt(1)); REQUIRE(chunk->GetFirstErrorCauseOfCode() == errorCause2); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause2, /*buffer*/ nullptr, /*bufferLength*/ 4, /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, /*unknownCode*/ false); errorCause3 = reinterpret_cast(chunk->GetErrorCauseAt(2)); REQUIRE(chunk->GetFirstErrorCauseOfCode() == errorCause3); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause3, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ static_cast(49159), /*unknownCode*/ true); REQUIRE(errorCause3->HasUnknownValue() == true); REQUIRE(errorCause3->GetUnknownValueLength() == 1); REQUIRE(errorCause3->GetUnknownValue()[0] == 0xAB); // These should be padding. REQUIRE(errorCause3->GetUnknownValue()[1] == 0x00); REQUIRE(errorCause3->GetUnknownValue()[2] == 0x00); REQUIRE(errorCause3->GetUnknownValue()[3] == 0x00); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 24, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 3); errorCause1 = reinterpret_cast( clonedChunk->GetErrorCauseAt(0)); REQUIRE( clonedChunk->GetFirstErrorCauseOfCode() == errorCause1); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause1, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, /*unknownType*/ false); REQUIRE(errorCause1->GetStreamIdentifier() == 0x1234); errorCause2 = reinterpret_cast(clonedChunk->GetErrorCauseAt(1)); REQUIRE( clonedChunk->GetFirstErrorCauseOfCode() == errorCause2); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause2, /*buffer*/ nullptr, /*bufferLength*/ 4, /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, /*unknownCode*/ false); errorCause3 = reinterpret_cast(clonedChunk->GetErrorCauseAt(2)); REQUIRE(clonedChunk->GetFirstErrorCauseOfCode() == errorCause3); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause3, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ static_cast(49159), /*unknownCode*/ true); REQUIRE(errorCause3->HasUnknownValue() == true); REQUIRE(errorCause3->GetUnknownValueLength() == 1); REQUIRE(errorCause3->GetUnknownValue()[0] == 0xAB); // These should be padding. REQUIRE(errorCause3->GetUnknownValue()[1] == 0x00); REQUIRE(errorCause3->GetUnknownValue()[2] == 0x00); REQUIRE(errorCause3->GetUnknownValue()[3] == 0x00); delete clonedChunk; } SECTION("OperationErrorChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::OperationErrorChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 0); REQUIRE(chunk->GetFirstErrorCauseOfCode() == nullptr); REQUIRE(chunk->GetFirstErrorCauseOfCode() == nullptr); /* Modify it by adding Error Causes. */ auto* errorCause1 = chunk->BuildErrorCauseInPlace(); // Unrecognized Chunk length is 5 so 3 bytes of padding will be added. errorCause1->SetUnrecognizedChunk(sctpCommon::DataBuffer, 5); errorCause1->Consolidate(); REQUIRE( chunk->GetFirstErrorCauseOfCode() == errorCause1); // // Let's add another UnrecognizedChunkTypeErrorCause. auto* errorCause2 = chunk->BuildErrorCauseInPlace(); // Unrecognized Chunk is 2 so 2 bytes of padding will be added. errorCause2->SetUnrecognizedChunk(sctpCommon::DataBuffer, 2); errorCause2->Consolidate(); // Still must return the first Error Cause. REQUIRE( chunk->GetFirstErrorCauseOfCode() == errorCause1); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2), /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 2); const auto* addedErrorCause1 = reinterpret_cast(chunk->GetErrorCauseAt(0)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ addedErrorCause1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(addedErrorCause1->HasUnrecognizedChunk() == true); REQUIRE(addedErrorCause1->GetUnrecognizedChunkLength() == 5); REQUIRE(addedErrorCause1->GetUnrecognizedChunk()[0] == 0x00); REQUIRE(addedErrorCause1->GetUnrecognizedChunk()[1] == 0x01); REQUIRE(addedErrorCause1->GetUnrecognizedChunk()[2] == 0x02); REQUIRE(addedErrorCause1->GetUnrecognizedChunk()[3] == 0x03); REQUIRE(addedErrorCause1->GetUnrecognizedChunk()[4] == 0x04); // These should be padding. REQUIRE(addedErrorCause1->GetUnrecognizedChunk()[5] == 0x00); REQUIRE(addedErrorCause1->GetUnrecognizedChunk()[6] == 0x00); const auto* addedErrorCause2 = reinterpret_cast(chunk->GetErrorCauseAt(1)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ addedErrorCause2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(addedErrorCause2->HasUnrecognizedChunk() == true); REQUIRE(addedErrorCause2->GetUnrecognizedChunkLength() == 2); REQUIRE(addedErrorCause2->GetUnrecognizedChunk()[0] == 0x00); REQUIRE(addedErrorCause2->GetUnrecognizedChunk()[1] == 0x01); // These should be padding. REQUIRE(addedErrorCause2->GetUnrecognizedChunk()[2] == 0x00); REQUIRE(addedErrorCause2->GetUnrecognizedChunk()[3] == 0x00); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::OperationErrorChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4 + (4 + 5 + 3) + (4 + 2 + 2), /*length*/ 4 + (4 + 5 + 3) + (4 + 2 + 2), /*chunkType*/ RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ true, /*errorCausesCount*/ 2); const auto* parsedErrorCause1 = reinterpret_cast( parsedChunk->GetErrorCauseAt(0)); REQUIRE( parsedChunk->GetFirstErrorCauseOfCode() == parsedErrorCause1); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause1, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(parsedErrorCause1->HasUnrecognizedChunk() == true); REQUIRE(parsedErrorCause1->GetUnrecognizedChunkLength() == 5); REQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[0] == 0x00); REQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[1] == 0x01); REQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[2] == 0x02); REQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[3] == 0x03); REQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[4] == 0x04); // These should be padding. REQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[5] == 0x00); REQUIRE(parsedErrorCause1->GetUnrecognizedChunk()[6] == 0x00); const auto* parsedErrorCause2 = reinterpret_cast( parsedChunk->GetErrorCauseAt(1)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause2, /*buffer*/ nullptr, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(parsedErrorCause2->HasUnrecognizedChunk() == true); REQUIRE(parsedErrorCause2->GetUnrecognizedChunkLength() == 2); REQUIRE(parsedErrorCause2->GetUnrecognizedChunk()[0] == 0x00); REQUIRE(parsedErrorCause2->GetUnrecognizedChunk()[1] == 0x01); // These should be padding. REQUIRE(parsedErrorCause2->GetUnrecognizedChunk()[2] == 0x00); REQUIRE(parsedErrorCause2->GetUnrecognizedChunk()[3] == 0x00); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestReConfigChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/chunks/ReConfigChunk.hpp" #include "RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp" #include "RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp" #include "RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp" #include #include // std::memset() #include SCENARIO("SCTP Re-Config Chunk (130)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("ReConfigChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:130 (RE_CONFIG), Flags:0b00000000, Length: 38 // NOTE: Length field must exclude the padding of the last Parameter. 0x82, 0b00000000, 0x00, 0x26, // Parameter 1: Type:13 (OUTGOING_SSN_RESET_REQUEST), Length: 22 0x00, 0x0D, 0x00, 0x16, // Re-configuration Request Sequence Number: 0x11223344 0x11, 0x22, 0x33, 0x44, // Re-configuration Response Sequence Number: 0x55667788 0x55, 0x66, 0x77, 0x88, // Sender's Last Assigned TSN: 0xAABBCCDD 0xAA, 0xBB, 0xCC, 0xDD, // Stream 1: 0x5001, Stream 2: 0x5002 0x50, 0x01, 0x50, 0x02, // Stream 3: 0x5003, 2 bytes of padding 0x50, 0x03, 0x00, 0x00, // Parameter 2: Type:14 (INCOMING_SSN_RESET_REQUEST), Length: 10 0x00, 0x0E, 0x00, 0x0A, // Re-configuration Request Sequence Number: 0x44332211 0x44, 0x33, 0x22, 0x11, // Stream 1: 0x6001, 2 bytes of padding 0x60, 0x01, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* chunk = RTC::SCTP::ReConfigChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 40, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 24, /*length*/ 24, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->GetReconfigurationRequestSequenceNumber() == 0x11223344); REQUIRE(parameter1->GetReconfigurationResponseSequenceNumber() == 0x55667788); REQUIRE(parameter1->GetSenderLastAssignedTsn() == 0xAABBCCDD); const std::vector expectedStreamIds1{ { 0x5001, 0x5002, 0x5003 }, }; REQUIRE(parameter1->GetStreamIds() == expectedStreamIds1); const auto* parameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter2->GetReconfigurationRequestSequenceNumber() == 0x44332211); const std::vector expectedStreamIds2{ 0x6001, }; REQUIRE(parameter2->GetStreamIds() == expectedStreamIds2); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 40, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); parameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 24, /*length*/ 24, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->GetReconfigurationRequestSequenceNumber() == 0x11223344); REQUIRE(parameter1->GetReconfigurationResponseSequenceNumber() == 0x55667788); REQUIRE(parameter1->GetSenderLastAssignedTsn() == 0xAABBCCDD); REQUIRE(parameter1->GetStreamIds() == expectedStreamIds1); parameter2 = reinterpret_cast(chunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter2->GetReconfigurationRequestSequenceNumber() == 0x44332211); REQUIRE(parameter2->GetStreamIds() == expectedStreamIds2); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 40, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 2, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); parameter1 = reinterpret_cast( clonedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter1, /*buffer*/ nullptr, /*bufferLength*/ 24, /*length*/ 24, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter1->GetReconfigurationRequestSequenceNumber() == 0x11223344); REQUIRE(parameter1->GetReconfigurationResponseSequenceNumber() == 0x55667788); REQUIRE(parameter1->GetSenderLastAssignedTsn() == 0xAABBCCDD); REQUIRE(parameter1->GetStreamIds() == expectedStreamIds1); parameter2 = reinterpret_cast( clonedChunk->GetParameterAt(1)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter2, /*buffer*/ nullptr, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter2->GetReconfigurationRequestSequenceNumber() == 0x44332211); REQUIRE(parameter2->GetStreamIds() == expectedStreamIds2); delete clonedChunk; } SECTION("ReConfigChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::ReConfigChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); /* Modify it by adding Parameters. */ auto* parameter1 = chunk->BuildParameterInPlace(); // Parameter will have 20 bytes length. parameter1->SetReconfigurationResponseSequenceNumber(11112222); parameter1->SetResult(RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS); parameter1->SetNextTsns(100000001, 200000002); parameter1->Consolidate(); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4 + 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 1, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* addedParameter1 = reinterpret_cast(chunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ addedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(addedParameter1->GetReconfigurationResponseSequenceNumber() == 11112222); REQUIRE( addedParameter1->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS); REQUIRE(addedParameter1->HasNextTsns() == true); REQUIRE(addedParameter1->GetSenderNextTsn() == 100000001); REQUIRE(addedParameter1->GetReceiverNextTsn() == 200000002); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::ReConfigChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4 + 20, /*length*/ 4 + 20, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::RE_CONFIG, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP, /*flags*/ 0b00000000, /*canHaveParameters*/ true, /*parametersCount*/ 1, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); const auto* parsedParameter1 = reinterpret_cast( parsedChunk->GetParameterAt(0)); CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter1, /*buffer*/ nullptr, /*bufferLength*/ 20, /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter1->GetReconfigurationResponseSequenceNumber() == 11112222); REQUIRE( parsedParameter1->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS); REQUIRE(parsedParameter1->HasNextTsns() == true); REQUIRE(parsedParameter1->GetSenderNextTsn() == 100000001); REQUIRE(parsedParameter1->GetReceiverNextTsn() == 200000002); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestSackChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include #include // std::memset() #include SCENARIO("Selective Acknowledgement Chunk (3)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("SackChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:3 (SACK), Flags: 0b00000000, Length: 36 0x03, 0b00000000, 0x00, 0x24, // Cumulative TSN Ack: 287454020, 0x11, 0x22, 0x33, 0x44, // Advertised Receiver Window Credit: 4278216311 0xFF, 0x00, 0x66, 0x77, // Number of Gap Ack Blocks: 2, Number of Duplicate TSNs: 3 0x00, 0x02, 0x00, 0x03, // Gap Ack Block 1: Start: 1000, End: 1999 0x03, 0xE8, 0x07, 0xCF, // Gap Ack Block 2: Start: 2000, End: 2999 // Notice that this is wrong since it should be merged with the first // Gap Ack Block. 0x07, 0xD0, 0x0B, 0xB7, // Duplicate TSN 1: 287454020, 0x11, 0x22, 0x33, 0x44, // Duplicate TSN 2: 4278216311 0xFF, 0x00, 0x66, 0x77, // Duplicate TSN 3: 556942164 0x21, 0x32, 0x43, 0x54, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* chunk = RTC::SCTP::SackChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 36, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetCumulativeTsnAck() == 287454020); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311); REQUIRE( chunk->GetDuplicateTsns() == std::vector{ { 287454020, 4278216311, 556942164 } }); REQUIRE( chunk->GetGapAckBlocks() == std::vector{ { 1000, 1999 }, { 2000, 2999 } }); REQUIRE( chunk->GetValidatedGapAckBlocks() == std::vector{ { 1000, 2999 }, }); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 36, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetCumulativeTsnAck() == 287454020); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 4278216311); REQUIRE( chunk->GetDuplicateTsns() == std::vector{ { 287454020, 4278216311, 556942164 } }); REQUIRE( chunk->GetGapAckBlocks() == std::vector{ { 1000, 1999 }, { 2000, 2999 } }); REQUIRE( chunk->GetValidatedGapAckBlocks() == std::vector{ { 1000, 2999 } }); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 36, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(clonedChunk->GetCumulativeTsnAck() == 287454020); REQUIRE(clonedChunk->GetAdvertisedReceiverWindowCredit() == 4278216311); REQUIRE( clonedChunk->GetDuplicateTsns() == std::vector{ { 287454020, 4278216311, 556942164 } }); REQUIRE( clonedChunk->GetGapAckBlocks() == std::vector{ { 1000, 1999 }, { 2000, 2999 } }); REQUIRE( clonedChunk->GetValidatedGapAckBlocks() == std::vector{ { 1000, 2999 } }); delete clonedChunk; } SECTION("SackChunk::Parse() fails") { // Length field doesn't match Number of Gap Ack Blocks + Number of // Duplicate TSNs. // clang-format off alignas(4) uint8_t buffer1[] = { // Type:3 (SACK), Flags: 0b00000000, Length: 24 (should be 28) 0x03, 0b00000000, 0x00, 0x18, // Cumulative TSN Ack: 287454020, 0x11, 0x22, 0x33, 0x44, // Advertised Receiver Window Credit: 4278216311 0xFF, 0x00, 0x66, 0x77, // Number of Gap Ack Blocks: 1, Number of Duplicate TSNs: 2 0x00, 0x01, 0x00, 0x02, // Gap Ack Block 1: Start: 1000, End: 1999 0x03, 0xE8, 0x07, 0xCF, // Duplicate TSN 1: 287454020, 0x11, 0x22, 0x33, 0x44, // Duplicate TSN 2: 4278216311 0xFF, 0x00, 0x66, 0x77, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on REQUIRE(!RTC::SCTP::SackChunk::Parse(buffer1, sizeof(buffer1))); // Length field doesn't match Number of Gap Ack Blocks + Number of // Duplicate TSNs. // clang-format off alignas(4) uint8_t buffer2[] = { // Type:3 (SACK), Flags: 0b00000000, Length: 32 (should be 28) 0x03, 0b00000000, 0x00, 0x20, // Cumulative TSN Ack: 287454020, 0x11, 0x22, 0x33, 0x44, // Advertised Receiver Window Credit: 4278216311 0xFF, 0x00, 0x66, 0x77, // Number of Gap Ack Blocks: 1, Number of Duplicate TSNs: 2 0x00, 0x01, 0x00, 0x02, // Gap Ack Block 1: Start: 1000, End: 1999 0x03, 0xE8, 0x07, 0xCF, // Duplicate TSN 1: 287454020, 0x11, 0x22, 0x33, 0x44, // Duplicate TSN 2: 4278216311 0xFF, 0x00, 0x66, 0x77, // Duplicate TSN 3: 4278216312 (exceeds Number of Duplicate TSNs) 0xFF, 0x00, 0x66, 0x78, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on REQUIRE(!RTC::SCTP::SackChunk::Parse(buffer2, sizeof(buffer2))); // Wrong Length field (smaller than buffer). // clang-format off alignas(4) uint8_t buffer3[] = { // Type:3 (SACK), Flags: 0b00000000, Length: 24 (buffer is 20) 0x03, 0b00000000, 0x00, 0x18, // Cumulative TSN Ack: 287454020, 0x11, 0x22, 0x33, 0x44, // Advertised Receiver Window Credit: 4278216311 0xFF, 0x00, 0x66, 0x77, // Number of Gap Ack Blocks: 1, Number of Duplicate TSNs: 0 0x00, 0x01, 0x00, 0x00, // Gap Ack Block 1: Start: 1000, End: 1999 0x03, 0xE8, 0x07, 0xCF, }; // clang-format on REQUIRE(!RTC::SCTP::SackChunk::Parse(buffer3, sizeof(buffer3))); } SECTION("SackChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::SackChunk::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetCumulativeTsnAck() == 0); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 0); REQUIRE(chunk->GetDuplicateTsns().empty()); REQUIRE(chunk->GetGapAckBlocks().empty()); REQUIRE(chunk->GetValidatedGapAckBlocks().empty()); /* Modify it. */ chunk->SetCumulativeTsnAck(1234); chunk->SetAdvertisedReceiverWindowCredit(5678); chunk->AddDuplicateTsn(10000000); chunk->AddAckBlock(10000, 19999); // Notice that here we are creating a semig-wrong SACK Chunk since these // two ranges shoyuld be merged into one. chunk->AddAckBlock(RTC::SCTP::SackChunk::GapAckBlock(20000, 20999)); chunk->AddDuplicateTsn(20000000); chunk->AddAckBlock(60000, 60999); chunk->AddDuplicateTsn(30000000); chunk->AddDuplicateTsn(40000000); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 44, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetCumulativeTsnAck() == 1234); REQUIRE(chunk->GetAdvertisedReceiverWindowCredit() == 5678); REQUIRE( chunk->GetDuplicateTsns() == std::vector{ 10000000, 20000000, 30000000, 40000000 }); REQUIRE( chunk->GetGapAckBlocks() == std::vector{ { 10000, 19999 }, { 20000, 20999 }, { 60000, 60999 } }); REQUIRE( chunk->GetValidatedGapAckBlocks() == std::vector{ { 10000, 20999 }, { 60000, 60999 } }); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::SackChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 44, /*length*/ 44, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(parsedChunk->GetCumulativeTsnAck() == 1234); REQUIRE(parsedChunk->GetAdvertisedReceiverWindowCredit() == 5678); REQUIRE( parsedChunk->GetDuplicateTsns() == std::vector{ 10000000, 20000000, 30000000, 40000000 }); REQUIRE( parsedChunk->GetGapAckBlocks() == std::vector{ { 10000, 19999 }, { 20000, 20999 }, { 60000, 60999 } }); REQUIRE( parsedChunk->GetValidatedGapAckBlocks() == std::vector{ { 10000, 20999 }, { 60000, 60999 } }); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestShutdownAckChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownAckChunk.hpp" #include #include // std::memset() SCENARIO("SCTP Shutdown Ack Chunk (8)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("ShutdownAckChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:8 (SHUTDOWN_ACK), Flags:0x00000000, Length: 4 0x08, 0b01000000, 0x00, 0x04, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* chunk = RTC::SCTP::ShutdownAckChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b01000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b01000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b01000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); delete clonedChunk; } SECTION("ShutdownAckChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::ShutdownAckChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::ShutdownAckChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4, /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_ACK, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestShutdownChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownChunk.hpp" #include #include // std::memset() SCENARIO("SCTP Shutdown Association Chunk (7)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("ShutdownChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:7 (SHUTDOWN), Flags:0x00000000, Length: 8 0x07, 0b00000000, 0x00, 0x08, // Cumulative TSN Ack: 0x11223344, 0x11, 0x22, 0x33, 0x44, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* chunk = RTC::SCTP::ShutdownChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetCumulativeTsnAck() == 0x11223344); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetCumulativeTsnAck() == 0x11223344); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(clonedChunk->GetCumulativeTsnAck() == 0x11223344); delete clonedChunk; } SECTION("ShutdownChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::ShutdownChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetCumulativeTsnAck() == 0); /* Modify it. */ chunk->SetCumulativeTsnAck(99887766); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetCumulativeTsnAck() == 99887766); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::ShutdownChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(parsedChunk->GetCumulativeTsnAck() == 99887766); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestShutdownCompleteChunk.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/ShutdownCompleteChunk.hpp" #include #include // std::memset() SCENARIO("SCTP Shutdown Complete Chunk (14)", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("ShutdownCompleteChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:8 (SHUTDOWN_COMPLETE), Flags:0x00000001, T: 1, Length: 4 0x0E, 0b00000001, 0x00, 0x04, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD }; // clang-format on auto* chunk = RTC::SCTP::ShutdownCompleteChunk::Parse(buffer, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetT() == true); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetT() == true); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(clonedChunk->GetT() == true); delete clonedChunk; } SECTION("ShutdownCompleteChunk::Factory() succeeds") { auto* chunk = RTC::SCTP::ShutdownCompleteChunk::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000000, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetT() == false); /* Modify it. */ chunk->SetT(true); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->GetT() == true); /* Parse itself and compare. */ auto* parsedChunk = RTC::SCTP::ShutdownCompleteChunk::Parse(chunk->GetBuffer(), chunk->GetLength()); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ parsedChunk, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4, /*length*/ 4, /*chunkType*/ RTC::SCTP::Chunk::ChunkType::SHUTDOWN_COMPLETE, /*unknownType*/ false, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::STOP, /*flags*/ 0b00000001, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(parsedChunk->GetT() == true); delete parsedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/chunks/TestUnknownChunk.cpp ================================================ #include "common.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/chunks/UnknownChunk.hpp" #include #include // std::memset() SCENARIO("SCTP Unknown Chunk", "[serializable][sctp][chunk]") { sctpCommon::ResetBuffers(); SECTION("UnknownChunk::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:0xEE (UNKNOWN), Flags: 0b1100, Length: 7 0xEE, 0b10001100, 0x00, 0x07, // Unknown value: 0xAABBCC, 1 byte of padding 0xAA, 0xBB, 0xCC, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* chunk = RTC::SCTP::UnknownChunk::Parse(buffer, sizeof(buffer)); // NOTE: Chunk Type is 0xEE (0b11101110) so first 2 bits are 11, meaning // that the action to take if we receive this Chunk Type is SKIP_AND_REPORT. CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*chunkType*/ static_cast(0xEE), /*unknownType*/ true, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b10001100, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->HasUnknownValue() == true); REQUIRE(chunk->GetUnknownValueLength() == 3); REQUIRE(chunk->GetUnknownValue()[0] == 0xAA); REQUIRE(chunk->GetUnknownValue()[1] == 0xBB); REQUIRE(chunk->GetUnknownValue()[2] == 0xCC); /* Serialize it. */ chunk->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_CHUNK( /*chunk*/ chunk, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*chunkType*/ static_cast(0xEE), /*unknownType*/ true, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b10001100, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(chunk->HasUnknownValue() == true); REQUIRE(chunk->GetUnknownValueLength() == 3); REQUIRE(chunk->GetUnknownValue()[0] == 0xAA); REQUIRE(chunk->GetUnknownValue()[1] == 0xBB); REQUIRE(chunk->GetUnknownValue()[2] == 0xCC); /* Clone it. */ auto* clonedChunk = chunk->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete chunk; CHECK_SCTP_CHUNK( /*chunk*/ clonedChunk, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*chunkType*/ static_cast(0xEE), /*unknownType*/ true, /*actionForUnknownChunkType*/ RTC::SCTP::Chunk::ActionForUnknownChunkType::SKIP_AND_REPORT, /*flags*/ 0b10001100, /*canHaveParameters*/ false, /*parametersCount*/ 0, /*canHaveErrorCauses*/ false, /*errorCausesCount*/ 0); REQUIRE(clonedChunk->HasUnknownValue() == true); REQUIRE(clonedChunk->GetUnknownValueLength() == 3); REQUIRE(clonedChunk->GetUnknownValue()[0] == 0xAA); REQUIRE(clonedChunk->GetUnknownValue()[1] == 0xBB); REQUIRE(clonedChunk->GetUnknownValue()[2] == 0xCC); delete clonedChunk; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestCookieReceivedWhileShuttingDownErrorCause.cpp ================================================ #include "common.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/CookieReceivedWhileShuttingDownErrorCause.hpp" #include #include // std::memset() SCENARIO("Cookie Received While Shutting Down Error Cause (10)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("CookieReceivedWhileShuttingDownErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:10 (COOKIE_RECEIVED_WHILE_SHUTTING_DOWN), Length: 4 0x00, 0x0A, 0x00, 0x04, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* errorCause = RTC::SCTP::CookieReceivedWhileShuttingDownErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN, /*unknownCode*/ false); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN, /*unknownCode*/ false); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN, /*unknownCode*/ false); delete clonedErrorCause; } SECTION("CookieReceivedWhileShuttingDownErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::CookieReceivedWhileShuttingDownErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN, /*unknownCode*/ false); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::CookieReceivedWhileShuttingDownErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4, /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::COOKIE_RECEIVED_WHILE_SHUTTING_DOWN, /*unknownCode*/ false); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestInvalidMandatoryParameterErrorCause.cpp ================================================ #include "common.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/InvalidMandatoryParameterErrorCause.hpp" #include #include // std::memset() SCENARIO("Invalid Mandatory Parameter Error Cause (7)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("InvalidMandatoryParameterErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:7 (INVALID_MANDATORY_PARAMETER), Length: 4 0x00, 0x07, 0x00, 0x04, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* errorCause = RTC::SCTP::InvalidMandatoryParameterErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER, /*unknownCode*/ false); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER, /*unknownCode*/ false); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER, /*unknownCode*/ false); delete clonedErrorCause; } SECTION("InvalidMandatoryParameterErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::InvalidMandatoryParameterErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER, /*unknownCode*/ false); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::InvalidMandatoryParameterErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4, /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_MANDATORY_PARAMETER, /*unknownCode*/ false); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestInvalidStreamIdentifierErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/InvalidStreamIdentifierErrorCause.hpp" #include #include // std::memset() SCENARIO("Invalid Stream Identifier Error Cause (1)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("InvalidStreamIdentifierErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:1 (INVALID_STREAM_IDENTIFIER), Length: 8 0x00, 0x01, 0x00, 0x08, // Stream Identifier: 12345 0x30, 0x39, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* errorCause = RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, /*unknownCode*/ false); REQUIRE(errorCause->GetStreamIdentifier() == 12345); // Reserved bytes must be 0. REQUIRE(errorCause->GetBuffer()[6] == 0); REQUIRE(errorCause->GetBuffer()[7] == 0); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, /*unknownCode*/ false); REQUIRE(errorCause->GetStreamIdentifier() == 12345); // Reserved bytes must be 0. REQUIRE(errorCause->GetBuffer()[6] == 0); REQUIRE(errorCause->GetBuffer()[7] == 0); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, /*unknownCode*/ false); REQUIRE(clonedErrorCause->GetStreamIdentifier() == 12345); // Reserved bytes must be 0. REQUIRE(clonedErrorCause->GetBuffer()[6] == 0); REQUIRE(clonedErrorCause->GetBuffer()[7] == 0); delete clonedErrorCause; } SECTION("InvalidStreamIdentifierErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 8 0x03, 0xE7, 0x00, 0x08, // Stream Identifier: 12345 0x30, 0x39, 0x00, 0x00, }; // clang-format on REQUIRE(!RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer1, sizeof(buffer1))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:1 (INVALID_STREAM_IDENTIFIER), Length: 7 0x00, 0x01, 0x00, 0x07, // Stream Identifier: 12345 0x30, 0x39, 0x00 }; // clang-format on REQUIRE(!RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer2, sizeof(buffer2))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer3[] = { // Code:1 (INVALID_STREAM_IDENTIFIER), Length: 9 0x00, 0x01, 0x00, 0x09, // Stream Identifier: 12345 0x30, 0x39, 0x00, 0x00, 0xEE }; // clang-format on REQUIRE(!RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer3, sizeof(buffer3))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer4[] = { // Code:1 (INVALID_STREAM_IDENTIFIER), Length: 8 0x00, 0x01, 0x00, 0x08, // Stream Identifier: 12345 0x30, 0x39, 0x00 }; // clang-format on REQUIRE(!RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse(buffer4, sizeof(buffer4))); } SECTION("InvalidStreamIdentifierErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::InvalidStreamIdentifierErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, /*unknownCode*/ false); REQUIRE(errorCause->GetStreamIdentifier() == 0); // Reserved bytes must be 0. REQUIRE(errorCause->GetBuffer()[6] == 0); REQUIRE(errorCause->GetBuffer()[7] == 0); /* Modify it. */ errorCause->SetStreamIdentifier(6666); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, /*unknownCode*/ false); REQUIRE(errorCause->GetStreamIdentifier() == 6666); // Reserved bytes must be 0. REQUIRE(errorCause->GetBuffer()[6] == 0); REQUIRE(errorCause->GetBuffer()[7] == 0); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::InvalidStreamIdentifierErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::INVALID_STREAM_IDENTIFIER, /*unknownCode*/ false); REQUIRE(parsedErrorCause->GetStreamIdentifier() == 6666); // Reserved bytes must be 0. REQUIRE(parsedErrorCause->GetBuffer()[6] == 0); REQUIRE(parsedErrorCause->GetBuffer()[7] == 0); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestMissingMandatoryParameterErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/errorCauses/MissingMandatoryParameterErrorCause.hpp" #include #include // std::memset() SCENARIO("Invalid Stream Identifier Error Cause (2)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("MissingMandatoryParameterErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:2 (MISSING_MANDATORY_PARAMETER), Length: 14 0x00, 0x02, 0x00, 0x0E, // Number of missing params: 3 0x00, 0x00, 0x00, 0x03, // Missing Param 1: 5 (IPV4_ADDRESS), Missing Param 2: 6 (IPV6_ADDRESS) 0x00, 0x05, 0x00, 0x06, // Missing Param 3: 9 (COOKIE_PRESERVATIVE), 2 bytes of padding 0x00, 0x09, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* errorCause = RTC::SCTP::MissingMandatoryParameterErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 16, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER, /*unknownCode*/ false); REQUIRE(errorCause->GetNumberOfMissingParameters() == 3); REQUIRE( errorCause->GetMissingParameterTypeAt(0) == RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS); REQUIRE( errorCause->GetMissingParameterTypeAt(1) == RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS); REQUIRE( errorCause->GetMissingParameterTypeAt(2) == RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE); // These should be padding. REQUIRE(errorCause->GetBuffer()[14] == 0); REQUIRE(errorCause->GetBuffer()[15] == 0); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 16, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER, /*unknownCode*/ false); REQUIRE(errorCause->GetNumberOfMissingParameters() == 3); REQUIRE( errorCause->GetMissingParameterTypeAt(0) == RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS); REQUIRE( errorCause->GetMissingParameterTypeAt(1) == RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS); REQUIRE( errorCause->GetMissingParameterTypeAt(2) == RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE); // These should be padding. REQUIRE(errorCause->GetBuffer()[14] == 0); REQUIRE(errorCause->GetBuffer()[15] == 0); // /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 16, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER, /*unknownCode*/ false); REQUIRE(clonedErrorCause->GetNumberOfMissingParameters() == 3); REQUIRE( clonedErrorCause->GetMissingParameterTypeAt(0) == RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS); REQUIRE( clonedErrorCause->GetMissingParameterTypeAt(1) == RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS); REQUIRE( clonedErrorCause->GetMissingParameterTypeAt(2) == RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE); // These should be padding. REQUIRE(clonedErrorCause->GetBuffer()[14] == 0); REQUIRE(clonedErrorCause->GetBuffer()[15] == 0); delete clonedErrorCause; } SECTION("MissingMandatoryParameterErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 14 0x03, 0xE7, 0x00, 0x0E, // Number of missing params: 3 0x00, 0x00, 0x00, 0x03, // Missing Param 1: 5 (IPV4_ADDRESS), Missing Param 2: 6 (IPV6_ADDRESS) 0x00, 0x05, 0x00, 0x06, // Missing Param 3: 9 (COOKIE_PRESERVATIVE), 2 bytes of padding 0x00, 0x09, 0x00, 0x00, }; // clang-format on REQUIRE(!RTC::SCTP::MissingMandatoryParameterErrorCause::Parse(buffer1, sizeof(buffer1))); // Length field doesn't match Number of missing parameters. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:2 (MISSING_MANDATORY_PARAMETER), Length: 14 0x00, 0x02, 0x00, 0x0E, // Number of missing params: 2 0x00, 0x00, 0x00, 0x02, // Missing Param 1: 5 (IPV4_ADDRESS), Missing Param 2: 6 (IPV6_ADDRESS) 0x00, 0x05, 0x00, 0x06, // Missing Param 3: 9 (COOKIE_PRESERVATIVE) (exceeds number of missing // parameters), 2 bytes of padding 0x00, 0x09, 0x00, 0x00, }; // clang-format on REQUIRE(!RTC::SCTP::MissingMandatoryParameterErrorCause::Parse(buffer2, sizeof(buffer2))); // Wrong Length field (smaller than buffer). // clang-format off alignas(4) uint8_t buffer3[] = { // Code:2 (MISSING_MANDATORY_PARAMETER), Length: 8 (buffer is 12) 0x00, 0x02, 0x00, 0x08, // Number of missing params: 4 0x00, 0x00, 0x00, 0x02, // Missing Param 1: 5 (IPV4_ADDRESS), Missing Param 2: 6 (IPV6_ADDRESS) 0x00, 0x05, 0x00, 0x06, }; // clang-format on REQUIRE(!RTC::SCTP::MissingMandatoryParameterErrorCause::Parse(buffer3, sizeof(buffer3))); } SECTION("MissingMandatoryParameterErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::MissingMandatoryParameterErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER, /*unknownCode*/ false); REQUIRE(errorCause->GetNumberOfMissingParameters() == 0); /* Modify it. */ errorCause->AddMissingParameterType(RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS); errorCause->AddMissingParameterType(RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS); errorCause->AddMissingParameterType(RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER, /*unknownCode*/ false); REQUIRE(errorCause->GetNumberOfMissingParameters() == 3); REQUIRE( errorCause->GetMissingParameterTypeAt(0) == RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS); REQUIRE( errorCause->GetMissingParameterTypeAt(1) == RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS); REQUIRE( errorCause->GetMissingParameterTypeAt(2) == RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE); // These should be padding. REQUIRE(errorCause->GetBuffer()[14] == 0); REQUIRE(errorCause->GetBuffer()[15] == 0); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::MissingMandatoryParameterErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 16, /*length*/ 16, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::MISSING_MANDATORY_PARAMETER, /*unknownCode*/ false); REQUIRE(parsedErrorCause->GetNumberOfMissingParameters() == 3); REQUIRE( parsedErrorCause->GetMissingParameterTypeAt(0) == RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS); REQUIRE( parsedErrorCause->GetMissingParameterTypeAt(1) == RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS); REQUIRE( parsedErrorCause->GetMissingParameterTypeAt(2) == RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE); // These should be padding. REQUIRE(parsedErrorCause->GetBuffer()[14] == 0); REQUIRE(parsedErrorCause->GetBuffer()[15] == 0); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestNoUserDataErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/NoUserDataErrorCause.hpp" #include #include // std::memset() SCENARIO("No User Data Error Cause (9)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("NoUserDataErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:9 (NO_USER_DATA), Length: 8 0x00, 0x09, 0x00, 0x08, // TSN: 987654321 0x3A, 0xDE, 0x68, 0xB1, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* errorCause = RTC::SCTP::NoUserDataErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA, /*unknownCode*/ false); REQUIRE(errorCause->GetTsn() == 987654321); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA, /*unknownCode*/ false); REQUIRE(errorCause->GetTsn() == 987654321); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA, /*unknownCode*/ false); REQUIRE(clonedErrorCause->GetTsn() == 987654321); delete clonedErrorCause; } SECTION("NoUserDataErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 8 0x03, 0xE7, 0x00, 0x08, // TSN: 987654321 0x3A, 0xDE, 0x68, 0xB1, }; // clang-format on REQUIRE(!RTC::SCTP::NoUserDataErrorCause::Parse(buffer1, sizeof(buffer1))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:9 (NO_USER_DATA), Length: 7 0x00, 0x09, 0x00, 0x07, // TSN: 987654321 0x3A, 0xDE, 0x68, }; // clang-format on REQUIRE(!RTC::SCTP::NoUserDataErrorCause::Parse(buffer2, sizeof(buffer2))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer3[] = { // Code:9 (NO_USER_DATA), Length: 9 0x00, 0x09, 0x00, 0x09, // TSN: 987654321 0x3A, 0xDE, 0x68, 0xB1, 0xEE }; // clang-format on REQUIRE(!RTC::SCTP::NoUserDataErrorCause::Parse(buffer3, sizeof(buffer3))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer4[] = { // Code:9 (NO_USER_DATA), Length: 8 0x00, 0x09, 0x00, 0x08, // TSN (last byte missing) 0x3A, 0xDE, 0x68 }; // clang-format on REQUIRE(!RTC::SCTP::NoUserDataErrorCause::Parse(buffer4, sizeof(buffer4))); } SECTION("NoUserDataErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::NoUserDataErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA, /*unknownCode*/ false); REQUIRE(errorCause->GetTsn() == 0); /* Modify it. */ errorCause->SetTsn(666666); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA, /*unknownCode*/ false); REQUIRE(errorCause->GetTsn() == 666666); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::NoUserDataErrorCause::Parse(errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::NO_USER_DATA, /*unknownCode*/ false); REQUIRE(parsedErrorCause->GetTsn() == 666666); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestOutOfResourceErrorCause.cpp ================================================ #include "common.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/OutOfResourceErrorCause.hpp" #include #include // std::memset() SCENARIO("Out of Resource Error Cause (4)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("OutOfResourceErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:4 (OUT_OF_RESOURCE), Length: 4 0x00, 0x04, 0x00, 0x04, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* errorCause = RTC::SCTP::OutOfResourceErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, /*unknownCode*/ false); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, /*unknownCode*/ false); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, /*unknownCode*/ false); delete clonedErrorCause; } SECTION("OutOfResourceErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 4 0x03, 0xE7, 0x00, 0x04 }; // clang-format on REQUIRE(!RTC::SCTP::OutOfResourceErrorCause::Parse(buffer1, sizeof(buffer1))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:4 (OUT_OF_RESOURCE), Length: 5 0x00, 0x04, 0x00, 0x07, 0x3A, }; // clang-format on REQUIRE(!RTC::SCTP::OutOfResourceErrorCause::Parse(buffer2, sizeof(buffer2))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer3[] = { // Code:4 (OUT_OF_RESOURCE), Length (broken) 0x00, 0x04, 0x00, }; // clang-format on REQUIRE(!RTC::SCTP::OutOfResourceErrorCause::Parse(buffer3, sizeof(buffer3))); } SECTION("OutOfResourceErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::OutOfResourceErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, /*unknownCode*/ false); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::OutOfResourceErrorCause::Parse(errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4, /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::OUT_OF_RESOURCE, /*unknownCode*/ false); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestProtocolViolationErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/ProtocolViolationErrorCause.hpp" #include #include // std::memset() SCENARIO("Protocol Violation Error Cause (13)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("ProtocolViolationErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:13 (PROTOCOL_VIOLATION), Length: 10 0x00, 0x0D, 0x00, 0x0A, // Additional Information: "error1" 0x65, 0x72, 0x72, 0x6F, // 2 bytes of padding. 0x72, 0x31, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, }; // clang-format on auto* errorCause = RTC::SCTP::ProtocolViolationErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION, /*unknownCode*/ false); REQUIRE(errorCause->HasAdditionalInformation() == true); REQUIRE(errorCause->GetAdditionalInformationLength() == 6); REQUIRE(errorCause->GetAdditionalInformation()[0] == 0x65); REQUIRE(errorCause->GetAdditionalInformation()[1] == 0x72); REQUIRE(errorCause->GetAdditionalInformation()[2] == 0x72); REQUIRE(errorCause->GetAdditionalInformation()[3] == 0x6F); REQUIRE(errorCause->GetAdditionalInformation()[4] == 0x72); REQUIRE(errorCause->GetAdditionalInformation()[5] == 0x31); std::string additionalInfo( reinterpret_cast(errorCause->GetAdditionalInformation()), errorCause->GetAdditionalInformationLength()); REQUIRE(additionalInfo == "error1"); // These should be padding. REQUIRE(errorCause->GetAdditionalInformation()[6] == 0x00); REQUIRE(errorCause->GetAdditionalInformation()[7] == 0x00); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION, /*unknownCode*/ false); REQUIRE(errorCause->HasAdditionalInformation() == true); REQUIRE(errorCause->GetAdditionalInformationLength() == 6); REQUIRE(errorCause->GetAdditionalInformation()[0] == 0x65); REQUIRE(errorCause->GetAdditionalInformation()[1] == 0x72); REQUIRE(errorCause->GetAdditionalInformation()[2] == 0x72); REQUIRE(errorCause->GetAdditionalInformation()[3] == 0x6F); REQUIRE(errorCause->GetAdditionalInformation()[4] == 0x72); REQUIRE(errorCause->GetAdditionalInformation()[5] == 0x31); additionalInfo = std::string( reinterpret_cast(errorCause->GetAdditionalInformation()), errorCause->GetAdditionalInformationLength()); REQUIRE(additionalInfo == "error1"); // These should be padding. REQUIRE(errorCause->GetAdditionalInformation()[6] == 0x00); REQUIRE(errorCause->GetAdditionalInformation()[7] == 0x00); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION, /*unknownCode*/ false); REQUIRE(clonedErrorCause->HasAdditionalInformation() == true); REQUIRE(clonedErrorCause->GetAdditionalInformationLength() == 6); REQUIRE(clonedErrorCause->GetAdditionalInformation()[0] == 0x65); REQUIRE(clonedErrorCause->GetAdditionalInformation()[1] == 0x72); REQUIRE(clonedErrorCause->GetAdditionalInformation()[2] == 0x72); REQUIRE(clonedErrorCause->GetAdditionalInformation()[3] == 0x6F); REQUIRE(clonedErrorCause->GetAdditionalInformation()[4] == 0x72); REQUIRE(clonedErrorCause->GetAdditionalInformation()[5] == 0x31); additionalInfo = std::string( reinterpret_cast(clonedErrorCause->GetAdditionalInformation()), clonedErrorCause->GetAdditionalInformationLength()); REQUIRE(additionalInfo == "error1"); // These should be padding. REQUIRE(clonedErrorCause->GetAdditionalInformation()[6] == 0x00); REQUIRE(clonedErrorCause->GetAdditionalInformation()[7] == 0x00); delete clonedErrorCause; } SECTION("ProtocolViolationErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 8 0x03, 0xE7, 0x00, 0x08, // Additional Information: 0x12345678 0x12, 0x34, 0x56, 0x78, }; // clang-format on REQUIRE(!RTC::SCTP::ProtocolViolationErrorCause::Parse(buffer1, sizeof(buffer1))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:13 (PROTOCOL_VIOLATION), Length: 7 0x00, 0x0D, 0x00, 0x07, // Additional Information: 0x123456 (missing padding byte) 0x12, 0x34, 0x56, }; // clang-format on REQUIRE(!RTC::SCTP::ProtocolViolationErrorCause::Parse(buffer2, sizeof(buffer2))); } SECTION("ProtocolViolationErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::ProtocolViolationErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION, /*unknownCode*/ false); REQUIRE(errorCause->HasAdditionalInformation() == false); REQUIRE(errorCause->GetAdditionalInformationLength() == 0); /* Modify it. */ // Verify that replacing the value works. errorCause->SetAdditionalInformation(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(errorCause->GetLength() == 3004); REQUIRE(errorCause->HasAdditionalInformation() == true); REQUIRE(errorCause->GetAdditionalInformationLength() == 3000); errorCause->SetAdditionalInformation(nullptr, 0); REQUIRE(errorCause->GetLength() == 4); REQUIRE(errorCause->HasAdditionalInformation() == false); REQUIRE(errorCause->GetAdditionalInformationLength() == 0); // 6 bytes + 2 bytes of padding. errorCause->SetAdditionalInformation("iñaki"); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION, /*unknownCode*/ false); REQUIRE(errorCause->HasAdditionalInformation() == true); REQUIRE(errorCause->GetAdditionalInformationLength() == 6); std::string additionalInfo( reinterpret_cast(errorCause->GetAdditionalInformation()), errorCause->GetAdditionalInformationLength()); REQUIRE(additionalInfo == "iñaki"); // These should be padding. REQUIRE(errorCause->GetAdditionalInformation()[6] == 0x00); REQUIRE(errorCause->GetAdditionalInformation()[7] == 0x00); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::ProtocolViolationErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::PROTOCOL_VIOLATION, /*unknownCode*/ false); REQUIRE(parsedErrorCause->HasAdditionalInformation() == true); REQUIRE(parsedErrorCause->GetAdditionalInformationLength() == 6); additionalInfo = std::string( reinterpret_cast(parsedErrorCause->GetAdditionalInformation()), parsedErrorCause->GetAdditionalInformationLength()); REQUIRE(additionalInfo == "iñaki"); // These should be padding. REQUIRE(parsedErrorCause->GetAdditionalInformation()[6] == 0x00); REQUIRE(parsedErrorCause->GetAdditionalInformation()[7] == 0x00); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestRestartOfAnAssociationWithNewAddressesErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/RestartOfAnAssociationWithNewAddressesErrorCause.hpp" #include #include // std::memset() SCENARIO("Restart of an Association with New Addresses Error Cause (11)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("RestartOfAnAssociationWithNewAddressesErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:11 (RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES), Length: 11 0x00, 0x0B, 0x00, 0x0B, // New Address TLVs: 0x1234567890AB 0x12, 0x34, 0x56, 0x78, // 1 byte of padding. 0x90, 0xAB, 0xCD, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, }; // clang-format on auto* errorCause = RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES, /*unknownCode*/ false); REQUIRE(errorCause->HasNewAddressTlvs() == true); REQUIRE(errorCause->GetNewAddressTlvsLength() == 7); REQUIRE(errorCause->GetNewAddressTlvs()[0] == 0x12); REQUIRE(errorCause->GetNewAddressTlvs()[1] == 0x34); REQUIRE(errorCause->GetNewAddressTlvs()[2] == 0x56); REQUIRE(errorCause->GetNewAddressTlvs()[3] == 0x78); REQUIRE(errorCause->GetNewAddressTlvs()[4] == 0x90); REQUIRE(errorCause->GetNewAddressTlvs()[5] == 0xAB); REQUIRE(errorCause->GetNewAddressTlvs()[6] == 0xCD); // This should be padding. REQUIRE(errorCause->GetNewAddressTlvs()[7] == 0x00); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES, /*unknownCode*/ false); REQUIRE(errorCause->HasNewAddressTlvs() == true); REQUIRE(errorCause->GetNewAddressTlvsLength() == 7); REQUIRE(errorCause->GetNewAddressTlvs()[0] == 0x12); REQUIRE(errorCause->GetNewAddressTlvs()[1] == 0x34); REQUIRE(errorCause->GetNewAddressTlvs()[2] == 0x56); REQUIRE(errorCause->GetNewAddressTlvs()[3] == 0x78); REQUIRE(errorCause->GetNewAddressTlvs()[4] == 0x90); REQUIRE(errorCause->GetNewAddressTlvs()[5] == 0xAB); REQUIRE(errorCause->GetNewAddressTlvs()[6] == 0xCD); // This should be padding. REQUIRE(errorCause->GetNewAddressTlvs()[7] == 0x00); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES, /*unknownCode*/ false); REQUIRE(clonedErrorCause->HasNewAddressTlvs() == true); REQUIRE(clonedErrorCause->GetNewAddressTlvsLength() == 7); REQUIRE(clonedErrorCause->GetNewAddressTlvs()[0] == 0x12); REQUIRE(clonedErrorCause->GetNewAddressTlvs()[1] == 0x34); REQUIRE(clonedErrorCause->GetNewAddressTlvs()[2] == 0x56); REQUIRE(clonedErrorCause->GetNewAddressTlvs()[3] == 0x78); REQUIRE(clonedErrorCause->GetNewAddressTlvs()[4] == 0x90); REQUIRE(clonedErrorCause->GetNewAddressTlvs()[5] == 0xAB); REQUIRE(clonedErrorCause->GetNewAddressTlvs()[6] == 0xCD); // This should be padding. REQUIRE(clonedErrorCause->GetNewAddressTlvs()[7] == 0x00); delete clonedErrorCause; } SECTION("RestartOfAnAssociationWithNewAddressesErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 8 0x03, 0xE7, 0x00, 0x08, // NewAddressTlvs: 0x12345678 0x12, 0x34, 0x56, 0x78, }; // clang-format on REQUIRE(!RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Parse( buffer1, sizeof(buffer1))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:11 (RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES), Length: 7 0x00, 0x0B, 0x00, 0x07, // NewAddressTlvs: 0x123456 (missing padding byte) 0x12, 0x34, 0x56, }; // clang-format on REQUIRE(!RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Parse( buffer2, sizeof(buffer2))); } SECTION("RestartOfAnAssociationWithNewAddressesErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES, /*unknownCode*/ false); REQUIRE(errorCause->HasNewAddressTlvs() == false); REQUIRE(errorCause->GetNewAddressTlvsLength() == 0); /* Modify it. */ // Verify that replacing the value works. errorCause->SetNewAddressTlvs(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(errorCause->GetLength() == 3004); REQUIRE(errorCause->HasNewAddressTlvs() == true); REQUIRE(errorCause->GetNewAddressTlvsLength() == 3000); errorCause->SetNewAddressTlvs(nullptr, 0); REQUIRE(errorCause->GetLength() == 4); REQUIRE(errorCause->HasNewAddressTlvs() == false); REQUIRE(errorCause->GetNewAddressTlvsLength() == 0); // 6 bytes + 2 bytes of padding. errorCause->SetNewAddressTlvs(sctpCommon::DataBuffer, 6); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES, /*unknownCode*/ false); REQUIRE(errorCause->HasNewAddressTlvs() == true); REQUIRE(errorCause->GetNewAddressTlvsLength() == 6); REQUIRE(errorCause->GetNewAddressTlvs()[0] == 0x00); REQUIRE(errorCause->GetNewAddressTlvs()[1] == 0x01); REQUIRE(errorCause->GetNewAddressTlvs()[2] == 0x02); REQUIRE(errorCause->GetNewAddressTlvs()[3] == 0x03); REQUIRE(errorCause->GetNewAddressTlvs()[4] == 0x04); REQUIRE(errorCause->GetNewAddressTlvs()[5] == 0x05); // These should be padding. REQUIRE(errorCause->GetNewAddressTlvs()[6] == 0x00); REQUIRE(errorCause->GetNewAddressTlvs()[7] == 0x00); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::RestartOfAnAssociationWithNewAddressesErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::RESTART_OF_AN_ASSOCIATION_WITH_NEW_ADDRESSES, /*unknownCode*/ false); REQUIRE(parsedErrorCause->HasNewAddressTlvs() == true); REQUIRE(parsedErrorCause->GetNewAddressTlvsLength() == 6); REQUIRE(parsedErrorCause->GetNewAddressTlvs()[0] == 0x00); REQUIRE(parsedErrorCause->GetNewAddressTlvs()[1] == 0x01); REQUIRE(parsedErrorCause->GetNewAddressTlvs()[2] == 0x02); REQUIRE(parsedErrorCause->GetNewAddressTlvs()[3] == 0x03); REQUIRE(parsedErrorCause->GetNewAddressTlvs()[4] == 0x04); REQUIRE(parsedErrorCause->GetNewAddressTlvs()[5] == 0x05); // These should be padding. REQUIRE(parsedErrorCause->GetNewAddressTlvs()[6] == 0x00); REQUIRE(parsedErrorCause->GetNewAddressTlvs()[7] == 0x00); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestStaleCookieErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/StaleCookieErrorCause.hpp" #include #include // std::memset() SCENARIO("Stale Cookie Error Cause (3)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("StaleCookieErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:3 (STALE_COOKIE), Length: 8 0x00, 0x03, 0x00, 0x08, // Measure of Staleness: 987654321 0x3A, 0xDE, 0x68, 0xB1, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* errorCause = RTC::SCTP::StaleCookieErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(errorCause->GetMeasureOfStaleness() == 987654321); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(errorCause->GetMeasureOfStaleness() == 987654321); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(clonedErrorCause->GetMeasureOfStaleness() == 987654321); delete clonedErrorCause; } SECTION("StaleCookieErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 8 0x03, 0xE7, 0x00, 0x08, // Measure of Staleness: 987654321 0x3A, 0xDE, 0x68, 0xB1, }; // clang-format on REQUIRE(!RTC::SCTP::StaleCookieErrorCause::Parse(buffer1, sizeof(buffer1))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:3 (STALE_COOKIE), Length: 7 0x00, 0x03, 0x00, 0x07, // Measure of Staleness: 987654321 0x3A, 0xDE, 0x68, }; // clang-format on REQUIRE(!RTC::SCTP::StaleCookieErrorCause::Parse(buffer2, sizeof(buffer2))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer3[] = { // Code:3 (STALE_COOKIE), Length: 9 0x00, 0x03, 0x00, 0x09, // Measure of Staleness: 987654321 0x3A, 0xDE, 0x68, 0xB1, 0xEE }; // clang-format on REQUIRE(!RTC::SCTP::StaleCookieErrorCause::Parse(buffer3, sizeof(buffer3))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer4[] = { // Code:3 (STALE_COOKIE), Length: 8 0x00, 0x03, 0x00, 0x08, // Measure of Staleness (last byte missing) 0x3A, 0xDE, 0x68 }; // clang-format on REQUIRE(!RTC::SCTP::StaleCookieErrorCause::Parse(buffer4, sizeof(buffer4))); } SECTION("StaleCookieErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::StaleCookieErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(errorCause->GetMeasureOfStaleness() == 0); /* Modify it. */ errorCause->SetMeasureOfStaleness(666666); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(errorCause->GetMeasureOfStaleness() == 666666); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::StaleCookieErrorCause::Parse(errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::STALE_COOKIE, /*unknownCode*/ false); REQUIRE(parsedErrorCause->GetMeasureOfStaleness() == 666666); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestUnknownErrorCause.cpp ================================================ #include "common.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnknownErrorCause.hpp" #include #include // std::memset() SCENARIO("Unknown Error Cause", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("UnknownErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:999 (UNKNOWN), Length: 11 0x03, 0xE7, 0x00, 0x0B, // Unknown value: 0x0123456789ABCD 0x01, 0x23, 0x45, 0x67, // 1 byte of padding 0x89, 0xAB, 0xCD, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; // clang-format on auto* errorCause = RTC::SCTP::UnknownErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*causeCode*/ static_cast(999), /*unknownCode*/ true); REQUIRE(errorCause->HasUnknownValue() == true); REQUIRE(errorCause->GetUnknownValueLength() == 7); REQUIRE(errorCause->GetUnknownValue()[1] == 0x23); REQUIRE(errorCause->GetUnknownValue()[0] == 0x01); REQUIRE(errorCause->GetUnknownValue()[1] == 0x23); REQUIRE(errorCause->GetUnknownValue()[2] == 0x45); REQUIRE(errorCause->GetUnknownValue()[3] == 0x67); REQUIRE(errorCause->GetUnknownValue()[4] == 0x89); REQUIRE(errorCause->GetUnknownValue()[5] == 0xAB); REQUIRE(errorCause->GetUnknownValue()[6] == 0xCD); // This should be padding. REQUIRE(errorCause->GetUnknownValue()[7] == 0x00); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*causeCode*/ static_cast(999), /*unknownCode*/ true); REQUIRE(errorCause->HasUnknownValue() == true); REQUIRE(errorCause->GetUnknownValueLength() == 7); REQUIRE(errorCause->GetUnknownValue()[1] == 0x23); REQUIRE(errorCause->GetUnknownValue()[0] == 0x01); REQUIRE(errorCause->GetUnknownValue()[1] == 0x23); REQUIRE(errorCause->GetUnknownValue()[2] == 0x45); REQUIRE(errorCause->GetUnknownValue()[3] == 0x67); REQUIRE(errorCause->GetUnknownValue()[4] == 0x89); REQUIRE(errorCause->GetUnknownValue()[5] == 0xAB); REQUIRE(errorCause->GetUnknownValue()[6] == 0xCD); // This should be padding. REQUIRE(errorCause->GetUnknownValue()[7] == 0x00); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*causeCode*/ static_cast(999), /*unknownCode*/ true); REQUIRE(clonedErrorCause->HasUnknownValue() == true); REQUIRE(clonedErrorCause->GetUnknownValueLength() == 7); REQUIRE(clonedErrorCause->GetUnknownValue()[1] == 0x23); REQUIRE(clonedErrorCause->GetUnknownValue()[0] == 0x01); REQUIRE(clonedErrorCause->GetUnknownValue()[1] == 0x23); REQUIRE(clonedErrorCause->GetUnknownValue()[2] == 0x45); REQUIRE(clonedErrorCause->GetUnknownValue()[3] == 0x67); REQUIRE(clonedErrorCause->GetUnknownValue()[4] == 0x89); REQUIRE(clonedErrorCause->GetUnknownValue()[5] == 0xAB); REQUIRE(clonedErrorCause->GetUnknownValue()[6] == 0xCD); // This should be padding. REQUIRE(clonedErrorCause->GetUnknownValue()[7] == 0x00); delete clonedErrorCause; } SECTION("UnknownErrorCause::Parse() fails") { // Wrong Length field. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:49159 (UNKNOWN), Length: 3 0xC0, 0x07, 0x00, 0x03, // Unknown value: 0x0123456789ABCD 0x01, 0x23, 0x45, 0x67, // 1 byte of padding 0x89, 0xAB, 0xCD, 0x00, }; // clang-format on REQUIRE(!RTC::SCTP::UnknownErrorCause::Parse(buffer1, sizeof(buffer1))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:49159 (UNKNOWN), Length: 11 0xC0, 0x07, 0x00, 0x0B, // Unknown value: 0x0123456789ABCD 0x01, 0x23, 0x45, 0x67, // 1 byte of padding (missing) 0x89, 0xAB, 0xCD }; // clang-format on REQUIRE(!RTC::SCTP::UnknownErrorCause::Parse(buffer2, sizeof(buffer2))); } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestUnrecognizedChunkTypeErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnrecognizedChunkTypeErrorCause.hpp" #include #include // std::memset() SCENARIO("Unrecognized Chunk Type Error Cause (6)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("UnrecognizedChunkTypeErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:6 (UNRECOGNIZED_CHUNK_TYPE), Length: 10 0x00, 0x06, 0x00, 0x0A, // Unrecognized Chunk: 0x1234567890AB 0x12, 0x34, 0x56, 0x78, // 2 bytes of padding. 0x90, 0xAB, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, }; // clang-format on auto* errorCause = RTC::SCTP::UnrecognizedChunkTypeErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(errorCause->HasUnrecognizedChunk() == true); REQUIRE(errorCause->GetUnrecognizedChunkLength() == 6); REQUIRE(errorCause->GetUnrecognizedChunk()[0] == 0x12); REQUIRE(errorCause->GetUnrecognizedChunk()[1] == 0x34); REQUIRE(errorCause->GetUnrecognizedChunk()[2] == 0x56); REQUIRE(errorCause->GetUnrecognizedChunk()[3] == 0x78); REQUIRE(errorCause->GetUnrecognizedChunk()[4] == 0x90); REQUIRE(errorCause->GetUnrecognizedChunk()[5] == 0xAB); // These should be padding. REQUIRE(errorCause->GetUnrecognizedChunk()[6] == 0x00); REQUIRE(errorCause->GetUnrecognizedChunk()[7] == 0x00); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(errorCause->HasUnrecognizedChunk() == true); REQUIRE(errorCause->GetUnrecognizedChunkLength() == 6); REQUIRE(errorCause->GetUnrecognizedChunk()[0] == 0x12); REQUIRE(errorCause->GetUnrecognizedChunk()[1] == 0x34); REQUIRE(errorCause->GetUnrecognizedChunk()[2] == 0x56); REQUIRE(errorCause->GetUnrecognizedChunk()[3] == 0x78); REQUIRE(errorCause->GetUnrecognizedChunk()[4] == 0x90); REQUIRE(errorCause->GetUnrecognizedChunk()[5] == 0xAB); // These should be padding. REQUIRE(errorCause->GetUnrecognizedChunk()[6] == 0x00); REQUIRE(errorCause->GetUnrecognizedChunk()[7] == 0x00); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(clonedErrorCause->HasUnrecognizedChunk() == true); REQUIRE(clonedErrorCause->GetUnrecognizedChunkLength() == 6); REQUIRE(clonedErrorCause->GetUnrecognizedChunk()[0] == 0x12); REQUIRE(clonedErrorCause->GetUnrecognizedChunk()[1] == 0x34); REQUIRE(clonedErrorCause->GetUnrecognizedChunk()[2] == 0x56); REQUIRE(clonedErrorCause->GetUnrecognizedChunk()[3] == 0x78); REQUIRE(clonedErrorCause->GetUnrecognizedChunk()[4] == 0x90); REQUIRE(clonedErrorCause->GetUnrecognizedChunk()[5] == 0xAB); // These should be padding. REQUIRE(clonedErrorCause->GetUnrecognizedChunk()[6] == 0x00); REQUIRE(clonedErrorCause->GetUnrecognizedChunk()[7] == 0x00); delete clonedErrorCause; } SECTION("UnrecognizedChunkTypeErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 8 0x03, 0xE7, 0x00, 0x08, // Unrecognized Chunk: 0x12345678 0x12, 0x34, 0x56, 0x78, }; // clang-format on REQUIRE(!RTC::SCTP::UnrecognizedChunkTypeErrorCause::Parse(buffer1, sizeof(buffer1))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:6 (UNRECOGNIZED_CHUNK_TYPE), Length: 7 0x00, 0x06, 0x00, 0x07, // Unrecognized Chunk: 0x123456 (missing padding byte) 0x12, 0x34, 0x56, }; // clang-format on REQUIRE(!RTC::SCTP::UnrecognizedChunkTypeErrorCause::Parse(buffer2, sizeof(buffer2))); } SECTION("UnrecognizedChunkTypeErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::UnrecognizedChunkTypeErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(errorCause->HasUnrecognizedChunk() == false); REQUIRE(errorCause->GetUnrecognizedChunkLength() == 0); /* Modify it. */ // Verify that replacing the value works. errorCause->SetUnrecognizedChunk(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(errorCause->GetLength() == 3004); REQUIRE(errorCause->HasUnrecognizedChunk() == true); REQUIRE(errorCause->GetUnrecognizedChunkLength() == 3000); errorCause->SetUnrecognizedChunk(nullptr, 0); REQUIRE(errorCause->GetLength() == 4); REQUIRE(errorCause->HasUnrecognizedChunk() == false); REQUIRE(errorCause->GetUnrecognizedChunkLength() == 0); // 6 bytes + 2 bytes of padding. errorCause->SetUnrecognizedChunk(sctpCommon::DataBuffer, 6); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(errorCause->HasUnrecognizedChunk() == true); REQUIRE(errorCause->GetUnrecognizedChunkLength() == 6); REQUIRE(errorCause->GetUnrecognizedChunk()[0] == 0x00); REQUIRE(errorCause->GetUnrecognizedChunk()[1] == 0x01); REQUIRE(errorCause->GetUnrecognizedChunk()[2] == 0x02); REQUIRE(errorCause->GetUnrecognizedChunk()[3] == 0x03); REQUIRE(errorCause->GetUnrecognizedChunk()[4] == 0x04); REQUIRE(errorCause->GetUnrecognizedChunk()[5] == 0x05); // These should be padding. REQUIRE(errorCause->GetUnrecognizedChunk()[6] == 0x00); REQUIRE(errorCause->GetUnrecognizedChunk()[7] == 0x00); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::UnrecognizedChunkTypeErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_CHUNK_TYPE, /*unknownCode*/ false); REQUIRE(parsedErrorCause->HasUnrecognizedChunk() == true); REQUIRE(parsedErrorCause->GetUnrecognizedChunkLength() == 6); REQUIRE(parsedErrorCause->GetUnrecognizedChunk()[0] == 0x00); REQUIRE(parsedErrorCause->GetUnrecognizedChunk()[1] == 0x01); REQUIRE(parsedErrorCause->GetUnrecognizedChunk()[2] == 0x02); REQUIRE(parsedErrorCause->GetUnrecognizedChunk()[3] == 0x03); REQUIRE(parsedErrorCause->GetUnrecognizedChunk()[4] == 0x04); REQUIRE(parsedErrorCause->GetUnrecognizedChunk()[5] == 0x05); // These should be padding. REQUIRE(parsedErrorCause->GetUnrecognizedChunk()[6] == 0x00); REQUIRE(parsedErrorCause->GetUnrecognizedChunk()[7] == 0x00); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestUnrecognizedParametersErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnrecognizedParametersErrorCause.hpp" #include #include // std::memset() SCENARIO("Unrecognized Parameters Error Cause (8)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("UnrecognizedParametersErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:8 (UNRECOGNIZED_PARAMETERS), Length: 11 0x00, 0x08, 0x00, 0x0B, // Unrecognized Parameters: 0x1234567890ABCD 0x12, 0x34, 0x56, 0x78, // 1 byte of padding. 0x90, 0xAB, 0xCD, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, }; // clang-format on auto* errorCause = RTC::SCTP::UnrecognizedParametersErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS, /*unknownCode*/ false); REQUIRE(errorCause->HasUnrecognizedParameters() == true); REQUIRE(errorCause->GetUnrecognizedParametersLength() == 7); REQUIRE(errorCause->GetUnrecognizedParameters()[0] == 0x12); REQUIRE(errorCause->GetUnrecognizedParameters()[1] == 0x34); REQUIRE(errorCause->GetUnrecognizedParameters()[2] == 0x56); REQUIRE(errorCause->GetUnrecognizedParameters()[3] == 0x78); REQUIRE(errorCause->GetUnrecognizedParameters()[4] == 0x90); REQUIRE(errorCause->GetUnrecognizedParameters()[5] == 0xAB); REQUIRE(errorCause->GetUnrecognizedParameters()[6] == 0xCD); // This should be padding. REQUIRE(errorCause->GetUnrecognizedParameters()[7] == 0x00); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS, /*unknownCode*/ false); REQUIRE(errorCause->HasUnrecognizedParameters() == true); REQUIRE(errorCause->GetUnrecognizedParametersLength() == 7); REQUIRE(errorCause->GetUnrecognizedParameters()[0] == 0x12); REQUIRE(errorCause->GetUnrecognizedParameters()[1] == 0x34); REQUIRE(errorCause->GetUnrecognizedParameters()[2] == 0x56); REQUIRE(errorCause->GetUnrecognizedParameters()[3] == 0x78); REQUIRE(errorCause->GetUnrecognizedParameters()[4] == 0x90); REQUIRE(errorCause->GetUnrecognizedParameters()[5] == 0xAB); REQUIRE(errorCause->GetUnrecognizedParameters()[6] == 0xCD); // This should be padding. REQUIRE(errorCause->GetUnrecognizedParameters()[7] == 0x00); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS, /*unknownCode*/ false); REQUIRE(clonedErrorCause->HasUnrecognizedParameters() == true); REQUIRE(clonedErrorCause->GetUnrecognizedParametersLength() == 7); REQUIRE(clonedErrorCause->GetUnrecognizedParameters()[0] == 0x12); REQUIRE(clonedErrorCause->GetUnrecognizedParameters()[1] == 0x34); REQUIRE(clonedErrorCause->GetUnrecognizedParameters()[2] == 0x56); REQUIRE(clonedErrorCause->GetUnrecognizedParameters()[3] == 0x78); REQUIRE(clonedErrorCause->GetUnrecognizedParameters()[4] == 0x90); REQUIRE(clonedErrorCause->GetUnrecognizedParameters()[5] == 0xAB); REQUIRE(clonedErrorCause->GetUnrecognizedParameters()[6] == 0xCD); // This should be padding. REQUIRE(clonedErrorCause->GetUnrecognizedParameters()[7] == 0x00); delete clonedErrorCause; } SECTION("UnrecognizedParametersErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::UnrecognizedParametersErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS, /*unknownCode*/ false); REQUIRE(errorCause->HasUnrecognizedParameters() == false); REQUIRE(errorCause->GetUnrecognizedParametersLength() == 0); /* Modify it. */ // Verify that replacing the value works. errorCause->SetUnrecognizedParameters(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(errorCause->GetLength() == 3004); REQUIRE(errorCause->HasUnrecognizedParameters() == true); REQUIRE(errorCause->GetUnrecognizedParametersLength() == 3000); errorCause->SetUnrecognizedParameters(nullptr, 0); REQUIRE(errorCause->GetLength() == 4); REQUIRE(errorCause->HasUnrecognizedParameters() == false); REQUIRE(errorCause->GetUnrecognizedParametersLength() == 0); // 6 bytes + 2 bytes of padding. errorCause->SetUnrecognizedParameters(sctpCommon::DataBuffer, 6); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS, /*unknownCode*/ false); REQUIRE(errorCause->HasUnrecognizedParameters() == true); REQUIRE(errorCause->GetUnrecognizedParametersLength() == 6); REQUIRE(errorCause->GetUnrecognizedParameters()[0] == 0x00); REQUIRE(errorCause->GetUnrecognizedParameters()[1] == 0x01); REQUIRE(errorCause->GetUnrecognizedParameters()[2] == 0x02); REQUIRE(errorCause->GetUnrecognizedParameters()[3] == 0x03); REQUIRE(errorCause->GetUnrecognizedParameters()[4] == 0x04); REQUIRE(errorCause->GetUnrecognizedParameters()[5] == 0x05); // These should be padding. REQUIRE(errorCause->GetUnrecognizedParameters()[6] == 0x00); REQUIRE(errorCause->GetUnrecognizedParameters()[7] == 0x00); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::UnrecognizedParametersErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRECOGNIZED_PARAMETERS, /*unknownCode*/ false); REQUIRE(parsedErrorCause->HasUnrecognizedParameters() == true); REQUIRE(parsedErrorCause->GetUnrecognizedParametersLength() == 6); REQUIRE(parsedErrorCause->GetUnrecognizedParameters()[0] == 0x00); REQUIRE(parsedErrorCause->GetUnrecognizedParameters()[1] == 0x01); REQUIRE(parsedErrorCause->GetUnrecognizedParameters()[2] == 0x02); REQUIRE(parsedErrorCause->GetUnrecognizedParameters()[3] == 0x03); REQUIRE(parsedErrorCause->GetUnrecognizedParameters()[4] == 0x04); REQUIRE(parsedErrorCause->GetUnrecognizedParameters()[5] == 0x05); // These should be padding. REQUIRE(parsedErrorCause->GetUnrecognizedParameters()[6] == 0x00); REQUIRE(parsedErrorCause->GetUnrecognizedParameters()[7] == 0x00); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestUnresolvableAddressErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UnresolvableAddressErrorCause.hpp" #include #include // std::memset() SCENARIO("Unresolvable Address Error Cause (5)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("UnresolvableAddressErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:5 (UNRESOLVABLE_ADDRESS), Length: 9 0x00, 0x05, 0x00, 0x09, // Unresolvable Address: 0x1234567890 0x12, 0x34, 0x56, 0x78, // 3 bytes of padding. 0x90, 0x00, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, }; // clang-format on auto* errorCause = RTC::SCTP::UnresolvableAddressErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS, /*unknownCode*/ false); REQUIRE(errorCause->HasUnresolvableAddress() == true); REQUIRE(errorCause->GetUnresolvableAddressLength() == 5); REQUIRE(errorCause->GetUnresolvableAddress()[0] == 0x12); REQUIRE(errorCause->GetUnresolvableAddress()[1] == 0x34); REQUIRE(errorCause->GetUnresolvableAddress()[2] == 0x56); REQUIRE(errorCause->GetUnresolvableAddress()[3] == 0x78); REQUIRE(errorCause->GetUnresolvableAddress()[4] == 0x90); // These should be padding. REQUIRE(errorCause->GetUnresolvableAddress()[5] == 0x00); REQUIRE(errorCause->GetUnresolvableAddress()[6] == 0x00); REQUIRE(errorCause->GetUnresolvableAddress()[7] == 0x00); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS, /*unknownCode*/ false); REQUIRE(errorCause->HasUnresolvableAddress() == true); REQUIRE(errorCause->GetUnresolvableAddressLength() == 5); REQUIRE(errorCause->GetUnresolvableAddress()[0] == 0x12); REQUIRE(errorCause->GetUnresolvableAddress()[1] == 0x34); REQUIRE(errorCause->GetUnresolvableAddress()[2] == 0x56); REQUIRE(errorCause->GetUnresolvableAddress()[3] == 0x78); REQUIRE(errorCause->GetUnresolvableAddress()[4] == 0x90); // These should be padding. REQUIRE(errorCause->GetUnresolvableAddress()[5] == 0x00); REQUIRE(errorCause->GetUnresolvableAddress()[6] == 0x00); REQUIRE(errorCause->GetUnresolvableAddress()[7] == 0x00); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS, /*unknownCode*/ false); REQUIRE(clonedErrorCause->HasUnresolvableAddress() == true); REQUIRE(clonedErrorCause->GetUnresolvableAddressLength() == 5); REQUIRE(clonedErrorCause->GetUnresolvableAddress()[0] == 0x12); REQUIRE(clonedErrorCause->GetUnresolvableAddress()[1] == 0x34); REQUIRE(clonedErrorCause->GetUnresolvableAddress()[2] == 0x56); REQUIRE(clonedErrorCause->GetUnresolvableAddress()[3] == 0x78); REQUIRE(clonedErrorCause->GetUnresolvableAddress()[4] == 0x90); // These should be padding. REQUIRE(clonedErrorCause->GetUnresolvableAddress()[5] == 0x00); REQUIRE(clonedErrorCause->GetUnresolvableAddress()[6] == 0x00); REQUIRE(clonedErrorCause->GetUnresolvableAddress()[7] == 0x00); delete clonedErrorCause; } SECTION("UnresolvableAddressErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 8 0x03, 0xE7, 0x00, 0x08, // Unresolvable Address: 0x12345678 0x12, 0x34, 0x56, 0x78, }; // clang-format on REQUIRE(!RTC::SCTP::UnresolvableAddressErrorCause::Parse(buffer1, sizeof(buffer1))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:5 (UNRESOLVABLE_ADDRESS), Length: 7 0x00, 0x05, 0x00, 0x07, // Unresolvable Address: 0x123456 (missing padding byte) 0x12, 0x34, 0x56, }; // clang-format on REQUIRE(!RTC::SCTP::UnresolvableAddressErrorCause::Parse(buffer2, sizeof(buffer2))); } SECTION("UnresolvableAddressErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::UnresolvableAddressErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS, /*unknownCode*/ false); REQUIRE(errorCause->HasUnresolvableAddress() == false); REQUIRE(errorCause->GetUnresolvableAddressLength() == 0); /* Modify it. */ // Verify that replacing the value works. errorCause->SetUnresolvableAddress(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(errorCause->GetLength() == 3004); REQUIRE(errorCause->HasUnresolvableAddress() == true); REQUIRE(errorCause->GetUnresolvableAddressLength() == 3000); errorCause->SetUnresolvableAddress(nullptr, 0); REQUIRE(errorCause->GetLength() == 4); REQUIRE(errorCause->HasUnresolvableAddress() == false); REQUIRE(errorCause->GetUnresolvableAddressLength() == 0); // 6 bytes + 2 bytes of padding. errorCause->SetUnresolvableAddress(sctpCommon::DataBuffer, 6); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS, /*unknownCode*/ false); REQUIRE(errorCause->HasUnresolvableAddress() == true); REQUIRE(errorCause->GetUnresolvableAddressLength() == 6); REQUIRE(errorCause->GetUnresolvableAddress()[0] == 0x00); REQUIRE(errorCause->GetUnresolvableAddress()[1] == 0x01); REQUIRE(errorCause->GetUnresolvableAddress()[2] == 0x02); REQUIRE(errorCause->GetUnresolvableAddress()[3] == 0x03); REQUIRE(errorCause->GetUnresolvableAddress()[4] == 0x04); REQUIRE(errorCause->GetUnresolvableAddress()[5] == 0x05); // These should be padding. REQUIRE(errorCause->GetUnresolvableAddress()[6] == 0x00); REQUIRE(errorCause->GetUnresolvableAddress()[7] == 0x00); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::UnresolvableAddressErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::UNRESOLVABLE_ADDRESS, /*unknownCode*/ false); REQUIRE(parsedErrorCause->HasUnresolvableAddress() == true); REQUIRE(parsedErrorCause->GetUnresolvableAddressLength() == 6); REQUIRE(parsedErrorCause->GetUnresolvableAddress()[0] == 0x00); REQUIRE(parsedErrorCause->GetUnresolvableAddress()[1] == 0x01); REQUIRE(parsedErrorCause->GetUnresolvableAddress()[2] == 0x02); REQUIRE(parsedErrorCause->GetUnresolvableAddress()[3] == 0x03); REQUIRE(parsedErrorCause->GetUnresolvableAddress()[4] == 0x04); REQUIRE(parsedErrorCause->GetUnresolvableAddress()[5] == 0x05); // These should be padding. REQUIRE(parsedErrorCause->GetUnresolvableAddress()[6] == 0x00); REQUIRE(parsedErrorCause->GetUnresolvableAddress()[7] == 0x00); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/errorCauses/TestUserInitiatedAbortErrorCause.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/ErrorCause.hpp" #include "RTC/SCTP/packet/errorCauses/UserInitiatedAbortErrorCause.hpp" #include #include // std::memset() SCENARIO("User-Initiated Abort Error Cause (12)", "[serializable][sctp][errorcause]") { sctpCommon::ResetBuffers(); SECTION("UserInitiatedAbortErrorCause::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Code:12 (USER_INITIATED_ABORT), Length: 10 0x00, 0x0C, 0x00, 0x0A, // Upper Layer Abort Reason: "I DIE!" 0x49, 0x20, 0x44, 0x49, // 2 bytes of padding. 0x45, 0x21, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, }; // clang-format on auto* errorCause = RTC::SCTP::UserInitiatedAbortErrorCause::Parse(buffer, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT, /*unknownCode*/ false); REQUIRE(errorCause->HasUpperLayerAbortReason() == true); REQUIRE(errorCause->GetUpperLayerAbortReason() == "I DIE!"); /* Serialize it. */ errorCause->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT, /*unknownCode*/ false); REQUIRE(errorCause->HasUpperLayerAbortReason() == true); REQUIRE(errorCause->GetUpperLayerAbortReason() == "I DIE!"); /* Clone it. */ auto* clonedErrorCause = errorCause->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ clonedErrorCause, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT, /*unknownCode*/ false); REQUIRE(clonedErrorCause->HasUpperLayerAbortReason() == true); REQUIRE(clonedErrorCause->GetUpperLayerAbortReason() == "I DIE!"); delete clonedErrorCause; } SECTION("UserInitiatedAbortErrorCause::Parse() fails") { // Wrong code. // clang-format off alignas(4) uint8_t buffer1[] = { // Code:999 (UNKNOWN), Length: 8 0x03, 0xE7, 0x00, 0x08, // Upper Layer Abort Reason: 0x12345678 0x12, 0x34, 0x56, 0x78, }; // clang-format on REQUIRE(!RTC::SCTP::UserInitiatedAbortErrorCause::Parse(buffer1, sizeof(buffer1))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer2[] = { // Code:12 (USER_INITIATED_ABORT), Length: 7 0x00, 0x0C, 0x00, 0x07, // Upper Layer Abort Reason: 0x123456 (missing padding byte) 0x12, 0x34, 0x56, }; // clang-format on REQUIRE(!RTC::SCTP::UserInitiatedAbortErrorCause::Parse(buffer2, sizeof(buffer2))); } SECTION("UserInitiatedAbortErrorCause::Factory() succeeds") { auto* errorCause = RTC::SCTP::UserInitiatedAbortErrorCause::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT, /*unknownCode*/ false); REQUIRE(errorCause->HasUpperLayerAbortReason() == false); REQUIRE(errorCause->GetUpperLayerAbortReason().empty()); /* Modify it. */ // Verify that replacing the value works. This is 17 bytes long. errorCause->SetUpperLayerAbortReason("I'm dying! ☺️"); REQUIRE(errorCause->GetLength() == 24); REQUIRE(errorCause->HasUpperLayerAbortReason() == true); REQUIRE(errorCause->GetUpperLayerAbortReason() == "I'm dying! ☺️"); errorCause->SetUpperLayerAbortReason(""); REQUIRE(errorCause->GetLength() == 4); REQUIRE(errorCause->HasUpperLayerAbortReason() == false); REQUIRE(errorCause->GetUpperLayerAbortReason().empty()); // 6 bytes + 2 bytes of padding. errorCause->SetUpperLayerAbortReason("go go go"); CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ errorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT, /*unknownCode*/ false); REQUIRE(errorCause->HasUpperLayerAbortReason() == true); REQUIRE(errorCause->GetUpperLayerAbortReason() == "go go go"); /* Parse itself and compare. */ auto* parsedErrorCause = RTC::SCTP::UserInitiatedAbortErrorCause::Parse( errorCause->GetBuffer(), errorCause->GetLength()); delete errorCause; CHECK_SCTP_ERROR_CAUSE( /*errorCause*/ parsedErrorCause, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*causeCode*/ RTC::SCTP::ErrorCause::ErrorCauseCode::USER_INITIATED_ABORT, /*unknownCode*/ false); REQUIRE(parsedErrorCause->HasUpperLayerAbortReason() == true); REQUIRE(parsedErrorCause->GetUpperLayerAbortReason() == "go go go"); delete parsedErrorCause; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestAddIncomingStreamsRequestParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/AddIncomingStreamsRequestParameter.hpp" #include #include // std::memset() SCENARIO("Add Incoming Streams Request Parameter (18)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("AddIncomingStreamsRequestParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:18 (ADD_INCOMING_STREAMS_REQUEST), Length: 12 0x00, 0x12, 0x00, 0x0C, // Re-configuration Request Sequence Number: 666777888 0x27, 0xBE, 0x39, 0x20, // Number of new streams: 1024 0x04, 0x00, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::AddIncomingStreamsRequestParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888); REQUIRE(parameter->GetNumberOfNewStreams() == 1024); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888); REQUIRE(parameter->GetNumberOfNewStreams() == 1024); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 666777888); REQUIRE(clonedParameter->GetNumberOfNewStreams() == 1024); delete clonedParameter; } SECTION("AddIncomingStreamsRequestParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::AddIncomingStreamsRequestParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0); REQUIRE(parameter->GetNumberOfNewStreams() == 0); /* Modify it. */ parameter->SetReconfigurationRequestSequenceNumber(12345678); parameter->SetNumberOfNewStreams(2048); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 12345678); REQUIRE(parameter->GetNumberOfNewStreams() == 2048); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::AddIncomingStreamsRequestParameter::Parse( parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_INCOMING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 12345678); REQUIRE(parsedParameter->GetNumberOfNewStreams() == 2048); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestAddOutgoingStreamsRequestParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/AddOutgoingStreamsRequestParameter.hpp" #include #include // std::memset() SCENARIO("Add Outgoing Streams Request Parameter (17)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("AddOutgoingStreamsRequestParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:17 (ADD_OUTGOING_STREAMS_REQUEST), Length: 12 0x00, 0x11, 0x00, 0x0C, // Re-configuration Request Sequence Number: 666777888 0x27, 0xBE, 0x39, 0x20, // Number of new streams: 1024 0x04, 0x00, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::AddOutgoingStreamsRequestParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888); REQUIRE(parameter->GetNumberOfNewStreams() == 1024); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888); REQUIRE(parameter->GetNumberOfNewStreams() == 1024); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 666777888); REQUIRE(clonedParameter->GetNumberOfNewStreams() == 1024); delete clonedParameter; } SECTION("AddOutgoingStreamsRequestParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::AddOutgoingStreamsRequestParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0); REQUIRE(parameter->GetNumberOfNewStreams() == 0); /* Modify it. */ parameter->SetReconfigurationRequestSequenceNumber(12345678); parameter->SetNumberOfNewStreams(2048); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 12345678); REQUIRE(parameter->GetNumberOfNewStreams() == 2048); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::AddOutgoingStreamsRequestParameter::Parse( parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ADD_OUTGOING_STREAMS_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 12345678); REQUIRE(parsedParameter->GetNumberOfNewStreams() == 2048); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestCookiePreservativeParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/CookiePreservativeParameter.hpp" #include #include // std::memset() SCENARIO("Cookie Preservative Parameter (9)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("CookiePreservativeParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:9 (COOKIE_PRESERVATIVE), Length: 8 0x00, 0x09, 0x00, 0x08, // Suggested Cookie Life-Span Increment: 4278194466 0xFF, 0x00, 0x11, 0x22, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::CookiePreservativeParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetLifeSpanIncrement() == 4278194466); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetLifeSpanIncrement() == 4278194466); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetLifeSpanIncrement() == 4278194466); delete clonedParameter; } SECTION("CookiePreservativeParameter::Parse() fails") { // Wrong type. // clang-format off alignas(4) uint8_t buffer1[] = { // Type:9 (IPV6_ADDRESS), Length: 8 0x00, 0x06, 0x00, 0x08, // Suggested Cookie Life-Span Increment: 4278194466 0xFF, 0x00, 0x11, 0x22, }; // clang-format on REQUIRE(!RTC::SCTP::CookiePreservativeParameter::Parse(buffer1, sizeof(buffer1))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer2[] = { // Type:9 (COOKIE_PRESERVATIVE), Length: 7 0x00, 0x09, 0x00, 0x07, // Suggested Cookie Life-Span Increment: 4278194466 0xFF, 0x00, 0x11, 0x22, }; // clang-format on REQUIRE(!RTC::SCTP::CookiePreservativeParameter::Parse(buffer2, sizeof(buffer2))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer3[] = { // Type:9 (COOKIE_PRESERVATIVE), Length: 9 0x00, 0x09, 0x00, 0x09, // Suggested Cookie Life-Span Increment: 4278194466 0xFF, 0x00, 0x11, 0x22, 0x69 }; // clang-format on REQUIRE(!RTC::SCTP::CookiePreservativeParameter::Parse(buffer3, sizeof(buffer3))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer4[] = { // Type:5 (IPV4_ADDRESS), Length: 8 0x00, 0x05, 0x00, 0x08, // Suggested Cookie Life-Span Increment (wrong length) 0xAA, 0xBB, 0xCC }; // clang-format on REQUIRE(!RTC::SCTP::CookiePreservativeParameter::Parse(buffer4, sizeof(buffer4))); } SECTION("CookiePreservativeParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::CookiePreservativeParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetLifeSpanIncrement() == 0); /* Modify it. */ parameter->SetLifeSpanIncrement(88776655); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetLifeSpanIncrement() == 88776655); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::CookiePreservativeParameter::Parse(parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::COOKIE_PRESERVATIVE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetLifeSpanIncrement() == 88776655); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestForwardTsnSupportedParameter.cpp ================================================ #include "common.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/ForwardTsnSupportedParameter.hpp" #include #include // std::memset() SCENARIO("Forward-TSN-Supported Parameter (32769)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("ForwardTsnSupportedParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:49152 (FORWARD_TSN_SUPPORTED), Length: 4 0xC0, 0x00, 0x00, 0x04, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::ForwardTsnSupportedParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); delete clonedParameter; } SECTION("ForwardTsnSupportedParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::ForwardTsnSupportedParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::ForwardTsnSupportedParameter::Parse(parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4, /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::FORWARD_TSN_SUPPORTED, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestHeartbeatInfoParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/HeartbeatInfoParameter.hpp" #include #include // std::memset() SCENARIO("Heartbeat Info Parameter (1)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("HeartbeatInfoParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:1 (HEARBEAT_INFO), Length: 11 0x00, 0x01, 0x00, 0x0B, // Heartbeat Information (7 bytes): 0x11223344556677 0x11, 0x22, 0x33, 0x44, // 1 byte of padding 0x55, 0x66, 0x77, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, }; // clang-format on auto* parameter = RTC::SCTP::HeartbeatInfoParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->HasInfo() == true); REQUIRE(parameter->GetInfoLength() == 7); REQUIRE(parameter->GetInfo()[0] == 0x11); REQUIRE(parameter->GetInfo()[1] == 0x22); REQUIRE(parameter->GetInfo()[2] == 0x33); REQUIRE(parameter->GetInfo()[3] == 0x44); REQUIRE(parameter->GetInfo()[4] == 0x55); REQUIRE(parameter->GetInfo()[5] == 0x66); REQUIRE(parameter->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter->GetInfo()[7] == 0x00); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->HasInfo() == true); REQUIRE(parameter->GetInfoLength() == 7); REQUIRE(parameter->GetInfo()[0] == 0x11); REQUIRE(parameter->GetInfo()[1] == 0x22); REQUIRE(parameter->GetInfo()[2] == 0x33); REQUIRE(parameter->GetInfo()[3] == 0x44); REQUIRE(parameter->GetInfo()[4] == 0x55); REQUIRE(parameter->GetInfo()[5] == 0x66); REQUIRE(parameter->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(parameter->GetInfo()[7] == 0x00); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->HasInfo() == true); REQUIRE(clonedParameter->GetInfoLength() == 7); REQUIRE(clonedParameter->GetInfo()[0] == 0x11); REQUIRE(clonedParameter->GetInfo()[1] == 0x22); REQUIRE(clonedParameter->GetInfo()[2] == 0x33); REQUIRE(clonedParameter->GetInfo()[3] == 0x44); REQUIRE(clonedParameter->GetInfo()[4] == 0x55); REQUIRE(clonedParameter->GetInfo()[5] == 0x66); REQUIRE(clonedParameter->GetInfo()[6] == 0x77); // This should be padding. REQUIRE(clonedParameter->GetInfo()[7] == 0x00); delete clonedParameter; } SECTION("HeartbeatInfoParameter::Parse() fails") { // Wrong type. // clang-format off alignas(4) uint8_t buffer1[] = { // Type:6 (IPV6_ADDRESS), Length: 8 0x00, 0x06, 0x00, 0x0B, // Heartbeat Information (7 bytes): 0x11223344556677 0x11, 0x22, 0x33, 0x44, // 1 byte of padding 0x55, 0x66, 0x77, 0x00, }; // clang-format on REQUIRE(!RTC::SCTP::HeartbeatInfoParameter::Parse(buffer1, sizeof(buffer1))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer2[] = { // Type:1 (HEARBEAT_INFO), Length: 3 0x00, 0x01, 0x00, 0x03, // Heartbeat Information (7 bytes): 0x11223344556677 0x11, 0x22, 0x33, 0x44, // 1 byte of padding 0x55, 0x66, 0x77, 0x00, }; // clang-format on REQUIRE(!RTC::SCTP::HeartbeatInfoParameter::Parse(buffer2, sizeof(buffer2))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer4[] = { // Type:1 (HEARBEAT_INFO), Length: 11 0x00, 0x01, 0x00, 0x0B, // Heartbeat Information (7 bytes): 0x11223344556677 0x11, 0x22, 0x33, 0x44, // 1 byte of padding (missing) 0x55, 0x66, 0x77 }; // clang-format on REQUIRE(!RTC::SCTP::HeartbeatInfoParameter::Parse(buffer4, sizeof(buffer4))); } SECTION("HeartbeatInfoParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::HeartbeatInfoParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->HasInfo() == false); REQUIRE(parameter->GetInfoLength() == 0); /* Modify it. */ // Verify that replacing the value works. parameter->SetInfo(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(parameter->GetLength() == 3004); REQUIRE(parameter->HasInfo() == true); REQUIRE(parameter->GetInfoLength() == 3000); parameter->SetInfo(nullptr, 0); REQUIRE(parameter->GetLength() == 4); REQUIRE(parameter->HasInfo() == false); REQUIRE(parameter->GetInfoLength() == 0); parameter->SetInfo(sctpCommon::DataBuffer, 2); REQUIRE(parameter->GetLength() == 8); REQUIRE(parameter->HasInfo() == true); REQUIRE(parameter->GetInfoLength() == 2); parameter->SetInfo(sctpCommon::DataBuffer + 2000, 2000); REQUIRE(parameter->GetLength() == 2004); REQUIRE(parameter->HasInfo() == true); REQUIRE(parameter->GetInfoLength() == 2000); // Info length is 5 so 3 bytes of padding will be added. parameter->SetInfo(sctpCommon::DataBuffer, 5); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->HasInfo() == true); REQUIRE(parameter->GetInfoLength() == 5); REQUIRE(parameter->GetInfo()[0] == 0x00); REQUIRE(parameter->GetInfo()[1] == 0x01); REQUIRE(parameter->GetInfo()[2] == 0x02); REQUIRE(parameter->GetInfo()[3] == 0x03); REQUIRE(parameter->GetInfo()[4] == 0x04); // These should be padding. REQUIRE(parameter->GetInfo()[5] == 0x00); REQUIRE(parameter->GetInfo()[6] == 0x00); REQUIRE(parameter->GetInfo()[7] == 0x00); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::HeartbeatInfoParameter::Parse(parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->HasInfo() == true); REQUIRE(parsedParameter->GetInfoLength() == 5); REQUIRE(parsedParameter->GetInfo()[0] == 0x00); REQUIRE(parsedParameter->GetInfo()[1] == 0x01); REQUIRE(parsedParameter->GetInfo()[2] == 0x02); REQUIRE(parsedParameter->GetInfo()[3] == 0x03); REQUIRE(parsedParameter->GetInfo()[4] == 0x04); // These should be padding. REQUIRE(parsedParameter->GetInfo()[5] == 0x00); REQUIRE(parsedParameter->GetInfo()[6] == 0x00); REQUIRE(parsedParameter->GetInfo()[7] == 0x00); delete parsedParameter; } SECTION("HeartbeatInfoParameter::SetInfo() throws if infoLength is too big") { auto* parameter = RTC::SCTP::HeartbeatInfoParameter::Factory( sctpCommon::ThrowBuffer, sizeof(sctpCommon::ThrowBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::ThrowBuffer, /*bufferLength*/ sizeof(sctpCommon::ThrowBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE_THROWS_AS(parameter->SetInfo(sctpCommon::ThrowBuffer, 65535), MediaSoupError); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::ThrowBuffer, /*bufferLength*/ sizeof(sctpCommon::ThrowBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::HEARTBEAT_INFO, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); delete parameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestIPv4AddressParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/IPv4AddressParameter.hpp" #include #include // std::memset() SCENARIO("IPv4 Adress Parameter (5)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("IPv4AddressParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:5 (IPV4_ADDRESS), Length: 8 0x00, 0x05, 0x00, 0x08, // IPv4 Address: "1.2.3.4" 0x01, 0x02, 0x03, 0x04, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::IPv4AddressParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetIPv4Address()[0] == 0x01); REQUIRE(parameter->GetIPv4Address()[1] == 0x02); REQUIRE(parameter->GetIPv4Address()[2] == 0x03); REQUIRE(parameter->GetIPv4Address()[3] == 0x04); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetIPv4Address()[0] == 0x01); REQUIRE(parameter->GetIPv4Address()[1] == 0x02); REQUIRE(parameter->GetIPv4Address()[2] == 0x03); REQUIRE(parameter->GetIPv4Address()[3] == 0x04); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetIPv4Address()[0] == 0x01); REQUIRE(clonedParameter->GetIPv4Address()[1] == 0x02); REQUIRE(clonedParameter->GetIPv4Address()[2] == 0x03); REQUIRE(clonedParameter->GetIPv4Address()[3] == 0x04); delete clonedParameter; } SECTION("IPv4AddressParameter::Parse() fails") { // Wrong type. // clang-format off alignas(4) uint8_t buffer1[] = { // Type:6 (IPV6_ADDRESS), Length: 8 0x00, 0x06, 0x00, 0x08, // IPv4 Address: 0xAABBCCDD 0xAA, 0xBB, 0xCC, 0xDD }; // clang-format on REQUIRE(!RTC::SCTP::IPv4AddressParameter::Parse(buffer1, sizeof(buffer1))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer2[] = { // Type:5 (IPV4_ADDRESS), Length: 7 0x00, 0x05, 0x00, 0x07, // IPv4 Address: 0xAABBCC 0xAA, 0xBB, 0xCC }; // clang-format on REQUIRE(!RTC::SCTP::IPv4AddressParameter::Parse(buffer2, sizeof(buffer2))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer3[] = { // Type:5 (IPV4_ADDRESS), Length: 9 0x00, 0x05, 0x00, 0x09, // IPv4 Address: 0xAABBCCDD 0xAA, 0xBB, 0xCC, 0xDD, 0xEE }; // clang-format on REQUIRE(!RTC::SCTP::IPv4AddressParameter::Parse(buffer3, sizeof(buffer3))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer4[] = { // Type:5 (IPV4_ADDRESS), Length: 8 0x00, 0x05, 0x00, 0x08, // IPv4 Address (wrong length) 0xAA, 0xBB, 0xCC }; // clang-format on REQUIRE(!RTC::SCTP::IPv4AddressParameter::Parse(buffer4, sizeof(buffer4))); } SECTION("IPv4AddressParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::IPv4AddressParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetIPv4Address()[0] == 0x00); REQUIRE(parameter->GetIPv4Address()[1] == 0x00); REQUIRE(parameter->GetIPv4Address()[2] == 0x00); REQUIRE(parameter->GetIPv4Address()[3] == 0x00); /* Modify it. */ // 11.22.33.44 IPv4 in network order. uint8_t ipBuffer[] = { 0x0B, 0x16, 0x21, 0x2C }; parameter->SetIPv4Address(ipBuffer); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetIPv4Address()[0] == 0x0B); REQUIRE(parameter->GetIPv4Address()[1] == 0x16); REQUIRE(parameter->GetIPv4Address()[2] == 0x21); REQUIRE(parameter->GetIPv4Address()[3] == 0x2C); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::IPv4AddressParameter::Parse(parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV4_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetIPv4Address()[0] == 0x0B); REQUIRE(parsedParameter->GetIPv4Address()[1] == 0x16); REQUIRE(parsedParameter->GetIPv4Address()[2] == 0x21); REQUIRE(parsedParameter->GetIPv4Address()[3] == 0x2C); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestIPv6AddressParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/IPv6AddressParameter.hpp" #include #include // std::memset() SCENARIO("IPv6 Adress Parameter (6)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("IPv6AddressParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:6 (IPV6_ADDRESS), Length: 20 0x00, 0x06, 0x00, 0x14, // IPv6 Address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" 0x20, 0x01, 0x0D, 0xB8, 0x85, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x2E, 0x03, 0x70, 0x73, 0x34, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::IPv6AddressParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetIPv6Address()[0] == 0x20); REQUIRE(parameter->GetIPv6Address()[1] == 0x01); REQUIRE(parameter->GetIPv6Address()[2] == 0x0D); REQUIRE(parameter->GetIPv6Address()[3] == 0xB8); REQUIRE(parameter->GetIPv6Address()[15] == 0x34); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetIPv6Address()[0] == 0x20); REQUIRE(parameter->GetIPv6Address()[1] == 0x01); REQUIRE(parameter->GetIPv6Address()[2] == 0x0D); REQUIRE(parameter->GetIPv6Address()[3] == 0xB8); REQUIRE(parameter->GetIPv6Address()[15] == 0x34); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetIPv6Address()[0] == 0x20); REQUIRE(clonedParameter->GetIPv6Address()[1] == 0x01); REQUIRE(clonedParameter->GetIPv6Address()[2] == 0x0D); REQUIRE(clonedParameter->GetIPv6Address()[3] == 0xB8); REQUIRE(clonedParameter->GetIPv6Address()[15] == 0x34); delete clonedParameter; } SECTION("IPv6AddressParameter::Parse() fails") { // Wrong type. // clang-format off alignas(4) uint8_t buffer1[] = { // Type:5 (IPV4_ADDRESS), Length: 20 0x00, 0x05, 0x00, 0x14, // IPv6 Address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" 0x20, 0x01, 0x0D, 0xB8, 0x85, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x2E, 0x03, 0x70, 0x73, 0x34, }; // clang-format on REQUIRE(!RTC::SCTP::IPv6AddressParameter::Parse(buffer1, sizeof(buffer1))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer2[] = { // Type:6 (IPV6_ADDRESS), Length: 19 0x00, 0x06, 0x00, 0x14, // IPv6 Address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" 0x20, 0x01, 0x0D, 0xB8, 0x85, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x2E, 0x03, 0x70, 0x73, }; // clang-format on REQUIRE(!RTC::SCTP::IPv6AddressParameter::Parse(buffer2, sizeof(buffer2))); // Wrong Length field. // clang-format off alignas(4) uint8_t buffer3[] = { // Type:6 (IPV6_ADDRESS), Length: 21 0x00, 0x06, 0x00, 0x15, // IPv6 Address: "2001:0db8:85a3:0000:0000:8a2e:0370:7334" 0x20, 0x01, 0x0D, 0xB8, 0x85, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x2E, 0x03, 0x70, 0x73, 0x34, 0x00 }; // clang-format on REQUIRE(!RTC::SCTP::IPv6AddressParameter::Parse(buffer3, sizeof(buffer3))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer4[] = { // Type:6 (IPV6_ADDRESS), Length: 20 0x00, 0x06, 0x00, 0x14, // IPv6 Address (wrong length) 0x20, 0x01, 0x0D, 0xB8, 0x85, 0xA3, 0x00, 0x00, 0x00, 0x00, 0x8A, 0x2E, 0x03, 0x70, 0x73 }; // clang-format on REQUIRE(!RTC::SCTP::IPv6AddressParameter::Parse(buffer4, sizeof(buffer4))); } SECTION("IPv6AddressParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::IPv6AddressParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetIPv6Address()[0] == 0x00); REQUIRE(parameter->GetIPv6Address()[1] == 0x00); REQUIRE(parameter->GetIPv6Address()[2] == 0x00); REQUIRE(parameter->GetIPv6Address()[3] == 0x00); REQUIRE(parameter->GetIPv6Address()[15] == 0x00); /* Modify it. */ // 2345:0425:2CA1:0000:0000:0567:5673:23b5 IPv6 in network order. uint8_t ipBuffer[] = { 0x23, 0x45, 0x04, 0x25, 0x2C, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x05, 0x67, 0x56, 0x73, 0x23, 0xB5 }; parameter->SetIPv6Address(ipBuffer); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetIPv6Address()[0] == 0x23); REQUIRE(parameter->GetIPv6Address()[1] == 0x45); REQUIRE(parameter->GetIPv6Address()[2] == 0x04); REQUIRE(parameter->GetIPv6Address()[3] == 0x25); REQUIRE(parameter->GetIPv6Address()[15] == 0xB5); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::IPv6AddressParameter::Parse(parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 20, /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::IPV6_ADDRESS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetIPv6Address()[0] == 0x23); REQUIRE(parsedParameter->GetIPv6Address()[1] == 0x45); REQUIRE(parsedParameter->GetIPv6Address()[2] == 0x04); REQUIRE(parsedParameter->GetIPv6Address()[3] == 0x25); REQUIRE(parsedParameter->GetIPv6Address()[15] == 0xB5); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestIncomingSsnResetRequestParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/IncomingSsnResetRequestParameter.hpp" #include #include // std::memset() #include SCENARIO("Incoming SSN Reset Request Parameter (14)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("IncomingSsnResetRequestParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:14 (INCOMING_SSN_RESET_REQUEST), Length: 14 0x00, 0x0E, 0x00, 0x0E, // Re-configuration Request Sequence Number: 0x11223344 0x11, 0x22, 0x33, 0x44, // Stream 1: 0x5001, Stream 2: 0x5002 0x50, 0x01, 0x50, 0x02, // Stream 3: 0x5003, 2 bytes of padding 0x50, 0x03, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::IncomingSsnResetRequestParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 16, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0x11223344); const std::vector expectedStreamIds{ { 0x5001, 0x5002, 0x5003 }, }; REQUIRE(parameter->GetStreamIds() == expectedStreamIds); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 16, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0x11223344); REQUIRE(parameter->GetStreamIds() == expectedStreamIds); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 16, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 0x11223344); REQUIRE(clonedParameter->GetStreamIds() == expectedStreamIds); delete clonedParameter; } SECTION("IncomingSsnResetRequestParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::IncomingSsnResetRequestParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0); std::vector expectedStreamIds{}; REQUIRE(parameter->GetStreamIds() == expectedStreamIds); /* Modify it. */ parameter->SetReconfigurationRequestSequenceNumber(111000); parameter->AddStreamId(4444); parameter->AddStreamId(4445); parameter->AddStreamId(4446); parameter->AddStreamId(4447); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 111000); expectedStreamIds = { { 4444, 4445, 4446, 4447 }, }; REQUIRE(parameter->GetStreamIds() == expectedStreamIds); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::IncomingSsnResetRequestParameter::Parse( parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 16, /*length*/ 16, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::INCOMING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 111000); REQUIRE(parsedParameter->GetStreamIds() == expectedStreamIds); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestOutgoingSsnResetRequestParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/OutgoingSsnResetRequestParameter.hpp" #include #include // std::memset() #include SCENARIO("Outgoing SSN Reset Request Parameter (13)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("OutgoingSsnResetRequestParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:13 (OUTGOING_SSN_RESET_REQUEST), Length: 22 0x00, 0x0D, 0x00, 0x16, // Re-configuration Request Sequence Number: 0x11223344 0x11, 0x22, 0x33, 0x44, // Re-configuration Response Sequence Number: 0x55667788 0x55, 0x66, 0x77, 0x88, // Sender's Last Assigned TSN: 0xAABBCCDD 0xAA, 0xBB, 0xCC, 0xDD, // Stream 1: 0x5001, Stream 2: 0x5002 0x50, 0x01, 0x50, 0x02, // Stream 3: 0x5003, 2 bytes of padding 0x50, 0x03, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::OutgoingSsnResetRequestParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 24, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0x11223344); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 0x55667788); REQUIRE(parameter->GetSenderLastAssignedTsn() == 0xAABBCCDD); const std::vector expectedStreamIds{ { 0x5001, 0x5002, 0x5003 }, }; REQUIRE(parameter->GetStreamIds() == expectedStreamIds); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 24, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0x11223344); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 0x55667788); REQUIRE(parameter->GetSenderLastAssignedTsn() == 0xAABBCCDD); REQUIRE(parameter->GetStreamIds() == expectedStreamIds); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 24, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 0x11223344); REQUIRE(clonedParameter->GetReconfigurationResponseSequenceNumber() == 0x55667788); REQUIRE(clonedParameter->GetSenderLastAssignedTsn() == 0xAABBCCDD); REQUIRE(clonedParameter->GetStreamIds() == expectedStreamIds); delete clonedParameter; } SECTION("OutgoingSsnResetRequestParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::OutgoingSsnResetRequestParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 0); REQUIRE(parameter->GetSenderLastAssignedTsn() == 0); std::vector expectedStreamIds{}; REQUIRE(parameter->GetStreamIds() == expectedStreamIds); /* Modify it. */ parameter->SetReconfigurationRequestSequenceNumber(111000); parameter->SetReconfigurationResponseSequenceNumber(222000); parameter->SetSenderLastAssignedTsn(333000); parameter->AddStreamId(4444); parameter->AddStreamId(4445); parameter->AddStreamId(4446); parameter->AddStreamId(4447); parameter->AddStreamId(4448); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 28, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 111000); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 222000); REQUIRE(parameter->GetSenderLastAssignedTsn() == 333000); expectedStreamIds = { { 4444, 4445, 4446, 4447, 4448 }, }; REQUIRE(parameter->GetStreamIds() == expectedStreamIds); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::OutgoingSsnResetRequestParameter::Parse( parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 28, /*length*/ 28, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::OUTGOING_SSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 111000); REQUIRE(parsedParameter->GetReconfigurationResponseSequenceNumber() == 222000); REQUIRE(parsedParameter->GetSenderLastAssignedTsn() == 333000); REQUIRE(parsedParameter->GetStreamIds() == expectedStreamIds); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestReconfigurationResponseParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/ReconfigurationResponseParameter.hpp" #include #include // std::memset() SCENARIO("Re-configuration Response Parameter (16)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("ReconfigurationResponseParameter::Parse() with Sender's and Receiver's Next TSN succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:16 (RECONFIGURATION_RESPONSE), Length: 20 0x00, 0x10, 0x00, 0x14, // Re-configuration Request Sequence Number: 287454020 0x11, 0x22, 0x33, 0x44, // Result: SUCCESS_PERFORMED 0x00, 0x00, 0x00, 0x01, // Sender's Next TSN: 1111111111 0x42, 0x3A, 0x35, 0xC7, // Receiver's Next TSN: 1111111111 0x84, 0x74, 0x6B, 0x8E, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::ReconfigurationResponseParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 287454020); REQUIRE( parameter->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED); REQUIRE(parameter->HasNextTsns() == true); REQUIRE(parameter->GetSenderNextTsn() == 1111111111); REQUIRE(parameter->GetReceiverNextTsn() == 2222222222); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 287454020); REQUIRE( parameter->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED); REQUIRE(parameter->HasNextTsns() == true); REQUIRE(parameter->GetSenderNextTsn() == 1111111111); REQUIRE(parameter->GetReceiverNextTsn() == 2222222222); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetReconfigurationResponseSequenceNumber() == 287454020); REQUIRE( clonedParameter->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::SUCCESS_PERFORMED); REQUIRE(clonedParameter->HasNextTsns() == true); REQUIRE(clonedParameter->GetSenderNextTsn() == 1111111111); REQUIRE(clonedParameter->GetReceiverNextTsn() == 2222222222); delete clonedParameter; } SECTION("ReconfigurationResponseParameter::Parse() without Sender's and Receiver's Next TSN succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:16 (RECONFIGURATION_RESPONSE), Length: 12 0x00, 0x10, 0x00, 0x0C, // Re-configuration Request Sequence Number: 3333333333 0xC6, 0xAE, 0xA1, 0x55, // Result: ERROR_REQUEST_ALREADY_IN_PROGRESS 0x00, 0x00, 0x00, 0x04, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC, 0xDD, }; // clang-format on auto* parameter = RTC::SCTP::ReconfigurationResponseParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 3333333333); REQUIRE( parameter->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS); REQUIRE(parameter->HasNextTsns() == false); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 3333333333); REQUIRE( parameter->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS); REQUIRE(parameter->HasNextTsns() == false); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetReconfigurationResponseSequenceNumber() == 3333333333); REQUIRE( clonedParameter->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::ERROR_REQUEST_ALREADY_IN_PROGRESS); REQUIRE(clonedParameter->HasNextTsns() == false); delete clonedParameter; } SECTION("ReconfigurationResponseParameter::Parse() fails") { // Wrong Length field. // clang-format off alignas(4) uint8_t buffer1[] = { // Type:16 (RECONFIGURATION_RESPONSE), Length: 16 (should be 12 or 20) 0x00, 0x10, 0x00, 0x10, // Re-configuration Request Sequence Number: 287454020 0x11, 0x22, 0x33, 0x44, // Result: SUCCESS_PERFORMED 0x00, 0x00, 0x00, 0x01, // Sender's Next TSN: 1111111111 0x42, 0x3A, 0x35, 0xC7, // Receiver's Next TSN: 1111111111 0x84, 0x74, 0x6B, 0x8E, }; // clang-format on REQUIRE(!RTC::SCTP::ReconfigurationResponseParameter::Parse(buffer1, sizeof(buffer1))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer2[] = { // Type:16 (RECONFIGURATION_RESPONSE), Length: 20 0x00, 0x10, 0x00, 0x14, // Re-configuration Request Sequence Number: 287454020 0x11, 0x22, 0x33, 0x44, // Result: SUCCESS_PERFORMED 0x00, 0x00, 0x00, 0x01, // Sender's Next TSN: 1111111111 0x42, 0x3A, 0x35, 0xC7, // Receiver's Next TSN (missing) }; // clang-format on REQUIRE(!RTC::SCTP::ReconfigurationResponseParameter::Parse(buffer2, sizeof(buffer2))); } SECTION("ReconfigurationResponseParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::ReconfigurationResponseParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 0); REQUIRE( parameter->GetResult() == static_cast(0)); REQUIRE(parameter->HasNextTsns() == false); /* Modify it. */ parameter->SetReconfigurationResponseSequenceNumber(111000); parameter->SetResult(RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS); parameter->SetNextTsns(100000000, 200000000); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationResponseSequenceNumber() == 111000); REQUIRE( parameter->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS); REQUIRE(parameter->HasNextTsns() == true); REQUIRE(parameter->GetSenderNextTsn() == 100000000); REQUIRE(parameter->GetReceiverNextTsn() == 200000000); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::ReconfigurationResponseParameter::Parse( parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 20, /*length*/ 20, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::RECONFIGURATION_RESPONSE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetReconfigurationResponseSequenceNumber() == 111000); REQUIRE( parsedParameter->GetResult() == RTC::SCTP::ReconfigurationResponseParameter::Result::IN_PROGRESS); REQUIRE(parsedParameter->HasNextTsns() == true); REQUIRE(parsedParameter->GetSenderNextTsn() == 100000000); REQUIRE(parsedParameter->GetReceiverNextTsn() == 200000000); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestSsnTsnResetRequestParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/SsnTsnResetRequestParameter.hpp" #include #include // std::memset() SCENARIO("SSN/TSN Reset Request Parameter (15)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("SsnTsnResetRequestParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:15 (SSN_TSN_RESET_REQUEST), Length: 8 0x00, 0x0F, 0x00, 0x08, // Re-configuration Request Sequence Number: 666777888 0x27, 0xBE, 0x39, 0x20, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::SsnTsnResetRequestParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 666777888); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetReconfigurationRequestSequenceNumber() == 666777888); delete clonedParameter; } SECTION("SsnTsnResetRequestParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::SsnTsnResetRequestParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 0); /* Modify it. */ parameter->SetReconfigurationRequestSequenceNumber(12345678); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetReconfigurationRequestSequenceNumber() == 12345678); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::SsnTsnResetRequestParameter::Parse(parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SSN_TSN_RESET_REQUEST, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetReconfigurationRequestSequenceNumber() == 12345678); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestStateCookieParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/association/NegotiatedCapabilities.hpp" #include "RTC/SCTP/association/StateCookie.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/StateCookieParameter.hpp" #include #include // std::memset() SCENARIO("State Cookie Parameter (7)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("StateCookieParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:7 (STATE_COOKIE), Length: 7 0x00, 0x07, 0x00, 0x07, // Cookie: 0xDDCCEE, 1 byte of padding 0xDD, 0xCC, 0xEE, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::StateCookieParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->HasCookie() == true); REQUIRE(parameter->GetCookieLength() == 3); REQUIRE(parameter->GetCookie()[0] == 0xDD); REQUIRE(parameter->GetCookie()[1] == 0xCC); REQUIRE(parameter->GetCookie()[2] == 0xEE); // This should be padding. REQUIRE(parameter->GetCookie()[3] == 0x00); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->HasCookie() == true); REQUIRE(parameter->GetCookieLength() == 3); REQUIRE(parameter->GetCookie()[0] == 0xDD); REQUIRE(parameter->GetCookie()[1] == 0xCC); REQUIRE(parameter->GetCookie()[2] == 0xEE); // This should be padding. REQUIRE(parameter->GetCookie()[3] == 0x00); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->HasCookie() == true); REQUIRE(clonedParameter->GetCookieLength() == 3); REQUIRE(clonedParameter->GetCookie()[0] == 0xDD); REQUIRE(clonedParameter->GetCookie()[1] == 0xCC); REQUIRE(clonedParameter->GetCookie()[2] == 0xEE); // This should be padding. REQUIRE(clonedParameter->GetCookie()[3] == 0x00); delete clonedParameter; } SECTION("StateCookieParameter::Factory() succeeds (1)") { auto* parameter = RTC::SCTP::StateCookieParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); /* Modify it. */ // Verify that replacing the cookie works. parameter->SetCookie(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(parameter->GetLength() == 3004); REQUIRE(parameter->HasCookie() == true); REQUIRE(parameter->GetCookieLength() == 3000); parameter->SetCookie(nullptr, 0); REQUIRE(parameter->GetLength() == 4); REQUIRE(parameter->HasCookie() == false); REQUIRE(parameter->GetCookieLength() == 0); // 1 bytes + 3 bytes of padding. Note that first (and unique byte) is // sctpCommon::DataBuffer + 1 which is initialized to 0x0A. parameter->SetCookie(sctpCommon::DataBuffer + 10, 1); REQUIRE(parameter->HasCookie() == true); REQUIRE(parameter->GetCookieLength() == 1); REQUIRE(parameter->GetCookie()[0] == 0x0A); // These should be padding. REQUIRE(parameter->GetCookie()[1] == 0x00); REQUIRE(parameter->GetCookie()[2] == 0x00); REQUIRE(parameter->GetCookie()[3] == 0x00); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::StateCookieParameter::Parse(parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->HasCookie() == true); REQUIRE(parsedParameter->GetCookieLength() == 1); REQUIRE(parsedParameter->GetCookie()[0] == 0x0A); // These should be padding. REQUIRE(parsedParameter->GetCookie()[1] == 0x00); REQUIRE(parsedParameter->GetCookie()[2] == 0x00); REQUIRE(parsedParameter->GetCookie()[3] == 0x00); delete parsedParameter; } SECTION("StateCookieParameter::Factory() succeeds (2)") { auto* parameter = RTC::SCTP::StateCookieParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); /* Modify it. */ // Create a StateCookie. const RTC::SCTP::NegotiatedCapabilities negotiatedCapabilities = { .negotiatedMaxOutboundStreams = 62000, .negotiatedMaxInboundStreams = 55555, .partialReliability = true, .messageInterleaving = true, .reConfig = true, .zeroChecksum = false }; // Build the StateCookie in place within the StateCookieParameter. parameter->WriteStateCookieInPlace( /*localVerificationTag*/ 6660666, /*remoteVerificationTag*/ 9990999, /*localInitialTsn*/ 1110111, /*remoteInitialTsn*/ 2220222, /*remoteAdvertisedReceiverWindowCredit*/ 999909999, /*tieTag*/ 1111222233334444, negotiatedCapabilities); REQUIRE(parameter->HasCookie() == true); REQUIRE(parameter->GetCookieLength() == RTC::SCTP::StateCookie::StateCookieLength); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::StateCookieParameter::Parse(parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 4 + RTC::SCTP::StateCookie::StateCookieLength, /*length*/ 4 + RTC::SCTP::StateCookie::StateCookieLength, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::STATE_COOKIE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->HasCookie() == true); REQUIRE(parsedParameter->GetCookieLength() == RTC::SCTP::StateCookie::StateCookieLength); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestSupportedAddressTypesParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/SupportedAddressTypesParameter.hpp" #include #include // std::memset() SCENARIO("Supported Address Types Parameter (12)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("SupportedAddressTypesParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:12 (SUPPORTED_ADDRESS_TYPES), Length: 10 0x00, 0x0C, 0x00, 0x0A, // Address Type 1: 0x1001, Address Type 2: 0x2002 0x10, 0x01, 0x20, 0x02, // Address Type 3: 0x3003, 2 bytes of padding 0x30, 0x03, 0x00, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::SupportedAddressTypesParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetNumberOfAddressTypes() == 3); REQUIRE(parameter->GetAddressTypeAt(0) == 0x1001); REQUIRE(parameter->GetAddressTypeAt(1) == 0x2002); REQUIRE(parameter->GetAddressTypeAt(2) == 0x3003); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetNumberOfAddressTypes() == 3); REQUIRE(parameter->GetAddressTypeAt(0) == 0x1001); REQUIRE(parameter->GetAddressTypeAt(1) == 0x2002); REQUIRE(parameter->GetAddressTypeAt(2) == 0x3003); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->GetNumberOfAddressTypes() == 3); REQUIRE(clonedParameter->GetAddressTypeAt(0) == 0x1001); REQUIRE(clonedParameter->GetAddressTypeAt(1) == 0x2002); REQUIRE(clonedParameter->GetAddressTypeAt(2) == 0x3003); delete clonedParameter; } SECTION("SupportedAddressTypesParameter::Parse() fails") { // Wrong Length field (not even). // clang-format off alignas(4) uint8_t buffer1[] = { // Type:12 (SUPPORTED_ADDRESS_TYPES), Length: 7 0x00, 0x0C, 0x00, 0x0A, // Address Type 1: 0x1001, Address Type 2: 0x2002 0x10, 0x01, 0x20, 0x02, }; // clang-format on REQUIRE(!RTC::SCTP::SupportedAddressTypesParameter::Parse(buffer1, sizeof(buffer1))); } SECTION("SupportedAddressTypesParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::SupportedAddressTypesParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetNumberOfAddressTypes() == 0); /* Modify it. */ parameter->AddAddressType(11111); parameter->AddAddressType(22222); parameter->AddAddressType(33333); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetNumberOfAddressTypes() == 3); REQUIRE(parameter->GetAddressTypeAt(0) == 11111); REQUIRE(parameter->GetAddressTypeAt(1) == 22222); REQUIRE(parameter->GetAddressTypeAt(2) == 33333); parameter->AddAddressType(44444); parameter->AddAddressType(55555); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 16, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->GetNumberOfAddressTypes() == 5); REQUIRE(parameter->GetAddressTypeAt(0) == 11111); REQUIRE(parameter->GetAddressTypeAt(1) == 22222); REQUIRE(parameter->GetAddressTypeAt(2) == 33333); REQUIRE(parameter->GetAddressTypeAt(3) == 44444); REQUIRE(parameter->GetAddressTypeAt(4) == 55555); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::SupportedAddressTypesParameter::Parse( parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 16, /*length*/ 16, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_ADDRESS_TYPES, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->GetNumberOfAddressTypes() == 5); REQUIRE(parsedParameter->GetAddressTypeAt(0) == 11111); REQUIRE(parsedParameter->GetAddressTypeAt(1) == 22222); REQUIRE(parsedParameter->GetAddressTypeAt(2) == 33333); REQUIRE(parsedParameter->GetAddressTypeAt(3) == 44444); REQUIRE(parsedParameter->GetAddressTypeAt(4) == 55555); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestSupportedExtensionsParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" // in worker/test/include/ #include "RTC/SCTP/packet/Chunk.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/SupportedExtensionsParameter.hpp" #include #include // std::memset() SCENARIO("Supported Extensions Parameter (32776)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("SupportedExtensionsParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:32776 (SUPPORTED_EXTENSIONS), Length: 7 0x80, 0x08, 0x00, 0x07, // Chunk Type 1: RE_CONFIG (0x82), Chunk Type 2: ECNE (0x0C), // Chunk Type 3: UNKNOWN (0x42), 1 byte of padding 0x82, 0x0C, 0x42, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::SupportedExtensionsParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE(parameter->GetNumberOfChunkTypes() == 3); REQUIRE(parameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG); REQUIRE(parameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::ECNE); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(parameter->GetChunkTypeAt(2) == static_cast(0x42)); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::ECNE) == true); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(parameter->IncludesChunkType(static_cast(0x42)) == true); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA) == false); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE(parameter->GetNumberOfChunkTypes() == 3); REQUIRE(parameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG); REQUIRE(parameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::ECNE); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(parameter->GetChunkTypeAt(2) == static_cast(0x42)); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::ECNE) == true); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(parameter->IncludesChunkType(static_cast(0x42)) == true); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA) == false); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE(clonedParameter->GetNumberOfChunkTypes() == 3); REQUIRE(clonedParameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG); REQUIRE(clonedParameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::ECNE); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(clonedParameter->GetChunkTypeAt(2) == static_cast(0x42)); REQUIRE(clonedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true); REQUIRE(clonedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::ECNE) == true); REQUIRE( // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) clonedParameter->IncludesChunkType(static_cast(0x42)) == true); REQUIRE(clonedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::I_DATA) == false); delete clonedParameter; } SECTION("SupportedExtensionsParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::SupportedExtensionsParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE(parameter->GetNumberOfChunkTypes() == 0); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == false); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::ECNE) == false); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(parameter->IncludesChunkType(static_cast(0x42)) == false); /* Modify it. */ parameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG); parameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::CWR); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE(parameter->GetNumberOfChunkTypes() == 2); REQUIRE(parameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG); REQUIRE(parameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::CWR); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::CWR) == true); parameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR); parameter->AddChunkType(RTC::SCTP::Chunk::ChunkType::COOKIE_ACK); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) parameter->AddChunkType(static_cast(99)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE(parameter->GetNumberOfChunkTypes() == 5); REQUIRE(parameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG); REQUIRE(parameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::CWR); REQUIRE(parameter->GetChunkTypeAt(2) == RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR); REQUIRE(parameter->GetChunkTypeAt(3) == RTC::SCTP::Chunk::ChunkType::COOKIE_ACK); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(parameter->GetChunkTypeAt(4) == static_cast(99)); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::CWR) == true); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR) == true); REQUIRE(parameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::COOKIE_ACK) == true); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(parameter->IncludesChunkType(static_cast(99)) == true); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::SupportedExtensionsParameter::Parse(parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 12, /*length*/ 12, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::SUPPORTED_EXTENSIONS, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE(parsedParameter->GetNumberOfChunkTypes() == 5); REQUIRE(parsedParameter->GetChunkTypeAt(0) == RTC::SCTP::Chunk::ChunkType::RE_CONFIG); REQUIRE(parsedParameter->GetChunkTypeAt(1) == RTC::SCTP::Chunk::ChunkType::CWR); REQUIRE(parsedParameter->GetChunkTypeAt(2) == RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR); REQUIRE(parsedParameter->GetChunkTypeAt(3) == RTC::SCTP::Chunk::ChunkType::COOKIE_ACK); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(parsedParameter->GetChunkTypeAt(4) == static_cast(99)); REQUIRE(parsedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::RE_CONFIG) == true); REQUIRE(parsedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::CWR) == true); REQUIRE(parsedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::OPERATION_ERROR) == true); REQUIRE(parsedParameter->IncludesChunkType(RTC::SCTP::Chunk::ChunkType::COOKIE_ACK) == true); // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) REQUIRE(parsedParameter->IncludesChunkType(static_cast(99)) == true); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestUnknownParameter.cpp ================================================ #include "common.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/UnknownParameter.hpp" #include #include // std::memset() SCENARIO("Unknown Parameter", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("UnknownParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:49159 (UNKNOWN), Length: 11 0xC0, 0x07, 0x00, 0x0B, // Unknown value: 0x0123456789ABCD 0x01, 0x23, 0x45, 0x67, // 1 byte of padding 0x89, 0xAB, 0xCD, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::UnknownParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 12, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter->HasUnknownValue() == true); REQUIRE(parameter->GetUnknownValueLength() == 7); REQUIRE(parameter->GetUnknownValue()[1] == 0x23); REQUIRE(parameter->GetUnknownValue()[0] == 0x01); REQUIRE(parameter->GetUnknownValue()[1] == 0x23); REQUIRE(parameter->GetUnknownValue()[2] == 0x45); REQUIRE(parameter->GetUnknownValue()[3] == 0x67); REQUIRE(parameter->GetUnknownValue()[4] == 0x89); REQUIRE(parameter->GetUnknownValue()[5] == 0xAB); REQUIRE(parameter->GetUnknownValue()[6] == 0xCD); // This should be padding. REQUIRE(parameter->GetUnknownValue()[7] == 0x00); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 12, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(parameter->HasUnknownValue() == true); REQUIRE(parameter->GetUnknownValueLength() == 7); REQUIRE(parameter->GetUnknownValue()[0] == 0x01); REQUIRE(parameter->GetUnknownValue()[1] == 0x23); REQUIRE(parameter->GetUnknownValue()[2] == 0x45); REQUIRE(parameter->GetUnknownValue()[3] == 0x67); REQUIRE(parameter->GetUnknownValue()[4] == 0x89); REQUIRE(parameter->GetUnknownValue()[5] == 0xAB); REQUIRE(parameter->GetUnknownValue()[6] == 0xCD); // This should be padding. REQUIRE(parameter->GetUnknownValue()[7] == 0x00); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 12, // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange) /*parameterType*/ static_cast(49159), /*unknownType*/ true, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP_AND_REPORT); REQUIRE(clonedParameter->HasUnknownValue() == true); REQUIRE(clonedParameter->GetUnknownValueLength() == 7); REQUIRE(clonedParameter->GetUnknownValue()[0] == 0x01); REQUIRE(clonedParameter->GetUnknownValue()[1] == 0x23); REQUIRE(clonedParameter->GetUnknownValue()[2] == 0x45); REQUIRE(clonedParameter->GetUnknownValue()[3] == 0x67); REQUIRE(clonedParameter->GetUnknownValue()[4] == 0x89); REQUIRE(clonedParameter->GetUnknownValue()[5] == 0xAB); REQUIRE(clonedParameter->GetUnknownValue()[6] == 0xCD); // This should be padding. REQUIRE(clonedParameter->GetUnknownValue()[7] == 0x00); delete clonedParameter; } SECTION("UnknownParameter::Parse() fails") { // Wrong Length field. // clang-format off alignas(4) uint8_t buffer1[] = { // Type:49159 (UNKNOWN), Length: 3 0xC0, 0x07, 0x00, 0x03, // Unknown value: 0x0123456789ABCD 0x01, 0x23, 0x45, 0x67, // 1 byte of padding 0x89, 0xAB, 0xCD, 0x00, }; // clang-format on REQUIRE(!RTC::SCTP::UnknownParameter::Parse(buffer1, sizeof(buffer1))); // Wrong buffer length. // clang-format off alignas(4) uint8_t buffer2[] = { // Type:49159 (UNKNOWN), Length: 11 0xC0, 0x07, 0x00, 0x0B, // Unknown value: 0x0123456789ABCD 0x01, 0x23, 0x45, 0x67, // 1 byte of padding (missing) 0x89, 0xAB, 0xCD }; // clang-format on REQUIRE(!RTC::SCTP::UnknownParameter::Parse(buffer2, sizeof(buffer2))); } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestUnrecognizedParameterParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/UnrecognizedParameterParameter.hpp" #include #include // std::memset() SCENARIO("Unrecognized Parameter Parameter (7)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("UnrecognizedParameterParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:8 (UNRECOGNIZED_PARAMETER), Length: 7 0x00, 0x08, 0x00, 0x07, // Unrecognized Parameter: 0xDDCCEE, 1 byte of padding 0xDD, 0xCC, 0xEE, 0x00, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::UnrecognizedParameterParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->HasUnrecognizedParameter() == true); REQUIRE(parameter->GetUnrecognizedParameterLength() == 3); REQUIRE(parameter->GetUnrecognizedParameter()[0] == 0xDD); REQUIRE(parameter->GetUnrecognizedParameter()[1] == 0xCC); REQUIRE(parameter->GetUnrecognizedParameter()[2] == 0xEE); // This should be padding. REQUIRE(parameter->GetUnrecognizedParameter()[3] == 0x00); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parameter->HasUnrecognizedParameter() == true); REQUIRE(parameter->GetUnrecognizedParameterLength() == 3); REQUIRE(parameter->GetUnrecognizedParameter()[0] == 0xDD); REQUIRE(parameter->GetUnrecognizedParameter()[1] == 0xCC); REQUIRE(parameter->GetUnrecognizedParameter()[2] == 0xEE); // This should be padding. REQUIRE(parameter->GetUnrecognizedParameter()[3] == 0x00); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(clonedParameter->HasUnrecognizedParameter() == true); REQUIRE(clonedParameter->GetUnrecognizedParameterLength() == 3); REQUIRE(clonedParameter->GetUnrecognizedParameter()[0] == 0xDD); REQUIRE(clonedParameter->GetUnrecognizedParameter()[1] == 0xCC); REQUIRE(clonedParameter->GetUnrecognizedParameter()[2] == 0xEE); // This should be padding. REQUIRE(clonedParameter->GetUnrecognizedParameter()[3] == 0x00); delete clonedParameter; } SECTION("UnrecognizedParameterParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::UnrecognizedParameterParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 4, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); /* Modify it. */ // Verify that replacing the unrecognized parameter works. parameter->SetUnrecognizedParameter(sctpCommon::DataBuffer + 1000, 3000); REQUIRE(parameter->GetLength() == 3004); REQUIRE(parameter->HasUnrecognizedParameter() == true); REQUIRE(parameter->GetUnrecognizedParameterLength() == 3000); parameter->SetUnrecognizedParameter(nullptr, 0); REQUIRE(parameter->GetLength() == 4); REQUIRE(parameter->HasUnrecognizedParameter() == false); REQUIRE(parameter->GetUnrecognizedParameterLength() == 0); // 1 bytes + 3 bytes of padding. Note that first (and unique byte) is // sctpCommon::DataBuffer + 1 which is initialized to 0x0A. parameter->SetUnrecognizedParameter(sctpCommon::DataBuffer + 10, 1); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::UnrecognizedParameterParameter::Parse( parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::UNRECOGNIZED_PARAMETER, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::STOP); REQUIRE(parsedParameter->HasUnrecognizedParameter() == true); REQUIRE(parsedParameter->GetUnrecognizedParameterLength() == 1); REQUIRE(parsedParameter->GetUnrecognizedParameter()[0] == 0x0A); // These should be padding. REQUIRE(parsedParameter->GetUnrecognizedParameter()[1] == 0x00); REQUIRE(parsedParameter->GetUnrecognizedParameter()[2] == 0x00); REQUIRE(parsedParameter->GetUnrecognizedParameter()[3] == 0x00); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/packet/parameters/TestZeroChecksumAcceptableParameter.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Parameter.hpp" #include "RTC/SCTP/packet/parameters/ZeroChecksumAcceptableParameter.hpp" #include #include // std::memset() SCENARIO("Zero Checksum Acceptable Parameter (32769)", "[serializable][sctp][parameter]") { sctpCommon::ResetBuffers(); SECTION("ZeroChecksumAcceptableParameter::Parse() succeeds") { // clang-format off alignas(4) uint8_t buffer[] = { // Type:32769 (ZERO_CHECKSUM_ACCEPTABLE), Length: 8 0x80, 0x01, 0x00, 0x08, // Alternate Error Detection Method (EDMID) : 0x0001 0x00, 0x00, 0x00, 0x01, // Extra bytes that should be ignored 0xAA, 0xBB, 0xCC, 0xDD, 0xAA, 0xBB, 0xCC }; // clang-format on auto* parameter = RTC::SCTP::ZeroChecksumAcceptableParameter::Parse(buffer, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ buffer, /*bufferLength*/ sizeof(buffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE( parameter->GetAlternateErrorDetectionMethod() == RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS); /* Serialize it. */ parameter->Serialize(sctpCommon::SerializeBuffer, sizeof(sctpCommon::SerializeBuffer)); std::memset(buffer, 0x00, sizeof(buffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::SerializeBuffer, /*bufferLength*/ sizeof(sctpCommon::SerializeBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE( parameter->GetAlternateErrorDetectionMethod() == RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS); /* Clone it. */ auto* clonedParameter = parameter->Clone(sctpCommon::CloneBuffer, sizeof(sctpCommon::CloneBuffer)); std::memset(sctpCommon::SerializeBuffer, 0x00, sizeof(sctpCommon::SerializeBuffer)); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ clonedParameter, /*buffer*/ sctpCommon::CloneBuffer, /*bufferLength*/ sizeof(sctpCommon::CloneBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE( clonedParameter->GetAlternateErrorDetectionMethod() == RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS); delete clonedParameter; } SECTION("ZeroChecksumAcceptableParameter::Factory() succeeds") { auto* parameter = RTC::SCTP::ZeroChecksumAcceptableParameter::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE( parameter->GetAlternateErrorDetectionMethod() == RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::NONE); /* Modify it. */ parameter->SetAlternateErrorDetectionMethod( RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS); CHECK_SCTP_PARAMETER( /*parameter*/ parameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ sizeof(sctpCommon::FactoryBuffer), /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE( parameter->GetAlternateErrorDetectionMethod() == RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS); /* Parse itself and compare. */ auto* parsedParameter = RTC::SCTP::ZeroChecksumAcceptableParameter::Parse( parameter->GetBuffer(), parameter->GetLength()); delete parameter; CHECK_SCTP_PARAMETER( /*parameter*/ parsedParameter, /*buffer*/ sctpCommon::FactoryBuffer, /*bufferLength*/ 8, /*length*/ 8, /*parameterType*/ RTC::SCTP::Parameter::ParameterType::ZERO_CHECKSUM_ACCEPTABLE, /*unknownType*/ false, /*actionForUnknownParameterType*/ RTC::SCTP::Parameter::ActionForUnknownParameterType::SKIP); REQUIRE( parsedParameter->GetAlternateErrorDetectionMethod() == RTC::SCTP::ZeroChecksumAcceptableParameter::AlternateErrorDetectionMethod::SCTP_OVER_DTLS); delete parsedParameter; } } ================================================ FILE: worker/test/src/RTC/SCTP/rx/TestDataTracker.cpp ================================================ #include "mocks/include/MockShared.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include "RTC/SCTP/rx/DataTracker.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include #include #include SCENARIO("SCTP DataTracker", "[sctp][datatracker]") { class MockBackoffTimerHandleListener : public BackoffTimerHandleInterface::Listener { /* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */ public: void OnBackoffTimer( BackoffTimerHandleInterface* /*backoffTimer*/, uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) override { } }; constexpr uint32_t Arwnd{ 10000 }; constexpr uint64_t Mtu{ 1191 }; constexpr uint32_t InitialTsn{ 11 }; MockBackoffTimerHandleListener backoffTimerHandleListener; uint64_t nowMs{ 10000 }; mocks::MockShared shared(/*getTimeMs*/ [&nowMs]() { return nowMs; }); const std::unique_ptr delayedAckTimerUniquePtr{ shared.CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = std::addressof(backoffTimerHandleListener), .label = "mock-sctp-delayed-ack", .baseTimeoutMs = 0, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL, .maxBackoffTimeoutMs = std::nullopt, .maxRestarts = 0 }) }; auto* delayedAckTimer = delayedAckTimerUniquePtr.get(); RTC::SCTP::DataTracker dataTracker(delayedAckTimer, InitialTsn); auto createPacket = []() { return std::unique_ptr{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, Mtu) }; }; auto addSackChunk = [&dataTracker](RTC::SCTP::Packet* packet, size_t aRwnd) { dataTracker.AddSackSelectiveAck(packet, aRwnd); const auto* sackChunk = packet->GetFirstChunkOfType(); return sackChunk; }; auto observe = [&dataTracker](std::initializer_list tsns, bool expectAsDuplicate = false) { for (const uint32_t tsn : tsns) { if (expectAsDuplicate) { REQUIRE(dataTracker.Observe(tsn, /*immediateAck*/ false) == false); } else { REQUIRE(dataTracker.Observe(tsn, /*immediateAck*/ false) == true); } } }; SECTION("empty") { const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE(sackChunk->GetGapAckBlocks().empty()); REQUIRE(sackChunk->GetDuplicateTsns().empty()); } SECTION("observe single in order packet") { observe({ 11 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 11); REQUIRE(sackChunk->GetGapAckBlocks().empty()); REQUIRE(sackChunk->GetDuplicateTsns().empty()); } SECTION("observe many in order moves cumulative tsn ack") { observe({ 11, 12, 13 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 13); REQUIRE(sackChunk->GetGapAckBlocks().empty()); REQUIRE(sackChunk->GetDuplicateTsns().empty()); } SECTION("observe out of order moves cumulative tsn ack") { observe({ 12, 13, 14, 11 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 14); REQUIRE(sackChunk->GetGapAckBlocks().empty()); REQUIRE(sackChunk->GetDuplicateTsns().empty()); } SECTION("single gap") { observe({ 12 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 2 } }); REQUIRE(sackChunk->GetDuplicateTsns().empty()); } SECTION("example from RFC 9260 section 3.3.4") { observe({ 11, 12, 14, 15, 17 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 12); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 3 }, { 5, 5 } }); REQUIRE(sackChunk->GetDuplicateTsns().empty()); } SECTION("ack already received chunks") { observe({ 11 }); const auto packet1 = createPacket(); const auto* sackChunk1 = addSackChunk(packet1.get(), Arwnd); REQUIRE(sackChunk1); REQUIRE(sackChunk1->GetCumulativeTsnAck() == 11); REQUIRE(sackChunk1->GetGapAckBlocks().empty()); // Receive old chunk. observe({ 8 }, /*expectAsDuplicate*/ true); const auto packet2 = createPacket(); const auto* sackChunk2 = addSackChunk(packet2.get(), Arwnd); REQUIRE(sackChunk2); REQUIRE(sackChunk2->GetCumulativeTsnAck() == 11); REQUIRE(sackChunk2->GetGapAckBlocks().empty()); } SECTION("double send retransmitted chunk") { observe({ 11, 13, 14, 15 }); const auto packet1 = createPacket(); const auto* sackChunk1 = addSackChunk(packet1.get(), Arwnd); REQUIRE(sackChunk1); REQUIRE(sackChunk1->GetCumulativeTsnAck() == 11); REQUIRE( sackChunk1->GetGapAckBlocks() == std::vector{ { 2, 4 }, }); // Fill in the hole. observe({ 12, 16, 17, 18 }); const auto packet2 = createPacket(); const auto* sackChunk2 = addSackChunk(packet2.get(), Arwnd); REQUIRE(sackChunk2); REQUIRE(sackChunk2->GetCumulativeTsnAck() == 18); REQUIRE(sackChunk2->GetGapAckBlocks().empty()); // Receive chunk 12 again. observe({ 12 }, /*expectAsDuplicate*/ true); observe({ 19, 20, 21 }); const auto packet3 = createPacket(); const auto* sackChunk3 = addSackChunk(packet3.get(), Arwnd); REQUIRE(sackChunk3); REQUIRE(sackChunk3->GetCumulativeTsnAck() == 21); REQUIRE(sackChunk3->GetGapAckBlocks().empty()); } SECTION("forward tsn simple") { // Messages (11, 12, 13), (14, 15) - first message expires. observe({ 11, 12, 15 }); dataTracker.HandleForwardTsn(13); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 13); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 2 }, }); REQUIRE(sackChunk->GetDuplicateTsns().empty()); } SECTION("forward tsn skips from gap block") { // Messages (11, 12, 13), (14, 15) - first message expires. observe({ 11, 12, 14 }); dataTracker.HandleForwardTsn(13); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 14); REQUIRE(sackChunk->GetGapAckBlocks().empty()); REQUIRE(sackChunk->GetDuplicateTsns().empty()); } SECTION("example from RFC 3758") { dataTracker.HandleForwardTsn(102); observe({ 102 }, /*expectAsDuplicate*/ true); observe({ 104, 105, 107 }); dataTracker.HandleForwardTsn(103); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 105); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 2 }, }); } SECTION("empty all acks") { observe({ 11, 13, 14, 15 }); dataTracker.HandleForwardTsn(100); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 100); REQUIRE(sackChunk->GetGapAckBlocks().empty()); REQUIRE(sackChunk->GetDuplicateTsns().empty()); } SECTION("sets arwnd correctly") { const auto packet1 = createPacket(); const auto* sackChunk1 = addSackChunk(packet1.get(), 100); REQUIRE(sackChunk1); REQUIRE(sackChunk1->GetAdvertisedReceiverWindowCredit() == 100); const auto packet2 = createPacket(); const auto* sackChunk2 = addSackChunk(packet2.get(), 101); REQUIRE(sackChunk2); REQUIRE(sackChunk2->GetAdvertisedReceiverWindowCredit() == 101); } SECTION("will increase cumulative ack tsn") { REQUIRE(dataTracker.GetLastCumulativeAckedTsn() == 10); REQUIRE(dataTracker.WillIncreaseCumAckTsn(10) == false); REQUIRE(dataTracker.WillIncreaseCumAckTsn(11) == true); REQUIRE(dataTracker.WillIncreaseCumAckTsn(12) == false); observe({ 11, 12, 13, 14, 15 }); REQUIRE(dataTracker.GetLastCumulativeAckedTsn() == 15); REQUIRE(dataTracker.WillIncreaseCumAckTsn(15) == false); REQUIRE(dataTracker.WillIncreaseCumAckTsn(16) == true); REQUIRE(dataTracker.WillIncreaseCumAckTsn(17) == false); } SECTION("force should send sack immediately") { REQUIRE(dataTracker.ShouldSendAck() == false); dataTracker.ForceImmediateSack(); REQUIRE(dataTracker.ShouldSendAck() == true); } SECTION("will accept valid tsns") { // The initial TSN is always one more than the last, which is our base. const uint32_t lastTsn = InitialTsn - 1; const int limit = static_cast(RTC::SCTP::DataTracker::MaxAcceptedOutstandingFragments); for (int i{ -limit }; i <= limit; ++i) { REQUIRE(dataTracker.IsTsnValid(lastTsn + i) == true); } } SECTION("will not accept invalid tsns") { // The initial TSN is always one more than the last, which is our base. const uint32_t lastTsn = InitialTsn - 1; const uint32_t limit = RTC::SCTP::DataTracker::MaxAcceptedOutstandingFragments; REQUIRE(dataTracker.IsTsnValid(lastTsn + limit + 1) == false); REQUIRE(dataTracker.IsTsnValid(lastTsn - (limit + 1)) == false); REQUIRE(dataTracker.IsTsnValid(lastTsn + 0x8000000) == false); REQUIRE(dataTracker.IsTsnValid(lastTsn - 0x8000000) == false); } SECTION("report single duplicate tsns") { observe({ 11, 12 }); observe({ 11 }, /*expectAsDuplicate*/ true); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 12); REQUIRE(sackChunk->GetGapAckBlocks().empty()); REQUIRE(sackChunk->GetDuplicateTsns() == std::vector{ 11 }); } SECTION("report multiple duplicate tsns") { observe({ 11, 12, 13, 14 }); observe({ 12, 13, 12, 13 }, /*expectAsDuplicate*/ true); observe({ 15, 16 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 16); REQUIRE(sackChunk->GetGapAckBlocks().empty()); REQUIRE(sackChunk->GetDuplicateTsns() == std::vector{ 12, 13 }); } SECTION("report duplicate tsns in gap-ack-blocks") { observe({ 11, /*12,*/ 13, 14 }); observe({ 13, 14 }, /*expectAsDuplicate*/ true); observe({ 15, 16 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 11); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 5 }, }); REQUIRE(sackChunk->GetDuplicateTsns() == std::vector{ 13, 14 }); } SECTION("clears duplicate tsns after creating sack") { observe({ 11, 12, 13, 14 }); observe({ 12, 13, 12, 13 }, /*expectAsDuplicate*/ true); observe({ 15, 16 }); const auto packet1 = createPacket(); const auto* sackChunk1 = addSackChunk(packet1.get(), Arwnd); REQUIRE(sackChunk1); REQUIRE(sackChunk1->GetCumulativeTsnAck() == 16); REQUIRE(sackChunk1->GetGapAckBlocks().empty()); REQUIRE(sackChunk1->GetDuplicateTsns() == std::vector{ 12, 13 }); observe({ 17 }); const auto packet2 = createPacket(); const auto* sackChunk2 = addSackChunk(packet2.get(), Arwnd); REQUIRE(sackChunk2); REQUIRE(sackChunk2->GetCumulativeTsnAck() == 17); REQUIRE(sackChunk2->GetGapAckBlocks().empty()); REQUIRE(sackChunk2->GetDuplicateTsns().empty()); } SECTION("limits number of duplicates reported") { for (size_t i{ 0 }; i < RTC::SCTP::DataTracker::MaxDuplicateTsnReported + 10; ++i) { const uint32_t tsn = 11 + static_cast(i); dataTracker.Observe(tsn, /*immediateAck*/ false); dataTracker.Observe(tsn, /*immediateAck*/ false); } const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetGapAckBlocks().empty()); REQUIRE(sackChunk->GetDuplicateTsns().size() == RTC::SCTP::DataTracker::MaxDuplicateTsnReported); } SECTION("limits number of gap-ack-blocks reported") { for (size_t i{ 0 }; i < RTC::SCTP::DataTracker::MaxGapAckBlocksReported + 10; ++i) { const uint32_t tsn = 11 + static_cast(i * 2); dataTracker.Observe(tsn, /*immediateAck*/ false); } const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 11); REQUIRE(sackChunk->GetGapAckBlocks().size() == RTC::SCTP::DataTracker::MaxGapAckBlocksReported); } SECTION("sends sack for first packet observed") { observe({ 11 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); const auto* backoffTimer = shared.GetBackoffTimer("mock-sctp-delayed-ack"); REQUIRE(backoffTimer); REQUIRE(backoffTimer->IsRunning() == false); } SECTION("sends sack every second packet when there is no packet loss") { auto* backoffTimer = shared.GetBackoffTimer("mock-sctp-delayed-ack"); REQUIRE(backoffTimer); observe({ 11 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); observe({ 12 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == false); REQUIRE(backoffTimer->IsRunning() == true); observe({ 13 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); observe({ 14 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == false); REQUIRE(backoffTimer->IsRunning() == true); observe({ 15 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); } SECTION("sends sack every packet on packet loss") { const auto* backoffTimer = shared.GetBackoffTimer("mock-sctp-delayed-ack"); REQUIRE(backoffTimer); observe({ 11 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); observe({ 13 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); observe({ 14 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); observe({ 15 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); observe({ 16 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); // Fill the hole. observe({ 12 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == false); REQUIRE(backoffTimer->IsRunning() == true); // Goes back to every second packet. observe({ 17 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); observe({ 18 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == false); REQUIRE(backoffTimer->IsRunning() == true); } SECTION("sends sack on duplicate data chunks") { const auto* backoffTimer = shared.GetBackoffTimer("mock-sctp-delayed-ack"); REQUIRE(backoffTimer); observe({ 11 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); observe({ 11 }, /*expectAsDuplicate*/ true); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); observe({ 12 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == false); REQUIRE(backoffTimer->IsRunning() == true); // Goes back to every second packet. observe({ 13 }); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); // Duplicate again. observe({ 12 }, /*expectAsDuplicate*/ true); dataTracker.ObservePacketEnd(); REQUIRE(dataTracker.ShouldSendAck() == true); REQUIRE(backoffTimer->IsRunning() == false); } SECTION("gap-ack-block add single block") { observe({ 12 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 2 }, }); } SECTION("gap-ack-block adds another") { observe({ 12 }); observe({ 14 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 2 }, { 4, 4 }, }); } SECTION("gap-ack-block adds duplicate") { observe({ 12 }); observe({ 12 }, /*expectAsDuplicate*/ true); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 2 }, }); REQUIRE(sackChunk->GetDuplicateTsns() == std::vector{ 12 }); } SECTION("gap-ack-block expands to right") { observe({ 12 }); observe({ 13 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 3 }, }); } SECTION("gap-ack-block expands to right with other") { observe({ 12 }); observe({ 20 }); observe({ 30 }); observe({ 21 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 2 }, { 10, 11 }, { 20, 20 }, }); } SECTION("gap-ack-block expands to left") { observe({ 13 }); observe({ 12 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 3 }, }); } SECTION("gap-ack-block expands to left with other") { observe({ 12 }); observe({ 21 }); observe({ 30 }); observe({ 20 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 2 }, { 10, 11 }, { 20, 20 }, }); } SECTION("gap-ack-block expands to right and merges") { observe({ 12 }); observe({ 20 }); observe({ 22 }); observe({ 30 }); observe({ 21 }); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 2 }, { 10, 12 }, { 20, 20 }, }); } SECTION("gap-ack-block merges many blocks into one") { auto getGapAckBlocks = [&]() { const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); return sackChunk->GetGapAckBlocks(); }; observe({ 22 }); REQUIRE( getGapAckBlocks() == std::vector{ { 12, 12 } }); observe({ 30 }); REQUIRE( getGapAckBlocks() == std::vector{ { 12, 12 }, { 20, 20 }, }); observe({ 24 }); REQUIRE( getGapAckBlocks() == std::vector{ { 12, 12 }, { 14, 14 }, { 20, 20 }, }); observe({ 28 }); REQUIRE( getGapAckBlocks() == std::vector{ { 12, 12 }, { 14, 14 }, { 18, 18 }, { 20, 20 }, }); observe({ 26 }); REQUIRE( getGapAckBlocks() == std::vector{ { 12, 12 }, { 14, 14 }, { 16, 16 }, { 18, 18 }, { 20, 20 }, }); observe({ 29 }); REQUIRE( getGapAckBlocks() == std::vector{ { 12, 12 }, { 14, 14 }, { 16, 16 }, { 18, 20 }, }); observe({ 23 }); REQUIRE( getGapAckBlocks() == std::vector{ { 12, 14 }, { 16, 16 }, { 18, 20 }, }); observe({ 27 }); REQUIRE( getGapAckBlocks() == std::vector{ { 12, 14 }, { 16, 20 }, }); observe({ 25 }); REQUIRE( getGapAckBlocks() == std::vector{ { 12, 20 } }); observe({ 20 }); REQUIRE( getGapAckBlocks() == std::vector{ { 10, 10 }, { 12, 20 }, }); observe({ 32 }); REQUIRE( getGapAckBlocks() == std::vector{ { 10, 10 }, { 12, 20 }, { 22, 22 }, }); observe({ 21 }); REQUIRE( getGapAckBlocks() == std::vector{ { 10, 20 }, { 22, 22 }, }); observe({ 31 }); REQUIRE( getGapAckBlocks() == std::vector{ { 10, 22 } }); } SECTION("gap-ack-block remove before cumulative ack tsn") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(8); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 10); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 4 }, { 10, 12 }, { 20, 21 }, }); } SECTION("gap-ack-block remove before first block") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(11); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 14); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 6, 8 }, { 16, 17 }, }); } SECTION("gap-ack-block remove at beginning of first block") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(12); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 14); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 6, 8 }, { 16, 17 }, }); } SECTION("gap-ack-block remove at middle of first block") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(13); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 14); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 6, 8 }, { 16, 17 }, }); } SECTION("gap-ack-block remove at end of first block") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(14); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 14); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 6, 8 }, { 16, 17 }, }); } SECTION("gap-ack-block remove right after first block") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(18); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 18); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 2, 4 }, { 12, 13 }, }); } SECTION("gap-ack-block remove right before second block") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(19); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 22); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 8, 9 }, }); } SECTION("gap-ack-block remove right at start of second block") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(20); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 22); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 8, 9 }, }); } SECTION("gap-ack-block remove right at middle of second block") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(21); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 22); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 8, 9 }, }); } SECTION("gap-ack-block remove right at end of second block") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(22); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 22); REQUIRE( sackChunk->GetGapAckBlocks() == std::vector{ { 8, 9 }, }); } SECTION("gap-ack-block remove far after all blocks") { observe({ 12, 13, 14, 20, 21, 22, 30, 31 }); dataTracker.HandleForwardTsn(40); const auto packet = createPacket(); const auto* sackChunk = addSackChunk(packet.get(), Arwnd); REQUIRE(sackChunk); REQUIRE(sackChunk->GetCumulativeTsnAck() == 40); REQUIRE(sackChunk->GetGapAckBlocks().empty()); } SECTION("does not accept data before forward tsn") { observe({ 12, 13, 14, 15, 17 }); dataTracker.ObservePacketEnd(); dataTracker.HandleForwardTsn(13); REQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == false); } SECTION("does not accept data at forward tsn") { observe({ 12, 13, 14, 15, 17 }); dataTracker.ObservePacketEnd(); dataTracker.HandleForwardTsn(16); REQUIRE(dataTracker.Observe(16, /*immediateAck*/ false) == false); } SECTION("does not accept data before cumulative ack tsn") { REQUIRE(InitialTsn == 11); REQUIRE(dataTracker.Observe(10, /*immediateAck*/ false) == false); } SECTION("does not accept contiguous duplicate data") { REQUIRE(InitialTsn == 11); REQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == true); REQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == false); REQUIRE(dataTracker.Observe(12, /*immediateAck*/ false) == true); REQUIRE(dataTracker.Observe(12, /*immediateAck*/ false) == false); REQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == false); REQUIRE(dataTracker.Observe(10, /*immediateAck*/ false) == false); } SECTION("does not accept gaps with duplicate data") { REQUIRE(InitialTsn == 11); REQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == true); REQUIRE(dataTracker.Observe(11, /*immediateAck*/ false) == false); REQUIRE(dataTracker.Observe(14, /*immediateAck*/ false) == true); REQUIRE(dataTracker.Observe(14, /*immediateAck*/ false) == false); REQUIRE(dataTracker.Observe(13, /*immediateAck*/ false) == true); REQUIRE(dataTracker.Observe(13, /*immediateAck*/ false) == false); REQUIRE(dataTracker.Observe(12, /*immediateAck*/ false) == true); REQUIRE(dataTracker.Observe(12, /*immediateAck*/ false) == false); } } ================================================ FILE: worker/test/src/RTC/SCTP/rx/TestInterleavedReassemblyStreams.cpp ================================================ #include "common.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include "RTC/SCTP/rx/InterleavedReassemblyStreams.hpp" #include "RTC/SCTP/rx/ReassemblyStreamsInterface.hpp" #include #include #include SCENARIO("SCTP InterleavedReassemblyStreams", "[sctp][interleavedreassemblystreams]") { class OnAssembledMessageTester { public: RTC::SCTP::ReassemblyStreamsInterface::OnAssembledMessage MakeCallback() { return [this](std::span tsns, RTC::SCTP::Message message) { this->callCount++; // Copy the span to a vector to survive the callback. this->lastTsns = std::vector(tsns.begin(), tsns.end()); this->lastMessages.push_back(std::move(message)); }; } bool GetCallCount(size_t expectedCallCount) const { return this->callCount == expectedCallCount; } std::vector GetLastTsns() const { if (this->lastTsns.empty()) { return {}; } std::vector tsns; tsns.reserve(this->lastTsns.size()); for (const auto& tsn : this->lastTsns) { tsns.push_back(tsn.Wrap()); } return tsns; } std::vector& GetLastMessages() { return this->lastMessages; } bool CheckCallbackNotCalled() const { return (this->callCount == 0 && this->lastTsns.empty() && this->lastMessages.empty()); } void Reset() { this->callCount = 0; this->lastTsns.clear(); this->lastMessages.clear(); } private: size_t callCount{ 0 }; std::vector lastTsns; std::vector lastMessages; }; RTC::SCTP::Types::UnwrappedTsn::Unwrapper tsn; auto getTsn = [&tsn](uint32_t value) { return tsn.Unwrap(value); }; SECTION("add unordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedReassemblyStreams(tester.MakeCallback()); REQUIRE( interleavedReassemblyStreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ true)) == 1); REQUIRE( interleavedReassemblyStreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, true)) == 3); REQUIRE( interleavedReassemblyStreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, true)) == 2); // Adding the end fragment should make it empty again. REQUIRE( interleavedReassemblyStreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, true)) == -6); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 1, 2, 3, 4 }); } SECTION("add simple ordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback()); REQUIRE( interleavedreassemblystreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); REQUIRE( interleavedreassemblystreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false)) == 3); REQUIRE( interleavedreassemblystreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( interleavedreassemblystreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, false)) == -6); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 1, 2, 3, 4 }); } SECTION("add more complex ordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback()); REQUIRE( interleavedreassemblystreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); // Captured without adding yet: mid=0, fsn=1 (second fragment of first message). RTC::SCTP::UserData lateData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false); REQUIRE( interleavedreassemblystreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( interleavedreassemblystreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, false)) == 1); // Second message: mid=1, single fragment. REQUIRE( interleavedreassemblystreams.AddData( getTsn(5), RTC::SCTP::UserData(1, 0, 1, 0, 53, { 0x01 }, true, true, false)) == 1); // Third message: mid=2, two fragments. REQUIRE( interleavedreassemblystreams.AddData( getTsn(6), RTC::SCTP::UserData(1, 0, 2, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2); REQUIRE( interleavedreassemblystreams.AddData( getTsn(7), RTC::SCTP::UserData(1, 0, 2, 1, 53, { 0x07 }, false, true, false)) == 1); // Adding the late chunk completes mid=0, which triggers delivery of // mid=1 and mid=2 as well. // NOTE: clang-tidy doesn't understand that this is fine. // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(interleavedreassemblystreams.AddData(getTsn(2), std::move(lateData)) == -8); REQUIRE(tester.GetCallCount(3)); REQUIRE(tester.GetLastTsns() == std::vector{ 6, 7 }); } SECTION("delete unordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback()); REQUIRE( interleavedreassemblystreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ true)) == 1); REQUIRE( interleavedreassemblystreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, true)) == 3); REQUIRE( interleavedreassemblystreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, true)) == 2); REQUIRE( interleavedreassemblystreams.HandleForwardTsn( getTsn(3), std::vector{ { /*unordered*/ true, /*streamId*/ 1, /*mid*/ 0 } }) == 6); } SECTION("delete simple ordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback()); REQUIRE( interleavedreassemblystreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); REQUIRE( interleavedreassemblystreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false)) == 3); REQUIRE( interleavedreassemblystreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( interleavedreassemblystreams.HandleForwardTsn( getTsn(3), std::vector{ { /*unordered*/ false, /*streamId*/ 1, /*mid*/ 0 } }) == 6); } SECTION("delete many ordered messages returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback()); REQUIRE( interleavedreassemblystreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); // mid=0 middle fragment (not added). // RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false) REQUIRE( interleavedreassemblystreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( interleavedreassemblystreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, false)) == 1); // Second message: mid=1. REQUIRE( interleavedreassemblystreams.AddData( getTsn(5), RTC::SCTP::UserData(1, 0, 1, 0, 53, { 0x01 }, true, true, false)) == 1); // Third message: mid=2. REQUIRE( interleavedreassemblystreams.AddData( getTsn(6), RTC::SCTP::UserData(1, 0, 2, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2); REQUIRE( interleavedreassemblystreams.AddData( getTsn(7), RTC::SCTP::UserData(1, 0, 2, 1, 53, { 0x07 }, false, true, false)) == 1); // Expire all three messages (skip through mid=2 inclusive). REQUIRE( interleavedreassemblystreams.HandleForwardTsn( getTsn(8), std::vector{ { /*unordered*/ false, /*streamId*/ 1, /*mid*/ 2 } }) == 8); } SECTION("delete ordered message delivers two returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback()); REQUIRE( interleavedreassemblystreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); // mid=0 middle fragment (not added). // RTC::SCTP::UserData(1, 0, 0, 1, 53, { 0x02, 0x03, 0x04 }, false, false, false) REQUIRE( interleavedreassemblystreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 2, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( interleavedreassemblystreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 3, 53, { 0x07 }, false, true, false)) == 1); // Second message: mid=1. REQUIRE( interleavedreassemblystreams.AddData( getTsn(5), RTC::SCTP::UserData(1, 0, 1, 0, 53, { 0x01 }, true, true, false)) == 1); // Third message: mid=2. REQUIRE( interleavedreassemblystreams.AddData( getTsn(6), RTC::SCTP::UserData(1, 0, 2, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2); REQUIRE( interleavedreassemblystreams.AddData( getTsn(7), RTC::SCTP::UserData(1, 0, 2, 1, 53, { 0x07 }, false, true, false)) == 1); // Expire the first message (mid=0). The following two (mid=1 and mid=2) are // delivered. REQUIRE( interleavedreassemblystreams.HandleForwardTsn( getTsn(4), std::vector{ { /*unordered*/ false, /*streamId*/ 1, /*mid*/ 0 } }) == 8); } SECTION("can delete first ordered message") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback()); // Not received: mid=0, sid=1, tsn=1 (BE, single-chunk message). // Deleted via I-FORWARD-tsn: sid=1, mid=0. REQUIRE( interleavedreassemblystreams.HandleForwardTsn( getTsn(1), std::vector{ { /*unordered*/ false, /*streamId*/ 1, /*mid*/ 0 } }) == 0); // Receive mid=1 (next after the skipped mid=0): should be delivered immediately. REQUIRE( interleavedreassemblystreams.AddData( getTsn(2), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 1, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x02, 0x03, 0x04 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ false)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 2 }); auto& lastMessages = tester.GetLastMessages(); REQUIRE(lastMessages.size() == 1); REQUIRE(std::move(lastMessages[0]).ReleasePayload() == std::vector{ 0x02, 0x03, 0x04 }); } SECTION("can reassemble fast path unordered") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback()); // Each chunk is a complete single-fragment message (BE), delivered immediately. REQUIRE( interleavedreassemblystreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ true)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 1 }); auto& lastMessages1 = tester.GetLastMessages(); REQUIRE(lastMessages1.size() == 1); REQUIRE(std::move(lastMessages1[0]).ReleasePayload() == std::vector{ 0x01 }); tester.Reset(); REQUIRE( interleavedreassemblystreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 1, 0, 53, { 0x03 }, true, true, true)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 3 }); auto& lastMessages2 = tester.GetLastMessages(); REQUIRE(lastMessages2.size() == 1); REQUIRE(std::move(lastMessages2[0]).ReleasePayload() == std::vector{ 0x03 }); tester.Reset(); REQUIRE( interleavedreassemblystreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 0, 2, 0, 53, { 0x02 }, true, true, true)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 2 }); auto& lastMessages3 = tester.GetLastMessages(); REQUIRE(lastMessages3.size() == 1); REQUIRE(std::move(lastMessages3[0]).ReleasePayload() == std::vector{ 0x02 }); tester.Reset(); REQUIRE( interleavedreassemblystreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 3, 0, 53, { 0x04 }, true, true, true)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 4 }); auto& lastMessages4 = tester.GetLastMessages(); REQUIRE(lastMessages4.size() == 1); REQUIRE(std::move(lastMessages4[0]).ReleasePayload() == std::vector{ 0x04 }); } SECTION("can reassemble fast path ordered") { OnAssembledMessageTester tester; RTC::SCTP::InterleavedReassemblyStreams interleavedreassemblystreams(tester.MakeCallback()); RTC::SCTP::UserData data1(1, 0, 0, 0, 53, { 0x01 }, true, true, false); RTC::SCTP::UserData data2(1, 0, 1, 0, 53, { 0x02 }, true, true, false); RTC::SCTP::UserData data3(1, 0, 2, 0, 53, { 0x03 }, true, true, false); RTC::SCTP::UserData data4(1, 0, 3, 0, 53, { 0x04 }, true, true, false); // tsn(1)/mid=0: delivered immediately (nextMid=0). // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(interleavedreassemblystreams.AddData(getTsn(1), std::move(data1)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 1 }); auto& lastMessages1 = tester.GetLastMessages(); REQUIRE(lastMessages1.size() == 1); REQUIRE(std::move(lastMessages1[0]).ReleasePayload() == std::vector{ 0x01 }); tester.Reset(); // tsn(3)/mid=2: buffered (nextMid=1). // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(interleavedreassemblystreams.AddData(getTsn(3), std::move(data3)) == 1); REQUIRE(tester.CheckCallbackNotCalled()); // tsn(2)/mid=1: completes mid=1, then mid=2 is also delivered. // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(interleavedreassemblystreams.AddData(getTsn(2), std::move(data2)) == -1); REQUIRE(tester.GetCallCount(2)); REQUIRE(tester.GetLastTsns() == std::vector{ 3 }); auto& lastMessages2 = tester.GetLastMessages(); REQUIRE(lastMessages2.size() == 2); REQUIRE(std::move(lastMessages2[0]).ReleasePayload() == std::vector{ 0x02 }); REQUIRE(std::move(lastMessages2[1]).ReleasePayload() == std::vector{ 0x03 }); tester.Reset(); // tsn(4)/mid=3: delivered immediately (nextMid=3). // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(interleavedreassemblystreams.AddData(getTsn(4), std::move(data4)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 4 }); auto& lastMessages3 = tester.GetLastMessages(); REQUIRE(lastMessages3.size() == 1); REQUIRE(std::move(lastMessages3[0]).ReleasePayload() == std::vector{ 0x04 }); } } ================================================ FILE: worker/test/src/RTC/SCTP/rx/TestReassemblyQueue.cpp ================================================ #include "common.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/rx/ReassemblyQueue.hpp" #include #include #include #include #include #include #include SCENARIO("SCTP ReassemblyQueue", "[sctp][reassemblyqueue]") { // The default maximum length of the reassembly queue. constexpr size_t BufferLength{ 10000 }; constexpr std::array ShortPayload = { 1, 2, 3, 4 }; constexpr std::array Message2Payload = { 5, 6, 7, 8 }; constexpr std::array SixBytePayload = { 1, 2, 3, 4, 5, 6 }; constexpr std::array MediumPayload1 = { 1, 2, 3, 4, 5, 6, 7, 8 }; constexpr std::array MediumPayload2 = { 9, 10, 11, 12, 13, 14, 15, 16 }; constexpr std::array LongPayload = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 }; auto flushMessages = [](RTC::SCTP::ReassemblyQueue& reassemblyQueue) { std::vector messages; while (reassemblyQueue.HasMessages()) { messages.emplace_back(reassemblyQueue.GetNextMessage().value()); } REQUIRE(reassemblyQueue.GetQueuedBytes() == 0); return messages; }; SECTION("empty queue") { const RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false); REQUIRE(reassemblyQueue.HasMessages() == false); REQUIRE(reassemblyQueue.GetQueuedBytes() == 0); } SECTION("single unordered chunk message") { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false); reassemblyQueue.AddData( /*tsn*/ 10, RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 1, 2, 3, 4 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ true)); REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 1); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(ShortPayload)); } SECTION("large unordered chunk all permutations") { std::vector tsns = { 10, 11, 12, 13 }; const std::span payload(LongPayload); do { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false); for (size_t i{ 0 }; i < tsns.size(); ++i) { const auto span = payload.subspan((tsns[i] - 10) * 4, 4); const bool isBeginning = (tsns[i] == 10); const bool isEnd = (tsns[i] == 13); reassemblyQueue.AddData( tsns[i], RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ std::vector(span.begin(), span.end()), /*isBeginning*/ isBeginning, /*isEnd*/ isEnd, /*isUnordered*/ false)); if (i < 3) { REQUIRE(reassemblyQueue.HasMessages() == false); } else { REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 1); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(LongPayload)); } } } while (std::ranges::next_permutation(tsns).found); } SECTION("single ordered chunk message") { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false); reassemblyQueue.AddData( /*tsn*/ 10, RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 1, 2, 3, 4 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ false)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 4); REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 1); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(ShortPayload)); } SECTION("many small ordered messages") { std::vector tsns = { 10, 11, 12, 13 }; const std::span payload(LongPayload); do { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false); for (size_t i{ 0 }; i < tsns.size(); ++i) { const auto span = payload.subspan((tsns[i] - 10) * 4, 4); const bool isBeginning{ true }; const bool isEnd{ true }; const auto ssn = static_cast(tsns[i] - 10); reassemblyQueue.AddData( tsns[i], RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ ssn, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ std::vector(span.begin(), span.end()), /*isBeginning*/ isBeginning, /*isEnd*/ isEnd, /*isUnordered*/ false)); } REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 4); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(payload.subspan(0, 4))); REQUIRE(messages[1].GetStreamId() == 1); REQUIRE(messages[1].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[1].GetPayload(), Catch::Matchers::RangeEquals(payload.subspan(4, 4))); REQUIRE(messages[2].GetStreamId() == 1); REQUIRE(messages[2].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[2].GetPayload(), Catch::Matchers::RangeEquals(payload.subspan(8, 4))); REQUIRE(messages[3].GetStreamId() == 1); REQUIRE(messages[3].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[3].GetPayload(), Catch::Matchers::RangeEquals(payload.subspan(12, 4))); } while (std::ranges::next_permutation(tsns).found); } SECTION("retransmission in large ordered") { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false); reassemblyQueue.AddData( /*tsn*/ 10, RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 1 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)); reassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 3 }, false, false, false)); reassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 4 }, false, false, false)); reassemblyQueue.AddData(14, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 5 }, false, false, false)); reassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 6 }, false, false, false)); reassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 7 }, false, false, false)); reassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 8 }, false, false, false)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 7); // Lost and retransmitted. reassemblyQueue.AddData(11, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 2 }, false, false, false)); reassemblyQueue.AddData(18, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 9 }, false, false, false)); reassemblyQueue.AddData(19, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 10 }, false, false, false)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 10); REQUIRE(reassemblyQueue.HasMessages() == false); // Bit "E". reassemblyQueue.AddData( 20, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 11, 12, 13, 14, 15, 16 }, false, true, false)); REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 1); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(LongPayload)); } SECTION("Forward-TSN remove unordered") { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false); reassemblyQueue.AddData( /*tsn*/ 10, RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 1 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ true)); reassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 3 }, false, false, true)); reassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 4 }, false, true, true)); reassemblyQueue.AddData(14, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 5 }, true, false, true)); reassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 6 }, false, false, true)); reassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 8 }, false, true, true)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 6); REQUIRE(reassemblyQueue.HasMessages() == false); reassemblyQueue.HandleForwardTsn(13, std::vector{}); REQUIRE(reassemblyQueue.GetQueuedBytes() == 3); // The second lost chunk comes, message is assembled. reassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 7 }, false, false, true)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 4); REQUIRE(reassemblyQueue.HasMessages() == true); } SECTION("Fforward-TSN remove ordered") { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false); // First message. reassemblyQueue.AddData( /*tsn*/ 10, RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 1 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)); reassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 3 }, false, false, false)); reassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 4 }, false, true, false)); // Second message. reassemblyQueue.AddData(14, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 5 }, true, false, false)); reassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 6 }, false, false, false)); reassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 7 }, false, false, false)); reassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 8 }, false, true, false)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 7); REQUIRE(reassemblyQueue.HasMessages() == false); reassemblyQueue.HandleForwardTsn( 13, std::vector{ { /*streamId*/ 1, /*ssn*/ 0 } }); REQUIRE(reassemblyQueue.GetQueuedBytes() == 4); // The lost chunk comes, but too late. REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 1); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(Message2Payload)); } SECTION("Forward-TSN remove a lot ordered") { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ false); reassemblyQueue.AddData( /*tsn*/ 10, RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 1 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)); reassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 3 }, false, false, false)); reassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 4 }, false, true, false)); reassemblyQueue.AddData(14, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 5 }, true, false, false)); reassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 6 }, false, false, false)); reassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 7 }, false, false, false)); reassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 1, 0, 0, 53, { 8 }, false, true, false)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 7); REQUIRE(reassemblyQueue.HasMessages() == false); reassemblyQueue.HandleForwardTsn( 13, std::vector{ { /*streamId*/ 1, /*ssn*/ 0 } }); REQUIRE(reassemblyQueue.GetQueuedBytes() == 4); // The lost chunk comes, but too late. REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 1); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(Message2Payload)); } SECTION("single unordered chunk message in RFC 8260") { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ true); reassemblyQueue.AddData( /*tsn*/ 10, RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 1, 2, 3, 4 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ true)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 4); REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 1); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(ShortPayload)); } SECTION("two interleaved chunks") { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ true); // Stream 1. reassemblyQueue.AddData( /*tsn*/ 10, RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 1, 2, 3, 4 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ true)); // Stream 2. reassemblyQueue.AddData( 11, RTC::SCTP::UserData(2, 0, 0, 0, 53, { 9, 10, 11, 12 }, true, false, true)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 8); // Stream 1. reassemblyQueue.AddData( 12, RTC::SCTP::UserData(1, 0, 0, 1, 53, { 5, 6, 7, 8 }, false, true, true)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 12); // Stream 2. reassemblyQueue.AddData( 13, RTC::SCTP::UserData(2, 0, 0, 1, 53, { 13, 14, 15, 16 }, false, true, true)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 16); REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 2); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(MediumPayload1)); REQUIRE(messages[1].GetStreamId() == 2); REQUIRE(messages[1].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[1].GetPayload(), Catch::Matchers::RangeEquals(MediumPayload2)); } SECTION("unordered interleaved messages all permutations") { std::vector idxs = { 0, 1, 2, 3, 4, 5 }; const std::vector tsns = { 10, 11, 12, 13, 14, 15 }; const std::vector streamIds = { 1, 2, 1, 1, 2, 2 }; const std::vector fsns = { 0, 0, 1, 2, 1, 2 }; const std::span payload(SixBytePayload); do { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ true); for (auto i : idxs) { const auto span = payload.subspan(fsns[i] * 2, 2); const bool isBeginning = (fsns[i] == 0); const bool isEnd = (fsns[i] == 2); reassemblyQueue.AddData( tsns[i], RTC::SCTP::UserData( /*streamId*/ streamIds[i], /*ssn*/ 0, /*mid*/ 0, /*fsn*/ fsns[i], /*ppid*/ 53, /*payload*/ std::vector(span.begin(), span.end()), /*isBeginning*/ isBeginning, /*isEnd*/ isEnd, /*isUnordered*/ true)); } REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 2); // NOTE: These are unordered messages so during the test permutations // they can be generated in different order. for (const auto& message : messages) { REQUIRE((message.GetStreamId() == 1 || message.GetStreamId() == 2)); REQUIRE(message.GetPayloadProtocolId() == 53); REQUIRE_THAT(message.GetPayload(), Catch::Matchers::RangeEquals(SixBytePayload)); } } while (std::ranges::next_permutation(idxs).found); } SECTION("I-Forward-TSN remove a lot ordered") { RTC::SCTP::ReassemblyQueue reassemblyQueue(BufferLength, /*useMessageInterleaving*/ true); reassemblyQueue.AddData( /*tsn*/ 10, RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 1 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)); // NOTE: Second fragment (fsn=1) is lost. reassemblyQueue.AddData(12, RTC::SCTP::UserData(1, 0, 0, 2, 53, { 3 }, false, false, false)); reassemblyQueue.AddData(13, RTC::SCTP::UserData(1, 0, 0, 3, 53, { 4 }, false, true, false)); reassemblyQueue.AddData(15, RTC::SCTP::UserData(1, 0, 1, 0, 53, { 5 }, true, false, false)); reassemblyQueue.AddData(16, RTC::SCTP::UserData(1, 0, 1, 1, 53, { 6 }, false, false, false)); reassemblyQueue.AddData(17, RTC::SCTP::UserData(1, 0, 1, 2, 53, { 7 }, false, false, false)); reassemblyQueue.AddData(18, RTC::SCTP::UserData(1, 0, 1, 3, 53, { 8 }, false, true, false)); REQUIRE(reassemblyQueue.GetQueuedBytes() == 7); REQUIRE(reassemblyQueue.HasMessages() == false); reassemblyQueue.HandleForwardTsn( 13, std::vector{ { /*unordered*/ false, /*streamId*/ 1, /*mid*/ 0 } }); REQUIRE(reassemblyQueue.GetQueuedBytes() == 4); // The lost chunk comes, but too late. REQUIRE(reassemblyQueue.HasMessages() == true); const auto& messages = flushMessages(reassemblyQueue); REQUIRE(messages.size() == 1); REQUIRE(messages[0].GetStreamId() == 1); REQUIRE(messages[0].GetPayloadProtocolId() == 53); REQUIRE_THAT(messages[0].GetPayload(), Catch::Matchers::RangeEquals(Message2Payload)); } } ================================================ FILE: worker/test/src/RTC/SCTP/rx/TestTraditionalReassemblyStreams.cpp ================================================ #include "common.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include "RTC/SCTP/rx/ReassemblyStreamsInterface.hpp" #include "RTC/SCTP/rx/TraditionalReassemblyStreams.hpp" #include #include #include SCENARIO("SCTP TraditionalReassemblyStreams", "[sctp][traditionalreassemblystreams]") { class OnAssembledMessageTester { public: RTC::SCTP::ReassemblyStreamsInterface::OnAssembledMessage MakeCallback() { return [this](std::span tsns, RTC::SCTP::Message message) { this->callCount++; // Copy the span to a vector to survive the callback. this->lastTsns = std::vector(tsns.begin(), tsns.end()); this->lastMessages.push_back(std::move(message)); }; } bool GetCallCount(size_t expectedCallCount) const { return this->callCount == expectedCallCount; } std::vector GetLastTsns() const { if (this->lastTsns.empty()) { return {}; } std::vector tsns; tsns.reserve(this->lastTsns.size()); for (const auto& tsn : this->lastTsns) { tsns.push_back(tsn.Wrap()); } return tsns; } std::vector& GetLastMessages() { return this->lastMessages; } bool CheckCallbackNotCalled() const { return (this->callCount == 0 && this->lastTsns.empty() && this->lastMessages.empty()); } void Reset() { this->callCount = 0; this->lastTsns.clear(); this->lastMessages.clear(); } private: size_t callCount{ 0 }; std::vector lastTsns; std::vector lastMessages; }; RTC::SCTP::Types::UnwrappedTsn::Unwrapper tsn; auto getTsn = [&tsn](uint32_t value) { return tsn.Unwrap(value); }; SECTION("add unordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ true)) == 1); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, true)) == 3); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, true)) == 2); // Adding the end fragment should make it empty again. REQUIRE( traditionalReassemblyStreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, true)) == -6); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 1, 2, 3, 4 }); } SECTION("add simple ordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false)) == 3); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, false)) == -6); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 1, 2, 3, 4 }); } SECTION("add more complex ordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); // Captured without adding yet: ssn=0, middle fragment of the first message. RTC::SCTP::UserData lateData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, false)) == 1); // Second message: ssn=1. REQUIRE( traditionalReassemblyStreams.AddData( getTsn(5), RTC::SCTP::UserData(1, 1, 0, 0, 53, { 0x01 }, true, true, false)) == 1); // Third message: ssn=2. REQUIRE( traditionalReassemblyStreams.AddData( getTsn(6), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(7), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x07 }, false, true, false)) == 1); // Adding the late chunk completes ssn=0, which triggers delivery of ssn=1 // and ssn=2 as well. // NOTE: clang-tidy doesn't understand that this is fine. // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(traditionalReassemblyStreams.AddData(getTsn(2), std::move(lateData)) == -8); REQUIRE(tester.GetCallCount(3)); REQUIRE(tester.GetLastTsns() == std::vector{ 6, 7 }); } SECTION("delete unordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ true)) == 1); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, true)) == 3); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, true)) == 2); REQUIRE( traditionalReassemblyStreams.HandleForwardTsn( getTsn(3), std::span{}) == 6); } SECTION("delete simple ordered message returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false)) == 3); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( traditionalReassemblyStreams.HandleForwardTsn( getTsn(3), std::vector{ { /*streamId*/ 1, /*ssn*/ 0 } }) == 6); } SECTION("delete many ordered messages returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); // ssn=0 middle fragment (not added, consumed to advance the generator). // RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false) REQUIRE( traditionalReassemblyStreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, false)) == 1); // Second message: ssn=1. REQUIRE( traditionalReassemblyStreams.AddData( getTsn(5), RTC::SCTP::UserData(1, 1, 0, 0, 53, { 0x01 }, true, true, false)) == 1); // Third message: ssn=2. REQUIRE( traditionalReassemblyStreams.AddData( getTsn(6), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(7), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x07 }, false, true, false)) == 1); // Expire all three messages (skip through ssn=2 inclusive). REQUIRE( traditionalReassemblyStreams.HandleForwardTsn( getTsn(8), std::vector{ { /*streamId*/ 1, /*ssn*/ 2 } }) == 8); } SECTION("delete ordered message delivers two returns correct size") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ false, /*isUnordered*/ false)) == 1); // ssn=0 middle fragment (not added, consumed to advance the generator). // RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x02, 0x03, 0x04 }, false, false, false) REQUIRE( traditionalReassemblyStreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x05, 0x06 }, false, false, false)) == 2); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x07 }, false, true, false)) == 1); // Second message: ssn=1. REQUIRE( traditionalReassemblyStreams.AddData( getTsn(5), RTC::SCTP::UserData(1, 1, 0, 0, 53, { 0x01 }, true, true, false)) == 1); // Third message: ssn=2. REQUIRE( traditionalReassemblyStreams.AddData( getTsn(6), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x05, 0x06 }, true, false, false)) == 2); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(7), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x07 }, false, true, false)) == 1); // Expire the first message (ssn=0). The following two (ssn=1 and ssn=2) are // delivered. REQUIRE( traditionalReassemblyStreams.HandleForwardTsn( getTsn(4), std::vector{ { /*streamId*/ 1, /*ssn*/ 0 } }) == 8); } SECTION("can delete first ordered message") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); // Not received: ssn=0, sid=1, tsn=1 (BE, single-chunk message). // Consumed here only to advance the ssn counter conceptually. // Deleted via FORWARD-tsn: sid=1, ssn=0. REQUIRE( traditionalReassemblyStreams.HandleForwardTsn( getTsn(1), std::vector{ { /*streamId*/ 1, /*ssn*/ 0 } }) == 0); // Receive ssn=1 (next after the skipped ssn=0): should be delivered immediately. REQUIRE( traditionalReassemblyStreams.AddData( getTsn(2), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 1, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x02, 0x03, 0x04 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ false)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 2 }); auto& lastMessages = tester.GetLastMessages(); REQUIRE(lastMessages.size() == 1); REQUIRE(std::move(lastMessages[0]).ReleasePayload() == std::vector{ 0x02, 0x03, 0x04 }); } SECTION("can reassemble fast path unordered") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); // Each chunk is a complete single-fragment message (BE), delivered immediately. REQUIRE( traditionalReassemblyStreams.AddData( getTsn(1), RTC::SCTP::UserData( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ true)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 1 }); auto& lastMessages1 = tester.GetLastMessages(); REQUIRE(lastMessages1.size() == 1); REQUIRE(std::move(lastMessages1[0]).ReleasePayload() == std::vector{ 0x01 }); tester.Reset(); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(3), RTC::SCTP::UserData(1, 1, 0, 0, 53, { 0x03 }, true, true, true)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 3 }); auto& lastMessages2 = tester.GetLastMessages(); REQUIRE(lastMessages2.size() == 1); REQUIRE(std::move(lastMessages2[0]).ReleasePayload() == std::vector{ 0x03 }); tester.Reset(); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(2), RTC::SCTP::UserData(1, 2, 0, 0, 53, { 0x02 }, true, true, true)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 2 }); auto& lastMessages3 = tester.GetLastMessages(); REQUIRE(lastMessages3.size() == 1); REQUIRE(std::move(lastMessages3[0]).ReleasePayload() == std::vector{ 0x02 }); tester.Reset(); REQUIRE( traditionalReassemblyStreams.AddData( getTsn(4), RTC::SCTP::UserData(1, 3, 0, 0, 53, { 0x04 }, true, true, true)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 4 }); auto& lastMessages4 = tester.GetLastMessages(); REQUIRE(lastMessages4.size() == 1); REQUIRE(std::move(lastMessages4[0]).ReleasePayload() == std::vector{ 0x04 }); } SECTION("can reassemble fast path ordered") { OnAssembledMessageTester tester; RTC::SCTP::TraditionalReassemblyStreams traditionalReassemblyStreams(tester.MakeCallback()); RTC::SCTP::UserData data1(1, 0, 0, 0, 53, { 0x01 }, true, true, false); RTC::SCTP::UserData data2(1, 1, 0, 0, 53, { 0x02 }, true, true, false); RTC::SCTP::UserData data3(1, 2, 0, 0, 53, { 0x03 }, true, true, false); RTC::SCTP::UserData data4(1, 3, 0, 0, 53, { 0x04 }, true, true, false); // tsn(1)/ssn=0: delivered immediately (nextSsn=0). // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(traditionalReassemblyStreams.AddData(getTsn(1), std::move(data1)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 1 }); auto& lastMessages1 = tester.GetLastMessages(); REQUIRE(lastMessages1.size() == 1); REQUIRE(std::move(lastMessages1[0]).ReleasePayload() == std::vector{ 0x01 }); tester.Reset(); // tsn(3)/ssn=2: buffered (nextSsn=1). // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(traditionalReassemblyStreams.AddData(getTsn(3), std::move(data3)) == 1); REQUIRE(tester.CheckCallbackNotCalled()); // tsn(2)/ssn=1: completes ssn=1, then ssn=2 is also delivered. // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(traditionalReassemblyStreams.AddData(getTsn(2), std::move(data2)) == -1); REQUIRE(tester.GetCallCount(2)); REQUIRE(tester.GetLastTsns() == std::vector{ 3 }); auto& lastMessages2 = tester.GetLastMessages(); // Notice that here we got message ssn=2 before last message ssn=3. REQUIRE(lastMessages2.size() == 2); REQUIRE(std::move(lastMessages2[0]).ReleasePayload() == std::vector{ 0x02 }); REQUIRE(std::move(lastMessages2[1]).ReleasePayload() == std::vector{ 0x03 }); tester.Reset(); // tsn(4)/ssn=3: delivered immediately (nextSsn=3). // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move, bugprone-use-after-move, hicpp-invalid-access-moved) REQUIRE(traditionalReassemblyStreams.AddData(getTsn(4), std::move(data4)) == 0); REQUIRE(tester.GetCallCount(1)); REQUIRE(tester.GetLastTsns() == std::vector{ 4 }); auto& lastMessages3 = tester.GetLastMessages(); REQUIRE(lastMessages3.size() == 1); REQUIRE(std::move(lastMessages3[0]).ReleasePayload() == std::vector{ 0x04 }); } } ================================================ FILE: worker/test/src/RTC/SCTP/sctpCommon.cpp ================================================ #include "test/include/RTC/SCTP/sctpCommon.hpp" // in worker/test/include/ #include // std::memset namespace sctpCommon { // NOTE: Buffers must be 4-byte aligned since SCTP Packet parsing casts them // to structs that require 4-byte alignment. Without this, accessing multi-byte // fields would be undefined behavior on strict-alignment architectures. alignas(4) thread_local uint8_t FactoryBuffer[]; alignas(4) thread_local uint8_t SerializeBuffer[]; alignas(4) thread_local uint8_t CloneBuffer[]; alignas(4) thread_local uint8_t DataBuffer[]; alignas(4) thread_local uint8_t ThrowBuffer[]; void ResetBuffers() { std::memset(FactoryBuffer, 0xAA, sizeof(FactoryBuffer)); std::memset(SerializeBuffer, 0xBB, sizeof(SerializeBuffer)); std::memset(CloneBuffer, 0xCC, sizeof(CloneBuffer)); std::memset(DataBuffer, 0xDD, sizeof(DataBuffer)); std::memset(ThrowBuffer, 0xEE, sizeof(ThrowBuffer)); for (size_t i = 0; i < 256; ++i) { sctpCommon::DataBuffer[i] = static_cast(i); } } } // namespace sctpCommon ================================================ FILE: worker/test/src/RTC/SCTP/tx/TestOutstandingData.cpp ================================================ #include "common.hpp" #include "Utils.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/DataChunk.hpp" #include "RTC/SCTP/packet/chunks/ForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include "RTC/SCTP/public/SctpTypes.hpp" #include "RTC/SCTP/tx/OutstandingData.hpp" #include #include #include SCENARIO("SCTP OutstandingData", "[sctp][outstandingdata]") { sctpCommon::ResetBuffers(); class DiscardFromSendQueueTester { public: void Prepare(bool returnValue) { if (this->prepared) { throw std::runtime_error("already prepared"); } this->prepared = true; this->returnValue = returnValue; } bool Called(uint16_t streamId, uint32_t outgoingMessageId) { if (!this->prepared) { return false; } this->callCount++; this->lastStreamId = streamId; this->lastOutgoingMessageId = outgoingMessageId; return this->returnValue; } void Test( size_t expectedCallCount, uint16_t expectedLastStreamId, uint32_t expectedLastOutgoingMessageId) { if (!this->prepared) { throw std::runtime_error("not prepared"); } REQUIRE(this->callCount == expectedCallCount); REQUIRE(this->lastStreamId == expectedLastStreamId); REQUIRE(this->lastOutgoingMessageId == expectedLastOutgoingMessageId); // Reset. this->prepared = false; this->callCount = 0; this->lastStreamId = 0; this->lastOutgoingMessageId = 0; this->returnValue = false; } private: bool prepared{ false }; size_t callCount{ 0 }; uint16_t lastStreamId{ 0 }; uint32_t lastOutgoingMessageId{ 0 }; bool returnValue{ false }; }; constexpr uint64_t NowMs{ 42 }; constexpr uint32_t OutgoingMessageId{ 17 }; RTC::SCTP::Types::UnwrappedTsn::Unwrapper unwrapper; DiscardFromSendQueueTester discardFromSendQueueTester; auto discardFromSendQueue = [&discardFromSendQueueTester](uint16_t streamId, uint32_t outgoingMessageId) { return discardFromSendQueueTester.Called(streamId, outgoingMessageId); }; RTC::SCTP::OutstandingData buffer( /*dataChunkHeaderLength*/ RTC::SCTP::DataChunk::DataChunkHeaderLength, /*lastCumulativeTsnAck*/ unwrapper.Unwrap(9), discardFromSendQueue); SECTION("has initial state") { REQUIRE(buffer.IsEmpty() == true); REQUIRE(buffer.GetUnackedPayloadBytes() == 0); REQUIRE(buffer.GetUnackedPacketBytes() == 0); REQUIRE(buffer.GetUnackedItems() == 0); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9); REQUIRE(buffer.GetNextTsn().Wrap() == 10); REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 9); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, }); REQUIRE(buffer.ShouldSendForwardTsn() == false); } SECTION("insert chunk") { const auto tsn = buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs); REQUIRE(tsn.has_value()); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) REQUIRE(tsn->Wrap() == 10); REQUIRE(buffer.IsEmpty() == false); REQUIRE(buffer.GetUnackedPayloadBytes() == 1); REQUIRE(buffer.GetUnackedItems() == 1); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9); REQUIRE(buffer.GetNextTsn().Wrap() == 11); REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 10); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); } SECTION("acks single chunk") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs); const auto ackInfo = buffer.HandleSack(unwrapper.Unwrap(10), {}, false); REQUIRE( ackInfo.bytesAcked == RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes(1)); REQUIRE(ackInfo.highestTsnAcked.Wrap() == 10); REQUIRE(ackInfo.hasPacketLoss == false); REQUIRE(buffer.IsEmpty() == true); REQUIRE(buffer.GetUnackedPayloadBytes() == 0); REQUIRE(buffer.GetUnackedPacketBytes() == 0); REQUIRE(buffer.GetUnackedItems() == 0); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 10); REQUIRE(buffer.GetNextTsn().Wrap() == 11); REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 10); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 10, RTC::SCTP::OutstandingData::State::ACKED }, }); } SECTION("acks previous chunk doesn't update") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs); REQUIRE(buffer.IsEmpty() == false); REQUIRE(buffer.GetUnackedPayloadBytes() == 1); REQUIRE(buffer.GetUnackedItems() == 1); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9); REQUIRE(buffer.GetNextTsn().Wrap() == 11); REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 10); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); } SECTION("acks and nacks with gap-ack-blocks") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs); const auto ackInfo = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 2 } }, false); REQUIRE( ackInfo.bytesAcked == RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes(1)); REQUIRE(ackInfo.highestTsnAcked.Wrap() == 11); REQUIRE(ackInfo.hasPacketLoss == false); // TSN 10 is still outstanding. REQUIRE(buffer.GetUnackedPayloadBytes() == 1); REQUIRE(buffer.GetUnackedItems() == 1); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9); REQUIRE(buffer.GetNextTsn().Wrap() == 12); REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 11); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::NACKED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, }); } SECTION("nacks three times with same TSN doesn't retransmit") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs); const std::vector gab1 = { { 2, 2 } }; REQUIRE(buffer.HandleSack(unwrapper.Unwrap(9), gab1, false).hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.HandleSack(unwrapper.Unwrap(9), gab1, false).hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.HandleSack(unwrapper.Unwrap(9), gab1, false).hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::NACKED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, }); } SECTION("nacks three times results in retransmission") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs); REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 2 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 3 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); const auto ackInfo = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 4 } }, false); REQUIRE( ackInfo.bytesAcked == RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes(1)); REQUIRE(ackInfo.highestTsnAcked.Wrap() == 13); REQUIRE(ackInfo.hasPacketLoss == true); REQUIRE(buffer.HasDataToBeRetransmitted() == true); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, }); std::vector> expectedChunksToBeFastRetransmitted; expectedChunksToBeFastRetransmitted.emplace_back( 10, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false)); REQUIRE(buffer.GetChunksToBeFastRetransmitted(1000) == expectedChunksToBeFastRetransmitted); REQUIRE(buffer.GetChunksToBeRetransmitted(1000).empty() == true); } SECTION("nacks three times results in abandoning") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs, /*maxRetransmits*/ 0); REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 2 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 3 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); discardFromSendQueueTester.Prepare(/*returnValue*/ false); const auto ackInfo = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 4 } }, false); discardFromSendQueueTester.Test( /*expectedCallCount*/ 1, /*expectedLastStreamId*/ 1, /*expectedLastOutgoingMessageId*/ OutgoingMessageId); REQUIRE( ackInfo.bytesAcked == RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes(1)); REQUIRE(ackInfo.highestTsnAcked.Wrap() == 13); REQUIRE(ackInfo.hasPacketLoss == true); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.GetNextTsn().Wrap() == 14); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, { 13, RTC::SCTP::OutstandingData::State::ABANDONED }, }); } SECTION("nacks three times results in abandoning with placeholder") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 2 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 3 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); discardFromSendQueueTester.Prepare(/*returnValue*/ true); const auto ackInfo = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 4 } }, false); discardFromSendQueueTester.Test( /*expectedCallCount*/ 1, /*expectedLastStreamId*/ 1, /*expectedLastOutgoingMessageId*/ OutgoingMessageId); REQUIRE( ackInfo.bytesAcked == RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes(1)); REQUIRE(ackInfo.highestTsnAcked.Wrap() == 13); REQUIRE(ackInfo.hasPacketLoss == true); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.GetNextTsn().Wrap() == 15); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, { 13, RTC::SCTP::OutstandingData::State::ABANDONED }, { 14, RTC::SCTP::OutstandingData::State::ABANDONED }, }); } SECTION("expires chunk before it is inserted") { constexpr uint64_t ExpiresAtMs = NowMs + 1; auto tsn = buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs, /*maxRetransmits*/ RTC::SCTP::Types::MaxRetransmitsNoLimit, ExpiresAtMs); REQUIRE(tsn.has_value()); tsn = buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ RTC::SCTP::Types::MaxRetransmitsNoLimit, ExpiresAtMs); REQUIRE(tsn.has_value()); discardFromSendQueueTester.Prepare(/*returnValue*/ false); tsn = buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs + 1, /*maxRetransmits*/ RTC::SCTP::Types::MaxRetransmitsNoLimit, ExpiresAtMs); REQUIRE(!tsn.has_value()); discardFromSendQueueTester.Test( /*expectedCallCount*/ 1, /*expectedLastStreamId*/ 1, /*expectedLastOutgoingMessageId*/ OutgoingMessageId); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE(buffer.GetLastCumulativeTsnAck().Wrap() == 9); REQUIRE(buffer.GetNextTsn().Wrap() == 13); REQUIRE(buffer.GetHighestOutstandingTsn().Wrap() == 12); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, }); } SECTION("can generate Forward-TSN") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs, /*maxRetransmits*/ 0); discardFromSendQueueTester.Prepare(/*returnValue*/ false); buffer.NackAll(); discardFromSendQueueTester.Test( /*expectedCallCount*/ 1, /*expectedLastStreamId*/ 1, /*expectedLastOutgoingMessageId*/ OutgoingMessageId); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, }); REQUIRE(buffer.ShouldSendForwardTsn() == true); std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) }; const auto* forwardTsnChunk = buffer.AddForwardTsn(packet.get()); REQUIRE(forwardTsnChunk); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 12); } SECTION("ack with gap blocks from RFC 9260 section 3.3.4") { for (int i{ 0 }; i < 8; ++i) { const bool isBeginning = (i == 0); const bool isEnd = (i == 7); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, isBeginning, isEnd, false), NowMs); } REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); buffer.HandleSack( unwrapper.Unwrap(12), std::vector{ { 2, 3 }, { 5, 5 } }, false); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::NACKED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::ACKED }, { 16, RTC::SCTP::OutstandingData::State::NACKED }, { 17, RTC::SCTP::OutstandingData::State::ACKED }, }); } SECTION("MeasureRtt()") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs + 1); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs + 2); constexpr uint64_t Duration{ 123 }; const auto duration = buffer.MeasureRtt(NowMs + Duration, unwrapper.Unwrap(11)); REQUIRE(duration.has_value()); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) REQUIRE(duration.value() == Duration - 1); } SECTION("must retransmit before getting nacked again") { // A chunk that has been nacked and scheduled for retransmission should not // get nacked again until it has actually been sent on the wire. for (int i{ 0 }; i <= 10; ++i) { const bool isBeginning = (i == 0); const bool isEnd = (i == 10); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, isBeginning, isEnd, false), NowMs, /*maxRetransmits*/ 1); } REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 2 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 3 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); const auto ackInfo = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 4 } }, false); REQUIRE(ackInfo.hasPacketLoss == true); REQUIRE(buffer.HasDataToBeRetransmitted() == true); // Don't retransmit yet. S simulate congestion window blocking it. // It still gets more SACKs indicating packet loss. REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 5 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == true); REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 6 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == true); const auto ackInfo2 = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 7 } }, false); REQUIRE(ackInfo2.hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == true); // Now retransmit. const auto fastRetransmit = buffer.GetChunksToBeFastRetransmitted(1000); REQUIRE(fastRetransmit.size() == 1); REQUIRE(fastRetransmit[0].first == 10); REQUIRE(buffer.GetChunksToBeRetransmitted(1000).empty() == true); // TSN 10 is now lost again. It gets nacked and eventually abandoned. REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 8 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE( buffer .HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 9 } }, false) .hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); const auto ackInfo3 = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 10 } }, false); REQUIRE(ackInfo3.hasPacketLoss == true); REQUIRE(buffer.HasDataToBeRetransmitted() == false); } SECTION("lifecyle returns acked items in ack-info") { buffer.Insert( 1, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs, RTC::SCTP::Types::MaxRetransmitsNoLimit, RTC::SCTP::Types::ExpiresAtMsInfinite, /*lifecycleId*/ 42); buffer.Insert( 2, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs, RTC::SCTP::Types::MaxRetransmitsNoLimit, RTC::SCTP::Types::ExpiresAtMsInfinite, /*lifecycleId*/ 43); buffer.Insert( 3, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs, RTC::SCTP::Types::MaxRetransmitsNoLimit, RTC::SCTP::Types::ExpiresAtMsInfinite, /*lifecycleId*/ 44); const auto ackInfo1 = buffer.HandleSack(unwrapper.Unwrap(11), {}, false); std::vector expectedAckedLifecycleIds = { 42, 43 }; REQUIRE(ackInfo1.ackedLifecycleIds == expectedAckedLifecycleIds); const auto ackInfo2 = buffer.HandleSack(unwrapper.Unwrap(12), {}, false); expectedAckedLifecycleIds = { 44 }; REQUIRE(ackInfo2.ackedLifecycleIds == expectedAckedLifecycleIds); } SECTION("lifecyle returns abandoned nacked three times") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs, /*maxRetransmits*/ 0); const auto ackInfo1 = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 2 } }, false); REQUIRE(ackInfo1.hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); const auto ackInfo2 = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 3 } }, false); REQUIRE(ackInfo2.hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); discardFromSendQueueTester.Prepare(/*returnValue*/ false); const auto ackInfo3 = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 4 } }, false); discardFromSendQueueTester.Test( /*expectedCallCount*/ 1, /*expectedLastStreamId*/ 1, /*expectedLastOutgoingMessageId*/ OutgoingMessageId); REQUIRE(ackInfo3.hasPacketLoss == true); REQUIRE(ackInfo3.abandonedLifecycleIds.empty() == true); } SECTION("lifecyle returns abandoned after T3 RTX timer expired") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, true, false), NowMs, /*maxRetransmits*/ 0, /*expiresAtMs*/ RTC::SCTP::Types::ExpiresAtMsInfinite, /*lifecycleId*/ 42); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); const auto ackInfo1 = buffer.HandleSack( unwrapper.Unwrap(9), std::vector{ { 2, 4 } }, false); REQUIRE(ackInfo1.hasPacketLoss == false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::NACKED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, }); discardFromSendQueueTester.Prepare(/*returnValue*/ false); buffer.NackAll(); discardFromSendQueueTester.Test( /*expectedCallCount*/ 1, /*expectedLastStreamId*/ 1, /*expectedLastOutgoingMessageId*/ OutgoingMessageId); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, { 13, RTC::SCTP::OutstandingData::State::ABANDONED }, }); ; REQUIRE(buffer.ShouldSendForwardTsn() == true); std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) }; const auto* forwardTsnChunk = buffer.AddForwardTsn(packet.get()); REQUIRE(forwardTsnChunk); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 13); const auto ackInfo2 = buffer.HandleSack(unwrapper.Unwrap(13), {}, false); REQUIRE(ackInfo2.hasPacketLoss == false); REQUIRE(ackInfo2.abandonedLifecycleIds == std::vector{ 42 }); } SECTION("generates Forward-TSN until next stream reset TSN") { // This test generates: // * Stream 1: TSN 10, 11, 12 // * Stream 2: TSN 13, 14 // * Stream 3: TSN 15, 16 // // Then it expires chunk 12-15, and ensures that the generated FORWARD-TSN // only includes up till TSN 12 until the cum ack TSN has reached 12, and // then 13 and 14 are included, and then after the cum ack TSN has reached // 14, then 15 is included. // // What it shouldn't do, is to generate a FORWARD-TSN directly at the start // with new TSN=15, and setting [(sid=1, ssn=44), (sid=2, ssn=46), // (sid=3, ssn=47)], because that will confuse the receiver at TSN=17, // receiving SID=1, SSN=0 (it's reset!), expecting SSN to be 45. // TSN 10-12. buffer.Insert( 0, RTC::SCTP::UserData(1, /*ssn*/ 42, 0, 0, 53, { 0x00 }, true, true, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( 1, RTC::SCTP::UserData(1, /*ssn*/ 43, 0, 0, 53, { 0x00 }, true, true, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( 2, RTC::SCTP::UserData(1, /*ssn*/ 44, 0, 0, 53, { 0x00 }, true, true, false), NowMs, /*maxRetransmits*/ 0); buffer.BeginResetStreams(); // TSN 13, 14. buffer.Insert( 3, RTC::SCTP::UserData(2, /*ssn*/ 45, 0, 0, 53, { 0x00 }, true, true, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert( 4, RTC::SCTP::UserData(2, /*ssn*/ 46, 0, 0, 53, { 0x00 }, true, true, false), NowMs, /*maxRetransmits*/ 0); buffer.BeginResetStreams(); // TSN 15, 16. buffer.Insert( 5, RTC::SCTP::UserData(3, /*ssn*/ 47, 0, 0, 53, { 0x00 }, true, true, false), NowMs, /*maxRetransmits*/ 0); buffer.Insert(6, RTC::SCTP::UserData(3, /*ssn*/ 48, 0, 0, 53, { 0x00 }, true, true, false), NowMs); REQUIRE(buffer.ShouldSendForwardTsn() == false); buffer.HandleSack(unwrapper.Unwrap(11), {}, false); buffer.NackAll(); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, { 13, RTC::SCTP::OutstandingData::State::ABANDONED }, { 14, RTC::SCTP::OutstandingData::State::ABANDONED }, { 15, RTC::SCTP::OutstandingData::State::ABANDONED }, { 16, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, }); REQUIRE(buffer.ShouldSendForwardTsn() == true); std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer)) }; const auto* forwardTsnChunk = buffer.AddForwardTsn(packet.get()); REQUIRE(forwardTsnChunk); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 12); REQUIRE( forwardTsnChunk->GetSkippedStreams() == std::vector{ { 1, 44 }, }); // Ack 12, allowing a FORWARD-TSN that spans to TSN=14 to be created. buffer.HandleSack(unwrapper.Unwrap(12), {}, false); REQUIRE(buffer.ShouldSendForwardTsn() == true); packet.reset( RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer))); forwardTsnChunk = buffer.AddForwardTsn(packet.get()); REQUIRE(forwardTsnChunk); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 14); REQUIRE( forwardTsnChunk->GetSkippedStreams() == std::vector{ { 2, 46 }, }); // Ack 13, allowing a FORWARD-TSN that spans to TSN=14 to be created. buffer.HandleSack(unwrapper.Unwrap(13), {}, false); REQUIRE(buffer.ShouldSendForwardTsn() == true); packet.reset( RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer))); forwardTsnChunk = buffer.AddForwardTsn(packet.get()); REQUIRE(forwardTsnChunk); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 14); REQUIRE( forwardTsnChunk->GetSkippedStreams() == std::vector{ { 2, 46 }, }); // Ack 14, allowing a FORWARD-TSN that spans to TSN=15 to be created. buffer.HandleSack(unwrapper.Unwrap(14), {}, false); REQUIRE(buffer.ShouldSendForwardTsn() == true); packet.reset( RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sizeof(sctpCommon::FactoryBuffer))); forwardTsnChunk = buffer.AddForwardTsn(packet.get()); REQUIRE(forwardTsnChunk); // NOLINTNEXTLINE(bugprone-unchecked-optional-access) REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 15); REQUIRE( forwardTsnChunk->GetSkippedStreams() == std::vector{ { 3, 47 }, }); buffer.HandleSack(unwrapper.Unwrap(15), {}, false); REQUIRE(buffer.ShouldSendForwardTsn() == false); } SECTION("treats unacked payload bytes different from packet bytes") { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs); REQUIRE(buffer.GetUnackedPayloadBytes() == 1); REQUIRE( buffer.GetUnackedPacketBytes() == RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes(1)); REQUIRE(buffer.GetUnackedItems() == 1); buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, true, true, false), NowMs); REQUIRE(buffer.GetUnackedPayloadBytes() == 2); REQUIRE( buffer.GetUnackedPacketBytes() == 2 * (RTC::SCTP::DataChunk::DataChunkHeaderLength + Utils::Byte::PadTo4Bytes(1))); REQUIRE(buffer.GetUnackedItems() == 2); } SECTION("fast recovery increments nack count when cumulative TSN advances") { // This test verifies that the Fast Recovery retransmission rules are // correctly applied when the Cumulative TSN Ack point advances. RFC 9260 // Section 7.2.4: "If an endpoint is in Fast Recovery and a SACK arrives that // advances the Cumulative TSN Ack Point, the miss indications are incremented // for all TSNs reported missing in the SACK." for (int i{ 10 }; i <= 16; ++i) { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs); } // SACK 1: Cumulative Ack = 10. Gap blocks for 12, 14, 16. // Missing: 11, 13, 15. // This marks 12, 14, 16 as Acked. // TSNs 11, 13, 15 get their 1st miss indication each. buffer.HandleSack( unwrapper.Unwrap(10), std::vector{ { 2, 2 }, // TSN 12 { 4, 4 }, // TSN 14 { 6, 6 } // TSN 16 }, /*isInFastRecovery*/ false); // SACK 2: Cumulative Ack advances to 11. Same gap blocks (12, 14, 16). // Endpoint is now in Fast Recovery (is_in_fast_recovery = true). Because the // Cumulative TSN Ack Point advanced from 10 to 11, 13 and 15 should get their // 2nd miss indication. buffer.HandleSack( unwrapper.Unwrap(11), std::vector{ { 1, 1 }, // TSN 12 { 3, 3 }, // TSN 14 { 5, 5 } // TSN 16 }, /*isInFastRecovery*/ true); // SACK 3: Cumulative Ack advances to 12. // Note: TSN 12 was already acked via gap block, so this just advances the // Cumulative Ack. 13 and 15 should get their 3rd miss indication and trigger // retransmission. buffer.HandleSack( unwrapper.Unwrap(12), std::vector{ { 2, 2 }, // TSN 14 { 4, 4 } // TSN 16 }, /*isInFastRecovery*/ true); REQUIRE( buffer.GetChunkStatesForTesting() == std::vector>{ { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, { 16, RTC::SCTP::OutstandingData::State::ACKED }, }); } SECTION("nack between ack blocks does not access out of bounds") { for (int i{ 0 }; i < 5; ++i) { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs); } // Inject a malformed SACK where the GapAckBlock exceeds the number of // outstanding items, potentially triggering an OOB read/write. std::vector malformedBlocks = { { 1, 40000 }, }; buffer.HandleSack(unwrapper.Unwrap(10), malformedBlocks, /*isInFastRecovery*/ false); REQUIRE(buffer.HasDataToBeRetransmitted() == false); } SECTION("handles SACKs with out of bounds TSNs") { // Send chunks with TSNs 10, 11, 12, 13, 14, 15, 16 for (int i{ 0 }; i < 7; ++i) { buffer.Insert( OutgoingMessageId, RTC::SCTP::UserData(1, 0, 0, 0, 53, { 0x00 }, false, false, false), NowMs); } // This NACKs TSN 11, 13, 15 (1st miss indication). buffer.HandleSack( unwrapper.Unwrap(10), std::vector{ { 2, 2 }, // TSN 12 { 4, 4 }, // TSN 14 { 6, 6 } // TSN 16 }, /*isInFastRecovery*/ false); REQUIRE(buffer.GetUnackedItems() == 3); // The gap between block1-end (12) and block2-start (1011) causes // NackBetweenAckBlocks to loop TSN 13..1010, but only TSN 13..16 are valid. buffer.HandleSack( unwrapper.Unwrap(11), std::vector{ { 1, 1 }, // TSN 12 { 1000, 60000 } // TSN 1011..60011 }, /*isInFastRecovery*/ true); // Packet 11 has been acknowledged. REQUIRE(buffer.GetUnackedItems() == 2); } } ================================================ FILE: worker/test/src/RTC/SCTP/tx/TestRetransmissionErrorCounter.cpp ================================================ #include "common.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SCTP/tx/RetransmissionErrorCounter.hpp" #include #include SCENARIO("SCTP RetransmissionErrorCounter", "[sctp][retransmissionerrorcounter]") { SECTION("can handle zero retransmission") { const RTC::SCTP::SctpOptions sctpOptions{ .maxRetransmissions = 0 }; RTC::SCTP::RetransmissionErrorCounter counter(sctpOptions); REQUIRE(counter.GetCounter() == 0); REQUIRE(counter.Increment("test") == false); // One is too many. REQUIRE(counter.GetCounter() == 1); } SECTION("is exhausted at maximum") { const RTC::SCTP::SctpOptions sctpOptions{ .maxRetransmissions = 3 }; RTC::SCTP::RetransmissionErrorCounter counter(sctpOptions); REQUIRE(counter.Increment("test") == true); // 1 REQUIRE(counter.GetCounter() == 1); REQUIRE(counter.IsExhausted() == false); REQUIRE(counter.Increment("test") == true); // 2 REQUIRE(counter.GetCounter() == 2); REQUIRE(counter.IsExhausted() == false); REQUIRE(counter.Increment("test") == true); // 3 REQUIRE(counter.GetCounter() == 3); REQUIRE(counter.IsExhausted() == false); REQUIRE(counter.Increment("test") == false); // Too many retransmissions. REQUIRE(counter.GetCounter() == 4); REQUIRE(counter.IsExhausted() == true); REQUIRE(counter.Increment("test") == false); // One after too many. REQUIRE(counter.GetCounter() == 5); REQUIRE(counter.IsExhausted() == true); } SECTION("clearing counter") { const RTC::SCTP::SctpOptions sctpOptions{ .maxRetransmissions = 3 }; RTC::SCTP::RetransmissionErrorCounter counter(sctpOptions); REQUIRE(counter.Increment("test") == true); // 1 REQUIRE(counter.Increment("test") == true); // 2 REQUIRE(counter.GetCounter() == 2); REQUIRE(counter.IsExhausted() == false); counter.Clear(); REQUIRE(counter.Increment("test") == true); // 1 REQUIRE(counter.Increment("test") == true); // 2 REQUIRE(counter.Increment("test") == true); // 3 REQUIRE(counter.IsExhausted() == false); REQUIRE(counter.Increment("test") == false); // Too many retransmissions. REQUIRE(counter.GetCounter() == 4); REQUIRE(counter.IsExhausted() == true); } SECTION("can be limitless") { const RTC::SCTP::SctpOptions sctpOptions{ .maxRetransmissions = std::nullopt }; RTC::SCTP::RetransmissionErrorCounter counter(sctpOptions); for (size_t i{ 1 }; i < 1000; ++i) { REQUIRE(counter.Increment("test") == true); REQUIRE(counter.GetCounter() == i); REQUIRE(counter.IsExhausted() == false); } } } ================================================ FILE: worker/test/src/RTC/SCTP/tx/TestRetransmissionQueue.cpp ================================================ #include "common.hpp" #include "Utils.hpp" #include "mocks/include/MockShared.hpp" #include "mocks/include/RTC/SCTP/association/MockAssociationListener.hpp" #include "mocks/include/RTC/SCTP/tx/MockSendQueue.hpp" #include "test/include/RTC/SCTP/sctpCommon.hpp" #include "test/include/catch2Macros.hpp" #include "RTC/SCTP/packet/Packet.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/packet/chunks/AnyForwardTsnChunk.hpp" #include "RTC/SCTP/packet/chunks/DataChunk.hpp" #include "RTC/SCTP/packet/chunks/SackChunk.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SCTP/tx/RetransmissionQueue.hpp" #include "RTC/SCTP/tx/SendQueueInterface.hpp" #include "handles/BackoffTimerHandleInterface.hpp" #include #include SCENARIO("SCTP RetransmissionQueue", "[sctp][retransmissionqueue]") { sctpCommon::ResetBuffers(); class MockRetransmissionQueueListener : public RTC::SCTP::RetransmissionQueue::Listener { public: void OnRetransmissionQueueNewRttMs(uint64_t rttMs) override { this->lastRttMs = rttMs; } void OnRetransmissionQueueClearRetransmissionCounter() override { ++this->clearRetransmissionCounterCalls; } public: uint64_t lastRttMs{ 0 }; size_t clearRetransmissionCounterCalls{ 0 }; }; class MockBackoffTimerHandleListener : public BackoffTimerHandleInterface::Listener { /* Pure virtual methods inherited from BackoffTimerHandleInterface::Listener. */ public: void OnBackoffTimer( BackoffTimerHandleInterface* /*backoffTimer*/, uint64_t& /*baseTimeoutMs*/, bool& /*stop*/) override { } }; constexpr uint32_t Arwnd{ 100000 }; constexpr uint64_t Mtu{ 1191 }; // InitialTsn is the first TSN that will be assigned. The TSN before it // (InitialTsn - 1) starts as ACKED in OutstandingData, matching dcsctp's // invariant that the initial state has the previous TSN already // cumulative-acked. constexpr uint32_t InitialTsn{ 10 }; const RTC::SCTP::SctpOptions sctpOptions{ .mtu = Mtu }; MockRetransmissionQueueListener retransmissionQueueListener; MockBackoffTimerHandleListener backoffTimerHandleListener; mocks::RTC::SCTP::MockAssociationListener associationListener; mocks::RTC::SCTP::MockSendQueue sendQueue; uint64_t nowMs{ 10000 }; mocks::MockShared shared(/*getTimeMs*/ [&nowMs]() { return nowMs; }); const std::unique_ptr t3RtxTimerUniquePtr{ shared.CreateBackoffTimer( BackoffTimerHandleInterface::BackoffTimerHandleOptions{ .listener = std::addressof(backoffTimerHandleListener), .label = "mock-sctp-t3-rtx", .baseTimeoutMs = sctpOptions.initialRtoMs, .backoffAlgorithm = BackoffTimerHandleInterface::BackoffAlgorithm::EXPONENTIAL, .maxBackoffTimeoutMs = sctpOptions.timerMaxBackoffTimeoutMs, .maxRestarts = std::nullopt }) }; auto* t3RtxTimer = t3RtxTimerUniquePtr.get(); auto createRetransmissionQueue = [&retransmissionQueueListener, &associationListener, &sendQueue, &t3RtxTimer, &sctpOptions]( bool supportsPartialReliability = true, bool useMessageInterleaving = false) { return RTC::SCTP::RetransmissionQueue( std::addressof(retransmissionQueueListener), associationListener, InitialTsn, Arwnd, sendQueue, t3RtxTimer, sctpOptions, supportsPartialReliability, useMessageInterleaving); }; auto createDataToSend = []( uint32_t outgoingMessageId, uint16_t maxRetransmissions = RTC::SCTP::Types::MaxRetransmitsNoLimit) { return [outgoingMessageId, maxRetransmissions](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data( /*streamId*/ 1, /*ssn*/ 0, /*mid*/ 0, /*fsn*/ 0, /*ppid*/ 53, /*payload*/ { 0x01, 0x02, 0x03, 0x04 }, /*isBeginning*/ true, /*isEnd*/ true, /*isUnordered*/ false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(outgoingMessageId, std::move(data)); dataToSend.maxRetransmissions = maxRetransmissions; return dataToSend; }; }; auto createSackChunk = [sctpOptions]( uint32_t tsn, uint32_t arwnd, const std::vector&& gapAckBlocks = {}) { std::unique_ptr chunk{ RTC::SCTP::SackChunk::Factory( sctpCommon::FactoryBuffer, sctpOptions.mtu) }; chunk->SetCumulativeTsnAck(tsn); chunk->SetAdvertisedReceiverWindowCredit(arwnd); for (const auto& gapAckBlock : gapAckBlocks) { chunk->AddAckBlock(gapAckBlock); } return chunk; }; auto getTSNsForFastRetransmit = [](RTC::SCTP::RetransmissionQueue& queue) { std::vector tsns; for (const auto& elem : queue.GetChunksForFastRetransmit(10000)) { tsns.push_back(elem.first); } return tsns; }; auto getSentPacketTSNs = [&nowMs](RTC::SCTP::RetransmissionQueue& queue, size_t maxLength = 10000) { std::vector tsns; for (const auto& elem : queue.GetChunksToSend(nowMs, maxLength)) { tsns.push_back(elem.first); } return tsns; }; SECTION("initial acked previous TSN") { auto retransmissionQueue = createRetransmissionQueue(); REQUIRE(retransmissionQueue.GetUnackedItems() == 0); REQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0); REQUIRE(retransmissionQueue.GetNextTsn() == InitialTsn); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { InitialTsn - 1, RTC::SCTP::OutstandingData::State::ACKED }, }); } SECTION("send one chunk") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 10 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); } SECTION("send one chunk and ack") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 10 }); retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 10, RTC::SCTP::OutstandingData::State::ACKED }, }); } SECTION("send three chunks and ack two") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceOnce(createDataToSend(1)) .WillProduceOnce(createDataToSend(2)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12 }); retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(11, Arwnd).get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); } SECTION("ack with gap blocks from RFC 4960 section 334") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceOnce(createDataToSend(1)) .WillProduceOnce(createDataToSend(2)) .WillProduceOnce(createDataToSend(3)) .WillProduceOnce(createDataToSend(4)) .WillProduceOnce(createDataToSend(5)) .WillProduceOnce(createDataToSend(6)) .WillProduceOnce(createDataToSend(7)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE( getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12, 13, 14, 15, 16, 17 }); retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 12, Arwnd, { { 2, 3 }, { 5, 5 } }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::NACKED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::ACKED }, { 16, RTC::SCTP::OutstandingData::State::NACKED }, { 17, RTC::SCTP::OutstandingData::State::ACKED }, }); } SECTION("resend packet when nacked three times") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceOnce(createDataToSend(1)) .WillProduceOnce(createDataToSend(2)) .WillProduceOnce(createDataToSend(3)) .WillProduceOnce(createDataToSend(4)) .WillProduceOnce(createDataToSend(5)) .WillProduceOnce(createDataToSend(6)) .WillProduceOnce(createDataToSend(7)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE( getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12, 13, 14, 15, 16, 17 }); // Send more chunks, but leave some as gaps to force retransmission after // three NACKs. // Send TSN 18. sendQueue.WillProduceOnce(createDataToSend(8)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 18 }); // Ack 12, 14-15, 17-18. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 12, Arwnd, { { 2, 3 }, { 5, 6 } }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::NACKED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::ACKED }, { 16, RTC::SCTP::OutstandingData::State::NACKED }, { 17, RTC::SCTP::OutstandingData::State::ACKED }, { 18, RTC::SCTP::OutstandingData::State::ACKED }, }); // Send TSN 19. sendQueue.WillProduceOnce(createDataToSend(9)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 19 }); // Ack 12, 14-15, 17-19. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 12, Arwnd, { { 2, 3 }, { 5, 7 } }) .get()); // Send TSN 20. sendQueue.WillProduceOnce(createDataToSend(10)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 20 }); // Ack 12, 14-15, 17-20. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 12, Arwnd, { { 2, 3 }, { 5, 8 } }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::ACKED }, { 16, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, { 17, RTC::SCTP::OutstandingData::State::ACKED }, { 18, RTC::SCTP::OutstandingData::State::ACKED }, { 19, RTC::SCTP::OutstandingData::State::ACKED }, { 20, RTC::SCTP::OutstandingData::State::ACKED }, }); // This will trigger "fast retransmit" mode and only chunks 13 and 16 will // be resent right now. The send queue will not even be queried. sendQueue.ExpectProduceCalledTimes(0); REQUIRE(getTSNsForFastRetransmit(retransmissionQueue) == std::vector{ 13, 16 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::ACKED }, { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 17, RTC::SCTP::OutstandingData::State::ACKED }, { 18, RTC::SCTP::OutstandingData::State::ACKED }, { 19, RTC::SCTP::OutstandingData::State::ACKED }, { 20, RTC::SCTP::OutstandingData::State::ACKED }, }); REQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations()); } SECTION("restarts T3-rtx timer on retransmit first outstanding TSN") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceOnce(createDataToSend(1)) .WillProduceOnce(createDataToSend(2)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); // Starting time. nowMs = 100 * 1000; // 100 seconds. REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12 }); // Ack 10, 12, after 100ms. nowMs += 100; retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 10, Arwnd, { { 2, 2 }, }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 10, RTC::SCTP::OutstandingData::State::ACKED }, { 11, RTC::SCTP::OutstandingData::State::NACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, }); // Send 13. sendQueue.WillProduceOnce(createDataToSend(3)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 13 }); // Ack 10, 12-13, after 100ms. nowMs += 100; retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 10, Arwnd, { { 2, 3 }, }) .get()); // Send 14. sendQueue.WillProduceOnce(createDataToSend(4)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 14 }); // Ack 10, 12-14, after 100 ms. nowMs += 100; retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 10, Arwnd, { { 2, 4 }, }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 10, RTC::SCTP::OutstandingData::State::ACKED }, { 11, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, }); // This will trigger "fast retransmit" mode and only chunks 13 and 16 will // be resent right now. The send queue will not even be queried. sendQueue.ExpectProduceCalledTimes(0); REQUIRE(getTSNsForFastRetransmit(retransmissionQueue) == std::vector{ 11 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 10, RTC::SCTP::OutstandingData::State::ACKED }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, }); // Verify that the timer was really restarted when fast-retransmitting. The // timeout is `sctpOptions.initialRtoMs`, so advance the time just before // that. nowMs += (sctpOptions.initialRtoMs - 1); auto* backoffTimer = shared.GetBackoffTimer("mock-sctp-t3-rtx"); REQUIRE(backoffTimer); REQUIRE(backoffTimer->EvaluateHasExpired() == false); nowMs += 1; REQUIRE(backoffTimer->EvaluateHasExpired() == true); } SECTION("can only produce two packets but wants to send three") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceOnce(createDataToSend(1)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10, 11 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); } SECTION("retransmits on T3-rtx expiry") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // Will force chunks to be retransmitted. retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, }); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); } SECTION("limited retransmission only with RFC 3758 support") { auto retransmissionQueue = createRetransmissionQueue(/*supportsPartialReliability*/ false); sendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 0)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // Will force chunks to be retransmitted. retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, }); // Discard must NOT be called. sendQueue.ExpectDiscardCalledTimes(0); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations()); } SECTION("limits retransmissions as UDP") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 0)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 10 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // Will force chunks to be retransmitted (abandoned because // `maxRetransmissions: 0`). sendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false); retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, }); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000).empty()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, }); } SECTION("limits retransmissions to three sends") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 3)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // `Discard()` must NOT be called for the first three retransmissions. sendQueue.ExpectDiscardCalledTimes(0); // Retransmission 1. retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).size() == 1); // Retransmission 2. retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).size() == 1); // Retransmission 3. retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).size() == 1); REQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations()); // Retransmission 4. Not allowed, chunk is abandoned. sendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false); retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true); REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).empty()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, }); } SECTION("retransmits when send buffer is full on T3-rtx expiry") { auto retransmissionQueue = createRetransmissionQueue(); constexpr size_t Cwnd{ 1200 }; retransmissionQueue.SetCwnd(Cwnd); REQUIRE(retransmissionQueue.GetCwnd() == Cwnd); REQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0); REQUIRE(retransmissionQueue.GetUnackedItems() == 0); const std::vector payload(1000, 0x00); sendQueue .WillProduceOnce( [&payload](uint64_t /*nowMs*/, size_t /*maxLength*/) { return RTC::SCTP::SendQueueInterface::DataToSend( 0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, true, true, false)); }) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1500) == std::vector{ 10 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); REQUIRE( retransmissionQueue.GetUnackedPacketBytes() == payload.size() + RTC::SCTP::DataChunk::DataChunkHeaderLength); REQUIRE(retransmissionQueue.GetUnackedItems() == 1); // Will force chunks to be retransmitted. retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, }); // After T3 expiry in-flight counters are cleared. REQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0); REQUIRE(retransmissionQueue.GetUnackedItems() == 0); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1500) == std::vector{ 10 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); REQUIRE( retransmissionQueue.GetUnackedPacketBytes() == payload.size() + RTC::SCTP::DataChunk::DataChunkHeaderLength); REQUIRE(retransmissionQueue.GetUnackedItems() == 1); } SECTION("produces valid FORWARD-TSN") { // Three middle fragments (no "E"), same message (outgoingMessageId=42, // ssn=42). `Discard()` returns true, placeholder TSN 13 created. // FORWARD-TSN newCumulativeTsn=13, skippedStreams={ (streamId=1, ssn=42) }. auto retransmissionQueue = createRetransmissionQueue(); // "B" — beginning. sendQueue .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) // Middle fragment. .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) // Another middle fragment (message not fully sent — no "E"). .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x09, 0x0a, 0x0b, 0x0c }, false, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10, 11, 12 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // Ack TSN 10, but the remaining are lost. retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()); // T3 expiry: TSN 11, 12 abandoned. `Discard()` returns true, placeholder TSN 13. sendQueue.WillDiscardOnce(1, 42, /*returnValue*/ true); retransmissionQueue.HandleT3RtxTimerExpiry(); // NOTE: TSN 13 represents the placeholder end fragment. REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 10, RTC::SCTP::OutstandingData::State::ACKED }, { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, { 13, RTC::SCTP::OutstandingData::State::ABANDONED }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true); const std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, sctpOptions.mtu) }; const auto* forwardTsnChunk = retransmissionQueue.AddForwardTsn(packet.get()); REQUIRE(forwardTsnChunk); REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 13); REQUIRE( forwardTsnChunk->GetSkippedStreams() == std::vector{ { 1, 42 } }); } SECTION("produces valid FORWARD-TSN when fully sent") { // Three fragments "B"/""/""E" (message fully sent). `Discard()` returns // false, no placeholder. FORWARD-TSN newCumulativeTsn=12. auto retransmissionQueue = createRetransmissionQueue(); sendQueue .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) // "E" — end fragment (message fully sent). .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 42, 0, 0, 53, { 0x09, 0x0a, 0x0b, 0x0c }, false, true, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10, 11, 12 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // Ack TSN 10, but the remaining are lost. retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()); // T3 expiry: TSN 11, 12 abandoned. `Discard()` returns false, no placeholder. sendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false); retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 10, RTC::SCTP::OutstandingData::State::ACKED }, { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true); const std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, sctpOptions.mtu) }; const auto* forwardTsnChunk = retransmissionQueue.AddForwardTsn(packet.get()); REQUIRE(forwardTsnChunk); REQUIRE(forwardTsnChunk->GetNewCumulativeTsn() == 12); REQUIRE( forwardTsnChunk->GetSkippedStreams() == std::vector{ { 1, 42 } }); } SECTION("produces valid I-FORWARD-TSN") { auto retransmissionQueue = createRetransmissionQueue( /*supportsPartialReliability*/ true, /*useMessageInterleaving*/ true); // Stream 1, ordered, outgoingMessageId=42, mid=42, "B". sendQueue .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 0, 42, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) // Stream 2, unordered, outgoingMessageId=43, mid=42, "B". .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(2, 0, 42, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, true); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(43, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) // Stream 3, ordered, outgoingMessageId=44, mid=42, "B". .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(3, 0, 42, 0, 53, { 0x09, 0x0a, 0x0b, 0x0c }, true, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(44, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) // Stream 4, ordered, outgoingMessageId=45, mid=42, "B". .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(4, 0, 42, 0, 53, { 0x0d, 0x0e, 0x0f, 0x10 }, true, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(45, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10, 11, 12, 13 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // TSN 13 is acked via gap block; TSN 10-12 are nacked. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 4, 4 }, }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::NACKED }, { 11, RTC::SCTP::OutstandingData::State::NACKED }, { 12, RTC::SCTP::OutstandingData::State::NACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, }); // T3 expiry: TSN 10-12 abandoned. `Discard()` called 3 times (one per stream), // each returns true, placeholder TSNs 14, 15, 16. sendQueue.WillDiscardOnce(1, 42, /*returnValue*/ true); sendQueue.WillDiscardOnce(2, 43, /*returnValue*/ true); sendQueue.WillDiscardOnce(3, 44, /*returnValue*/ true); retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, // Placeholder end fragments for streams 1, 2 and 3. { 14, RTC::SCTP::OutstandingData::State::ABANDONED }, { 15, RTC::SCTP::OutstandingData::State::ABANDONED }, { 16, RTC::SCTP::OutstandingData::State::ABANDONED }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true); // I-FORWARD-TSN: newCumulativeTsn=12 (can't go past ACKED TSN 13). std::unique_ptr packet{ RTC::SCTP::Packet::Factory( sctpCommon::FactoryBuffer, sctpOptions.mtu) }; const auto* iForwardTsnChunk1 = retransmissionQueue.AddIForwardTsn(packet.get()); REQUIRE(iForwardTsnChunk1); REQUIRE(iForwardTsnChunk1->GetNewCumulativeTsn() == 12); REQUIRE( iForwardTsnChunk1->GetSkippedStreams() == std::vector{ { false, 1, 42 }, { false, 3, 42 }, { true, 2, 42 }, }); // When TSN 13 is acked, the placeholder end fragments must be skipped too. // A receiver is more likely to ack TSN 13, but do it incrementally. retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(12, Arwnd).get()); sendQueue.ExpectDiscardCalledTimes(0); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations()); retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(13, Arwnd).get()); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 13, RTC::SCTP::OutstandingData::State::ACKED }, { 14, RTC::SCTP::OutstandingData::State::ABANDONED }, { 15, RTC::SCTP::OutstandingData::State::ABANDONED }, { 16, RTC::SCTP::OutstandingData::State::ABANDONED }, }); packet.reset(RTC::SCTP::Packet::Factory(sctpCommon::FactoryBuffer, sctpOptions.mtu)); const auto* iForwardTsnChunk2 = retransmissionQueue.AddIForwardTsn(packet.get()); REQUIRE(iForwardTsnChunk2); REQUIRE(iForwardTsnChunk2->GetNewCumulativeTsn() == 16); REQUIRE( iForwardTsnChunk2->GetSkippedStreams() == std::vector{ { false, 1, 42 }, { false, 3, 42 }, { true, 2, 42 }, }); } SECTION("measure RTT") { auto retransmissionQueue = createRetransmissionQueue( /*supportsPartialReliability*/ true, /*useMessageInterleaving*/ true); sendQueue.WillProduceOnce(createDataToSend(0, /*maxRetranmissions*/ 0)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 10 }); constexpr uint64_t DurationMs{ 123 }; nowMs += DurationMs; retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()); REQUIRE(retransmissionQueueListener.lastRttMs == DurationMs); } SECTION("validate cumulative TSN at rest") { // Nothing outstanding. TSN 8 is below lastCumulativeTsnAck(9) -> rejected. // TSN 9 equals lastCumulativeTsnAck(9) -> accepted (no-op). // TSN 10 is above highestOutstandingTsn(9) -> rejected. auto retransmissionQueue = createRetransmissionQueue(); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(8, Arwnd).get()) == false); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(9, Arwnd).get()) == true); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()) == false); } SECTION("validate cumulative TSN ack on inflight data") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceOnce(createDataToSend(1)) .WillProduceOnce(createDataToSend(2)) .WillProduceOnce(createDataToSend(3)) .WillProduceOnce(createDataToSend(4)) .WillProduceOnce(createDataToSend(5)) .WillProduceOnce(createDataToSend(6)) .WillProduceOnce(createDataToSend(7)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE( getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12, 13, 14, 15, 16, 17 }); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(8, Arwnd).get()) == false); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(9, Arwnd).get()) == true); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()) == true); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(11, Arwnd).get()) == true); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(12, Arwnd).get()) == true); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(13, Arwnd).get()) == true); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(14, Arwnd).get()) == true); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(15, Arwnd).get()) == true); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(16, Arwnd).get()) == true); REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(17, Arwnd).get()) == true); // TSN 18 has never been sent -> rejected. REQUIRE( retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(18, Arwnd).get()) == false); } SECTION("handle gap-ack-blocks matching no inflight data") { auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceOnce(createDataToSend(1)) .WillProduceOnce(createDataToSend(2)) .WillProduceOnce(createDataToSend(3)) .WillProduceOnce(createDataToSend(4)) .WillProduceOnce(createDataToSend(5)) .WillProduceOnce(createDataToSend(6)) .WillProduceOnce(createDataToSend(7)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE( getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12, 13, 14, 15, 16, 17 }); // Ack 9, 20-25. This is an invalid SACK Chunk, but should still be handled. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 11, 16 }, }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); } SECTION("handle invalid gap-ack-blocks") { // Nothing outstanding. Gap blocks referencing non-existent TSNs are // rejected. auto retransmissionQueue = createRetransmissionQueue(); // cumTsn=9 (no change), gap {3,4} -> TSN 12-13, both beyond // highestOutstandingTsn(9) -> rejected. State unchanged. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 3, 4 }, }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, }); } SECTION("gap-ack-blocks do not move cumulative TSN ack") { // cumTsn=9, gap {1,5} acks TSN 10-14 via gap blocks. The cumulative TSN // ack point itself must NOT advance, gap acks are renegable. auto retransmissionQueue = createRetransmissionQueue(); sendQueue.WillProduceOnce(createDataToSend(0)) .WillProduceOnce(createDataToSend(1)) .WillProduceOnce(createDataToSend(2)) .WillProduceOnce(createDataToSend(3)) .WillProduceOnce(createDataToSend(4)) .WillProduceOnce(createDataToSend(5)) .WillProduceOnce(createDataToSend(6)) .WillProduceOnce(createDataToSend(7)) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE( getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12, 13, 14, 15, 16, 17 }); // Ack 9, 10-14. This is actually an invalid ACK as the first gap can't be // adjacent to the cum-tsn-ack, but it's not strictly forbidden. However, // the cum-tsn-ack should not move, as the gap-ack-blocks are just advisory. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 1, 5 }, }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ACKED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); } SECTION("stays within available size") { // With `GetChunksToSend(nowMs, 1188-12=1176)`, the first `Produce()` receives // 1176 - DataChunkHeaderLength bytes, the second receives the remainder. auto retransmissionQueue = createRetransmissionQueue(); constexpr size_t AvailableBytes{ 1188 - 12 }; // 1176 bool sizeCheck1Ok{ false }; bool sizeCheck2Ok{ false }; sendQueue .WillProduceOnce( [&sizeCheck1Ok](uint64_t /*nowMs*/, size_t maxLength) { sizeCheck1Ok = (maxLength == AvailableBytes - RTC::SCTP::DataChunk::DataChunkHeaderLength); std::vector payload(183, 0x00); return RTC::SCTP::SendQueueInterface::DataToSend( 0, RTC::SCTP::UserData(1, 0, 0, 0, 53, std::move(payload), true, true, false)); }) .WillProduceOnce( [&sizeCheck2Ok](uint64_t /*nowMs*/, size_t maxLength) { sizeCheck2Ok = (maxLength == 976 - RTC::SCTP::DataChunk::DataChunkHeaderLength); std::vector payload(957, 0x00); return RTC::SCTP::SendQueueInterface::DataToSend( 1, RTC::SCTP::UserData(1, 0, 0, 0, 53, std::move(payload), true, true, false)); }); REQUIRE(getSentPacketTSNs(retransmissionQueue, AvailableBytes) == std::vector{ 10, 11 }); REQUIRE(sizeCheck1Ok == true); REQUIRE(sizeCheck2Ok == true); } SECTION("accounts nacked abandoned chunks as not outstanding") { auto retransmissionQueue = createRetransmissionQueue(); const size_t chunkSerializedLength = RTC::SCTP::DataChunk::DataChunkHeaderLength + 4; REQUIRE(chunkSerializedLength == 16 + 4); // Three middle fragments of the same message, maxRetransmissions=0. sendQueue .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) .WillProduceOnce( [](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x09, 0x0a, 0x0b, 0x0c }, false, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.maxRetransmissions = 0; return dataToSend; }) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10, 11, 12 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); REQUIRE(retransmissionQueue.GetUnackedPacketBytes() == chunkSerializedLength * 3); REQUIRE(retransmissionQueue.GetUnackedItems() == 3); // Mark the message as lost. sendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false); retransmissionQueue.HandleT3RtxTimerExpiry(); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, }); // Abandoned chunks are not counted as outstanding. REQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0); REQUIRE(retransmissionQueue.GetUnackedItems() == 0); // Acking abandoned chunks one by one changes nothing in the counters. retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()); REQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0); REQUIRE(retransmissionQueue.GetUnackedItems() == 0); retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(11, Arwnd).get()); REQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0); REQUIRE(retransmissionQueue.GetUnackedItems() == 0); retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(12, Arwnd).get()); REQUIRE(retransmissionQueue.GetUnackedPacketBytes() == 0); REQUIRE(retransmissionQueue.GetUnackedItems() == 0); } SECTION("expire from send queue when partially sent") { // Two fragments on stream 17, outgoingMessageId=42. First is produced and // goes in flight. After nowMs advances past `expiresAtMs`, the second is // produced but expired on Insert() -> first also abandoned, `Discard()` // called (returns true -> placeholder TSN 12). auto retransmissionQueue = createRetransmissionQueue(); const uint64_t expiresAtMs = nowMs + 10; sendQueue .WillProduceOnce( [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(17, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.expiresAtMs = expiresAtMs; return dataToSend; }) .WillProduceOnce( [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(17, 0, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.expiresAtMs = expiresAtMs; return dataToSend; }) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); // First `GetChunksToSend()` produces TSN 10 (nowMs < expiresAtMs). REQUIRE(getSentPacketTSNs(retransmissionQueue, 24) == std::vector{ 10 }); // Advance past expiry. nowMs += 100; // `Discard()` called for TSN 11 (unsent tail) -> returns true -> placeholder // TSN 12. sendQueue.WillDiscardOnce(17, 42, /*returnValue*/ true); // Second `GetChunksToSend()` produces TSN 11 but now > expiresAtMs -> // abandoned on `Insert()`, TSN 10 also abandoned, placeholder TSN 12 // created. REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24).empty()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, // Initial TSN. { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, // Produced and in-flight. { 11, RTC::SCTP::OutstandingData::State::ABANDONED }, // Produced and expired. { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, // Placeholder end. }); } SECTION("expire correct message from send queue") { // Three messages on stream 1. Messages 42 (mid=0) and 43 (mid=1) are // complete single-fragment messages. Message 44 (mid=0, stream reset) // has a beginning fragment produced before expiry and a middle fragment // produced after expiry -> message 44 gets abandoned, messages 42 and 43 // remain IN_FLIGHT. auto retransmissionQueue = createRetransmissionQueue(); const uint64_t expiresAtMs = nowMs + 10; // outgoingMessageId=42, mid=0, "BE" — complete message. sendQueue .WillProduceOnce( [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, true, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(42, std::move(data)); dataToSend.expiresAtMs = expiresAtMs; return dataToSend; }) // outgoingMessageId=43, mid=1, "BE" — complete message. .WillProduceOnce( [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 0, 1, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, true, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(43, std::move(data)); dataToSend.expiresAtMs = expiresAtMs; return dataToSend; }) // outgoingMessageId=44, mid=0 (stream reset), "B" — beginning only. .WillProduceOnce( [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(44, std::move(data)); dataToSend.expiresAtMs = expiresAtMs; return dataToSend; }) // outgoingMessageId=44, mid=0, middle fragment (produced after expiry). .WillProduceOnce( [expiresAtMs](uint64_t /*nowMs*/, size_t /*maxLength*/) { RTC::SCTP::UserData data(1, 0, 0, 0, 53, { 0x05, 0x06, 0x07, 0x08 }, false, false, false); RTC::SCTP::SendQueueInterface::DataToSend dataToSend(44, std::move(data)); dataToSend.expiresAtMs = expiresAtMs; return dataToSend; }) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); std::vector> expectedChunksToSend; // TSN 10, msgId=42. expectedChunksToSend.emplace_back( 10, RTC::SCTP::UserData{ 1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, true, false }); REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24) == expectedChunksToSend); // TSN 11, msgId=43. expectedChunksToSend.clear(); expectedChunksToSend.emplace_back( 11, RTC::SCTP::UserData{ 1, 0, 1, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, true, false }); REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24) == expectedChunksToSend); // TSN 12, msgId=44 "B" expectedChunksToSend.clear(); expectedChunksToSend.emplace_back( 12, RTC::SCTP::UserData{ 1, 0, 0, 0, 53, { 0x01, 0x02, 0x03, 0x04 }, true, false, false }); REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24) == expectedChunksToSend); // Advance past expiry. nowMs += 100; // `Discard()` called for message 44 (unsent middle fragment), returns true // -> placeholder TSN 14 created. sendQueue.WillDiscardOnce(1, 44, /*returnValue*/ true); // Fourth call produces TSN 13 (middle of message 44) but it's now expired // -> TSN 12 and 13 abandoned, placeholder TSN 14 created. REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 24).empty()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, // Initial TSN. { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, // msgId=42, BE. { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, // msgId=43, BE. { 12, RTC::SCTP::OutstandingData::State::ABANDONED }, // msgId=44, B. { 13, RTC::SCTP::OutstandingData::State::ABANDONED }, // msgId=44, produced and expired. { 14, RTC::SCTP::OutstandingData::State::ABANDONED }, // Placeholder end. }); } SECTION("limits retransmissions only when nacked three times") { // A chunk with maxRetransmissions=0 is NOT abandoned immediately when // nacked — it takes exactly three nacks like any other chunk, and is // abandoned on the third (not retransmitted, since maxRetransmissions=0). auto retransmissionQueue = createRetransmissionQueue(); // TSN 10: maxRetransmissions=0. sendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 0)) .WillProduceOnce(createDataToSend(0)) // TSN 11. .WillProduceOnce(createDataToSend(1)) // TSN 12. .WillProduceOnce(createDataToSend(2)) // TSN 13. .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12, 13 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); // `Discard()` must NOT be called for the first two nacks. sendQueue.ExpectDiscardCalledTimes(0); // First nack for TSN 10. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 2, 2 }, }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::NACKED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); // Second nack for TSN 10. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 2, 3 }, }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::NACKED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations()); // Third nack -> TSN 10 abandoned (maxRetransmissions=0 means 0 // retransmits). sendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false); retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 2, 4 }, }) .get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true); } SECTION("abandons rtx limit 2 when nacked nine times") { // maxRetransmits=2 for TSN 10: first 3 nacks -> fast-retransmit #1; // next 3 nacks -> regular retransmit #2; next 3 nacks -> abandoned. auto retransmissionQueue = createRetransmissionQueue(); // TSN 10: maxRetransmissions=2. sendQueue.WillProduceOnce(createDataToSend(42, /*maxRetransmissions*/ 2)) .WillProduceOnce(createDataToSend(0)) // TSN 11. .WillProduceOnce(createDataToSend(1)) // TSN 12. .WillProduceOnce(createDataToSend(2)) // TSN 13. .WillProduceOnce(createDataToSend(3)) // TSN 14. .WillProduceOnce(createDataToSend(4)) // TSN 15. .WillProduceOnce(createDataToSend(5)) // TSN 16. .WillProduceOnce(createDataToSend(6)) // TSN 17. .WillProduceOnce(createDataToSend(7)) // TSN 18. .WillProduceOnce(createDataToSend(8)) // TSN 19. .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE( getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 18, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 19, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // No `Discard()` calls during nack rounds 1 and 2. sendQueue.ExpectDiscardCalledTimes(0); // Ack TSN 11-13 — three nacks for TSN 10 -> TO_BE_RETRANSMITTED. for (uint32_t tsn{ 11 }; tsn <= 13; ++tsn) { retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 2, static_cast(tsn - 9) } }) .get()); } REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 15, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 16, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 18, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 19, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // Fast retransmit #1. REQUIRE(getTSNsForFastRetransmit(retransmissionQueue) == std::vector{ 10 }); // Ack TSN 14-16 — three more nacks -> retransmit #2 (TO_BE_RETRANSMITTED). for (uint32_t tsn{ 14 }; tsn <= 16; ++tsn) { retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 2, static_cast(tsn - 9) } }) .get()); } REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::TO_BE_RETRANSMITTED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::ACKED }, { 16, RTC::SCTP::OutstandingData::State::ACKED }, { 17, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 18, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 19, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // Regular retransmit #2. REQUIRE(getSentPacketTSNs(retransmissionQueue, 1000) == std::vector{ 10 }); // Ack TSN 17-18 — two more nacks (TSN 10 is now in-flight again after retransmit). for (uint32_t tsn{ 17 }; tsn <= 18; ++tsn) { retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 2, static_cast(tsn - 9) } }) .get()); } REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::NACKED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::ACKED }, { 16, RTC::SCTP::OutstandingData::State::ACKED }, { 17, RTC::SCTP::OutstandingData::State::ACKED }, { 18, RTC::SCTP::OutstandingData::State::ACKED }, { 19, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == false); REQUIRE_VERIFICATION_RESULT(sendQueue.VerifyExpectations()); // Ack TSN 19 — third nack; numRetransmissions(2) == maxRetransmissions(2) // -> ABANDON. sendQueue.WillDiscardOnce(1, 42, /*returnValue*/ false); retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, Arwnd, { { 2, 10 } }) .get()); REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, 1000).empty()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::ABANDONED }, { 11, RTC::SCTP::OutstandingData::State::ACKED }, { 12, RTC::SCTP::OutstandingData::State::ACKED }, { 13, RTC::SCTP::OutstandingData::State::ACKED }, { 14, RTC::SCTP::OutstandingData::State::ACKED }, { 15, RTC::SCTP::OutstandingData::State::ACKED }, { 16, RTC::SCTP::OutstandingData::State::ACKED }, { 17, RTC::SCTP::OutstandingData::State::ACKED }, { 18, RTC::SCTP::OutstandingData::State::ACKED }, { 19, RTC::SCTP::OutstandingData::State::ACKED }, }); REQUIRE(retransmissionQueue.ShouldSendForwardTsn(nowMs) == true); } SECTION("cwnd recovers when acking") { auto retransmissionQueue = createRetransmissionQueue(); constexpr size_t Cwnd{ 1200 }; retransmissionQueue.SetCwnd(Cwnd); REQUIRE(retransmissionQueue.GetCwnd() == Cwnd); const std::vector payload(1000, 0x00); const size_t chunkSerializedLength = RTC::SCTP::DataChunk::DataChunkHeaderLength + payload.size(); sendQueue .WillProduceOnce( [&payload](uint64_t /*nowMs*/, size_t /*maxLength*/) { return RTC::SCTP::SendQueueInterface::DataToSend( 0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, true, true, false)); }) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue, 1500) == std::vector{ 10 }); REQUIRE(retransmissionQueue.GetUnackedPacketBytes() == chunkSerializedLength); retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, Arwnd).get()); REQUIRE(retransmissionQueue.GetCwnd() == Cwnd + chunkSerializedLength); } SECTION("can always send one packet") { auto retransmissionQueue = createRetransmissionQueue(); const size_t mtu{ Utils::Byte::PadDownTo4Bytes(Mtu) }; // 1188. const std::vector payload(mtu - 100, 0x00); // 1088 bytes. sendQueue .WillProduceOnce( [&payload](uint64_t, size_t) { return RTC::SCTP::SendQueueInterface::DataToSend( 0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, true, false, false)); }) .WillProduceOnce( [&payload](uint64_t, size_t) { return RTC::SCTP::SendQueueInterface::DataToSend( 0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, false, false, false)); }) .WillProduceOnce( [&payload](uint64_t, size_t) { return RTC::SCTP::SendQueueInterface::DataToSend( 0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, false, false, false)); }) .WillProduceOnce( [&payload](uint64_t, size_t) { return RTC::SCTP::SendQueueInterface::DataToSend( 0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, false, false, false)); }) .WillProduceOnce( [&payload](uint64_t, size_t) { return RTC::SCTP::SendQueueInterface::DataToSend( 0, RTC::SCTP::UserData(1, 0, 0, 0, 53, payload, false, true, false)); }) .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); // Produce all 5 chunks (TSN 10-14) in one call. REQUIRE( getSentPacketTSNs(retransmissionQueue, 5 * mtu) == std::vector{ 10, 11, 12, 13, 14 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 13, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 14, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); // Ack 12, and report an empty receiver window (the peer obviously has a // tiny receive window). retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, /*aRwnd*/ 0, { { 3, 3 } }) .get()); // Force TSN 10 to be retransmitted. retransmissionQueue.HandleT3RtxTimerExpiry(); // Even with rwnd=0, one packet can be sent (no in-flight data after NackAll). REQUIRE(getSentPacketTSNs(retransmissionQueue, mtu) == std::vector{ 10 }); // But not a second one — TSN 10 is now in-flight again. REQUIRE(getSentPacketTSNs(retransmissionQueue, mtu).empty()); // Still rwnd=0, TSN 10 in-flight. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 9, /*aRwnd=*/0, { { 3, 3 } }) .get()); // There is in-flight data, so new data should not be allowed to be send since // the receiver window is full. REQUIRE(retransmissionQueue.GetChunksToSend(nowMs, mtu).empty()); // Ack TSN 10 (no more in-flight data), still rwnd=0. retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk( 10, /*aRwnd=*/0, { { 2, 2 } }) .get()); // TSN 11 can be sent since there is no in-flight data. REQUIRE(getSentPacketTSNs(retransmissionQueue, mtu) == std::vector{ 11 }); // But not a second one. REQUIRE(getSentPacketTSNs(retransmissionQueue, mtu).empty()); // Ack and recover the receiver window retransmissionQueue.HandleReceivedSackChunk( nowMs, createSackChunk(12, static_cast(5 * mtu)).get()); // Remaining TO_BE_RETRANSMITTED chunks can now be sent. REQUIRE(getSentPacketTSNs(retransmissionQueue, mtu) == std::vector{ 13 }); REQUIRE(getSentPacketTSNs(retransmissionQueue, mtu) == std::vector{ 14 }); REQUIRE(getSentPacketTSNs(retransmissionQueue, mtu).empty()); } SECTION("updates rwnd from SACK and unacked payload bytes") { auto retransmissionQueue = createRetransmissionQueue(); REQUIRE(retransmissionQueue.GetRwnd() == Arwnd); // Payload is 4 bytes (padded to 4). constexpr size_t PayloadSize{ 4 }; sendQueue .WillProduceOnce(createDataToSend(0)) // TSN 10. .WillProduceOnce(createDataToSend(1)) // TSN 11. .WillProduceOnce(createDataToSend(2)) // TSN 12. .WillProduceRepeatedly( [](uint64_t, size_t) { return std::nullopt; }); REQUIRE(getSentPacketTSNs(retransmissionQueue) == std::vector{ 10, 11, 12 }); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 9, RTC::SCTP::OutstandingData::State::ACKED }, { 10, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); REQUIRE(retransmissionQueue.GetRwnd() == Arwnd - (PayloadSize * 3)); // Ack TSN 10, new aRwnd=1000. retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(10, 1000).get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 10, RTC::SCTP::OutstandingData::State::ACKED }, { 11, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, { 12, RTC::SCTP::OutstandingData::State::IN_FLIGHT }, }); REQUIRE(retransmissionQueue.GetRwnd() == 1000 - (PayloadSize * 2)); // Ack everything, new aRwnd=2000. retransmissionQueue.HandleReceivedSackChunk(nowMs, createSackChunk(12, 2000).get()); REQUIRE( retransmissionQueue.GetChunkStatesForTesting() == std::vector>{ { 12, RTC::SCTP::OutstandingData::State::ACKED }, }); REQUIRE(retransmissionQueue.GetRwnd() == 2000); } } ================================================ FILE: worker/test/src/RTC/SCTP/tx/TestRetransmissionTimeout.cpp ================================================ #include "common.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SCTP/tx/RetransmissionTimeout.hpp" #include SCENARIO("SCTP RetransmissionTimeout", "[sctp][retransmissiontimeout]") { constexpr uint64_t MaxRttMs{ 8000 }; constexpr uint64_t InitialRtoMs{ 200 }; constexpr uint64_t MaxRtoMs{ 800 }; constexpr uint64_t MinRtoMs{ 120 }; constexpr uint64_t MinRttVarianceMs{ 220 }; // NOTE: No need to pass const integers to the lambda. auto makeSctpOptions = []() { RTC::SCTP::SctpOptions sctpOptions{ .maxRttMs = MaxRttMs, .initialRtoMs = InitialRtoMs, .minRtoMs = MinRtoMs, .maxRtoMs = MaxRtoMs, .minRttVarianceMs = MinRttVarianceMs }; return sctpOptions; }; SECTION("has valid initial RTO") { const RTC::SCTP::RetransmissionTimeout rto(makeSctpOptions()); REQUIRE(rto.GetRtoMs() == InitialRtoMs); } SECTION("too large values don't affect RTO") { RTC::SCTP::RetransmissionTimeout rto(makeSctpOptions()); rto.ObserveRttMs(MaxRttMs + 100); REQUIRE(rto.GetRtoMs() == InitialRtoMs); rto.ObserveRttMs(124); REQUIRE(rto.GetRtoMs() == 372); rto.ObserveRttMs(MaxRttMs + 100); REQUIRE(rto.GetRtoMs() == 372); } SECTION("will never go below minimum RTO") { RTC::SCTP::RetransmissionTimeout rto(makeSctpOptions()); for (int i{ 0 }; i < 1000; ++i) { rto.ObserveRttMs(1); } REQUIRE(rto.GetRtoMs() <= MinRtoMs); } SECTION("will never go above maximum RTO") { RTC::SCTP::RetransmissionTimeout rto(makeSctpOptions()); for (int i{ 0 }; i < 1000; ++i) { rto.ObserveRttMs(MaxRttMs - 1); // Adding jitter, which would make it RTO be well above RTT. rto.ObserveRttMs(MaxRttMs - 100); } REQUIRE(rto.GetRtoMs() >= MaxRtoMs); } SECTION("calculates RTO for stable RTT") { RTC::SCTP::RetransmissionTimeout rto(makeSctpOptions()); rto.ObserveRttMs(124); REQUIRE(rto.GetRtoMs() == 372); rto.ObserveRttMs(128); REQUIRE(rto.GetRtoMs() == 315); rto.ObserveRttMs(123); REQUIRE(rto.GetRtoMs() == 268); rto.ObserveRttMs(125); // NOTE: This should be 234 (as per same test in libwebrtc) but we are not // that precise. // REQUIRE(rto.GetRtoMs() == 234); REQUIRE(rto.GetRtoMs() == 233); rto.ObserveRttMs(127); // NOTE: This should be 235 (as per same test in libwebrtc) but we are not // that precise. // REQUIRE(rto.GetRtoMs() == 235); REQUIRE(rto.GetRtoMs() == 233); } SECTION("calculates RTO for unstable RTT") { RTC::SCTP::RetransmissionTimeout rto(makeSctpOptions()); rto.ObserveRttMs(124); REQUIRE(rto.GetRtoMs() == 372); rto.ObserveRttMs(402); REQUIRE(rto.GetRtoMs() == 623); rto.ObserveRttMs(728); REQUIRE(rto.GetRtoMs() == 800); rto.ObserveRttMs(89); REQUIRE(rto.GetRtoMs() == 800); rto.ObserveRttMs(126); REQUIRE(rto.GetRtoMs() == 800); } SECTION("will stabilize RTO after a while") { RTC::SCTP::RetransmissionTimeout rto(makeSctpOptions()); rto.ObserveRttMs(124); rto.ObserveRttMs(402); rto.ObserveRttMs(728); rto.ObserveRttMs(89); rto.ObserveRttMs(126); REQUIRE(rto.GetRtoMs() == 800); rto.ObserveRttMs(124); REQUIRE(rto.GetRtoMs() == 800); rto.ObserveRttMs(122); REQUIRE(rto.GetRtoMs() == 709); rto.ObserveRttMs(123); REQUIRE(rto.GetRtoMs() == 630); rto.ObserveRttMs(124); REQUIRE(rto.GetRtoMs() == 562); rto.ObserveRttMs(122); REQUIRE(rto.GetRtoMs() == 505); rto.ObserveRttMs(124); REQUIRE(rto.GetRtoMs() == 454); rto.ObserveRttMs(124); REQUIRE(rto.GetRtoMs() == 410); rto.ObserveRttMs(124); REQUIRE(rto.GetRtoMs() == 372); rto.ObserveRttMs(124); REQUIRE(rto.GetRtoMs() == 340); } SECTION("will always stay above RTT") { // In simulations, it's quite common to have a very stable RTT, and having // an RTO at the same value will cause issues as expiry timers will be // scheduled to be expire exactly when a packet is supposed to arrive. The // RTO must be larger than the RTT. In non-simulated environments, this is // a non-issue as any jitter will increase the RTO. RTC::SCTP::RetransmissionTimeout rto(makeSctpOptions()); for (int i{ 0 }; i < 1000; ++i) { rto.ObserveRttMs(124); } // NOTE: This should be 234 (as per same test in libwebrtc) but we are not // that precise. // REQUIRE(rto.GetRtoMs() == 234); REQUIRE(rto.GetRtoMs() == 232); } SECTION("can specify smaller minimum RTT variance") { auto sctpOptions = makeSctpOptions(); sctpOptions.minRttVarianceMs = MinRttVarianceMs - 100; RTC::SCTP::RetransmissionTimeout rto(sctpOptions); for (int i{ 0 }; i < 1000; ++i) { rto.ObserveRttMs(124); } REQUIRE(rto.GetRtoMs() == 184); } SECTION("can specify larger minimum RTT variance") { auto sctpOptions = makeSctpOptions(); sctpOptions.minRttVarianceMs = MinRttVarianceMs + 100; RTC::SCTP::RetransmissionTimeout rto(sctpOptions); for (int i{ 0 }; i < 1000; ++i) { rto.ObserveRttMs(124); } REQUIRE(rto.GetRtoMs() == 284); } } ================================================ FILE: worker/test/src/RTC/SCTP/tx/TestRoundRobinSendQueue.cpp ================================================ #include "common.hpp" #include "mocks/include/RTC/SCTP/association/MockAssociationListener.hpp" #include "RTC/SCTP/public/Message.hpp" #include "RTC/SCTP/public/SctpOptions.hpp" #include "RTC/SCTP/tx/RoundRobinSendQueue.hpp" #include #include #include SCENARIO("SCTP RoundRobinSendQueue", "[sctp][roundrobinsendqueue]") { constexpr size_t Mtu{ 1100 }; constexpr uint64_t NowMs{ 0 }; constexpr uint16_t StreamId{ 1 }; constexpr uint32_t Ppid{ 53 }; constexpr uint16_t DefaultPriority{ 10 }; constexpr size_t BufferedAmountLowThreshold{ 500 }; constexpr size_t OneFragmentPacketLength{ 100 }; constexpr size_t TwoFragmentPacketLength{ 101 }; SECTION("empty buffer") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); REQUIRE(q.IsEmpty()); REQUIRE(q.Produce(NowMs, OneFragmentPacketLength).has_value() == false); } SECTION("add and get single chunk") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, { 1, 2, 4, 5, 6 })); REQUIRE(!q.IsEmpty()); const auto dataToSend = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSend.has_value()); REQUIRE(dataToSend->data.IsBeginning()); REQUIRE(dataToSend->data.IsEnd()); } SECTION("carve out beginning middle and end") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(60); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto dataToSendBeg = q.Produce(NowMs, /*maxLength*/ 20); REQUIRE(dataToSendBeg.has_value()); REQUIRE(dataToSendBeg->data.IsBeginning()); REQUIRE(!dataToSendBeg->data.IsEnd()); const auto dataToSendMid = q.Produce(NowMs, /*maxLength*/ 20); REQUIRE(dataToSendMid.has_value()); REQUIRE(!dataToSendMid->data.IsBeginning()); REQUIRE(!dataToSendMid->data.IsEnd()); const auto dataToSendEnd = q.Produce(NowMs, /*maxLength*/ 20); REQUIRE(dataToSendEnd.has_value()); REQUIRE(!dataToSendEnd->data.IsBeginning()); REQUIRE(dataToSendEnd->data.IsEnd()); REQUIRE(q.Produce(NowMs, OneFragmentPacketLength).has_value() == false); } SECTION("get chunks from two messages") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(60); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(3, 54, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == StreamId); REQUIRE(dataToSendOne->data.GetPayloadProtocolId() == Ppid); REQUIRE(dataToSendOne->data.IsBeginning()); REQUIRE(dataToSendOne->data.IsEnd()); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == 3); REQUIRE(dataToSendTwo->data.GetPayloadProtocolId() == 54); REQUIRE(dataToSendTwo->data.IsBeginning()); REQUIRE(dataToSendTwo->data.IsEnd()); } SECTION("buffer becomes full and emptied") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(600); REQUIRE(q.GetTotalBufferedAmount() < 1000); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); REQUIRE(q.GetTotalBufferedAmount() < 1000); q.AddMessage(NowMs, RTC::SCTP::Message(3, 54, payload)); REQUIRE(q.GetTotalBufferedAmount() >= 1000); q.AddMessage(NowMs, RTC::SCTP::Message(5, 55, payload)); REQUIRE(q.GetTotalBufferedAmount() >= 1000); auto dataToSendOne = q.Produce(NowMs, 1000); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == StreamId); REQUIRE(dataToSendOne->data.GetPayloadProtocolId() == Ppid); REQUIRE(q.GetTotalBufferedAmount() >= 1000); auto dataToSendTwo = q.Produce(NowMs, 1000); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == 3); REQUIRE(dataToSendTwo->data.GetPayloadProtocolId() == 54); REQUIRE(q.GetTotalBufferedAmount() < 1000); REQUIRE(!q.IsEmpty()); auto dataToSendThree = q.Produce(NowMs, 1000); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetStreamId() == 5); REQUIRE(dataToSendThree->data.GetPayloadProtocolId() == 55); REQUIRE(q.GetTotalBufferedAmount() < 1000); REQUIRE(q.IsEmpty()); } SECTION("defaults to ordered send") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(20); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(!dataToSendOne->data.IsUnordered()); RTC::SCTP::SendMessageOptions options; options.unordered = true; q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload), options); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.IsUnordered()); } SECTION("produce with lifetime expiry") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(20); uint64_t now = NowMs; q.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload)); now += 1000000; REQUIRE(q.Produce(now, OneFragmentPacketLength).has_value()); RTC::SCTP::SendMessageOptions expires2s; expires2s.lifetimeMs = 2000; q.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires2s); now += 2000; REQUIRE(q.Produce(now, OneFragmentPacketLength).has_value()); q.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires2s); now += 2001; REQUIRE(!q.Produce(now, OneFragmentPacketLength).has_value()); q.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires2s); now += 1000000; REQUIRE(!q.Produce(now, OneFragmentPacketLength).has_value()); RTC::SCTP::SendMessageOptions expires4s; expires4s.lifetimeMs = 4000; q.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires2s); q.AddMessage(now, RTC::SCTP::Message(StreamId, Ppid, payload), expires4s); now += 2001; REQUIRE(q.Produce(now, OneFragmentPacketLength).has_value()); REQUIRE(!q.Produce(now, OneFragmentPacketLength).has_value()); } SECTION("discard partial packets") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(120); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(2, 54, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(!dataToSendOne->data.IsEnd()); REQUIRE(dataToSendOne->data.GetStreamId() == StreamId); q.Discard(dataToSendOne->data.GetStreamId(), dataToSendOne->outgoingMessageId); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(!dataToSendTwo->data.IsEnd()); REQUIRE(dataToSendTwo->data.GetStreamId() == 2); const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.IsEnd()); REQUIRE(dataToSendThree->data.GetStreamId() == 2); REQUIRE(!q.Produce(NowMs, OneFragmentPacketLength).has_value()); q.Discard(dataToSendOne->data.GetStreamId(), dataToSendOne->outgoingMessageId); REQUIRE(!q.Produce(NowMs, OneFragmentPacketLength).has_value()); } SECTION("prepare reset streams discards stream") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, { 1, 2, 3 })); q.AddMessage(NowMs, RTC::SCTP::Message(2, 54, { 1, 2, 3, 4, 5 })); REQUIRE(q.GetTotalBufferedAmount() == 8); q.PrepareResetStream(StreamId); REQUIRE(q.GetTotalBufferedAmount() == 5); const auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset(); REQUIRE(streamsReadyToBeReset.size() == 1); REQUIRE(std::ranges::find(streamsReadyToBeReset, StreamId) != streamsReadyToBeReset.end()); q.CommitResetStreams(); q.PrepareResetStream(2); REQUIRE(q.GetTotalBufferedAmount() == 0); } SECTION("prepare reset streams not partial packets") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(120); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, 50); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == StreamId); REQUIRE(q.GetTotalBufferedAmount() == (2 * payload.size()) - 50); q.PrepareResetStream(StreamId); REQUIRE(q.GetTotalBufferedAmount() == payload.size() - 50); } SECTION("enqueued items are paused during stream reset") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(50); q.PrepareResetStream(StreamId); REQUIRE(q.GetTotalBufferedAmount() == 0); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); REQUIRE(q.GetTotalBufferedAmount() == payload.size()); REQUIRE(!q.Produce(NowMs, OneFragmentPacketLength).has_value()); REQUIRE(q.HasStreamsReadyToBeReset()); const auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset(); REQUIRE(streamsReadyToBeReset.size() == 1); REQUIRE(std::ranges::find(streamsReadyToBeReset, StreamId) != streamsReadyToBeReset.end()); REQUIRE(!q.Produce(NowMs, OneFragmentPacketLength).has_value()); q.CommitResetStreams(); REQUIRE(q.GetTotalBufferedAmount() == payload.size()); const auto dataToSendOne = q.Produce(NowMs, 50); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == StreamId); REQUIRE(q.GetTotalBufferedAmount() == 0); } SECTION("paused streams still send partial messages until end") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const size_t payloadLength = 100; const size_t fragmentLength = 50; const std::vector payload(payloadLength); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, fragmentLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == StreamId); REQUIRE(q.GetTotalBufferedAmount() == (2 * payloadLength) - fragmentLength); q.PrepareResetStream(StreamId); REQUIRE(q.GetTotalBufferedAmount() == payloadLength - fragmentLength); const auto dataToSendTwo = q.Produce(NowMs, fragmentLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == StreamId); REQUIRE(q.GetTotalBufferedAmount() == 0); REQUIRE(!q.Produce(NowMs, fragmentLength).has_value()); } SECTION("committing resets SSN") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(50); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamSequenceNumber() == 0); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamSequenceNumber() == 1); q.PrepareResetStream(StreamId); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); REQUIRE(q.HasStreamsReadyToBeReset()); const auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset(); REQUIRE(streamsReadyToBeReset.size() == 1); REQUIRE(std::ranges::find(streamsReadyToBeReset, StreamId) != streamsReadyToBeReset.end()); q.CommitResetStreams(); const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetStreamSequenceNumber() == 0); } SECTION("committing does not reset message id") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(50); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamSequenceNumber() == 0); REQUIRE(dataToSendOne->outgoingMessageId == 0); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamSequenceNumber() == 1); REQUIRE(dataToSendTwo->outgoingMessageId == 1); q.PrepareResetStream(StreamId); const auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset(); REQUIRE(streamsReadyToBeReset.size() == 1); REQUIRE(std::ranges::find(streamsReadyToBeReset, StreamId) != streamsReadyToBeReset.end()); q.CommitResetStreams(); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetStreamSequenceNumber() == 0); REQUIRE(dataToSendThree->outgoingMessageId == 2); } SECTION("committing resets SSN for paused streams only") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(50); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); REQUIRE(dataToSendOne->data.GetStreamSequenceNumber() == 0); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == 3); REQUIRE(dataToSendTwo->data.GetStreamSequenceNumber() == 0); q.PrepareResetStream(3); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, payload)); const auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset(); REQUIRE(streamsReadyToBeReset.size() == 1); REQUIRE(std::ranges::find(streamsReadyToBeReset, 3) != streamsReadyToBeReset.end()); q.CommitResetStreams(); const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetStreamId() == 1); REQUIRE(dataToSendThree->data.GetStreamSequenceNumber() == 1); const auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendFour.has_value()); REQUIRE(dataToSendFour->data.GetStreamId() == 3); REQUIRE(dataToSendFour->data.GetStreamSequenceNumber() == 0); } SECTION("rollback resumes SSN") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(50); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamSequenceNumber() == 0); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamSequenceNumber() == 1); q.PrepareResetStream(1); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); const auto streamsReadyToBeReset = q.GetStreamsReadyToBeReset(); REQUIRE(streamsReadyToBeReset.size() == 1); REQUIRE(std::ranges::find(streamsReadyToBeReset, 1) != streamsReadyToBeReset.end()); q.RollbackResetStreams(); const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetStreamSequenceNumber() == 2); } SECTION("returns fragments for one message before moving to next") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(200); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == 1); const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetStreamId() == 2); const auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendFour.has_value()); REQUIRE(dataToSendFour->data.GetStreamId() == 2); } SECTION("returns also small fragments before moving to next") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(TwoFragmentPacketLength); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload)); q.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); REQUIRE(dataToSendOne->data.GetPayloadLength() == OneFragmentPacketLength); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == 1); REQUIRE( dataToSendTwo->data.GetPayloadLength() == TwoFragmentPacketLength - OneFragmentPacketLength); const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetStreamId() == 2); REQUIRE(dataToSendThree->data.GetPayloadLength() == OneFragmentPacketLength); const auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendFour.has_value()); REQUIRE(dataToSendFour->data.GetStreamId() == 2); REQUIRE( dataToSendFour->data.GetPayloadLength() == TwoFragmentPacketLength - OneFragmentPacketLength); } SECTION("will cycle in round robin fashion between streams") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(1))); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(2))); q.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector(3))); q.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector(4))); q.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, std::vector(5))); q.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, std::vector(6))); q.AddMessage(NowMs, RTC::SCTP::Message(4, Ppid, std::vector(7))); q.AddMessage(NowMs, RTC::SCTP::Message(4, Ppid, std::vector(8))); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); REQUIRE(dataToSendOne->data.GetPayloadLength() == 1); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == 2); REQUIRE(dataToSendTwo->data.GetPayloadLength() == 3); const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetStreamId() == 3); REQUIRE(dataToSendThree->data.GetPayloadLength() == 5); const auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendFour.has_value()); REQUIRE(dataToSendFour->data.GetStreamId() == 4); REQUIRE(dataToSendFour->data.GetPayloadLength() == 7); const auto dataToSendFive = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendFive.has_value()); REQUIRE(dataToSendFive->data.GetStreamId() == 1); REQUIRE(dataToSendFive->data.GetPayloadLength() == 2); const auto dataToSendSix = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendSix.has_value()); REQUIRE(dataToSendSix->data.GetStreamId() == 2); REQUIRE(dataToSendSix->data.GetPayloadLength() == 4); const auto dataToSendSeven = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendSeven.has_value()); REQUIRE(dataToSendSeven->data.GetStreamId() == 3); REQUIRE(dataToSendSeven->data.GetPayloadLength() == 6); const auto dataToSendEight = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendEight.has_value()); REQUIRE(dataToSendEight->data.GetStreamId() == 4); REQUIRE(dataToSendEight->data.GetPayloadLength() == 8); } SECTION("doesn't trigger stream buffered amount low when set to zero") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.SetStreamBufferedAmountLowThreshold(1, 0); REQUIRE(!associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1)); } SECTION("triggers stream buffered amount low when sent") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(1))); REQUIRE(q.GetStreamBufferedAmount(1) == 1); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1)); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); REQUIRE(dataToSendOne->data.GetPayloadLength() == 1); REQUIRE(q.GetStreamBufferedAmount(1) == 0); } SECTION("will retrigger stream buffered amount low if adding more") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(1))); REQUIRE(q.GetStreamBufferedAmount(1) == 1); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1)); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(1))); REQUIRE(q.GetStreamBufferedAmount(1) == 1); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1)); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 2); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); REQUIRE(dataToSendOne->data.GetPayloadLength() == 1); REQUIRE(q.GetStreamBufferedAmount(1) == 0); } SECTION("only triggers stream buffered amount low when transitioning from above to below or equal") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.SetStreamBufferedAmountLowThreshold(1, 1000); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(10))); REQUIRE(q.GetStreamBufferedAmount(1) == 10); // Shouldn't trigger the event. const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(!associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1)); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); REQUIRE(dataToSendOne->data.GetPayloadLength() == 10); REQUIRE(q.GetStreamBufferedAmount(1) == 0); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(20))); REQUIRE(q.GetStreamBufferedAmount(1) == 20); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(!associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1)); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == 1); REQUIRE(dataToSendTwo->data.GetPayloadLength() == 20); REQUIRE(q.GetStreamBufferedAmount(1) == 0); } SECTION("will trigger stream buffered amount low set above zero") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.SetStreamBufferedAmountLowThreshold(1, 700); const std::vector payload(1000); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload)); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); REQUIRE(dataToSendOne->data.GetPayloadLength() == OneFragmentPacketLength); REQUIRE(q.GetStreamBufferedAmount(1) == 900); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetPayloadLength() == OneFragmentPacketLength); REQUIRE(q.GetStreamBufferedAmount(1) == 800); // It goes beyond 700 bytes, it should trigger the event. const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1)); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetPayloadLength() == OneFragmentPacketLength); REQUIRE(q.GetStreamBufferedAmount(1) == 700); // Buffer decreases so it shouldn't emit the event. const auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1); REQUIRE(dataToSendFour.has_value()); REQUIRE(dataToSendFour->data.GetPayloadLength() == OneFragmentPacketLength); REQUIRE(q.GetStreamBufferedAmount(1) == 600); } SECTION("will retrigger stream buffered amount low set above zero") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.SetStreamBufferedAmountLowThreshold(1, 700); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(1000))); const auto dataToSendOne = q.Produce(NowMs, 400); REQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(1)); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 1); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); REQUIRE(dataToSendOne->data.GetPayloadLength() == 400); REQUIRE(q.GetStreamBufferedAmount(1) == 600); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(200))); REQUIRE(q.GetStreamBufferedAmount(1) == 800); const auto dataToSendTwo = q.Produce(NowMs, 200); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(1) == 2); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == 1); REQUIRE(dataToSendTwo->data.GetPayloadLength() == 200); REQUIRE(q.GetStreamBufferedAmount(1) == 600); } SECTION("triggers stream buffered amount low on threshold changed") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, std::vector(100))); // Modifying the threshold, still under buffered_amount, should not trigger // event. q.SetStreamBufferedAmountLowThreshold(StreamId, 50); q.SetStreamBufferedAmountLowThreshold(StreamId, 99); REQUIRE(!associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(StreamId)); // When the threshold reaches buffered_amount, it will trigger event. q.SetStreamBufferedAmountLowThreshold(StreamId, 100); REQUIRE(associationListener.HasOnStreamBufferedAmountLowBeenCalledWithStreamId(StreamId)); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(StreamId) == 1); // But not when it's set low again. q.SetStreamBufferedAmountLowThreshold(StreamId, 50); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(StreamId) == 1); // But it will trigger when it overshoots. q.SetStreamBufferedAmountLowThreshold(StreamId, 150); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(StreamId) == 2); // But not when it's set back to zero. q.SetStreamBufferedAmountLowThreshold(StreamId, 0); REQUIRE(associationListener.CountOnStreamBufferedAmountLowCallsWithStreamId(StreamId) == 2); } SECTION("total buffered amount low does not trigger on buffer filling up") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(BufferedAmountLowThreshold - 1); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); REQUIRE(q.GetTotalBufferedAmount() == payload.size()); // Will not trigger if going above but never below. q.AddMessage( NowMs, RTC::SCTP::Message(StreamId, Ppid, std::vector(OneFragmentPacketLength))); REQUIRE(associationListener.CountOnTotalBufferedAmountLowCalls() == 0); REQUIRE(q.GetTotalBufferedAmount() > payload.size()); } SECTION("triggers total buffered amount low when crossing") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(BufferedAmountLowThreshold); q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, payload)); REQUIRE(q.GetTotalBufferedAmount() == payload.size()); // Reaches it. q.AddMessage(NowMs, RTC::SCTP::Message(StreamId, Ppid, std::vector(1))); // Drain it a bit, will trigger. const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(associationListener.CountOnTotalBufferedAmountLowCalls() == 1); REQUIRE(dataToSendTwo.has_value()); REQUIRE(q.GetTotalBufferedAmount() < BufferedAmountLowThreshold); } SECTION("will stay in a stream as long as that message is sending") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); constexpr size_t OneFragmentPacketSize = OneFragmentPacketLength; q.AddMessage(NowMs, RTC::SCTP::Message(5, Ppid, std::vector(1))); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketSize); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 5); REQUIRE(dataToSendOne->data.GetPayloadLength() == 1); // Next, it should pick a different stream. q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(OneFragmentPacketSize * 2))); const auto dataToSendTwo = q.Produce(NowMs, OneFragmentPacketSize); REQUIRE(dataToSendTwo.has_value()); REQUIRE(dataToSendTwo->data.GetStreamId() == 1); REQUIRE(dataToSendTwo->data.GetPayloadLength() == OneFragmentPacketSize); // It should still stay on the Stream1 now, even if might be tempted to switch // to this stream, as it's the stream following 5. q.AddMessage(NowMs, RTC::SCTP::Message(6, Ppid, std::vector(1))); const auto dataToSendThree = q.Produce(NowMs, OneFragmentPacketSize); REQUIRE(dataToSendThree.has_value()); REQUIRE(dataToSendThree->data.GetStreamId() == 1); REQUIRE(dataToSendThree->data.GetPayloadLength() == OneFragmentPacketSize); // After stream 1 message is complete, it should move to stream 6. const auto dataToSendFour = q.Produce(NowMs, OneFragmentPacketSize); REQUIRE(dataToSendFour.has_value()); REQUIRE(dataToSendFour->data.GetStreamId() == 6); REQUIRE(dataToSendFour->data.GetPayloadLength() == 1); REQUIRE(q.Produce(NowMs, OneFragmentPacketSize).has_value() == false); } SECTION("streams have initial priority") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); REQUIRE(q.GetStreamPriority(1) == DefaultPriority); q.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector(40))); REQUIRE(q.GetStreamPriority(2) == DefaultPriority); } SECTION("can change stream priority") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.SetStreamPriority(1, 42); REQUIRE(q.GetStreamPriority(1) == 42); q.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector(40))); q.SetStreamPriority(2, 42); REQUIRE(q.GetStreamPriority(2) == 42); } SECTION("will send messages by priority") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); q.EnableMessageInterleaving(true); q.SetStreamPriority(1, 10); q.SetStreamPriority(2, 20); q.SetStreamPriority(3, 30); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, std::vector(40))); q.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, std::vector(20))); q.AddMessage(NowMs, RTC::SCTP::Message(3, Ppid, std::vector(10))); const std::vector expectedStreams = { 3, 2, 2, 1, 1, 1, 1 }; for (const uint16_t streamId : expectedStreams) { const auto dataToSend = q.Produce(NowMs, 10); REQUIRE(dataToSend.has_value()); REQUIRE(dataToSend->data.GetStreamId() == streamId); } REQUIRE(q.Produce(NowMs, 1).has_value() == false); } SECTION("will send lifecycle expire when expired in send queue") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(OneFragmentPacketLength); RTC::SCTP::SendMessageOptions options; options.lifetimeMs = 1000; options.lifecycleId = 1; q.AddMessage(NowMs, RTC::SCTP::Message(2, Ppid, payload), options); REQUIRE(q.Produce(NowMs + 1001, OneFragmentPacketLength).has_value() == false); REQUIRE(associationListener.HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId( 1, false)); REQUIRE(associationListener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(1)); } SECTION("will send lifecycle expire when discarding during pause") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(120); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload), { .lifecycleId = 1 }); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload), { .lifecycleId = 2 }); const auto dataToSendOne = q.Produce(NowMs, 50); REQUIRE(dataToSendOne.has_value()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); REQUIRE(q.GetTotalBufferedAmount() == (2 * payload.size()) - 50); q.PrepareResetStream(1); REQUIRE(associationListener.HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId( 2, false)); REQUIRE(associationListener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(2)); REQUIRE(q.GetTotalBufferedAmount() == payload.size() - 50); } SECTION("will send lifecycle expire when discarding explicitly") { mocks::RTC::SCTP::MockAssociationListener associationListener; RTC::SCTP::RoundRobinSendQueue q( associationListener, Mtu, DefaultPriority, BufferedAmountLowThreshold); const std::vector payload(OneFragmentPacketLength + 20); q.AddMessage(NowMs, RTC::SCTP::Message(1, Ppid, payload), { .lifecycleId = 1 }); const auto dataToSendOne = q.Produce(NowMs, OneFragmentPacketLength); REQUIRE(dataToSendOne.has_value()); REQUIRE(!dataToSendOne->data.IsEnd()); REQUIRE(dataToSendOne->data.GetStreamId() == 1); q.Discard(dataToSendOne->data.GetStreamId(), dataToSendOne->outgoingMessageId); REQUIRE(associationListener.HasOnAssociationLifecycleMessageExpiredSentBeenCalledWithLifecycleId( 1, false)); REQUIRE(associationListener.HasOnAssociationLifecycleMessageEndBeenCalledWithLifecycleId(1)); } } ================================================ FILE: worker/test/src/RTC/SCTP/tx/TestStreamScheduler.cpp ================================================ #include "common.hpp" #include "RTC/SCTP/packet/UserData.hpp" #include "RTC/SCTP/tx/SendQueueInterface.hpp" #include "RTC/SCTP/tx/StreamScheduler.hpp" #include #include #include #include namespace { constexpr uint64_t Mtu{ 1000 }; constexpr size_t PayloadLength{ 4 }; constexpr uint64_t NowMs{ 0 }; bool checkDataToSendHasMid( std::optional dataToSend, uint32_t mid) { if (!dataToSend.has_value()) { return false; } if (dataToSend->data.GetMessageId() != mid) { return false; } return true; } std::function(uint64_t, size_t)> createChunk( uint32_t outgoingMessageId, uint16_t streamId, uint32_t mid, size_t payloadLength = PayloadLength) { return [streamId, mid, payloadLength, outgoingMessageId](uint64_t /*nowMs*/, size_t /*maxLength*/) { return RTC::SCTP::SendQueueInterface::DataToSend( outgoingMessageId, RTC::SCTP::UserData( streamId, /*ssn*/ 0, mid, /*fsn*/ 0, /*ppid*/ 42, std::vector(payloadLength), /*isBeginning*/ true, /*isEnd*/ true, /*unoreded*/ true)); }; } std::map getPacketCounts( RTC::SCTP::StreamScheduler& scheduler, size_t packetsToGenerate) { std::map packetCounts; for (size_t i{ 0 }; i < packetsToGenerate; ++i) { const std::optional dataToSend = scheduler.Produce(NowMs, Mtu); if (dataToSend.has_value()) { ++packetCounts[dataToSend->data.GetStreamId()]; } } return packetCounts; } class MockStreamProducer : public RTC::SCTP::StreamScheduler::StreamProducer { public: /** * Equivalent to EXPECT_CALL(producer, Produce).WillOnce(...).WillOnce(...) * in dcsctp. */ void PushProduce( std::function(uint64_t, size_t)> fn) { this->produceQueue.push_back(std::move(fn)); } /** * Equivalent to EXPECT_CALL(producer, bytes_to_send_in_next_message) * .WillOnce(Return(n)) in dcsctp. */ void PushBytesToSend(size_t bytes) { this->bytesQueue.push_back(bytes); } std::optional Produce(uint64_t nowMs, size_t maxLength) override { REQUIRE(!this->produceQueue.empty()); const auto fn = std::move(this->produceQueue.front()); this->produceQueue.pop_front(); return fn(nowMs, maxLength); } size_t GetBytesToSendInNextMessage() const override { REQUIRE(!this->bytesQueue.empty()); const size_t bytes = this->bytesQueue.front(); this->bytesQueue.pop_front(); return bytes; } private: std::deque(uint64_t, size_t)>> produceQueue; mutable std::deque bytesQueue; }; class TestStream { public: TestStream( RTC::SCTP::StreamScheduler& scheduler, uint16_t streamId, uint16_t priority, size_t packetLength = PayloadLength) { this->producer.PushBytesToSend(packetLength); // MayMakeActive(). // Equivalent to WillRepeatedly() in dcsctp. for (int i{ 0 }; i < 100; ++i) { this->producer.PushProduce(createChunk(i, streamId, i, packetLength)); this->producer.PushBytesToSend(packetLength); } this->stream = scheduler.CreateStream(std::addressof(producer), streamId, priority); this->stream->MayMakeActive(); } RTC::SCTP::StreamScheduler::Stream& GetStream() { return *stream; } private: MockStreamProducer producer; std::unique_ptr stream; }; } // namespace SCENARIO("SCTP StreamScheduler", "[sctp][streamscheduler]") { // A scheduler without active streams doesn't produce data. SECTION("has no active streams") { RTC::SCTP::StreamScheduler scheduler(Mtu); REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // Stream properties can be set and retrieved. SECTION("can set and get stream properties") { RTC::SCTP::StreamScheduler scheduler(Mtu); MockStreamProducer producer; auto stream = scheduler.CreateStream(std::addressof(producer), 1, 2); REQUIRE(stream->GetStreamId() == 1); REQUIRE(stream->GetPriority() == 2); stream->SetPriority(0); REQUIRE(stream->GetPriority() == 0); } SECTION("can produce from a single stream") { RTC::SCTP::StreamScheduler scheduler(Mtu); MockStreamProducer producer; producer.PushBytesToSend(PayloadLength); producer.PushProduce(createChunk(0, 1, 0)); producer.PushBytesToSend(0); auto stream = scheduler.CreateStream(std::addressof(producer), 1, 2); stream->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 0)); REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // A scheduler with a single stream produced packets from it. SECTION("will round-robin between streams") { RTC::SCTP::StreamScheduler scheduler(Mtu); MockStreamProducer producer1; producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(0, 1, 100)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(1, 1, 101)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(2, 1, 102)); producer1.PushBytesToSend(0); auto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2); stream1->MayMakeActive(); MockStreamProducer producer2; producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(3, 2, 200)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(4, 2, 201)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(5, 2, 202)); producer2.PushBytesToSend(0); auto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2); stream2->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // Switches between two streams after every packet. SECTION("will round-robin between streams") { RTC::SCTP::StreamScheduler scheduler(Mtu); MockStreamProducer producer1; producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(0, 1, 100)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(1, 1, 101)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(2, 1, 102)); producer1.PushBytesToSend(0); auto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2); stream1->MayMakeActive(); MockStreamProducer producer2; producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(3, 2, 200)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(4, 2, 201)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(5, 2, 202)); producer2.PushBytesToSend(0); auto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2); stream2->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // Switches between two streams after every packet, but keeps producing from // the same stream when a packet contains of multiple fragments. SECTION("will round-robin only when finished producing chunk") { RTC::SCTP::StreamScheduler scheduler(Mtu); MockStreamProducer producer1; producer1.PushBytesToSend(PayloadLength); // MayMakeActive producer1.PushProduce(createChunk(0, 1, 100)); producer1.PushBytesToSend(PayloadLength); // MID(101) fragmented in 3 chunks: // 1. beginning:true, end:false // 2. beginning:false, end:false // 3. beginning:false, end:true producer1.PushProduce( [](uint64_t, size_t) { return RTC::SCTP::SendQueueInterface::DataToSend( 1, RTC::SCTP::UserData(1, 0, 101, 0, 42, std::vector(4), true, false, true)); }); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce( [](uint64_t, size_t) { return RTC::SCTP::SendQueueInterface::DataToSend( 1, RTC::SCTP::UserData(1, 0, 101, 0, 42, std::vector(4), false, false, true)); }); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce( [](uint64_t, size_t) { return RTC::SCTP::SendQueueInterface::DataToSend( 1, RTC::SCTP::UserData(1, 0, 101, 0, 42, std::vector(4), false, true, true)); }); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(2, 1, 102)); producer1.PushBytesToSend(0); auto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2); stream1->MayMakeActive(); MockStreamProducer producer2; producer2.PushBytesToSend(PayloadLength); // MayMakeActive(). producer2.PushProduce(createChunk(3, 2, 200)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(4, 2, 201)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(5, 2, 202)); producer2.PushBytesToSend(0); auto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2); stream2->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); // MID(101) is fully produced before giving up on stream2. REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // Resumes a paused stream - makes a stream active after inactivating it. SECTION("single stream can be resumed") { RTC::SCTP::StreamScheduler scheduler(Mtu); MockStreamProducer producer1; producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(0, 1, 100)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(1, 1, 101)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(2, 1, 102)); producer1.PushBytesToSend(PayloadLength); // When making active again. producer1.PushBytesToSend(0); auto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2); stream1->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); stream1->MakeInactive(); REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); stream1->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // Iterates between streams, where one is suddenly paused and later resumed. SECTION("will round-robin with paused stream") { RTC::SCTP::StreamScheduler scheduler(Mtu); MockStreamProducer producer1; producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(0, 1, 100)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(1, 1, 101)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(2, 1, 102)); producer1.PushBytesToSend(PayloadLength); // When making active again. producer1.PushBytesToSend(0); auto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2); stream1->MayMakeActive(); MockStreamProducer producer2; producer2.PushBytesToSend(PayloadLength); // MayMakeActive(). producer2.PushProduce(createChunk(3, 2, 200)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(4, 2, 201)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(5, 2, 202)); producer2.PushBytesToSend(0); auto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2); stream2->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); stream1->MakeInactive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); stream1->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // Verifies that packet counts are evenly distributed in round robin // scheduling. SECTION("will distribute round-robin packets evenly between two streams") { RTC::SCTP::StreamScheduler scheduler(Mtu); const TestStream stream1(scheduler, 1, 1); const TestStream stream2(scheduler, 2, 1); const auto packetCounts = getPacketCounts(scheduler, 10); REQUIRE(packetCounts.at(1) == 5); REQUIRE(packetCounts.at(2) == 5); } // Verifies that packet counts are evenly distributed among active streams, // where a stream is suddenly made inactive, two are added, and then the // paused stream is resumed. SECTION("will distribute evenly with paused and added streams") { RTC::SCTP::StreamScheduler scheduler(Mtu); const TestStream stream1(scheduler, 1, 1); TestStream stream2(scheduler, 2, 1); const auto counts1 = getPacketCounts(scheduler, 10); REQUIRE(counts1.at(1) == 5); REQUIRE(counts1.at(2) == 5); stream2.GetStream().MakeInactive(); const TestStream stream3(scheduler, 3, 1); const TestStream stream4(scheduler, 4, 1); const auto counts2 = getPacketCounts(scheduler, 15); REQUIRE(counts2.at(1) == 5); // stream2 is inative, it is not in the map. REQUIRE(!counts2.contains(2)); REQUIRE(counts2.at(3) == 5); REQUIRE(counts2.at(4) == 5); stream2.GetStream().MayMakeActive(); const auto counts3 = getPacketCounts(scheduler, 20); REQUIRE(counts3.at(1) == 5); REQUIRE(counts3.at(2) == 5); REQUIRE(counts3.at(3) == 5); REQUIRE(counts3.at(4) == 5); } // Degrades to fair queuing with streams having identical priority. SECTION("will do fair queuing with same priority") { RTC::SCTP::StreamScheduler scheduler(Mtu); scheduler.EnableMessageInterleaving(true); constexpr size_t SmallPacket{ 30 }; constexpr size_t LargePacket{ 70 }; MockStreamProducer producer1; producer1.PushBytesToSend(SmallPacket); // MayMakeActive(). producer1.PushProduce(createChunk(0, 1, 100, SmallPacket)); producer1.PushBytesToSend(SmallPacket); producer1.PushProduce(createChunk(1, 1, 101, SmallPacket)); producer1.PushBytesToSend(SmallPacket); producer1.PushProduce(createChunk(2, 1, 102, SmallPacket)); producer1.PushBytesToSend(0); auto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 2); stream1->MayMakeActive(); MockStreamProducer producer2; producer2.PushBytesToSend(LargePacket); // MayMakeActive(). producer2.PushProduce(createChunk(3, 2, 200, LargePacket)); producer2.PushBytesToSend(LargePacket); producer2.PushProduce(createChunk(4, 2, 201, LargePacket)); producer2.PushBytesToSend(LargePacket); producer2.PushProduce(createChunk(5, 2, 202, LargePacket)); producer2.PushBytesToSend(0); auto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 2); stream2->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); // t = 30 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); // t = 60 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); // t = 70 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); // t = 90 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); // t = 140 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); // t = 210 REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // Will do weighted fair queuing with three streams having different priority. SECTION("will do weighted fair queuing with same size and different priority") { RTC::SCTP::StreamScheduler scheduler(Mtu); scheduler.EnableMessageInterleaving(true); MockStreamProducer producer1; // Priority 125 -> allowed to produce every 1000/125 ~= 80 time units. producer1.PushBytesToSend(PayloadLength); // MayMakeActive(). producer1.PushProduce(createChunk(0, 1, 100)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(1, 1, 101)); producer1.PushBytesToSend(PayloadLength); producer1.PushProduce(createChunk(2, 1, 102)); producer1.PushBytesToSend(0); auto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 125); stream1->MayMakeActive(); MockStreamProducer producer2; // Priority 200 -> allowed to produce every 1000/200 ~= 50 time units. producer2.PushBytesToSend(PayloadLength); // MayMakeActive(). producer2.PushProduce(createChunk(3, 2, 200)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(4, 2, 201)); producer2.PushBytesToSend(PayloadLength); producer2.PushProduce(createChunk(5, 2, 202)); producer2.PushBytesToSend(0); auto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 200); stream2->MayMakeActive(); MockStreamProducer producer3; // Priority 500 -> allowed to produce every 1000/500 ~= 20 time units. producer3.PushBytesToSend(PayloadLength); // MayMakeActive(). producer3.PushProduce(createChunk(6, 3, 300)); producer3.PushBytesToSend(PayloadLength); producer3.PushProduce(createChunk(7, 3, 301)); producer3.PushBytesToSend(PayloadLength); producer3.PushProduce(createChunk(8, 3, 302)); producer3.PushBytesToSend(0); auto stream3 = scheduler.CreateStream(std::addressof(producer3), 3, 500); stream3->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 300)); // t ~= 20 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 301)); // t ~= 40 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); // t ~= 50 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 302)); // t ~= 60 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); // t ~= 80 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); // t ~= 100 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); // t ~= 150 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); // t ~= 160 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); // t ~= 240 REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // Will do weighted fair queuing with three streams having different priority // and sending different payload sizes. SECTION("will do weighted fair queuing with different size and priority") { RTC::SCTP::StreamScheduler scheduler(Mtu); scheduler.EnableMessageInterleaving(true); constexpr size_t SmallPacket{ 20 }; constexpr size_t MediumPacket{ 50 }; constexpr size_t LargePacket{ 70 }; MockStreamProducer producer1; // Stream with priority = 125 -> inverse weight ~= 80. producer1.PushBytesToSend(MediumPacket); // MayMakeActive(); vft ~ 0 + 50*80 = 4000 producer1.PushProduce(createChunk(0, 1, 100, MediumPacket)); producer1.PushBytesToSend(SmallPacket); // vft ~ 4000 + 20*80 = 5600 producer1.PushProduce(createChunk(1, 1, 101, SmallPacket)); producer1.PushBytesToSend(LargePacket); // vft ~ 5600 + 70*80 = 11200 producer1.PushProduce(createChunk(2, 1, 102, LargePacket)); producer1.PushBytesToSend(0); auto stream1 = scheduler.CreateStream(std::addressof(producer1), 1, 125); stream1->MayMakeActive(); MockStreamProducer producer2; // Stream with priority = 200 -> inverse weight ~= 50. producer2.PushBytesToSend(MediumPacket); // MayMakeActive(); vft ~ 0 + 50*50 = 2500 producer2.PushProduce(createChunk(3, 2, 200, MediumPacket)); producer2.PushBytesToSend(LargePacket); // vft ~ 2500 + 70*50 = 6000 producer2.PushProduce(createChunk(4, 2, 201, LargePacket)); producer2.PushBytesToSend(SmallPacket); // vft ~ 6000 + 20*50 = 7000 producer2.PushProduce(createChunk(5, 2, 202, SmallPacket)); producer2.PushBytesToSend(0); auto stream2 = scheduler.CreateStream(std::addressof(producer2), 2, 200); stream2->MayMakeActive(); MockStreamProducer producer3; // Stream with priority = 500 -> inverse weight ~= 20 producer3.PushBytesToSend(SmallPacket); // MayMakeActive; vft ~ 0 + 20*20 = 400 producer3.PushProduce(createChunk(6, 3, 300, SmallPacket)); producer3.PushBytesToSend(MediumPacket); // vft ~ 400 + 50*20 = 1400 producer3.PushProduce(createChunk(7, 3, 301, MediumPacket)); producer3.PushBytesToSend(LargePacket); // vft ~ 1400 + 70*20 = 2800 producer3.PushProduce(createChunk(8, 3, 302, LargePacket)); producer3.PushBytesToSend(0); auto stream3 = scheduler.CreateStream(std::addressof(producer3), 3, 500); stream3->MayMakeActive(); REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 300)); // t ~= 400 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 301)); // t ~= 1400 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 200)); // t ~= 2500 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 302)); // t ~= 2800 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 100)); // t ~= 4000 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 101)); // t ~= 5600 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 201)); // t ~= 6000 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 202)); // t ~= 7000 REQUIRE(checkDataToSendHasMid(scheduler.Produce(NowMs, Mtu), 102)); // t ~= 11200 REQUIRE(!scheduler.Produce(NowMs, Mtu).has_value()); } // Two streams of different priority, identical packet size: ratio of packets // must match ratio of priorities. SECTION("will distribute WFQ packets in two streams by priority") { RTC::SCTP::StreamScheduler scheduler(Mtu); scheduler.EnableMessageInterleaving(true); const TestStream stream1(scheduler, 1, 100); const TestStream stream2(scheduler, 2, 200); const auto packetCounts = getPacketCounts(scheduler, 15); REQUIRE(packetCounts.at(1) == 5); REQUIRE(packetCounts.at(2) == 10); } SECTION("will distribute WFQ packets in four streams by priority") { RTC::SCTP::StreamScheduler scheduler(Mtu); scheduler.EnableMessageInterleaving(true); const TestStream stream1(scheduler, 1, 100); const TestStream stream2(scheduler, 2, 200); const TestStream stream3(scheduler, 3, 300); const TestStream stream4(scheduler, 4, 400); const auto packetCounts = getPacketCounts(scheduler, 50); REQUIRE(packetCounts.at(1) == 5); REQUIRE(packetCounts.at(2) == 10); REQUIRE(packetCounts.at(3) == 15); REQUIRE(packetCounts.at(4) == 20); } // A simple test with two streams of different priority, but sending packets // of different size. Verifies that the ratio of total packet payload // represents their priority. // // In this example, // - stream1 has priority 100 and sends packets of size 8. // -stream2 has priority 400 and sends packets of size 4. // // With round robin, stream1 would get twice as many payload bytes on the wire // as stream2, but with WFQ and a 4x priority increase, stream2 should 4x as // many payload bytes on the wire. That translates to stream2 getting 8x as // many packets on the wire as they are half as large. SECTION("will distribute from two streams fairly") { RTC::SCTP::StreamScheduler scheduler(Mtu); scheduler.EnableMessageInterleaving(true); const TestStream stream1(scheduler, 1, 100, /*packetLength*/ 8); const TestStream stream2(scheduler, 2, 400, /*packetLength*/ 4); const auto packetCounts = getPacketCounts(scheduler, 90); REQUIRE(packetCounts.at(1) == 10); REQUIRE(packetCounts.at(2) == 80); } } ================================================ FILE: worker/test/src/RTC/TestKeyFrameRequestManager.cpp ================================================ #include "common.hpp" #include "mocks/include/MockShared.hpp" #include "RTC/KeyFrameRequestManager.hpp" #include SCENARIO("KeyFrameRequestManager", "[rtp][keyframe]") { class TestKeyFrameRequestManagerListener : public RTC::KeyFrameRequestManager::Listener { public: void OnKeyFrameNeeded(RTC::KeyFrameRequestManager* /*keyFrameRequestManager */, uint32_t /*ssrc*/) override { this->onKeyFrameNeededTimesCalled++; } void Reset() { this->onKeyFrameNeededTimesCalled = 0; } public: size_t onKeyFrameNeededTimesCalled{ 0 }; }; TestKeyFrameRequestManagerListener listener; mocks::MockShared shared(/*getTimeMs*/ []() { return 1000; }); SECTION("key frame requested once, not received on time") { listener.Reset(); RTC::KeyFrameRequestManager keyFrameRequestManager( std::addressof(listener), std::addressof(shared), 1000); keyFrameRequestManager.KeyFrameNeeded(1111); REQUIRE(listener.onKeyFrameNeededTimesCalled == 1); } SECTION("key frame requested many times, not received on time") { listener.Reset(); RTC::KeyFrameRequestManager keyFrameRequestManager( std::addressof(listener), std::addressof(shared), 500); keyFrameRequestManager.KeyFrameNeeded(1111); keyFrameRequestManager.KeyFrameNeeded(1111); keyFrameRequestManager.KeyFrameNeeded(1111); keyFrameRequestManager.KeyFrameNeeded(1111); REQUIRE(listener.onKeyFrameNeededTimesCalled == 1); } SECTION("key frame is received on time") { listener.Reset(); RTC::KeyFrameRequestManager keyFrameRequestManager( std::addressof(listener), std::addressof(shared), 500); keyFrameRequestManager.KeyFrameNeeded(1111); keyFrameRequestManager.KeyFrameReceived(1111); REQUIRE(listener.onKeyFrameNeededTimesCalled == 1); } SECTION("key frame is forced, no received on time") { listener.Reset(); RTC::KeyFrameRequestManager keyFrameRequestManager( std::addressof(listener), std::addressof(shared), 500); keyFrameRequestManager.KeyFrameNeeded(1111); keyFrameRequestManager.ForceKeyFrameNeeded(1111); REQUIRE(listener.onKeyFrameNeededTimesCalled == 2); } SECTION("key frame is forced, received on time") { listener.Reset(); RTC::KeyFrameRequestManager keyFrameRequestManager( std::addressof(listener), std::addressof(shared), 500); keyFrameRequestManager.KeyFrameNeeded(1111); keyFrameRequestManager.ForceKeyFrameNeeded(1111); keyFrameRequestManager.KeyFrameReceived(1111); REQUIRE(listener.onKeyFrameNeededTimesCalled == 2); } } ================================================ FILE: worker/test/src/RTC/TestNackGenerator.cpp ================================================ #include "common.hpp" #include "mocks/include/MockShared.hpp" #include "test/include/RTC/RTP/rtpCommon.hpp" #include "RTC/NackGenerator.hpp" #include "RTC/RTP/Codecs/PayloadDescriptorHandler.hpp" #include "RTC/RTP/Packet.hpp" #include #include SCENARIO("NACK generator", "[rtp][rtcp][nack]") { constexpr unsigned int SendNackDelay{ 0u }; // In ms. struct TestNackGeneratorInput { TestNackGeneratorInput() = default; TestNackGeneratorInput( uint16_t seq, bool isKeyFrame, uint16_t firstNacked, size_t numNacked, bool keyFrameRequired = false, size_t nackListSize = 0) : seq(seq), isKeyFrame(isKeyFrame), firstNacked(firstNacked), numNacked(numNacked), keyFrameRequired(keyFrameRequired), nackListSize(nackListSize) { } uint16_t seq{ 0 }; bool isKeyFrame{ false }; uint16_t firstNacked{ 0 }; size_t numNacked{ 0 }; bool keyFrameRequired{ false }; size_t nackListSize{ 0 }; }; class TestPayloadDescriptorHandler : public RTC::RTP::Codecs::PayloadDescriptorHandler { public: explicit TestPayloadDescriptorHandler(bool isKeyFrame) : isKeyFrame(isKeyFrame) {}; ~TestPayloadDescriptorHandler() override = default; void Dump(int indentation = 0) const override { } bool Process( RTC::RTP::Codecs::EncodingContext* /*context*/, RTC::RTP::Packet* /*packet*/, bool& /*marker*/) override { return true; } void RtpPacketChanged(RTC::RTP::Packet* packet) override { } std::unique_ptr GetEncoder() const override { return nullptr; } void Encode( RTC::RTP::Packet* /*packet*/, RTC::RTP::Codecs::PayloadDescriptor::Encoder* /*encoder*/) override { } void Restore(RTC::RTP::Packet* /*packet*/) override { } uint8_t GetSpatialLayer() const override { return 0; } uint8_t GetTemporalLayer() const override { return 0; } bool IsKeyFrame() const override { return this->isKeyFrame; } private: bool isKeyFrame{ false }; }; class TestNackGeneratorListener : public RTC::NackGenerator::Listener { void OnNackGeneratorNackRequired(const std::vector& seqNumbers) override { this->nackRequiredTriggered = true; auto it = seqNumbers.begin(); auto firstNacked = *it; auto numNacked = seqNumbers.size(); REQUIRE(this->currentInput.firstNacked == firstNacked); REQUIRE(this->currentInput.numNacked == numNacked); }; void OnNackGeneratorKeyFrameRequired() override { this->keyFrameRequiredTriggered = true; REQUIRE(this->currentInput.keyFrameRequired); } public: void Reset(TestNackGeneratorInput& input) { this->currentInput = input; this->nackRequiredTriggered = false; this->keyFrameRequiredTriggered = false; } void Check(RTC::NackGenerator& nackGenerator) { REQUIRE(this->nackRequiredTriggered == static_cast(this->currentInput.numNacked)); REQUIRE(this->keyFrameRequiredTriggered == this->currentInput.keyFrameRequired); } private: TestNackGeneratorInput currentInput{}; bool nackRequiredTriggered{ false }; bool keyFrameRequiredTriggered{ false }; }; mocks::MockShared shared(/*getTimeMs*/ []() { return 1000; }); auto validate = [&shared](std::unique_ptr& packet, std::vector& inputs) { TestNackGeneratorListener listener; auto nackGenerator = RTC::NackGenerator(std::addressof(listener), std::addressof(shared), SendNackDelay); for (auto input : inputs) { listener.Reset(input); auto* tpdh = new TestPayloadDescriptorHandler(input.isKeyFrame); packet->SetPayloadDescriptorHandler(tpdh); packet->SetSequenceNumber(input.seq); nackGenerator.ReceivePacket(packet.get(), /*isRecovered*/ false); listener.Check(nackGenerator); } }; // clang-format off alignas(4) uint8_t rtpBuffer[] = { 0x80, 0x7b, 0x52, 0x0e, 0x5b, 0x6b, 0xca, 0xb5, 0x00, 0x00, 0x00, 0x02 }; // clang-format on // [pt:123, seq:21006, timestamp:1533790901] std::unique_ptr packet{ RTC::RTP::Packet::Parse(rtpBuffer, sizeof(rtpBuffer)) }; packet->Serialize(rtpCommon::SerializeBuffer, sizeof(rtpCommon::SerializeBuffer)); SECTION("no NACKs required") { // clang-format off std::vector inputs = { { 2371, false, 0, 0, false, 0 }, { 2372, false, 0, 0, false, 0 }, { 2373, false, 0, 0, false, 0 }, { 2374, false, 0, 0, false, 0 }, { 2375, false, 0, 0, false, 0 }, { 2376, false, 0, 0, false, 0 }, { 2377, false, 0, 0, false, 0 }, { 2378, false, 0, 0, false, 0 }, { 2379, false, 0, 0, false, 0 }, { 2380, false, 0, 0, false, 0 }, { 2254, false, 0, 0, false, 0 }, { 2250, false, 0, 0, false, 0 }, }; // clang-format on validate(packet, inputs); } SECTION("generate NACK for missing ordered packet") { // clang-format off std::vector inputs = { { 2381, false, 0, 0, false, 0 }, { 2383, false, 2382, 1, false, 1 } }; // clang-format on validate(packet, inputs); } SECTION("sequence wrap generates no NACK") { // clang-format off std::vector inputs = { { 65534, false, 0, 0, false, 0 }, { 65535, false, 0, 0, false, 0 }, { 0, false, 0, 0, false, 0 } }; // clang-format on validate(packet, inputs); } SECTION("generate NACK after sequence wrap") { // clang-format off std::vector inputs = { { 65534, false, 0, 0, false, 0 }, { 65535, false, 0, 0, false, 0 }, { 1, false, 0, 1, false, 1 } }; // clang-format on validate(packet, inputs); } SECTION("generate NACK after sequence wrap, and yet another NACK") { // clang-format off std::vector inputs = { { 65534, false, 0, 0, false, 0 }, { 65535, false, 0, 0, false, 0 }, { 1, false, 0, 1, false, 1 }, { 11, false, 2, 9, false, 10 }, { 12, true, 0, 0, false, 10 }, { 13, true, 0, 0, false, 0 } }; // clang-format on validate(packet, inputs); } SECTION("intercalated missing packets") { // clang-format off std::vector inputs = { { 1, false, 0, 0, false, 0 }, { 3, false, 2, 1, false, 1 }, { 5, false, 4, 1, false, 2 }, { 7, false, 6, 1, false, 3 }, { 9, false, 8, 1, false, 4 } }; // clang-format on validate(packet, inputs); } SECTION("non contiguous intercalated missing packets") { // clang-format off std::vector inputs = { { 1, false, 0, 0, false, 0 }, { 3, false, 2, 1, false, 1 }, { 7, false, 4, 3, false, 4 }, { 9, false, 8, 1, false, 5 } }; // clang-format on validate(packet, inputs); } SECTION("big jump") { // clang-format off std::vector inputs = { { 1, false, 0, 0, false, 0 }, { 300, false, 2, 298, false, 298 }, { 3, false, 0, 0, false, 297 }, { 4, false, 0, 0, false, 296 }, { 5, false, 0, 0, false, 295 } }; // clang-format on validate(packet, inputs); } SECTION("Key Frame required. Nack list too large to be requested") { // clang-format off std::vector inputs = { { 1, false, 0, 0, false, 0 }, { 3000, false, 0, 0, true, 0 } }; // clang-format on validate(packet, inputs); } } ================================================ FILE: worker/test/src/RTC/TestRateCalculator.cpp ================================================ #include "common.hpp" #include "RTC/RateCalculator.hpp" #include #include // std::numeric_limits #include SCENARIO("RateCalculator", "[rate-calculator]") { struct TestRateCalculatorData { int64_t offset; uint32_t size; uint32_t rate; }; auto validate = [](RTC::RateCalculator& rate, uint64_t timeBaseMs, std::vector& input) { for (auto& item : input) { rate.Update(item.size, timeBaseMs + item.offset); REQUIRE(rate.GetRate(timeBaseMs + item.offset) == item.rate); } // Repeat forcing nowMs to be 0. rate.Reset(); for (auto& item : input) { rate.Update(item.size, timeBaseMs + item.offset); REQUIRE(rate.GetRate(0 + item.offset) == item.rate); } // Repeat forcing nowMs to be std::numeric_limits::max() - 100. rate.Reset(); for (auto& item : input) { rate.Update(item.size, timeBaseMs + item.offset); REQUIRE(rate.GetRate(std::numeric_limits::max() - 100 + item.offset) == item.rate); } }; const uint64_t nowMs = 12345678; SECTION("receive single item per 1000 ms") { RTC::RateCalculator rate; // clang-format off std::vector input = { { 0, 5, 40 } }; // clang-format on validate(rate, nowMs, input); } SECTION("receive multiple items per 1000 ms") { RTC::RateCalculator rate; // clang-format off std::vector input = { { 0, 5, 40 }, { 100, 2, 56 }, { 300, 2, 72 }, { 999, 4, 104 } }; // clang-format on validate(rate, nowMs, input); } SECTION("receive item every 1000 ms") { RTC::RateCalculator rate(1000, 8000, 100); // clang-format off std::vector input = { { 0, 5, 40 }, { 1000, 5, 40 }, { 2000, 5, 40 } }; // clang-format on validate(rate, nowMs, input); } SECTION("slide") { RTC::RateCalculator rate(1000, 8000, 1000); // clang-format off std::vector input = { { 0, 5, 40 }, { 999, 2, 56 }, { 1001, 1, 24 }, { 1001, 1, 32 }, { 2000, 1, 24 } }; // clang-format on validate(rate, nowMs, input); REQUIRE(rate.GetRate(nowMs + 3001) == 0); } SECTION("slide with 100 items") { RTC::RateCalculator rate(1000, 8000, 100); // clang-format off std::vector input = { { 0, 5, 40 }, { 999, 2, 56 }, { 1001, 1, 24 }, // merged inside 999 { 1001, 1, 32 }, // merged inside 999 { 2000, 1, 8 } // it will erase the item with timestamp=999, // removing also the next two samples. // The end estimation will include only the last sample. }; // clang-format on validate(rate, nowMs, input); REQUIRE(rate.GetRate(nowMs + 3001) == 0); } SECTION("wrap") { // window: 1000ms, items: 5 (granularity: 200ms) RTC::RateCalculator rate(1000, 8000, 5); // clang-format off std::vector input = { { 1000, 1, 1*8 }, { 1200, 1, 1*8 + 1*8 }, { 1400, 1, 1*8 + 2*8 }, { 1600, 1, 1*8 + 3*8 }, { 1800, 1, 1*8 + 4*8 }, { 2000, 1, 1*8 + (5-1)*8 }, // starts wrap here { 2200, 1, 1*8 + (6-2)*8 }, { 2400, 1, 1*8 + (7-3)*8 }, { 2600, 1, 1*8 + (8-4)*8 }, { 2800, 1, 1*8 + (9-5)*8 }, }; // clang-format on validate(rate, nowMs, input); } // NOTE: This test reproduces a crash (now fixed): // https://github.com/versatica/mediasoup/issues/1316 SECTION("buffer overflow should not crash") { // window: 1000ms, items: 3 (granularity: 333ms) RTC::RateCalculator rate(1000, 8000, 3); // clang-format off std::vector input = { { 0, 1, 8 }, { 333, 1, 16 }, { 666, 1, 24 }, { 999, 1, 32 }, }; // clang-format on validate(rate, nowMs, input); } } ================================================ FILE: worker/test/src/RTC/TestRtpEncodingParameters.cpp ================================================ #include "common.hpp" #include #include #include SCENARIO("parseScalabilityMode()") { static const std::regex ScalabilityModeRegex( "^[LS]([1-9]\\d{0,1})T([1-9]\\d{0,1})(_KEY)?.*", std::regex_constants::ECMAScript); struct ScalabilityMode { uint8_t spatialLayers = 1; uint8_t temporalLayers = 1; bool ksvc = false; }; auto parseScalabilityMode = [](const std::string& scalabilityMode) { struct ScalabilityMode result; std::smatch match; std::regex_match(scalabilityMode, match, ScalabilityModeRegex); if (!match.empty()) { try { result.spatialLayers = std::stoul(match[1].str()); result.temporalLayers = std::stoul(match[2].str()); result.ksvc = match.size() >= 4 && match[3].str() == "_KEY"; } catch (std::exception& error) // NOLINT(bugprone-empty-catch) { } } return result; }; SECTION("parse L1T3") { const auto scalabilityMode = parseScalabilityMode("L1T3"); REQUIRE(scalabilityMode.spatialLayers == 1); REQUIRE(scalabilityMode.temporalLayers == 3); REQUIRE(scalabilityMode.ksvc == false); } SECTION("parse S1T3") { const auto scalabilityMode = parseScalabilityMode("S1T3"); REQUIRE(scalabilityMode.spatialLayers == 1); REQUIRE(scalabilityMode.temporalLayers == 3); REQUIRE(scalabilityMode.ksvc == false); } SECTION("parse L3T2_KEY") { const auto scalabilityMode = parseScalabilityMode("L3T2_KEY"); REQUIRE(scalabilityMode.spatialLayers == 3); REQUIRE(scalabilityMode.temporalLayers == 2); REQUIRE(scalabilityMode.ksvc == true); } SECTION("parse S2T3") { const auto scalabilityMode = parseScalabilityMode("S2T3"); REQUIRE(scalabilityMode.spatialLayers == 2); REQUIRE(scalabilityMode.temporalLayers == 3); REQUIRE(scalabilityMode.ksvc == false); } SECTION("parse foo") { const auto scalabilityMode = parseScalabilityMode("foo"); REQUIRE(scalabilityMode.spatialLayers == 1); REQUIRE(scalabilityMode.temporalLayers == 1); REQUIRE(scalabilityMode.ksvc == false); } SECTION("parse ''") { const auto scalabilityMode = parseScalabilityMode(""); REQUIRE(scalabilityMode.spatialLayers == 1); REQUIRE(scalabilityMode.temporalLayers == 1); REQUIRE(scalabilityMode.ksvc == false); } SECTION("parse S0T3") { const auto scalabilityMode = parseScalabilityMode("S0T3"); REQUIRE(scalabilityMode.spatialLayers == 1); REQUIRE(scalabilityMode.temporalLayers == 1); REQUIRE(scalabilityMode.ksvc == false); } SECTION("parse S1T0") { const auto scalabilityMode = parseScalabilityMode("S1T0"); REQUIRE(scalabilityMode.spatialLayers == 1); REQUIRE(scalabilityMode.temporalLayers == 1); REQUIRE(scalabilityMode.ksvc == false); } SECTION("parse S20T3") { const auto scalabilityMode = parseScalabilityMode("S20T3"); REQUIRE(scalabilityMode.spatialLayers == 20); REQUIRE(scalabilityMode.temporalLayers == 3); REQUIRE(scalabilityMode.ksvc == false); } SECTION("parse S200T3") { const auto scalabilityMode = parseScalabilityMode("S200T3"); REQUIRE(scalabilityMode.spatialLayers == 1); REQUIRE(scalabilityMode.temporalLayers == 1); REQUIRE(scalabilityMode.ksvc == false); } } ================================================ FILE: worker/test/src/RTC/TestSeqManager.cpp ================================================ #include "common.hpp" #include "RTC/SeqManager.hpp" #include #include // std::numeric_limits #include #include namespace { template struct TestSeqManagerInput { TestSeqManagerInput(T input, T output, bool sync = false, bool drop = false, int64_t maxInput = -1) : input(input), output(output), sync(sync), drop(drop), maxInput(maxInput) { } T input{ 0 }; T output{ 0 }; bool sync{ false }; bool drop{ false }; int64_t maxInput{ -1 }; }; template std::pair validate(RTC::SeqManager seqManager, std::vector>& inputs) { for (auto& element : inputs) { if (element.sync) { seqManager.Sync(element.input - 1); } if (element.drop) { seqManager.Drop(element.input); } else { T output; seqManager.Input(element.input, output); if (output != element.output) { return std::make_pair(output, element.output); } if (element.maxInput != -1) { if (element.maxInput != seqManager.GetMaxInput()) { return std::make_pair(element.maxInput, seqManager.GetMaxInput()); } } } } // Success, return a pair of zeros for successful comparison. return std::make_pair(0, 0); } } // namespace SCENARIO("SeqManager", "[seqmanager]") { constexpr uint16_t MaxNumberFor15Bits = (1 << 15) - 1; SECTION("0 is greater than 65000") { REQUIRE(RTC::SeqManager::IsSeqHigherThan(0, 65000) == true); } SECTION("0 is greater than 32500 in range 15") { REQUIRE(RTC::SeqManager::IsSeqHigherThan(0, 32500) == true); } SECTION("receive ordered numbers, no sync, no drop") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 1, false, false }, { 2, 2, false, false }, { 3, 3, false, false }, { 4, 4, false, false }, { 5, 5, false, false }, { 6, 6, false, false }, { 7, 7, false, false }, { 8, 8, false, false }, { 9, 9, false, false }, { 10, 10, false, false }, { 11, 11, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive ordered numbers, sync, no drop") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 1, false, false }, { 2, 2, false, false }, { 80, 3, true, false }, { 81, 4, false, false }, { 82, 5, false, false }, { 83, 6, false, false }, { 84, 7, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive ordered numbers, sync, drop") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 1, false, false }, { 2, 2, false, false }, { 3, 3, false, false }, { 4, 4, true, false }, // sync. { 5, 5, false, false }, { 6, 6, false, false }, { 7, 7, true, false }, // sync. { 8, 0, false, true }, // drop. { 9, 8, false, false }, { 11, 0, false, true }, // drop. { 10, 9, false, false }, { 12, 10, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive ordered wrapped numbers") { // clang-format off std::vector> inputs = { { 65533, 65533, false, false }, { 65534, 65534, false, false }, { 65535, 65535, false, false }, { 0, 0, false, false }, { 1, 1, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive sequence numbers with a big jump") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 1, false, false }, { 1000, 1000, false, false }, { 1001, 1001, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive out of order numbers with a big jump") { // clang-format off std::vector> inputs = { { 4, 4, false, false }, { 3, 3, false, false }, { 65535, 65535, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive mixed numbers with a big jump, drop before jump") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 0, false, true }, // drop. { 100, 99, false, false }, { 100, 99, false, false }, { 103, 0, false, true }, // drop. { 101, 100, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive mixed numbers with a big jump, drop after jump") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 1, false, false }, { 100, 0, false, true }, // drop. { 103, 0, false, true }, // drop. { 101, 100, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("drop, receive numbers newer and older than the one dropped") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 2, 0, false, true }, // drop. { 3, 2, false, false }, { 4, 3, false, false }, { 1, 1, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive mixed numbers, sync, drop") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 1, false, false }, { 2, 2, false, false }, { 3, 3, false, false }, { 7, 7, false, false }, { 6, 0, false, true }, // drop. { 8, 8, false, false }, { 10, 10, false, false }, { 9, 9, false, false }, { 11, 11, false, false }, { 0, 12, true, false }, // sync. { 2, 14, false, false }, { 3, 15, false, false }, { 4, 16, false, false }, { 5, 17, false, false }, { 6, 18, false, false }, { 7, 19, false, false }, { 8, 20, false, false }, { 9, 21, false, false }, { 10, 22, false, false }, { 9, 0, false, true }, // drop. { 61, 23, true, false }, // sync. { 62, 24, false, false }, { 63, 25, false, false }, { 64, 26, false, false }, { 65, 27, false, false }, { 11, 28, true, false }, // sync. { 12, 29, false, false }, { 13, 30, false, false }, { 14, 31, false, false }, { 15, 32, false, false }, { 1, 33, true, false }, // sync. { 2, 34, false, false }, { 3, 35, false, false }, { 4, 36, false, false }, { 5, 37, false, false }, { 65533, 38, true, false }, // sync. { 65534, 39, false, false }, { 65535, 40, false, false }, { 0, 41, true, false }, // sync. { 1, 42, false, false }, { 3, 0, false, true }, // drop. { 4, 44, false, false }, { 5, 45, false, false }, { 6, 46, false, false }, { 7, 47, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive ordered numbers, sync, no drop, increase input") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 1, false, false }, { 2, 2, false, false }, { 80, 3, true, false }, { 81, 4, false, false }, { 82, 5, false, false }, { 83, 6, false, false }, { 84, 7, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("drop many inputs at the beginning (using uint16_t)") { // clang-format off std::vector> inputs = { { 1, 1, false, false }, { 2, 0, false, true }, // drop. { 3, 0, false, true }, // drop. { 4, 0, false, true }, // drop. { 5, 0, false, true }, // drop. { 6, 0, false, true }, // drop. { 7, 0, false, true }, // drop. { 8, 0, false, true }, // drop. { 9, 0, false, true }, // drop. { 120, 112, false, false }, { 121, 113, false, false }, { 122, 114, false, false }, { 123, 115, false, false }, { 124, 116, false, false }, { 125, 117, false, false }, { 126, 118, false, false }, { 127, 119, false, false }, { 128, 120, false, false }, { 129, 121, false, false }, { 130, 122, false, false }, { 131, 123, false, false }, { 132, 124, false, false }, { 133, 125, false, false }, { 134, 126, false, false }, { 135, 127, false, false }, { 136, 128, false, false }, { 137, 129, false, false }, { 138, 130, false, false }, { 139, 131, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("drop many inputs at the beginning (using uint8_t)") { // clang-format off std::vector> inputs = { { 1, 1, false, false }, { 2, 0, false, true }, // drop. { 3, 0, false, true }, // drop. { 4, 0, false, true }, // drop. { 5, 0, false, true }, // drop. { 6, 0, false, true }, // drop. { 7, 0, false, true }, // drop. { 8, 0, false, true }, // drop. { 9, 0, false, true }, // drop. { 120, 112, false, false }, { 121, 113, false, false }, { 122, 114, false, false }, { 123, 115, false, false }, { 124, 116, false, false }, { 125, 117, false, false }, { 126, 118, false, false }, { 127, 119, false, false }, { 128, 120, false, false }, { 129, 121, false, false }, { 130, 122, false, false }, { 131, 123, false, false }, { 132, 124, false, false }, { 133, 125, false, false }, { 134, 126, false, false }, { 135, 127, false, false }, { 136, 128, false, false }, { 137, 129, false, false }, { 138, 130, false, false }, { 139, 131, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive mixed numbers, sync, drop in range 15") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 1, false, false }, { 2, 2, false, false }, { 3, 3, false, false }, { 7, 7, false, false }, { 6, 0, false, true }, // drop. { 8, 8, false, false }, { 10, 10, false, false }, { 9, 9, false, false }, { 11, 11, false, false }, { 0, 12, true, false }, // sync. { 2, 14, false, false }, { 3, 15, false, false }, { 4, 16, false, false }, { 5, 17, false, false }, { 6, 18, false, false }, { 7, 19, false, false }, { 8, 20, false, false }, { 9, 21, false, false }, { 10, 22, false, false }, { 9, 0, false, true }, // drop. { 61, 23, true, false }, // sync. { 62, 24, false, false }, { 63, 25, false, false }, { 64, 26, false, false }, { 65, 27, false, false }, { 11, 28, true, false }, // sync. { 12, 29, false, false }, { 13, 30, false, false }, { 14, 31, false, false }, { 15, 32, false, false }, { 1, 33, true, false }, // sync. { 2, 34, false, false }, { 3, 35, false, false }, { 4, 36, false, false }, { 5, 37, false, false }, { 32767, 38, true, false }, // sync. { 32768, 39, false, false }, { 32769, 40, false, false }, { 0, 41, true, false }, // sync. { 1, 42, false, false }, { 3, 0, false, true }, // drop. { 4, 44, false, false }, { 5, 45, false, false }, { 6, 46, false, false }, { 7, 47, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("drop many inputs at the beginning (using uint16_t with high values)") { // clang-format off std::vector> inputs = { { 1, 1, false, false }, { 2, 0, false, true }, // drop. { 3, 0, false, true }, // drop. { 4, 0, false, true }, // drop. { 5, 0, false, true }, // drop. { 6, 0, false, true }, // drop. { 7, 0, false, true }, // drop. { 8, 0, false, true }, // drop. { 9, 0, false, true }, // drop. { 32768, 32760, false, false }, { 32769, 32761, false, false }, { 32770, 32762, false, false }, { 32771, 32763, false, false }, { 32772, 32764, false, false }, { 32773, 32765, false, false }, { 32774, 32766, false, false }, { 32775, 32767, false, false }, { 32776, 32768, false, false }, { 32777, 32769, false, false }, { 32778, 32770, false, false }, { 32779, 32771, false, false }, { 32780, 32772, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("sync and drop some input near max-value") { // clang-format off std::vector> inputs = { { 65530, 1, true, false }, { 65531, 2, false, false }, { 65532, 3, false, false }, { 65533, 0, false, true }, { 65534, 0, false, true }, { 65535, 4, false, false }, { 0, 5, false, false }, { 1, 6, false, false }, { 2, 7, false, false }, { 3, 8, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("drop many inputs at the beginning (using uint16_t range 15 with high values)") { // clang-format off std::vector> inputs = { { 1, 1, false, false }, { 2, 0, false, true }, // drop. { 3, 0, false, true }, // drop. { 4, 0, false, true }, // drop. { 5, 0, false, true }, // drop. { 6, 0, false, true }, // drop. { 7, 0, false, true }, // drop. { 8, 0, false, true }, // drop. { 9, 0, false, true }, // drop. { 16384, 16376, false, false }, { 16385, 16377, false, false }, { 16386, 16378, false, false }, { 16387, 16379, false, false }, { 16388, 16380, false, false }, { 16389, 16381, false, false }, { 16390, 16382, false, false }, { 16391, 16383, false, false }, { 16392, 16384, false, false }, { 16393, 16385, false, false }, { 16394, 16386, false, false }, { 16395, 16387, false, false }, { 16396, 16388, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("sync and drop some input near max-value in a 15bit range") { // clang-format off std::vector> inputs = { { 32762, 1, true, false, 32762 }, { 32763, 2, false, false, 32763 }, { 32764, 3, false, false, 32764 }, { 32765, 0, false, true, 32765 }, { 32766, 0, false, true, 32766 }, { 32767, 4, false, false, 32767 }, { 0, 5, false, false, 0 }, { 1, 6, false, false, 1 }, { 2, 7, false, false, 2 }, { 3, 8, false, false, 3 } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("should update all values during multiple roll overs") { // clang-format off std::vector> inputs = { { 0, 1, true, false, 0 }, }; for (uint16_t j = 0; j < 3; ++j) { for (uint16_t i = 1; i < std::numeric_limits::max(); ++i) { const uint16_t output = i + 1; inputs.emplace_back( i, output, false, false, i ); } } // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("should update all values during multiple roll overs (15 bits range)") { // clang-format off std::vector> inputs = { { 0, 1, true, false, 0, }, }; for (uint16_t j = 0; j < 3; ++j) { for (uint16_t i = 1; i < MaxNumberFor15Bits; ++i) { const uint16_t output = i + 1; inputs.emplace_back( i, output, false, false, i ); } } // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("should produce same output for same old input before drop (15 bits range)") { // clang-format off std::vector> inputs = { { 10, 1, true, false }, // sync. { 11, 2, false, false }, { 12, 3, false, false }, { 13, 4, false, false }, { 14, 0, false, true }, // drop. { 15, 5, false, false }, { 12, 3, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("should properly clean previous cycle drops") { // clang-format off std::vector> inputs = { { 1, 1, false, false }, { 2, 0, false, true }, // Drop. { 3, 2, false, false }, { 4, 3, false, false }, { 5, 4, false, false }, { 6, 5, false, false }, { 7, 6, false, false }, { 0, 7, false, false }, { 1, 0, false, false }, { 2, 1, false, false }, { 3, 2, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("dropped inputs to be removed going out of range, 1.") { // clang-format off std::vector> inputs = { { 36964, 36964, false, false }, { 25923, 0, false, true }, // Drop. { 25701, 25701, false, false }, { 17170, 0, false, true }, // Drop. { 25923, 25923, false, false }, { 4728, 0, false, true }, // Drop. { 17170, 17170, false, false }, { 30738, 0, false, true }, // Drop. { 4728, 4728, false, false }, { 4806, 0, false, true }, // Drop. { 30738, 30738, false, false }, { 50886, 0, false, true }, // Drop. { 4806, 4805, false, false }, // Previously dropped. { 50774, 0, false, true }, // Drop. { 50886, 4805, false, false }, // Previously dropped. { 22136, 0, false, true }, // Drop. { 50774, 50773, false, false }, { 30910, 0, false, true }, // Drop. { 22136, 50773, false, false }, // Previously dropped. { 48862, 0, false, true }, // Drop. { 30910, 30909, false, false }, { 56832, 0, false, true }, // Drop. { 48862, 48861, false, false }, { 2, 0, false, true }, // Drop. { 56832, 48861, false, false }, // Previously dropped. { 530, 0, false, true }, // Drop. { 2, 48861, false, false }, // Previously dropped. }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("dropped inputs to be removed go out of range, 2.") { // clang-format off std::vector> inputs = { { 36960, 36960, false, false }, { 3328, 0, false, true }, // Drop. { 24589, 24588, false, false }, { 120, 0, false, true }, // Drop. { 3328, 24588, false, false }, // Previously dropped. { 30848, 0, false, true }, // Drop. { 120, 120, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("dropped inputs to be removed go out of range, 3.") { // clang-format off std::vector> inputs = { { 36964, 36964, false, false }, { 65396 , 0, false, true }, // Drop. { 25855, 25854, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive ordered numbers, no sync, no drop (with initial output)") { // clang-format off std::vector> inputs = { { 0, 1000, false, false }, { 1, 1001, false, false }, { 2, 1002, false, false }, { 3, 1003, false, false }, { 4, 1004, false, false }, { 5, 1005, false, false }, { 6, 1006, false, false }, { 7, 1007, false, false }, { 8, 1008, false, false }, { 9, 1009, false, false }, { 10, 1010, false, false }, { 11, 1011, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 1000u }, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{ /*initialOutput*/ 1000u }, inputs); REQUIRE(result.first == result.second); } SECTION("receive ordered numbers, sync, no drop (with initial output)") { // clang-format off std::vector> inputs = { { 0, 2000, false, false }, { 1, 2001, false, false }, { 2, 2002, false, false }, { 80, 2003, true, false }, { 81, 2004, false, false }, { 82, 2005, false, false }, { 83, 2006, false, false }, { 84, 2007, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 2000u }, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{ /*initialOutput*/ 2000u }, inputs); REQUIRE(result.first == result.second); } SECTION("receive ordered numbers, sync, drop (with initial output)") { // clang-format off std::vector> inputs = { { 0, 3000, false, false }, { 1, 3001, false, false }, { 2, 3002, false, false }, { 3, 3003, false, false }, { 4, 3004, true, false }, // sync. { 5, 3005, false, false }, { 6, 3006, false, false }, { 7, 3007, true, false }, // sync. { 8, 3000, false, true }, // drop. { 9, 3008, false, false }, { 11, 3000, false, true }, // drop. { 10, 3009, false, false }, { 12, 3010, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 3000u }, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{ /*initialOutput*/ 3000u }, inputs); REQUIRE(result.first == result.second); } SECTION("receive ordered wrapped numbers (with initial output)") { // clang-format off std::vector> inputs = { { 65533, 997, false, false }, { 65534, 998, false, false }, { 65535, 999, false, false }, { 0, 1000, false, false }, { 1, 1001, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 1000u }, inputs); REQUIRE(result.first == result.second); } SECTION("receive sequence numbers with a big jump (with initial output)") { // clang-format off std::vector> inputs1 = { { 0, 32000, false, false }, { 1, 32001, false, false }, { 1000, 33000, false, false }, { 1001, 33001, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 32000u }, inputs1); REQUIRE(result.first == result.second); // clang-format off std::vector> inputs2 = { { 0, 32000, false, false }, { 1, 32001, false, false }, { 1000, 232, false, false }, { 1001, 233, false, false } }; // clang-format on result = validate(RTC::SeqManager{ /*initialOutput*/ 32000u }, inputs2); REQUIRE(result.first == result.second); } SECTION("receive out of order numbers with a big jump (with initial output)") { // clang-format off std::vector> inputs = { { 4, 1004, false, false }, { 3, 1003, false, false }, { 65535, 999, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 1000u }, inputs); REQUIRE(result.first == result.second); } SECTION("receive mixed numbers with a big jump, drop before jump (with initial output)") { // clang-format off std::vector> inputs = { { 0, 1000, false, false }, { 1, 1000, false, true }, // drop. { 100, 1099, false, false }, { 100, 1099, false, false }, { 103, 1000, false, true }, // drop. { 101, 1100, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 1000u }, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{ /*initialOutput*/ 1000u }, inputs); REQUIRE(result.first == result.second); } SECTION("receive mixed numbers with a big jump, drop after jump (with initial output)") { // clang-format off std::vector> inputs = { { 0, 2000, false, false }, { 1, 2001, false, false }, { 100, 2000, false, true }, // drop. { 103, 2000, false, true }, // drop. { 101, 2100, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 2000u }, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{ /*initialOutput*/ 2000u }, inputs); REQUIRE(result.first == result.second); } SECTION("drop, receive numbers newer and older than the one dropped (with initial output)") { // clang-format off std::vector> inputs = { { 0, 2000, false, false }, { 2, 2000, false, true }, // drop. { 3, 2002, false, false }, { 4, 2003, false, false }, { 1, 2001, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 2000u }, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{ /*initialOutput*/ 2000u }, inputs); REQUIRE(result.first == result.second); } SECTION("receive mixed numbers, sync, drop (with initial output)") { // clang-format off std::vector> inputs = { { 0, 10000, false, false }, { 1, 10001, false, false }, { 2, 10002, false, false }, { 3, 10003, false, false }, { 7, 10007, false, false }, { 6, 10000, false, true }, // drop. { 8, 10008, false, false }, { 10, 10010, false, false }, { 9, 10009, false, false }, { 11, 10011, false, false }, { 0, 10012, true, false }, // sync. { 2, 10014, false, false }, { 3, 10015, false, false }, { 4, 10016, false, false }, { 5, 10017, false, false }, { 6, 10018, false, false }, { 7, 10019, false, false }, { 8, 10020, false, false }, { 9, 10021, false, false }, { 10, 10022, false, false }, { 9, 10000, false, true }, // drop. { 61, 10023, true, false }, // sync. { 62, 10024, false, false }, { 63, 10025, false, false }, { 64, 10026, false, false }, { 65, 10027, false, false }, { 11, 10028, true, false }, // sync. { 12, 10029, false, false }, { 13, 10030, false, false }, { 14, 10031, false, false }, { 15, 10032, false, false }, { 1, 10033, true, false }, // sync. { 2, 10034, false, false }, { 3, 10035, false, false }, { 4, 10036, false, false }, { 5, 10037, false, false }, { 65533, 10038, true, false }, // sync. { 65534, 10039, false, false }, { 65535, 10040, false, false }, { 0, 10041, true, false }, // sync. { 1, 10042, false, false }, { 3, 10000, false, true }, // drop. { 4, 10044, false, false }, { 5, 10045, false, false }, { 6, 10046, false, false }, { 7, 10047, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 10000u }, inputs); REQUIRE(result.first == result.second); } SECTION("receive ordered numbers, sync, no drop, increase input (with initial output)") { // clang-format off std::vector> inputs = { { 0, 1, false, false }, { 1, 2, false, false }, { 2, 3, false, false }, { 80, 4, true, false }, { 81, 5, false, false }, { 82, 6, false, false }, { 83, 7, false, false }, { 84, 8, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 1u }, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{ /*initialOutput*/ 1u }, inputs); REQUIRE(result.first == result.second); } SECTION("drop many inputs at the beginning (using uint16_t) (with initial output)") { // clang-format off std::vector> inputs = { { 1, 1001, false, false }, { 2, 1000, false, true }, // drop. { 3, 1000, false, true }, // drop. { 4, 1000, false, true }, // drop. { 5, 1000, false, true }, // drop. { 6, 1000, false, true }, // drop. { 7, 1000, false, true }, // drop. { 8, 1000, false, true }, // drop. { 9, 1000, false, true }, // drop. { 120, 1112, false, false }, { 121, 1113, false, false }, { 122, 1114, false, false }, { 123, 1115, false, false }, { 124, 1116, false, false }, { 125, 1117, false, false }, { 126, 1118, false, false }, { 127, 1119, false, false }, { 128, 1120, false, false }, { 129, 1121, false, false }, { 130, 1122, false, false }, { 131, 1123, false, false }, { 132, 1124, false, false }, { 133, 1125, false, false }, { 134, 1126, false, false }, { 135, 1127, false, false }, { 136, 1128, false, false }, { 137, 1129, false, false }, { 138, 1130, false, false }, { 139, 1131, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 1000u }, inputs); REQUIRE(result.first == result.second); result = validate(RTC::SeqManager{ /*initialOutput*/ 1000u }, inputs); REQUIRE(result.first == result.second); } SECTION("drop many inputs at the beginning (using uint8_t) (with initial output)") { // clang-format off std::vector> inputs = { { 1, 201, false, false }, { 2, 200, false, true }, // drop. { 3, 200, false, true }, // drop. { 4, 200, false, true }, // drop. { 5, 200, false, true }, // drop. { 6, 200, false, true }, // drop. { 7, 200, false, true }, // drop. { 8, 200, false, true }, // drop. { 9, 200, false, true }, // drop. { 120, 56, false, false }, { 121, 57, false, false }, { 122, 58, false, false }, { 123, 59, false, false }, { 124, 60, false, false }, { 125, 61, false, false }, { 126, 62, false, false }, { 127, 63, false, false }, { 128, 64, false, false }, { 129, 65, false, false }, { 130, 66, false, false }, { 131, 67, false, false }, { 132, 68, false, false }, { 133, 69, false, false }, { 134, 70, false, false }, { 135, 71, false, false }, { 136, 72, false, false }, { 137, 73, false, false }, { 138, 74, false, false }, { 139, 75, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 200u }, inputs); REQUIRE(result.first == result.second); } SECTION("receive mixed numbers, sync, drop in range 15 (with initial output)") { // clang-format off std::vector> inputs = { { 0, 100, false, false }, { 1, 101, false, false }, { 2, 102, false, false }, { 3, 103, false, false }, { 7, 107, false, false }, { 6, 100, false, true }, // drop. { 8, 108, false, false }, { 10, 110, false, false }, { 9, 109, false, false }, { 11, 111, false, false }, { 0, 112, true, false }, // sync. { 2, 114, false, false }, { 3, 115, false, false }, { 4, 116, false, false }, { 5, 117, false, false }, { 6, 118, false, false }, { 7, 119, false, false }, { 8, 120, false, false }, { 9, 121, false, false }, { 10, 122, false, false }, { 9, 100, false, true }, // drop. { 61, 123, true, false }, // sync. { 62, 124, false, false }, { 63, 125, false, false }, { 64, 126, false, false }, { 65, 127, false, false }, { 11, 128, true, false }, // sync. { 12, 129, false, false }, { 13, 130, false, false }, { 14, 131, false, false }, { 15, 132, false, false }, { 1, 133, true, false }, // sync. { 2, 134, false, false }, { 3, 135, false, false }, { 4, 136, false, false }, { 5, 137, false, false }, { 32767, 138, true, false }, // sync. { 32768, 139, false, false }, { 32769, 140, false, false }, { 0, 141, true, false }, // sync. { 1, 142, false, false }, { 3, 100, false, true }, // drop. { 4, 144, false, false }, { 5, 145, false, false }, { 6, 146, false, false }, { 7, 147, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 100u }, inputs); REQUIRE(result.first == result.second); } SECTION("drop many inputs at the beginning (using uint16_t with high values) (with initial output)") { // clang-format off std::vector> inputs = { { 1, 201, false, false }, { 2, 200, false, true }, // drop. { 3, 200, false, true }, // drop. { 4, 200, false, true }, // drop. { 5, 200, false, true }, // drop. { 6, 200, false, true }, // drop. { 7, 200, false, true }, // drop. { 8, 200, false, true }, // drop. { 9, 200, false, true }, // drop. { 32768, 32960, false, false }, { 32769, 32961, false, false }, { 32770, 32962, false, false }, { 32771, 32963, false, false }, { 32772, 32964, false, false }, { 32773, 32965, false, false }, { 32774, 32966, false, false }, { 32775, 32967, false, false }, { 32776, 32968, false, false }, { 32777, 32969, false, false }, { 32778, 32970, false, false }, { 32779, 32971, false, false }, { 32780, 32972, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 200u }, inputs); REQUIRE(result.first == result.second); } SECTION("sync and drop some input near max-value (with initial output)") { // clang-format off std::vector> inputs = { { 65530, 201, true, false }, { 65531, 202, false, false }, { 65532, 203, false, false }, { 65533, 200, false, true }, { 65534, 200, false, true }, { 65535, 204, false, false }, { 0, 205, false, false }, { 1, 206, false, false }, { 2, 207, false, false }, { 3, 208, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 200u }, inputs); REQUIRE(result.first == result.second); } SECTION( "drop many inputs at the beginning (using uint16_t range 15 with high values) (with initial output)") { // clang-format off std::vector> inputs = { { 1, 101, false, false }, { 2, 100, false, true }, // drop. { 3, 100, false, true }, // drop. { 4, 100, false, true }, // drop. { 5, 100, false, true }, // drop. { 6, 100, false, true }, // drop. { 7, 100, false, true }, // drop. { 8, 100, false, true }, // drop. { 9, 100, false, true }, // drop. { 16384, 16476, false, false }, { 16385, 16477, false, false }, { 16386, 16478, false, false }, { 16387, 16479, false, false }, { 16388, 16480, false, false }, { 16389, 16481, false, false }, { 16390, 16482, false, false }, { 16391, 16483, false, false }, { 16392, 16484, false, false }, { 16393, 16485, false, false }, { 16394, 16486, false, false }, { 16395, 16487, false, false }, { 16396, 16488, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 100u }, inputs); REQUIRE(result.first == result.second); } SECTION("sync and drop some input near max-value in a 15bit range (with initial output)") { // clang-format off std::vector> inputs = { { 32762, 101, true, false, 32762 }, { 32763, 102, false, false, 32763 }, { 32764, 103, false, false, 32764 }, { 32765, 100, false, true, 32765 }, { 32766, 100, false, true, 32766 }, { 32767, 104, false, false, 32767 }, { 0, 105, false, false, 0 }, { 1, 106, false, false, 1 }, { 2, 107, false, false, 2 }, { 3, 108, false, false, 3 } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 100u }, inputs); REQUIRE(result.first == result.second); } SECTION("should update all values during multiple roll overs (with initial output)") { // clang-format off std::vector> inputs = { { 0, 101, true, false, 0 }, }; for (uint16_t j = 0; j < 3; ++j) { for (uint16_t i = 1; i < std::numeric_limits::max(); ++i) { const uint16_t output = (i + 1 + 100) & std::numeric_limits::max(); inputs.emplace_back( i, output, false, false, i ); } } // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 100u }, inputs); REQUIRE(result.first == result.second); } SECTION("should update all values during multiple roll overs (15 bits range) (with initial output)") { // clang-format off std::vector> inputs = { { 0, 101, true, false, 0, }, }; for (uint16_t j = 0; j < 3; ++j) { for (uint16_t i = 1; i < MaxNumberFor15Bits; ++i) { const uint16_t output = (i + 1 + 100) & MaxNumberFor15Bits; inputs.emplace_back( i, output, false, false, i ); } } // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 100u }, inputs); REQUIRE(result.first == result.second); } SECTION( "should produce same output for same old input before drop (15 bits range) (with initial output)") { // clang-format off std::vector> inputs = { { 10, 10001, true, false }, // sync. { 11, 10002, false, false }, { 12, 10003, false, false }, { 13, 10004, false, false }, { 14, 10000, false, true }, // drop. { 15, 10005, false, false }, { 12, 10003, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 10000u }, inputs); REQUIRE(result.first == result.second); } SECTION("should properly clean previous cycle drops (with initial output)") { // clang-format off std::vector> inputs = { { 1, 3, false, false }, { 2, 2, false, true }, // Drop. { 3, 4, false, false }, { 4, 5, false, false }, { 5, 6, false, false }, { 6, 7, false, false }, { 7, 0, false, false }, { 0, 1, false, false }, { 1, 2, false, false }, { 2, 3, false, false }, { 3, 4, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 2u }, inputs); REQUIRE(result.first == result.second); } SECTION("dropped inputs to be removed going out of range, 1. (with initial output)") { // clang-format off std::vector> inputs = { { 36964, 46964, false, false }, { 25923, 10000, false, true }, // Drop. { 25701, 35701, false, false }, { 17170, 10000, false, true }, // Drop. { 25923, 35923, false, false }, { 4728, 10000, false, true }, // Drop. { 17170, 27170, false, false }, { 30738, 10000, false, true }, // Drop. { 4728, 14728, false, false }, { 4806, 10000, false, true }, // Drop. { 30738, 40738, false, false }, { 50886, 10000, false, true }, // Drop. { 4806, 14805, false, false }, // Previously dropped. { 50774, 10000, false, true }, // Drop. { 50886, 14805, false, false }, // Previously dropped. { 22136, 10000, false, true }, // Drop. { 50774, 60773, false, false }, { 30910, 10000, false, true }, // Drop. { 22136, 60773, false, false }, // Previously dropped. { 48862, 10000, false, true }, // Drop. { 30910, 40909, false, false }, { 56832, 10000, false, true }, // Drop. { 48862, 58861, false, false }, { 2, 10000, false, true }, // Drop. { 56832, 58861, false, false }, // Previously dropped. { 530, 10000, false, true }, // Drop. { 2, 58861, false, false }, // Previously dropped. }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 10000u }, inputs); REQUIRE(result.first == result.second); } SECTION("dropped inputs to be removed go out of range, 2. (with initial output)") { // clang-format off std::vector> inputs = { { 36960, 37060, false, false }, { 3328, 100, false, true }, // Drop. { 24589, 24688, false, false }, { 120, 100, false, true }, // Drop. { 3328, 24688, false, false }, // Previously dropped. { 30848, 100, false, true }, // Drop. { 120, 220, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 100u }, inputs); REQUIRE(result.first == result.second); } SECTION("dropped inputs to be removed go out of range, 3. (with initial output)") { // clang-format off std::vector> inputs = { { 36964, 37964, false, false }, { 65396 , 1000, false, true }, // Drop. { 25855, 26854, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{ /*initialOutput*/ 1000u }, inputs); REQUIRE(result.first == result.second); } // https://github.com/versatica/mediasoup/issues/1615 SECTION("receive dropped inputs out of order") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 2, 0, false, true }, { 1, 0, false, true }, { 3, 1, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive dropped inputs out of order, 2") { // clang-format off std::vector> inputs = { { 0, 0, false, false }, { 1, 1, false, false }, { 2, 2, false, false }, { 4, 4, false, false }, { 5, 5, false, false }, { 6, 0, false, true }, { 7, 0, false, true }, { 8, 0, false, true }, { 3, 0, false, true }, { 9, 0, false, true }, { 10, 6, false, false } }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive dropped inputs out of order, 3") { // clang-format off std::vector> inputs = { { 1, 1, false, false }, { 2, 2, false, false }, { 3, 3, false, false }, { 4, 4, false, false }, { 5, 0, false, true }, { 6, 0, false, true }, { 7, 0, false, true }, { 8, 0, false, true }, { 0, 0, false, true }, { 9, 5, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } SECTION("receive dropped inputs out of order, 4") { // clang-format off std::vector> inputs = { { 1, 1, false, false }, { 2, 2, false, false }, { 3, 3, false, false }, { 4, 4, false, false }, { 5, 5, false, false }, { 6, 6, false, false }, { 7, 7, false, false }, { 8, 8, false, false }, { 9, 9, false, false }, { 10, 10, false, false }, { 11, 11, false, false }, { 12, 12, false, false }, { 13, 13, false, false }, { 14, 14, false, false }, { 15, 15, false, false }, { 16, 16, false, false }, { 17, 17, false, false }, { 18, 18, false, false }, { 19, 19, false, false }, { 21, 21, false, false }, { 22, 22, false, false }, { 23, 23, false, false }, { 24, 24, false, false }, { 25, 25, false, false }, { 26, 26, false, false }, { 27, 0, false, true }, { 28, 0, false, true }, { 29, 0, false, true }, { 20, 20, false, false }, { 30, 0, false, true }, { 31, 0, false, true }, { 32, 0, false, true }, { 33, 0, false, true }, { 34, 0, false, true }, { 35, 0, false, true }, { 36, 0, false, true }, { 38, 0, false, true }, { 39, 0, false, true }, { 40, 0, false, true }, { 41, 0, false, true }, { 42, 0, false, true }, { 44, 0, false, true }, { 45, 0, false, true }, { 46, 0, false, true }, { 47, 29, false, false }, { 48, 30, false, false }, { 49, 31, false, false }, { 51, 33, false, false }, { 52, 34, false, false }, { 53, 35, false, false }, { 54, 36, false, false }, { 55, 0, false, true }, { 56, 0, false, true }, { 57, 0, false, true }, { 58, 0, false, true }, { 59, 0, false, true }, { 37, 0, false, true }, { 60, 0, false, true }, { 61, 0, false, true }, { 62, 0, false, true }, { 63, 0, false, true }, { 64, 0, false, true }, { 43, 0, false, true }, { 50, 32, false, false }, }; // clang-format on auto result = validate(RTC::SeqManager{}, inputs); REQUIRE(result.first == result.second); } } ================================================ FILE: worker/test/src/RTC/TestSimpleConsumer.cpp ================================================ #include "flatbuffers/buffer.h" #include "mocks/include/MockShared.hpp" #include "FBS/rtpParameters.h" #include "FBS/transport.h" #include "RTC/RTP/Packet.hpp" #include "RTC/RTP/RtpStream.hpp" #include "RTC/RTP/RtpStreamRecv.hpp" #include "RTC/RTP/SharedPacket.hpp" #include "RTC/RtpDictionaries.hpp" #include "RTC/SimpleConsumer.hpp" #include namespace { // NOLINTBEGIN(readability-identifier-naming) const uint8_t payloadType = 111; mocks::MockShared shared(/*getTimeMs*/ []() { return 1000; }); // NOLINTEND(readability-identifier-naming) class RtpStreamRecvListener : public RTC::RTP::RtpStreamRecv::Listener { public: void OnRtpStreamScore( RTC::RTP::RtpStream* /*rtpStream*/, uint8_t /*score*/, uint8_t /*previousScore*/) override { } void OnRtpStreamSendRtcpPacket(RTC::RTP::RtpStreamRecv* rtpStream, RTC::RTCP::Packet* packet) override { } void OnRtpStreamNeedWorstRemoteFractionLost( RTC::RTP::RtpStreamRecv* /*rtpStream*/, uint8_t& /*worstRemoteFractionLost*/) override { } }; class ConsumerListener : public RTC::Consumer::Listener { void OnConsumerSendRtpPacket(RTC::Consumer* /*consumer*/, RTC::RTP::Packet* packet) final { this->sent.push_back(packet->GetSequenceNumber()); }; void OnConsumerRetransmitRtpPacket(RTC::Consumer* consumer, RTC::RTP::Packet* packet) final { } void OnConsumerKeyFrameRequested(RTC::Consumer* consumer, uint32_t mappedSsrc) final {}; void OnConsumerNeedBitrateChange(RTC::Consumer* consumer) final {}; void OnConsumerNeedZeroBitrate(RTC::Consumer* consumer) final {}; void OnConsumerProducerClosed(RTC::Consumer* consumer) final {}; public: // Verifies that the given number of packets have been sent, // and that the sequence numbers are consecutive. void Verify(size_t size) { REQUIRE(this->sent.size() == size); if (this->sent.size() <= 1) { return; } auto currentSeq = this->sent[0]; for (auto it = std::next(this->sent.begin()); it != this->sent.end(); ++it) { REQUIRE(*it == currentSeq + 1); currentSeq = *it; } } private: std::vector sent; }; flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> createRtpEncodingParameters( flatbuffers::FlatBufferBuilder& builder) { std::vector> encodings; auto encoding = RTC::RtpEncodingParameters(); encoding.ssrc = 1234567890; encodings.emplace_back(encoding.FillBuffer(builder)); return builder.CreateVector(encodings); }; flatbuffers::Offset createRtpParameters( flatbuffers::FlatBufferBuilder& builder) { auto rtpParameters = RTC::RtpParameters(); auto codec = RTC::RtpCodecParameters(); auto encoding = RTC::RtpEncodingParameters(); codec.mimeType.SetMimeType("audio/opus"); codec.payloadType = payloadType; encoding.ssrc = 1234567890; rtpParameters.mid = "mid"; rtpParameters.codecs.emplace_back(codec); rtpParameters.encodings.emplace_back(encoding); rtpParameters.headerExtensions = std::vector(); return rtpParameters.FillBuffer(builder); }; std::unique_ptr createConsumer(ConsumerListener* listener) { flatbuffers::FlatBufferBuilder bufferBuilder; auto consumerId = bufferBuilder.CreateString("consumerId"); auto producerId = bufferBuilder.CreateString("producerId"); auto rtpParameters = createRtpParameters(bufferBuilder); auto consumableEncodings = createRtpEncodingParameters(bufferBuilder); auto consumeRequestBuilder = FBS::Transport::ConsumeRequestBuilder(bufferBuilder); consumeRequestBuilder.add_consumerId(consumerId); consumeRequestBuilder.add_producerId(producerId); consumeRequestBuilder.add_kind(FBS::RtpParameters::MediaKind::AUDIO); consumeRequestBuilder.add_rtpParameters(rtpParameters); consumeRequestBuilder.add_type(FBS::RtpParameters::Type::SIMPLE); consumeRequestBuilder.add_consumableRtpEncodings(consumableEncodings); consumeRequestBuilder.add_paused(false); consumeRequestBuilder.add_preferredLayers(0); consumeRequestBuilder.add_ignoreDtx(false); auto offset = consumeRequestBuilder.Finish(); bufferBuilder.Finish(offset); auto* buf = bufferBuilder.GetBufferPointer(); const auto* consumeRequest = flatbuffers::GetRoot(buf); return std::make_unique( std::addressof(shared), consumeRequest->consumerId()->str(), consumeRequest->producerId()->str(), listener, consumeRequest); } std::unique_ptr createRtpStreamRecv() { RtpStreamRecvListener streamRecvListener; RTC::RTP::RtpStream::Params params; return std::make_unique( &streamRecvListener, std::addressof(shared), params, 0u, false); } /** * Centralize common setup and helper methods. */ class Fixture { public: Fixture() : listener(std::make_unique()), consumer(createConsumer(listener.get())), rtpStream(createRtpStreamRecv()) { // NOTE: This must be static because the Consumer stores the given vector // pointer which is supposed to exist in the associated Producer (but here // there is no associated Producer). const std::vector scores{ 10 }; consumer->ProducerRtpStreamScores(&scores); // NOTE: mappedSsrc here MUST be 1234567890 (otherwise Consumer will crash). // This is guaranteed by Producer class, however here we must do it manually. consumer->ProducerNewRtpStream(rtpStream.get(), 1234567890); } std::unique_ptr listener; std::unique_ptr consumer; std::unique_ptr rtpStream; }; } // namespace SCENARIO("SimpleConsumer", "[rtp][consumer]") { // TODO: We should NOT parse RTP packets for tests anymore. We should use // RTC::RTP::Packet::Factory() instead. // clang-format off alignas(4) uint8_t buffer[] = { 0x80, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x04, 0x49, 0x96, 0x02, 0xD2, // SSRC: 1234567890 (must be this exact value). // Payload (4 bytes). 0xFF, 0xFF, 0xFF, 0xFF, // From here this is just buffer enough for the variable length payload so // when cloning the packet it doesn't read non allocated memory. 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, }; // clang-format on // This is the size of the original packet. const size_t originalPacketLength{ 16 }; SECTION("RTP packets are not forwarded when the consumer is not active") { Fixture fixture; auto* packet = RTC::RTP::Packet::Parse(buffer, originalPacketLength + 64); RTC::RTP::SharedPacket sharedPacket(packet); packet->SetPayloadType(payloadType); fixture.consumer->SendRtpPacket(packet, sharedPacket); fixture.listener->Verify(0); delete packet; } SECTION("RTP packets are not forwarded for unsupported payload types") { Fixture fixture; // Indicate that the transport is connected in order to activate the consumer. dynamic_cast(fixture.consumer.get())->TransportConnected(); auto* packet = RTC::RTP::Packet::Parse(buffer, originalPacketLength + 64); RTC::RTP::SharedPacket sharedPacket(packet); packet->SetPayloadType(payloadType + 1); fixture.consumer->SendRtpPacket(packet, sharedPacket); fixture.listener->Verify(0); delete packet; } SECTION("RTP packets with empty payload are not forwarded") { Fixture fixture; // Indicate that the transport is connected in order to activate the consumer. dynamic_cast(fixture.consumer.get())->TransportConnected(); auto* packet = RTC::RTP::Packet::Parse(buffer, originalPacketLength + 0); RTC::RTP::SharedPacket sharedPacket(packet); packet->SetPayloadType(payloadType + 1); fixture.consumer->SendRtpPacket(packet, sharedPacket); fixture.listener->Verify(0); delete packet; } SECTION("outgoing RTP packets are forwarded with increased sequence number") { Fixture fixture; // Indicate that the transport is connected in order to activate the consumer. dynamic_cast(fixture.consumer.get())->TransportConnected(); auto* packet = RTC::RTP::Packet::Parse(buffer, originalPacketLength + 64); RTC::RTP::SharedPacket sharedPacket(packet); uint16_t seq{ 1 }; packet->SetSequenceNumber(seq++); packet->SetPayloadType(payloadType); sharedPacket.Assign(packet); fixture.consumer->SendRtpPacket(packet, sharedPacket); packet->SetSequenceNumber(seq++); sharedPacket.Assign(packet); fixture.consumer->SendRtpPacket(packet, sharedPacket); packet->SetSequenceNumber(seq++); sharedPacket.Assign(packet); fixture.consumer->SendRtpPacket(packet, sharedPacket); packet->SetSequenceNumber(seq++); // Remove the payload so it won't be sent. packet->RemovePayload(); sharedPacket.Assign(packet); fixture.consumer->SendRtpPacket(packet, sharedPacket); fixture.listener->Verify(3); delete packet; } } ================================================ FILE: worker/test/src/RTC/TestTransportCongestionControlServer.cpp ================================================ #include "common.hpp" #include "mocks/include/MockShared.hpp" #include "RTC/Consts.hpp" #include "RTC/RTP/HeaderExtensionIds.hpp" #include "RTC/RTP/Packet.hpp" #include "RTC/TransportCongestionControlServer.hpp" #include #include #include SCENARIO("TransportCongestionControlServer", "[rtp]") { struct TestTransportCongestionControlServerInput { uint16_t wideSeqNumber; uint64_t nowMs; }; struct TestTransportCongestionControlServerResult { uint16_t wideSeqNumber; bool received; uint64_t timestamp; }; using TestResults = std::deque>; class TestTransportCongestionControlServerListener : public RTC::TransportCongestionControlServer::Listener { public: virtual void OnTransportCongestionControlServerSendRtcpPacket( RTC::TransportCongestionControlServer* tccServer, RTC::RTCP::Packet* packet) override { auto* tccPacket = dynamic_cast(packet); if (!tccPacket) { return; } auto packetResults = tccPacket->GetPacketResults(); REQUIRE(!this->results.empty()); auto testResults = this->results.front(); this->results.pop_front(); REQUIRE(testResults.size() == packetResults.size()); auto packetResultIt = packetResults.begin(); auto testResultIt = testResults.begin(); for (; packetResultIt != packetResults.end() && testResultIt != testResults.end(); ++packetResultIt, ++testResultIt) { REQUIRE(packetResultIt->sequenceNumber == testResultIt->wideSeqNumber); REQUIRE(packetResultIt->received == testResultIt->received); if (packetResultIt->received) { REQUIRE(packetResultIt->receivedAtMs == static_cast(testResultIt->timestamp)); } } } public: void SetResults(TestResults& results) { this->results = results; } void Check() { REQUIRE(this->results.empty()); } private: TestResults results; }; mocks::MockShared shared(/*getTimeMs*/ []() { return 1000; }); // clang-format off alignas(4) uint8_t buffer[] = { 0x90, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0xbe, 0xde, 0x00, 0x01, // Header Extensions 0x51, 0x60, 0xee, 0x00 // TCC Feedback }; // clang-format on auto validate = [&buffer, &shared](std::vector& inputs, TestResults& results) { TestTransportCongestionControlServerListener listener; auto tccServer = RTC::TransportCongestionControlServer( std::addressof(listener), std::addressof(shared), RTC::BweType::TRANSPORT_CC, RTC::Consts::MtuSize); tccServer.SetMaxIncomingBitrate(150000); tccServer.TransportConnected(); std::unique_ptr packet{ RTC::RTP::Packet::Parse(buffer, sizeof(buffer)) }; RTC::RTP::HeaderExtensionIds headerExtensionIds{}; headerExtensionIds.transportWideCc01 = 5; packet->AssignExtensionIds(headerExtensionIds); packet->SetSequenceNumber(1); // Save results. listener.SetResults(results); uint64_t startTs = inputs[0].nowMs; uint64_t TransportCcFeedbackSendInterval{ 100u }; // In ms. for (auto input : inputs) { // Periodic sending TCC packets. uint64_t diffTs = input.nowMs - startTs; if (diffTs >= TransportCcFeedbackSendInterval) { tccServer.FillAndSendTransportCcFeedback(); startTs = input.nowMs; } packet->UpdateTransportWideCc01(input.wideSeqNumber); tccServer.IncomingPacket(input.nowMs, packet.get()); } tccServer.FillAndSendTransportCcFeedback(); listener.Check(); }; SECTION("normal time and sequence") { // clang-format off std::vector inputs { { 1u, 1000u }, { 2u, 1050u }, { 3u, 1100u }, { 4u, 1150u }, { 5u, 1200u }, }; TestResults results { { { 1u, true, 1000u }, { 2u, true, 1050u }, }, { { 3u, true, 1100u }, { 4u, true, 1150u }, }, { { 5u, true, 1200u }, }, }; // clang-format on validate(inputs, results); } SECTION("lost packets") { // clang-format off std::vector inputs { { 1u, 1000u }, { 3u, 1050u }, { 5u, 1100u }, { 6u, 1150u }, }; TestResults results { { { 1u, true, 1000u }, { 2u, false, 0u }, { 3u, true, 1050u }, }, { { 4u, false, 0u }, { 5u, true, 1100u }, { 6u, true, 1150u }, }, }; // clang-format on validate(inputs, results); } SECTION("duplicate packets") { // clang-format off std::vector inputs { { 1u, 1000u }, { 1u, 1050u }, { 2u, 1100u }, { 3u, 1150u }, { 3u, 1200u }, { 4u, 1250u }, }; TestResults results { { { 1u, true, 1000u }, }, { { 2u, true, 1100u }, { 3u, true, 1150u }, }, { { 4u, true, 1250u }, }, }; // clang-format on validate(inputs, results); } SECTION("packets arrive out of order") { // clang-format off std::vector inputs { { 1u, 1000u }, { 2u, 1050u }, { 4u, 1100u }, { 5u, 1150u }, { 3u, 1200u }, // Out of order { 6u, 1250u }, }; TestResults results { { { 1u, true, 1000u }, { 2u, true, 1050u }, }, { { 3u, false, 0u }, { 4u, true, 1100u }, { 5u, true, 1150u }, }, { { 3u, true, 1200u }, { 4u, true, 1100u }, { 5u, true, 1150u }, { 6u, true, 1250u }, }, }; // clang-format on validate(inputs, results); } } ================================================ FILE: worker/test/src/RTC/TestTransportTuple.cpp ================================================ #include "common.hpp" #include "RTC/Transport.hpp" #include "RTC/TransportTuple.hpp" #include "RTC/UdpSocket.hpp" #include #include SCENARIO("TransportTuple", "[transport-tuple]") { class UdpSocketListener : public RTC::UdpSocket::Listener { public: void OnUdpSocketPacketReceived( RTC::UdpSocket* /*socket*/, const uint8_t* /*data*/, size_t /*len*/, size_t /*bufferLen*/, const struct sockaddr* /*remoteAddr*/) override { } }; auto makeUdpSocket = [](const std::string& ip, uint16_t minPort, uint16_t maxPort) { UdpSocketListener listener; auto flags = RTC::Transport::SocketFlags{ .ipv6Only = false, .udpReusePort = false }; uint64_t portRangeHash{ 0u }; auto* udpSocket = new RTC::UdpSocket( std::addressof(listener), const_cast(ip), minPort, maxPort, flags, portRangeHash); return std::unique_ptr(udpSocket); }; auto makeUdpSockAddr = [](int family, const std::string& ip, uint16_t port) { if (family == AF_INET) { auto addr = std::make_unique(); addr->sin_family = AF_INET; addr->sin_port = htons(port); if (uv_inet_pton(AF_INET, ip.c_str(), std::addressof(addr->sin_addr)) != 0) { throw std::runtime_error("invalid IPv4 address"); } return std::unique_ptr(reinterpret_cast(addr.release())); } else if (family == AF_INET6) { auto addr6 = std::make_unique(); addr6->sin6_family = AF_INET6; addr6->sin6_port = htons(port); if (uv_inet_pton(AF_INET6, ip.c_str(), std::addressof(addr6->sin6_addr)) != 0) { throw std::runtime_error("invalid IPv6 address"); } return std::unique_ptr(reinterpret_cast(addr6.release())); } else { throw std::runtime_error("invalid network family"); } }; SECTION("2 tuples with same local and remote IP:port have the same hash") { auto udpSocket = makeUdpSocket("0.0.0.0", 10000, 50000); auto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, "1.2.3.4", 1234); auto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, "1.2.3.4", 1234); RTC::TransportTuple udpTuple1(udpSocket.get(), udpRemoteAddr1.get()); RTC::TransportTuple udpTuple2(udpSocket.get(), udpRemoteAddr2.get()); REQUIRE(udpTuple1.hash == udpTuple2.hash); } SECTION( "2 tuples with same local IP:port, same remote IP and different remote port have different hashes") { auto udpSocket = makeUdpSocket("0.0.0.0", 10000, 50000); auto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, "1.2.3.4", 10001); auto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, "1.2.3.4", 10002); RTC::TransportTuple udpTuple1(udpSocket.get(), udpRemoteAddr1.get()); RTC::TransportTuple udpTuple2(udpSocket.get(), udpRemoteAddr2.get()); REQUIRE(udpTuple1.hash != udpTuple2.hash); for (uint16_t remotePort{ 1 }; remotePort < 65535; ++remotePort) { // SKip if same port as the one in udpRemoteAddr1. if (remotePort == 10001) { continue; } auto udpRemoteAddr3 = makeUdpSockAddr(AF_INET, "1.2.3.4", remotePort); RTC::TransportTuple udpTuple3(udpSocket.get(), udpRemoteAddr3.get()); REQUIRE(udpTuple1.hash != udpTuple3.hash); } } SECTION( "2 tuples with same local IP:port, different remote IP and same remote port have different hashes") { auto udpSocket = makeUdpSocket("0.0.0.0", 10000, 50000); auto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, "1.2.3.4", 10001); auto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, "1.2.3.5", 10001); RTC::TransportTuple udpTuple1(udpSocket.get(), udpRemoteAddr1.get()); RTC::TransportTuple udpTuple2(udpSocket.get(), udpRemoteAddr2.get()); REQUIRE(udpTuple1.hash != udpTuple2.hash); } SECTION( "2 tuples with same remote IP:port, same local IP and different local port have different hashes") { auto udpSocket1 = makeUdpSocket("0.0.0.0", 10000, 20000); auto udpSocket2 = makeUdpSocket("0.0.0.0", 30000, 40000); auto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, "5.4.3.2", 22222); auto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, "5.4.3.2", 22222); RTC::TransportTuple udpTuple1(udpSocket1.get(), udpRemoteAddr1.get()); RTC::TransportTuple udpTuple2(udpSocket2.get(), udpRemoteAddr2.get()); REQUIRE(udpTuple1.hash != udpTuple2.hash); } SECTION( "2 tuples with same local IP:port, same remote IP and different remote port have different hashes") { auto udpSocket = makeUdpSocket("0.0.0.0", 10000, 50000); auto udpRemoteAddr1 = makeUdpSockAddr(AF_INET, "1.2.3.4", 40001); auto udpRemoteAddr2 = makeUdpSockAddr(AF_INET, "1.2.3.4", 40002); RTC::TransportTuple udpTuple1(udpSocket.get(), udpRemoteAddr1.get()); RTC::TransportTuple udpTuple2(udpSocket.get(), udpRemoteAddr2.get()); REQUIRE(udpTuple1.hash != udpTuple2.hash); } } ================================================ FILE: worker/test/src/RTC/TestTrendCalculator.cpp ================================================ #include "common.hpp" #include "RTC/TrendCalculator.hpp" #include SCENARIO("TrendCalculator") { SECTION("trend values after same elapsed time match") { RTC::TrendCalculator trendA; RTC::TrendCalculator trendB; trendA.Update(1000u, 0u); trendB.Update(1000u, 0u); REQUIRE(trendA.GetValue() == 1000u); REQUIRE(trendB.GetValue() == 1000u); trendA.Update(200u, 500u); trendA.Update(200u, 1000u); trendB.Update(200u, 1000u); REQUIRE(trendA.GetValue() == trendB.GetValue()); trendA.Update(200u, 2000u); trendA.Update(200u, 4000u); trendB.Update(200u, 4000u); REQUIRE(trendA.GetValue() == trendB.GetValue()); trendA.Update(2000u, 5000u); trendB.Update(2000u, 5000u); REQUIRE(trendA.GetValue() == 2000u); REQUIRE(trendB.GetValue() == 2000u); trendA.ForceUpdate(0u, 5500u); trendB.ForceUpdate(100u, 5000u); REQUIRE(trendA.GetValue() == 0u); REQUIRE(trendB.GetValue() == 100u); } } ================================================ FILE: worker/test/src/Utils/TestBits.cpp ================================================ #include "common.hpp" #include "Utils.hpp" #include SCENARIO("Utils::Bits", "[utils][bits]") { SECTION("CountSetBits()") { uint16_t mask; mask = 0b0000000000000000; REQUIRE(Utils::Bits::CountSetBits(mask) == 0); mask = 0b0000000000000001; REQUIRE(Utils::Bits::CountSetBits(mask) == 1); mask = 0b1000000000000001; REQUIRE(Utils::Bits::CountSetBits(mask) == 2); mask = 0b1111111111111111; REQUIRE(Utils::Bits::CountSetBits(mask) == 16); } } ================================================ FILE: worker/test/src/Utils/TestByte.cpp ================================================ #include "common.hpp" #include "Utils.hpp" #include SCENARIO("Utils::Byte", "[utils][byte]") { // NOTE: The setup and teardown are implicit in how Catch2 works, meaning that // this buffer is initialized before each SECTION below. // Docs: https://github.com/catchorg/Catch2/blob/devel/docs/tutorial.md#test-cases-and-sections // clang-format off uint8_t buffer[] = { 0b00000000, 0b00000001, 0b00000010, 0b00000011, 0b10000000, 0b01000000, 0b00100000, 0b00010000, 0b01111111, 0b11111111, 0b11111111, 0b00000000, 0b11111111, 0b11111111, 0b11111111, 0b00000000, 0b10000000, 0b00000000, 0b00000000, 0b00000000 }; // clang-format on SECTION("Get3Bytes()") { // Bytes 4,5 and 6 in the array are number 8405024. REQUIRE(Utils::Byte::Get3Bytes(buffer, 4) == 8405024); } SECTION("Set3Bytes()") { Utils::Byte::Set3Bytes(buffer, 4, 5666777); REQUIRE(Utils::Byte::Get3Bytes(buffer, 4) == 5666777); } SECTION("Get3BytesSigned()") { // Bytes 8, 9 and 10 in the array are number 8388607 since first bit is 0 and // all other bits are 1, so it must be maximum positive 24 bits signed integer, // which is Math.pow(2, 23) - 1 = 8388607. REQUIRE(Utils::Byte::Get3BytesSigned(buffer, 8) == 8388607); // Bytes 12, 13 and 14 in the array are number -1. REQUIRE(Utils::Byte::Get3BytesSigned(buffer, 12) == -1); // Bytes 16, 17 and 18 in the array are number -8388608 since first bit is 1 // and all other bits are 0, so it must be minimum negative 24 bits signed // integer, which is -1 * Math.pow(2, 23) = -8388608. REQUIRE(Utils::Byte::Get3BytesSigned(buffer, 16) == -8388608); } SECTION("Set3BytesSigned()") { Utils::Byte::Set3BytesSigned(buffer, 0, 8388607); REQUIRE(Utils::Byte::Get3BytesSigned(buffer, 0) == 8388607); Utils::Byte::Set3BytesSigned(buffer, 0, -1); REQUIRE(Utils::Byte::Get3BytesSigned(buffer, 0) == -1); Utils::Byte::Set3BytesSigned(buffer, 0, -8388608); REQUIRE(Utils::Byte::Get3BytesSigned(buffer, 0) == -8388608); } SECTION("IsPaddedTo4Bytes()") { REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 4u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 252u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint8_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 4u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 252u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 256u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 65532u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint16_t{ 65535u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 4u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 252u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 256u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 65532u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 65535u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967288u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 4294967292u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint32_t{ 4294967295u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 4u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 252u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 256u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 65532u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 65535u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967288u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 4294967292u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 4294967295u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 18446744073709551608u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 18446744073709551612u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(uint64_t{ 18446744073709551615u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 4u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 252u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 256u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 65532u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 65535u }) == false); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 4294967288u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 4294967292u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 4294967295u }) == false); // Check if size_t in current host is 64 bits. Otherwise the test would fail. #if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 18446744073709551608u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 18446744073709551612u }) == true); REQUIRE(Utils::Byte::IsPaddedTo4Bytes(size_t{ 18446744073709551615u }) == false); #endif } SECTION("IsPaddedTo8Bytes()") { REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 4u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 252u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint8_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 4u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 252u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 256u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 65532u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint16_t{ 65535u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 252u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 256u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 65532u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 65535u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967288u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967292u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint32_t{ 4294967295u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 4u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 252u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 256u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 65532u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 65535u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 4294967288u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 4294967292u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 4294967295u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 18446744073709551608u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 18446744073709551612u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(uint64_t{ 18446744073709551615u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 0u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 1u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 2u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 3u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 4u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 5u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 8u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 9u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 252u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 255u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 256u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 65532u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 65535u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 4294967288u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 4294967292u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 4294967295u }) == false); // Check if size_t in current host is 64 bits. Otherwise the test would fail. #if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 18446744073709551608u }) == true); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 18446744073709551612u }) == false); REQUIRE(Utils::Byte::IsPaddedTo8Bytes(size_t{ 18446744073709551615u }) == false); #endif } SECTION("PadTo4Bytes()") { REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 1u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 2u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 3u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 9u }) == 12u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 15u }) == 16u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 254u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(uint8_t{ 255u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 1u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 2u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 3u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 9u }) == 12u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 15u }) == 16u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 254u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 255u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 65532u }) == 65532u); REQUIRE(Utils::Byte::PadTo4Bytes(uint16_t{ 65535u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 1u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 2u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 3u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 9u }) == 12u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 15u }) == 16u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 254u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 255u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 65532u }) == 65532u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 65535u }) == 65536u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 4294967288u }) == 4294967288u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 4294967292u }) == 4294967292u); REQUIRE(Utils::Byte::PadTo4Bytes(uint32_t{ 4294967295u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 1u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 2u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 3u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 9u }) == 12u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 15u }) == 16u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 254u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 255u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 65532u }) == 65532u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 65535u }) == 65536u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 4294967288u }) == 4294967288u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 4294967292u }) == 4294967292u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 4294967295u }) == 4294967296u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 18446744073709551608u }) == 18446744073709551608u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 18446744073709551612u }) == 18446744073709551612u); REQUIRE(Utils::Byte::PadTo4Bytes(uint64_t{ 18446744073709551615u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 1u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 2u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 3u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 9u }) == 12u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 15u }) == 16u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 254u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 255u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 65532u }) == 65532u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 65535u }) == 65536u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 4294967288u }) == 4294967288u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 4294967292u }) == 4294967292u); // Check if size_t in current host is 64 bits. Otherwise the test would fail. #if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 18446744073709551608u }) == 18446744073709551608u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 18446744073709551612u }) == 18446744073709551612u); REQUIRE(Utils::Byte::PadTo4Bytes(size_t{ 18446744073709551615u }) == 0u); #endif } SECTION("PadDownTo4Bytes()") { REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 1u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 2u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 3u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 5u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 9u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 15u }) == 12u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 254u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint8_t{ 255u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 1u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 2u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 3u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 5u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 9u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 15u }) == 12u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 254u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 255u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 65532u }) == 65532u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint16_t{ 65535u }) == 65532u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 1u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 2u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 3u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 5u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 9u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 15u }) == 12u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 254u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 255u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 65532u }) == 65532u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 65535u }) == 65532u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 4294967288u }) == 4294967288u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 4294967292u }) == 4294967292u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint32_t{ 4294967295u }) == 4294967292u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 1u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 2u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 3u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 5u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 9u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 15u }) == 12u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 254u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 255u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 65532u }) == 65532u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 65535u }) == 65532u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 4294967288u }) == 4294967288u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 4294967292u }) == 4294967292u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 4294967295u }) == 4294967292u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 18446744073709551608u }) == 18446744073709551608u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 18446744073709551612u }) == 18446744073709551612u); REQUIRE(Utils::Byte::PadDownTo4Bytes(uint64_t{ 18446744073709551615u }) == 18446744073709551612u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 1u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 2u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 3u }) == 0u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 4u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 5u }) == 4u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 9u }) == 8u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 15u }) == 12u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 252u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 254u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 255u }) == 252u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 65532u }) == 65532u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 65535u }) == 65532u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 4294967288u }) == 4294967288u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 4294967292u }) == 4294967292u); // Check if size_t in current host is 64 bits. Otherwise the test would fail. #if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 18446744073709551608u }) == 18446744073709551608u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 18446744073709551612u }) == 18446744073709551612u); REQUIRE(Utils::Byte::PadDownTo4Bytes(size_t{ 18446744073709551615u }) == 18446744073709551612u); #endif } SECTION("PadTo8Bytes()") { REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 1u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 2u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 3u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 4u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 6u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 7u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 9u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 15u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 16u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 17u }) == 24u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 252u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 254u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint8_t{ 255u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 1u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 2u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 3u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 4u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 6u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 7u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 9u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 15u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 16u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 17u }) == 24u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 252u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 254u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 255u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 65532u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint16_t{ 65535u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 1u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 2u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 3u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 4u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 6u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 7u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 9u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 16u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 252u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 254u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 255u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 65532u }) == 65536u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 65535u }) == 65536u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 4294967288u }) == 4294967288u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 4294967292u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint32_t{ 4294967295u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 1u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 2u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 3u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 4u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 6u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 7u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 9u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 16u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 252u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 254u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 255u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 65532u }) == 65536u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 65535u }) == 65536u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 4294967288u }) == 4294967288u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 4294967292u }) == 4294967296u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 4294967295u }) == 4294967296u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 18446744073709551608u }) == 18446744073709551608u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 18446744073709551612u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(uint64_t{ 18446744073709551615u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 0u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 1u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 2u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 3u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 4u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 5u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 6u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 7u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 8u }) == 8u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 9u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 15u }) == 16u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 252u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 254u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 255u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 256u }) == 256u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 65532u }) == 65536u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 65535u }) == 65536u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 4294967288u }) == 4294967288u); // Check if size_t in current host is 64 bits. Otherwise the test would fail. #if SIZE_MAX == 0xFFFFFFFFFFFFFFFFu REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 4294967292u }) == 4294967296u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 18446744073709551608u }) == 18446744073709551608u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 18446744073709551612u }) == 0u); REQUIRE(Utils::Byte::PadTo8Bytes(size_t{ 18446744073709551615u }) == 0u); #endif } } ================================================ FILE: worker/test/src/Utils/TestCrypto.cpp ================================================ #include "common.hpp" #include "Utils.hpp" #include #include // std::numeric_limits() #include SCENARIO("Utils::Crypto", "[utils][crypto]") { SECTION("GetCRC32()") { uint8_t dataEmpty[] = {}; uint8_t dataZero[] = { 0 }; // clang-format off uint8_t dataRandom[] = { 0xFF, 0x00, 0xAB, 0xCD, 0x12, 0x39, 0x54, 0xBB, 0xDD, 0xEE, 0x01, 0x01, 0x01, 0x01, 0x88, 0x88, 0xAA }; // clang-format on REQUIRE(Utils::Crypto::GetCRC32(dataEmpty, sizeof(dataEmpty)) == 0U); REQUIRE(Utils::Crypto::GetCRC32(dataZero, sizeof(dataZero)) == 0xD202EF8D); REQUIRE(Utils::Crypto::GetCRC32(dataRandom, sizeof(dataRandom)) == 0xEEE31378); } SECTION("GetCRC32c()") { // Tests copied from dcSCTP code in libwebrtc: // https://webrtc.googlesource.com/src//+/refs/heads/main/net/dcsctp/packet/crc32c_test.cc uint8_t dataEmpty[] = {}; uint8_t dataZero[] = { 0 }; uint8_t dataManyZeros[] = { 0, 0, 0, 0 }; uint8_t dataShort[] = { 1, 2, 3, 4 }; uint8_t dataLong[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; // clang-format off uint8_t data32Zeros[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; uint8_t data32Ones[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; uint8_t data32Incrementing[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 }; uint8_t data32Decrementing[] = { 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; uint8_t dataSCSICommandPDU[] = { 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // clang-format on REQUIRE(Utils::Crypto::GetCRC32c(dataEmpty, sizeof(dataEmpty)) == 0); REQUIRE(Utils::Crypto::GetCRC32c(dataZero, sizeof(dataZero)) == 0x51537d52); REQUIRE(Utils::Crypto::GetCRC32c(dataManyZeros, sizeof(dataManyZeros)) == 0xC74B6748); REQUIRE(Utils::Crypto::GetCRC32c(dataShort, sizeof(dataShort)) == 0xF48C3029); REQUIRE(Utils::Crypto::GetCRC32c(dataLong, sizeof(dataLong)) == 0x811F8946); // https://tools.ietf.org/html/rfc3720#appendix-B.4 REQUIRE(Utils::Crypto::GetCRC32c(data32Zeros, sizeof(data32Zeros)) == 0xAA36918A); REQUIRE(Utils::Crypto::GetCRC32c(data32Ones, sizeof(data32Ones)) == 0x43ABA862); REQUIRE(Utils::Crypto::GetCRC32c(data32Incrementing, sizeof(data32Incrementing)) == 0x4E79DD46); REQUIRE(Utils::Crypto::GetCRC32c(data32Decrementing, sizeof(data32Decrementing)) == 0x5CDB3F11); REQUIRE(Utils::Crypto::GetCRC32c(dataSCSICommandPDU, sizeof(dataSCSICommandPDU)) == 0x563A96D9); } } SCENARIO("Utils::Crypto::GetRandomUInt()", "[utils][crypto]") { std::set randomUint32Numbers; std::set randomUint64Numbers; for (size_t i = 0; i < 200; ++i) { auto randomNumber = Utils::Crypto::GetRandomUInt(0, std::numeric_limits::max()); REQUIRE(randomUint32Numbers.find(randomNumber) == randomUint32Numbers.end()); randomUint32Numbers.insert(randomNumber); } for (size_t i = 0; i < 200; ++i) { auto randomNumber = Utils::Crypto::GetRandomUInt(0, std::numeric_limits::max()); REQUIRE(randomUint64Numbers.find(randomNumber) == randomUint64Numbers.end()); randomUint64Numbers.insert(randomNumber); } } ================================================ FILE: worker/test/src/Utils/TestIP.cpp ================================================ #include "common.hpp" #include "MediaSoupErrors.hpp" #include "Utils.hpp" #include #include // std::memset() SCENARIO("Utils::IP", "[utils][ip]") { SECTION("GetFamily()") { std::string ip; ip = "1.2.3.4"; REQUIRE(Utils::IP::GetFamily(ip) == AF_INET); ip = "127.0.0.1"; REQUIRE(Utils::IP::GetFamily(ip) == AF_INET); ip = "255.255.255.255"; REQUIRE(Utils::IP::GetFamily(ip) == AF_INET); ip = "1::1"; REQUIRE(Utils::IP::GetFamily(ip) == AF_INET6); ip = "a:b:c:D::0"; REQUIRE(Utils::IP::GetFamily(ip) == AF_INET6); ip = "0000:0000:0000:0000:0000:ffff:192.168.100.228"; REQUIRE(Utils::IP::GetFamily(ip) == AF_INET6); ip = "::0:"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = "3::3:1:"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = "chicken"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = "1.2.3.256"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = "1.2.3.1111"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = "1.2.3.01"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = "1::abcde"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = "1:::"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = "1.2.3.4 "; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = " ::1"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = ""; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); ip = "0000:0000:0000:0000:0000:ffff:192.168.100.228.4567"; REQUIRE(Utils::IP::GetFamily(ip) == AF_UNSPEC); } SECTION("NormalizeIp()") { std::string ip; ip = "1.2.3.4"; Utils::IP::NormalizeIp(ip); REQUIRE(ip == "1.2.3.4"); ip = "255.255.255.255"; Utils::IP::NormalizeIp(ip); REQUIRE(ip == "255.255.255.255"); ip = "aA::8"; Utils::IP::NormalizeIp(ip); REQUIRE(ip == "aa::8"); ip = "aA::0:0008"; Utils::IP::NormalizeIp(ip); REQUIRE(ip == "aa::8"); ip = "001.2.3.4"; REQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError); ip = "0255.255.255.255"; REQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError); ip = "1::2::3"; REQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError); ip = "::1 "; REQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError); ip = "0.0.0."; REQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError); ip = "::0:"; REQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError); ip = "3::3:1:"; REQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError); ip = ""; REQUIRE_THROWS_AS(Utils::IP::NormalizeIp(ip), MediaSoupTypeError); } SECTION("GetAddressInfo()") { struct sockaddr_in sin{}; std::memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(10251); sin.sin_addr.s_addr = inet_addr("82.99.219.114"); const auto* addr = reinterpret_cast(&sin); int family; std::string ip; uint16_t port; Utils::IP::GetAddressInfo(addr, family, ip, port); REQUIRE(family == AF_INET); REQUIRE(ip == "82.99.219.114"); REQUIRE(port == 10251); } } ================================================ FILE: worker/test/src/Utils/TestNumber.cpp ================================================ #include "common.hpp" #include "Utils.hpp" #include #include // std::numeric_limits SCENARIO("Utils::Number", "[utils][number]") { SECTION("IsEqualThan()") { // 0 is not equal than 16. REQUIRE(Utils::Number::IsEqualThan(0, 16) == false); // Using N=4 bits, 0 is equal than 16. REQUIRE(Utils::Number::IsEqualThan(0, 16) == true); REQUIRE(Utils::Number::IsEqualThan(0, 16) == true); REQUIRE(Utils::Number::IsEqualThan(0, 16) == true); REQUIRE(Utils::Number::IsEqualThan(0, 16) == true); // Using N=7 bits, 0 is equal than 128. REQUIRE(Utils::Number::IsEqualThan(0, 128) == true); REQUIRE(Utils::Number::IsEqualThan(0, 128) == true); REQUIRE(Utils::Number::IsEqualThan(0, 128) == true); REQUIRE(Utils::Number::IsEqualThan(0, 128) == true); } SECTION("IsHigherThan()") { // 10 is higher than std::numeric_limits::max(). REQUIRE(Utils::Number::IsHigherThan(10, std::numeric_limits::max()) == true); // 0 is greater than std::numeric_limits::max(). REQUIRE(Utils::Number::IsHigherThan(0, std::numeric_limits::max()) == true); // std::numeric_limits::max() / 2) - 1 is higher than // std::numeric_limits::max(). REQUIRE( Utils::Number::IsHigherThan( (std::numeric_limits::max() / 2) - 1, std::numeric_limits::max()) == true); // std::numeric_limits::max() is higher than // (std::numeric_limits::max() / 2) + 1. REQUIRE( Utils::Number::IsHigherThan( std::numeric_limits::max(), (std::numeric_limits::max() / 2) + 1) == true); // Using N=4 bits, 0 is higher than 14. REQUIRE(Utils::Number::IsHigherThan(0, 14) == true); REQUIRE(Utils::Number::IsHigherThan(0, 14) == true); REQUIRE(Utils::Number::IsHigherThan(0, 14) == true); REQUIRE(Utils::Number::IsHigherThan(0, 14) == true); // Using N=6 bits, 0 is not higher than 64. REQUIRE(Utils::Number::IsHigherThan(0, 64) == false); REQUIRE(Utils::Number::IsHigherThan(0, 64) == false); REQUIRE(Utils::Number::IsHigherThan(0, 64) == false); REQUIRE(Utils::Number::IsHigherThan(0, 64) == false); } SECTION("IsLowerThan()") { // 1 is lower than 2. REQUIRE(Utils::Number::IsLowerThan(1, 2) == true); // std::numeric_limits::max() is lower than 0. REQUIRE(Utils::Number::IsLowerThan(std::numeric_limits::max(), 0) == true); // 1000000 is lower than 2000000. REQUIRE(Utils::Number::IsLowerThan(1000000, 2000000) == true); // std::numeric_limits::max() is lower than 0. REQUIRE(Utils::Number::IsLowerThan(std::numeric_limits::max(), 0) == true); // (std::numeric_limits::max() / 2) + 1 is lower than // std::numeric_limits::max(). REQUIRE( Utils::Number::IsLowerThan( (std::numeric_limits::max() / 2) + 1, std::numeric_limits::max()) == true); // std::numeric_limits::max() is lower than // (std::numeric_limits::max() / 2) - 1. REQUIRE( Utils::Number::IsLowerThan( std::numeric_limits::max(), (std::numeric_limits::max() / 2) - 1) == true); // Using N=3 bits, 7 is lower than 2. REQUIRE(Utils::Number::IsLowerThan(15, 2) == true); REQUIRE(Utils::Number::IsLowerThan(15, 2) == true); REQUIRE(Utils::Number::IsLowerThan(15, 2) == true); REQUIRE(Utils::Number::IsLowerThan(15, 2) == true); // Using N=2 bits, 3 is lower than 1. REQUIRE(Utils::Number::IsLowerThan(3, 1) == true); REQUIRE(Utils::Number::IsLowerThan(3, 1) == true); REQUIRE(Utils::Number::IsLowerThan(3, 1) == true); REQUIRE(Utils::Number::IsLowerThan(3, 1) == true); } SECTION("IsHigherOrEqualThan()") { // 0 is greater or equal than std::numeric_limits::max(). REQUIRE( Utils::Number::IsHigherOrEqualThan(0, std::numeric_limits::max()) == true); // Using N=5 bits, 0 is higher or equal than 32. REQUIRE(Utils::Number::IsHigherOrEqualThan(0, 32) == true); REQUIRE(Utils::Number::IsHigherOrEqualThan(0, 32) == true); REQUIRE(Utils::Number::IsHigherOrEqualThan(0, 32) == true); REQUIRE(Utils::Number::IsHigherOrEqualThan(0, 32) == true); } SECTION("IsLowerOrEqualThan()") { // std::numeric_limits::max() is lower or equal than 0. REQUIRE( Utils::Number::IsLowerOrEqualThan(std::numeric_limits::max(), 0) == true); // Using N=2 bits, 0 is lower or equal than 4. REQUIRE(Utils::Number::IsLowerOrEqualThan(0, 4) == true); REQUIRE(Utils::Number::IsLowerOrEqualThan(0, 4) == true); REQUIRE(Utils::Number::IsLowerOrEqualThan(0, 4) == true); REQUIRE(Utils::Number::IsLowerOrEqualThan(0, 4) == true); // Using N=2 bits, 3 is lower or equal than 1. REQUIRE(Utils::Number::IsLowerOrEqualThan(3, 1) == true); REQUIRE(Utils::Number::IsLowerOrEqualThan(3, 1) == true); REQUIRE(Utils::Number::IsLowerOrEqualThan(3, 1) == true); REQUIRE(Utils::Number::IsLowerOrEqualThan(3, 1) == true); } SECTION("ForwardDiff()") { REQUIRE(static_cast(Utils::Number::ForwardDiff(4711u, 4711u)) == 0); uint8_t x{ 0 }; uint8_t y{ 255 }; for (uint16_t i{ 0 }; i < 256; ++i) { REQUIRE(static_cast(Utils::Number::ForwardDiff(x, y)) == 255); ++x; ++y; } uint32_t yi{ 255 }; for (uint16_t i{ 0 }; i < 512; ++i) { REQUIRE(static_cast(Utils::Number::ForwardDiff(x, yi)) == 255); ++x; ++yi; } } SECTION("ForwardDiff() with divisor") { REQUIRE(static_cast(Utils::Number::ForwardDiff(0, 122)) == 122); REQUIRE(static_cast(Utils::Number::ForwardDiff(122, 122)) == 0); REQUIRE(static_cast(Utils::Number::ForwardDiff(1, 0)) == 122); REQUIRE(static_cast(Utils::Number::ForwardDiff(0, 0)) == 0); REQUIRE(static_cast(Utils::Number::ForwardDiff(122, 0)) == 1); } SECTION("ReverseDiff()") { REQUIRE(static_cast(Utils::Number::ReverseDiff(4711u, 4711u)) == 0); uint8_t x{ 0 }; uint8_t y{ 255 }; for (uint16_t i{ 0 }; i < 256; ++i) { REQUIRE(static_cast(Utils::Number::ReverseDiff(x, y)) == 1); ++x; ++y; } uint32_t yi{ 255 }; for (uint16_t i{ 0 }; i < 512; ++i) { REQUIRE(static_cast(Utils::Number::ReverseDiff(x, yi)) == 1); ++x; ++yi; } } SECTION("ReverseDiff() with divisor") { REQUIRE(static_cast(Utils::Number::ReverseDiff(0, 122)) == 1); REQUIRE(static_cast(Utils::Number::ReverseDiff(122, 122)) == 0); REQUIRE(static_cast(Utils::Number::ReverseDiff(1, 0)) == 1); REQUIRE(static_cast(Utils::Number::ReverseDiff(0, 0)) == 0); REQUIRE(static_cast(Utils::Number::ReverseDiff(122, 0)) == 122); } } ================================================ FILE: worker/test/src/Utils/TestString.cpp ================================================ #include "common.hpp" #include "Utils.hpp" #include #include // std::memcmp() SCENARIO("Utils::String", "[utils][string]") { SECTION("ToLowerCase()") { std::string str; str = "Foo"; Utils::String::ToLowerCase(str); REQUIRE(str == "foo"); str = "Foo!œ"; Utils::String::ToLowerCase(str); REQUIRE(str == "foo!œ"); } SECTION("Base64Encode() and Base64Decode()") { std::string data; std::string encoded; size_t outLen; uint8_t* decodedPtr; std::string decoded; // NOTE: This is dangerous because we are using a string as binary. data = "abcd"; encoded = Utils::String::Base64Encode(data); decodedPtr = Utils::String::Base64Decode(encoded, outLen); decoded = std::string(reinterpret_cast(decodedPtr), outLen); REQUIRE(encoded == "YWJjZA=="); REQUIRE(decoded == data); // NOTE: This is dangerous because we are using a string as binary. data = "Iñaki"; encoded = Utils::String::Base64Encode(data); decodedPtr = Utils::String::Base64Decode(encoded, outLen); decoded = std::string(reinterpret_cast(decodedPtr), outLen); REQUIRE(encoded == "ScOxYWtp"); REQUIRE(decoded == data); REQUIRE(encoded == Utils::String::Base64Encode(decoded)); // NOTE: This is dangerous because we are using a string as binary. data = "kjsh 23 å∫∂ is89 ∫¶ §∂¶ i823y kjahsd 234u asd kasjhdii7682342 asdkjhaskjsahd k jashd kajsdhaksjdh skadhkjhkjh askdjhasdkjahs uyqiwey aså∫∂¢∞¬∫∂ ashksajdh kjasdhkajshda s kjahsdkjas 987897as897 97898623 9s kjsgå∫∂ 432å∫ƒ∂ å∫#¢ ouyqwiuyais kajsdhiuye ajshkkSAH SDFYÑÑÑ å∫∂Ω 87253847b asdbuiasdi as kasuœæ€\n321"; encoded = Utils::String::Base64Encode(data); decodedPtr = Utils::String::Base64Decode(encoded, outLen); decoded = std::string(reinterpret_cast(decodedPtr), outLen); REQUIRE( encoded == "a2pzaCAyMyDDpeKIq+KIgiBpczg5IOKIq8K2IMKn4oiCwrYgaTgyM3kga2phaHNkIDIzNHUgYXNkIGthc2poZGlpNzY4MjM0MiBhc2Rramhhc2tqc2FoZCAgIGsgamFzaGQga2Fqc2RoYWtzamRoIHNrYWRoa2poa2poICAgICAgIGFza2RqaGFzZGtqYWhzIHV5cWl3ZXkgYXPDpeKIq+KIgsKi4oiewqziiKviiIIgYXNoa3NhamRoIGtqYXNkaGthanNoZGEgcyBramFoc2RramFzIDk4Nzg5N2FzODk3IDk3ODk4NjIzIDlzIGtqc2fDpeKIq+KIgiA0MzLDpeKIq8aS4oiCIMOl4oirI8KiIG91eXF3aXV5YWlzIGthanNkaGl1eWUgIGFqc2hra1NBSCBTREZZw5HDkcORIMOl4oir4oiCzqkgODcyNTM4NDdiIGFzZGJ1aWFzZGkgYXMga2FzdcWTw6bigqwKMzIx"); REQUIRE(decoded == data); REQUIRE(encoded == Utils::String::Base64Encode(decoded)); encoded = "1WfmbWJXSlhTbGhUYkdoVVlrZG9WVmxyWkc5Vw=="; decodedPtr = Utils::String::Base64Decode(encoded, outLen); REQUIRE(outLen == 28); encoded = Utils::String::Base64Encode(decodedPtr, outLen); REQUIRE(encoded == "1WfmbWJXSlhTbGhUYkdoVVlrZG9WVmxyWkc5Vw=="); // clang-format off uint8_t rtpPacket[] = { 0xBE, 0xDE, 0, 3, // Header Extension 0b00010000, 0xFF, 0b00100001, 0xFF, 0xFF, 0, 0, 0b00110011, 0xFF, 0xFF, 0xFF, 0xFF }; // clang-format on encoded = Utils::String::Base64Encode(rtpPacket, sizeof(rtpPacket)); decodedPtr = Utils::String::Base64Decode(encoded, outLen); REQUIRE(outLen == sizeof(rtpPacket)); REQUIRE(std::memcmp(decodedPtr, rtpPacket, outLen) == 0); } } ================================================ FILE: worker/test/src/Utils/TestTime.cpp ================================================ #include "common.hpp" #include "DepLibUV.hpp" #include "Utils.hpp" #include SCENARIO("Utils::Time", "[utils][time]") { SECTION("Ntp2TimeMs()") { const auto nowMs = DepLibUV::GetTimeMs(); const auto ntp = Utils::Time::TimeMs2Ntp(nowMs); const auto nowMs2 = Utils::Time::Ntp2TimeMs(ntp); const auto ntp2 = Utils::Time::TimeMs2Ntp(nowMs2); REQUIRE(nowMs2 == nowMs); REQUIRE(ntp2.seconds == ntp.seconds); REQUIRE(ntp2.fractions == ntp.fractions); } } ================================================ FILE: worker/test/src/Utils/TestUnwrappedSequenceNumber.cpp ================================================ #include "common.hpp" #include "Utils/UnwrappedSequenceNumber.hpp" #include SCENARIO("SCTP UnwrappedSequenceNumber", "[sctp]") { using TestSequence = Utils::UnwrappedSequenceNumber; SECTION("simple unwrapping") { TestSequence::Unwrapper unwrapper; TestSequence s0 = unwrapper.Unwrap(0); TestSequence s1 = unwrapper.Unwrap(1); TestSequence s2 = unwrapper.Unwrap(2); TestSequence s3 = unwrapper.Unwrap(3); REQUIRE(s0 < s1); REQUIRE(s0 < s2); REQUIRE(s0 < s3); REQUIRE(s1 < s2); REQUIRE(s1 < s3); REQUIRE(s2 < s3); REQUIRE(TestSequence::Difference(s1, s0) == 1); REQUIRE(TestSequence::Difference(s2, s0) == 2); REQUIRE(TestSequence::Difference(s3, s0) == 3); REQUIRE(s1 > s0); REQUIRE(s2 > s0); REQUIRE(s3 > s0); REQUIRE(s2 > s1); REQUIRE(s3 > s1); REQUIRE(s3 > s2); s0.Increment(); REQUIRE(s0 == s1); s1.Increment(); REQUIRE(s1 == s2); s2.Increment(); REQUIRE(s2 == s3); REQUIRE(TestSequence::AddTo(s0, 2) == s3); } SECTION("mid value unwrapping") { TestSequence::Unwrapper unwrapper; TestSequence s0 = unwrapper.Unwrap(0x7FFE); TestSequence s1 = unwrapper.Unwrap(0x7FFF); TestSequence s2 = unwrapper.Unwrap(0x8000); TestSequence s3 = unwrapper.Unwrap(0x8001); REQUIRE(s0 < s1); REQUIRE(s0 < s2); REQUIRE(s0 < s3); REQUIRE(s1 < s2); REQUIRE(s1 < s3); REQUIRE(s2 < s3); REQUIRE(TestSequence::Difference(s1, s0) == 1); REQUIRE(TestSequence::Difference(s2, s0) == 2); REQUIRE(TestSequence::Difference(s3, s0) == 3); REQUIRE(s1 > s0); REQUIRE(s2 > s0); REQUIRE(s3 > s0); REQUIRE(s2 > s1); REQUIRE(s3 > s1); REQUIRE(s3 > s2); s0.Increment(); REQUIRE(s0 == s1); s1.Increment(); REQUIRE(s1 == s2); s2.Increment(); REQUIRE(s2 == s3); REQUIRE(TestSequence::AddTo(s0, 2) == s3); } SECTION("wrapped unwrapping") { TestSequence::Unwrapper unwrapper; TestSequence s0 = unwrapper.Unwrap(0xFFFE); TestSequence s1 = unwrapper.Unwrap(0xFFFF); TestSequence s2 = unwrapper.Unwrap(0x0000); TestSequence s3 = unwrapper.Unwrap(0x0001); REQUIRE(s0 < s1); REQUIRE(s0 < s2); REQUIRE(s0 < s3); REQUIRE(s1 < s2); REQUIRE(s1 < s3); REQUIRE(s2 < s3); REQUIRE(TestSequence::Difference(s1, s0) == 1); REQUIRE(TestSequence::Difference(s2, s0) == 2); REQUIRE(TestSequence::Difference(s3, s0) == 3); REQUIRE(s1 > s0); REQUIRE(s2 > s0); REQUIRE(s3 > s0); REQUIRE(s2 > s1); REQUIRE(s3 > s1); REQUIRE(s3 > s2); s0.Increment(); REQUIRE(s0 == s1); s1.Increment(); REQUIRE(s1 == s2); s2.Increment(); REQUIRE(s2 == s3); REQUIRE(TestSequence::AddTo(s0, 2) == s3); } SECTION("wrap around a few times") { TestSequence::Unwrapper unwrapper; const TestSequence s0 = unwrapper.Unwrap(0); TestSequence prev = s0; for (uint32_t i{ 1 }; i < 65536 * 3; ++i) { const auto wrapped = static_cast(i); const TestSequence si = unwrapper.Unwrap(wrapped); REQUIRE(s0 < si); REQUIRE(prev < si); prev = si; } } SECTION("increment is same as wrapped") { TestSequence::Unwrapper unwrapper; TestSequence s0 = unwrapper.Unwrap(0); TestSequence prev = s0; for (uint32_t i{ 1 }; i < 65536 * 2; ++i) { const auto wrapped = static_cast(i); const TestSequence si = unwrapper.Unwrap(wrapped); s0.Increment(); REQUIRE(s0 == si); prev = si; } } SECTION("unwrapping larger number is always larger") { TestSequence::Unwrapper unwrapper; for (uint32_t i{ 1 }; i < 65536 * 2; ++i) { const auto wrapped = static_cast(i); const TestSequence si = unwrapper.Unwrap(wrapped); REQUIRE(unwrapper.Unwrap(wrapped + 1) > si); REQUIRE(unwrapper.Unwrap(wrapped + 5) > si); REQUIRE(unwrapper.Unwrap(wrapped + 10) > si); REQUIRE(unwrapper.Unwrap(wrapped + 100) > si); } } SECTION("unwrapping smaller number is always smaller") { TestSequence::Unwrapper unwrapper; for (uint32_t i{ 1 }; i < 65536 * 2; ++i) { const auto wrapped = static_cast(i); const TestSequence si = unwrapper.Unwrap(wrapped); REQUIRE(unwrapper.Unwrap(wrapped - 1) < si); REQUIRE(unwrapper.Unwrap(wrapped - 5) < si); REQUIRE(unwrapper.Unwrap(wrapped - 10) < si); REQUIRE(unwrapper.Unwrap(wrapped - 100) < si); } } SECTION("difference is absolute") { TestSequence::Unwrapper unwrapper; const TestSequence thisValue = unwrapper.Unwrap(10); const TestSequence otherValue = TestSequence::AddTo(thisValue, 100); REQUIRE(TestSequence::Difference(thisValue, otherValue) == 100); REQUIRE(TestSequence::Difference(otherValue, thisValue) == 100); const TestSequence minusValue = TestSequence::AddTo(thisValue, -100); REQUIRE(TestSequence::Difference(thisValue, minusValue) == 100); REQUIRE(TestSequence::Difference(minusValue, thisValue) == 100); } } ================================================ FILE: worker/test/src/testHelpers.cpp ================================================ #define MS_CLASS "TEST::HELPERS" #include "test/include/testHelpers.hpp" #include "Logger.hpp" #include // std::memcmp() #include #include namespace helpers { bool readBinaryFile(const char* file, uint8_t* buffer, size_t* len) { MS_TRACE(); // NOLINTNEXTLINE(misc-const-correctness) std::string filePath = "test/" + std::string(file); #ifdef _WIN32 std::replace(filePath.begin(), filePath.end(), '/', '\\'); #endif std::ifstream in(filePath, std::ios::ate | std::ios::binary); if (!in) { return false; } *len = static_cast(in.tellg()) - 1; in.seekg(0, std::ios::beg); in.read(reinterpret_cast(buffer), *len); in.close(); return true; } bool areBuffersEqual(const uint8_t* data1, size_t size1, const uint8_t* data2, size_t size2) { MS_TRACE(); if (size1 != size2) { return false; } return std::memcmp(data1, data2, size1) == 0; } } // namespace helpers ================================================ FILE: worker/test/src/tests.cpp ================================================ #include "common.hpp" #include "DepLibSRTP.hpp" #include "DepLibUV.hpp" #include "DepLibWebRTC.hpp" #include "DepOpenSSL.hpp" // TODO: Remove once we only use built-in SCTP stack. #include "DepUsrSCTP.hpp" #include "Settings.hpp" #include "Utils.hpp" #include #include // std::getenv() #include // std::istringstream() #include #include int main(int argc, char* argv[]) { std::string logLevel{ "none" }; std::vector logTags = { "info" }; const auto* logLevelPtr = std::getenv("MS_TEST_LOG_LEVEL"); const auto* logTagsPtr = std::getenv("MS_TEST_LOG_TAGS"); // Get logLevel from ENV variable. if (logLevelPtr) { logLevel = std::string(logLevelPtr); } // Get logTags from ENV variable. if (logTagsPtr) { auto logTagsStr = std::string(logTagsPtr); std::istringstream iss(logTagsStr); std::string logTag; while (iss >> logTag) { logTags.push_back(logTag); } } Settings::SetLogLevel(logLevel); Settings::SetLogTags(logTags); Settings::PrintConfiguration(); // Initialize static stuff. DepLibUV::ClassInit(); DepOpenSSL::ClassInit(); DepLibSRTP::ClassInit(); // TODO: Remove once we only use built-in SCTP stack. if (!Settings::configuration.useBuiltInSctpStack) { DepUsrSCTP::ClassInit(); } DepLibWebRTC::ClassInit(); Utils::Crypto::ClassInit(); Catch::Session session; const int status = session.run(argc, argv); // Free static stuff. DepLibSRTP::ClassDestroy(); Utils::Crypto::ClassDestroy(); DepLibWebRTC::ClassDestroy(); // TODO: Remove once we only use built-in SCTP stack. if (!Settings::configuration.useBuiltInSctpStack) { DepUsrSCTP::ClassDestroy(); } DepLibUV::ClassDestroy(); return status; } ================================================ FILE: worker/ubsan_suppressions.txt ================================================ function:err_string_data_hash function:err_string_data_cmp function:value_free_hash function:value_free_stack_doall function:pd_free function:SHA1_Update function:alg_cleanup function:getrn function:doall_util_fn function:sa_doall function:OPENSSL_sk_pop_free function:EVP_DigestUpdate