//!
//!
//! A Sans I/O WebRTC implementation in Rust.
//!
//! This is a [Sans I/O][sansio] implementation meaning the `Rtc` instance itself is not doing any network
//! talking. Furthermore it has no internal threads or async tasks. All operations are happening from the
//! calls of the public API.
//!
//! This is deliberately not a standard `RTCPeerConnection` API since that isn't a great fit for Rust.
//! See more details in below section.
//!
//! # Join us
//!
//! We are discussing str0m things on Discord. Join us using this [invitation link][discord].
//!
//!
//!
//! # Usage
//!
//! The [`chat`][x-chat] example shows how to connect multiple browsers
//! together and act as an SFU (Selective Forwarding Unit). The example
//! multiplexes all traffic over one server UDP socket and uses two threads
//! (one for the web server, and one for the SFU loop).
//!
//! ## TLS
//!
//! For the browser to do WebRTC, all traffic must be under TLS. The
//! project ships with a self-signed certificate that is used for the
//! examples. The certificate is for hostname `str0m.test` since TLD .test
//! should never resolve to a real DNS name.
//!
//! ```text
//! cargo run --example chat
//! ```
//!
//! The log should prompt you to connect a browser to https://10.0.0.103:3000 – this will
//! most likely cause a security warning that you must get the browser to accept.
//!
//! The [`http-post`][x-post] example roughly illustrates how to receive
//! media data from a browser client. The example is single threaded and
//! is a bit simpler than the chat. It is a good starting point to understand the API.
//!
//! ```text
//! cargo run --example http-post
//! ```
//!
//! ### Real example
//!
//! To see how str0m is used in a real project, check out [BitWHIP][bitwhip] –
//! a CLI WebRTC Agent written in Rust.
//!
//! ## Passive
//!
//! For passive connections, i.e. where the media and initial OFFER is
//! made by a remote peer, we need these steps to open the connection.
//!
//! ```no_run
//! # use std::time::Instant;
//! # use str0m::{Rtc, Candidate};
//! // Instantiate a new Rtc instance.
//! let mut rtc = Rtc::new(Instant::now());
//!
//! // Add some ICE candidate such as a locally bound UDP port.
//! let addr = "1.2.3.4:5000".parse().unwrap();
//! let candidate = Candidate::host(addr, "udp").unwrap();
//! rtc.add_local_candidate(candidate);
//!
//! // Accept an incoming offer from the remote peer
//! // and get the corresponding answer.
//! let offer = todo!();
//! let answer = rtc.sdp_api().accept_offer(offer).unwrap();
//!
//! // Forward the answer to the remote peer.
//!
//! // Go to _run loop_
//! ```
//!
//! ## Active
//!
//! Active connections means we are making the inital OFFER and waiting for a
//! remote ANSWER to start the connection.
//!
//! ```no_run
//! # use std::time::Instant;
//! # use str0m::{Rtc, Candidate};
//! # use str0m::media::{MediaKind, Direction};
//! // Instantiate a new Rtc instance.
//! let mut rtc = Rtc::new(Instant::now());
//!
//! // Add some ICE candidate such as a locally bound UDP port.
//! let addr = "1.2.3.4:5000".parse().unwrap();
//! let candidate = Candidate::host(addr, "udp").unwrap();
//! rtc.add_local_candidate(candidate);
//!
//! // Create a `SdpApi`. The change lets us make multiple changes
//! // before sending the offer.
//! let mut change = rtc.sdp_api();
//!
//! // Do some change. A valid OFFER needs at least one "m-line" (media).
//! let mid = change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None);
//!
//! // Get the offer.
//! let (offer, pending) = change.apply().unwrap();
//!
//! // Forward the offer to the remote peer and await the answer.
//! // How to transfer this is outside the scope for this library.
//! let answer = todo!();
//!
//! // Apply answer.
//! rtc.sdp_api().accept_answer(pending, answer).unwrap();
//!
//! // Go to _run loop_
//! ```
//!
//! ## Run loop
//!
//! ### The single-mutation invariant
//!
//! str0m's API has one strict contract that the run loop is built around:
//!
//! > **Every mutation of an `Rtc` instance must be followed by a complete
//! > drain of `poll_output` until it returns `Output::Timeout`, before the
//! > next mutation on the same `Rtc`.**
//!
//! A "mutation" is anything that takes `&mut Rtc` (directly or through a
//! handle obtained from it). The common ones are:
//!
//! - `Rtc::handle_input` — feeding a network packet or a timeout
//! - `Writer::write` / `Writer::request_keyframe` — sending media
//! - `Channel::write` — sending data channel data
//! - `SdpApi::apply` / `DirectApi::*` — negotiation
//! - `Rtc::add_local_candidate` / `Rtc::add_remote_candidate`
//!
//! Always: **mutate → drain to `Timeout` → mutate → drain to `Timeout` → …**
//!
//! Doing two mutations back-to-back without draining in between, or
//! waiting on I/O while the engine still has output queued, leaves the
//! engine in an inconsistent state and produces wrong behavior. Mutations
//! issued from inside the drain loop (e.g. calling `Writer::write` in
//! response to an `Output::Event`) are fine — the drain loop naturally
//! continues calling `poll_output` afterward and so the invariant holds.
//!
//! ### Canonical shape
//!
//! Driving an `Rtc` forward follows the same six-step shape, regardless
//! of sync or async:
//!
//! 1. **Wait** for one of: the next timeout firing, an incoming network
//! packet, or the application wanting to perform a mutation (e.g.
//! write media).
//! 2. **Perform that ONE mutation** — feed `Input` to `handle_input`, or
//! call into a writer / channel / SDP API.
//! 3. **Poll** `Rtc::poll_output`.
//! 4. **Handle** the output: `Output::Transmit` is sent on the socket,
//! `Output::Event` is dispatched to the application, `Output::Timeout`
//! records the next deadline.
//! 5. **Goto 3** until `poll_output` returns `Output::Timeout`. Only then
//! is the engine fully drained.
//! 6. The returned timeout is what we wait on next — **goto 1**.
//!
//! ```no_run
//! # use str0m::{Rtc, Output, IceConnectionState, Event, Input};
//! # use str0m::net::{Receive, Protocol};
//! # use std::io::ErrorKind;
//! # use std::net::UdpSocket;
//! # use std::time::{Duration, Instant};
//! # let mut rtc = Rtc::new(Instant::now());
//! // A UdpSocket we obtained _somehow_.
//! let socket: UdpSocket = todo!();
//!
//! // Buffer for reading incoming UDP packets.
//! let mut buf = vec![0; 2000];
//!
//! loop {
//! // === Steps 3-5: drain poll_output until we get the next timeout. ===
//! let timeout = loop {
//! match rtc.poll_output().unwrap() {
//! // Step 5: a Timeout exits the drain loop.
//! Output::Timeout(t) => break t,
//!
//! // Step 4: transmit on the socket and keep draining.
//! // The destination IP comes from the ICE agent and may
//! // change during the session.
//! Output::Transmit(t) => {
//! socket.send_to(&t.contents, t.destination).unwrap();
//! }
//!
//! // Step 4: hand the event to the application and keep draining.
//! // Events are mainly incoming media data from the remote peer,
//! // but also data channel data and statistics.
//! Output::Event(e) => {
//! if e == Event::IceConnectionStateChange(IceConnectionState::Disconnected) {
//! return;
//! }
//! // TODO: handle other events here, such as incoming media data.
//! }
//! }
//! };
//!
//! // === Step 1: wait for ONE of: the timeout firing, an incoming
//! // packet, or application-side data. The example below uses a
//! // blocking UDP socket with a read timeout. With async you would
//! // `select!` over multiple futures; with application-side data you
//! // would also include a channel.
//! //
//! // set_read_timeout(Some(ZERO)) is not allowed, so clamp to >= 1ms.
//! let duration = (timeout - Instant::now()).max(Duration::from_millis(1));
//! socket.set_read_timeout(Some(duration)).unwrap();
//! buf.resize(2000, 0);
//!
//! // === Step 2: take ONE event and feed it as Input ===
//! let input = match socket.recv_from(&mut buf) {
//! Ok((n, source)) => {
//! buf.truncate(n);
//! Input::Receive(
//! Instant::now(),
//! Receive {
//! proto: Protocol::Udp,
//! source,
//! destination: socket.local_addr().unwrap(),
//! contents: buf.as_slice().try_into().unwrap(),
//! },
//! )
//! }
//!
//! // The socket read timed out — feed Input::Timeout to advance
//! // the engine to the deadline. WouldBlock is the unix error,
//! // TimedOut is the windows error.
//! Err(e) if matches!(e.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) => {
//! Input::Timeout(Instant::now())
//! }
//!
//! Err(e) => {
//! eprintln!("Error: {:?}", e);
//! return;
//! }
//! };
//!
//! rtc.handle_input(input).unwrap();
//!
//! // === Step 6: back to the top of the outer loop (goto step 3). ===
//! }
//! ```
//!
//! ## Sending media data
//!
//! When creating the media, we can decide which codecs to support, and they
//! are negotiated with the remote side. Each codec corresponds to a
//! "payload type" (PT). To send media data we need to figure out which PT
//! to use when sending.
//!
//! ```no_run
//! # use str0m::Rtc;
//! # use str0m::media::Mid;
//! # let rtc: Rtc = todo!();
//! // Obtain mid from Event::MediaAdded
//! let mid: Mid = todo!();
//!
//! // Create a media writer for the mid.
//! let writer = rtc.writer(mid).unwrap();
//!
//! // Get the payload type (pt) for the wanted codec.
//! let pt = writer.payload_params().nth(0).unwrap().pt();
//!
//! // Write the data
//! let wallclock = todo!(); // Absolute time of the data
//! let media_time = todo!(); // Media time, in RTP time
//! let data: &[u8] = todo!(); // Actual data
//! writer.write(pt, wallclock, media_time, data).unwrap();
//! ```
//!
//! `Writer::write` is a mutation, so the
//! [single-mutation invariant](#the-single-mutation-invariant) applies:
//! after writing, drain `Rtc::poll_output` to `Output::Timeout` before
//! the next mutation on this `Rtc`.
//!
//! ## Media time, wallclock and local time
//!
//! str0m has three main concepts of time. "now", media time and wallclock.
//!
//! ### Now
//!
//! Some calls in str0m, such as `Rtc::handle_input` takes a `now` argument
//! that is a `std::time::Instant`. These calls "drive the time forward" in
//! the internal state. This is used for everything like deciding when
//! to produce various feedback reports (RTCP) to remote peers, to
//! bandwidth estimation (BWE) and statistics.
//!
//! Str0m has _no internal clock_ calls. I.e. str0m never calls
//! `Instant::now()` itself. All time is external input. That means it's
//! possible to construct test cases driving an `Rtc` instance faster
//! than realtime (see the [integration tests][intg]).
//!
//! ### Media time
//!
//! Each RTP header has a 32 bit number that str0m calls _media time_.
//! Media time is in some time base that is dependent on the codec,
//! however all codecs in str0m use 90_000Hz for video and 48_000Hz
//! for audio.
//!
//! For video the `MediaTime` type is `/90_000` str0m extends
//! the 32 bit number in the RTP header to 64 bit taking into account
//! "rollover". 64 bit is such a large number the user doesn't need to
//! think about rollovers.
//!
//! ### Wallclock
//!
//! With _wallclock_ str0m means the time a sample of media was produced
//! at an originating source. I.e. if we are talking into a microphone the
//! wallclock is the NTP time the sound is sampled.
//!
//! We can't know the exact wallclock for media from a remote peer since
//! not every device is synchronized with NTP. Every sender does
//! periodically produce a Sender Report (SR) that contains the peer's
//! idea of its wallclock, however this number can be very wrong compared to
//! "real" NTP time.
//!
//! Furthermore, not all remote devices will have a linear idea of
//! time passing that exactly matches the local time. A minute on the
//! remote peer might not be exactly one minute locally.
//!
//! These timestamps become important when handling simultaneous audio from
//! multiple peers.
//!
//! When writing media we need to provide str0m with an estimated wallclock.
//! The simplest strategy is to only trust local time and use arrival time
//! of the incoming UDP packet. Another simple strategy is to lock some
//! time T at the first UDP packet, and then offset each wallclock using
//! `MediaTime`, i.e. for video we could have `T + /90_000`
//!
//! A production worthy SFU probably needs an even more sophisticated
//! strategy weighing in all possible time sources to get a good estimate
//! of the remote wallclock for a packet.
//!
//! # Crypto backends
//!
//! str0m supports multiple crypto backends via feature flags. The default is `aws-lc-rs`.
//!
//! | Feature | Crate | DTLS | Platforms |
//! |-------------------|-----------------------|----------------------------------|-----------|
//! | `aws-lc-rs` | `str0m-aws-lc-rs` | dimpl + AWS-LC-RS | All |
//! | `rust-crypto` | `str0m-rust-crypto` | dimpl + RustCrypto | All |
//! | `openssl` | `str0m-openssl` | OpenSSL (DTLS 1.2 only) | All |
//! | `openssl-dimpl` | `str0m-openssl` | dimpl + OpenSSL crypto | All |
//! | `apple-crypto` | `str0m-apple-crypto` | dimpl + Apple CryptoKit | macOS/iOS |
//! | `wincrypto` | `str0m-wincrypto` | Windows SChannel (DTLS 1.2 only) | Windows |
//! | `wincrypto-dimpl` | `str0m-wincrypto` | dimpl + Windows CNG | Windows |
//!
//! If multiple backend features are enabled, str0m automatically selects the backend in this
//! priority order: `aws-lc-rs`, `rust-crypto`, `openssl-dimpl`, `openssl`, `apple-crypto`
//! (Apple platforms only), `wincrypt-dimpl` (Windows only), `wincrypto` (Windows only).
//!
//! If you disable the default features, you MUST explicitly configure an alternative
//! crypto backend either process-wide or per-instance.
//!
//! ## Process-wide default
//!
//! For applications, the easiest is to set a process-wide default at startup.
//! Note that you can use any backend crate directly without enabling its feature flag:
//!
//! ```no_run
//! // Set process default (will panic if called twice)
//! // No need to enable the "rust-crypto" feature flag
//! str0m_rust_crypto::default_provider().install_process_default();
//! ```
//!
//! ## Crypto provider per Rtc instance
//!
//! ```no_run
//! use std::sync::Arc;
//! use std::time::Instant;
//! use str0m::Rtc;
//!
//! let rtc = Rtc::builder()
//! .set_crypto_provider(Arc::new(str0m_rust_crypto::default_provider()))
//! .build(Instant::now());
//! ```
//!
//! # Project status
//!
//! Str0m was originally developed by Martin Algesten of
//! [Lookback][lookback]. We use str0m for a specific use case: str0m as a
//! server SFU (as opposed to peer-2-peer). That means we are heavily
//! testing and developing the parts needed for our use case. Str0m is
//! intended to be an all-purpose WebRTC library, which means it also
//! works for peer-2-peer, though that aspect has received less testing.
//!
//! Performance is very good, there have been some work the discover and
//! optimize bottlenecks. Such efforts are of course never ending with
//! diminishing returns. While there are no glaringly obvious performance
//! bottlenecks, more work is always welcome – both algorithmically and
//! allocation/cloning in hot paths etc.
//!
//! # Design
//!
//! Output from the `Rtc` instance can be grouped into three kinds.
//!
//! 1. Events (such as receiving media or data channel data).
//! 2. Network output. Data to be sent, typically from a UDP socket.
//! 3. Timeouts. Indicates when the instance next expects a time input.
//!
//! Input to the `Rtc` instance is:
//!
//! 1. User operations (such as sending media or data channel data).
//! 2. Network input. Typically read from a UDP socket.
//! 3. Timeouts. As obtained from the output above.
//!
//! The correct use can be seen in the above [Run loop](#run-loop) or in the
//! examples.
//!
//! Sans I/O is a pattern where we turn both network input/output as well
//! as time passing into external input to the API. This means str0m has
//! no internal threads, just an enormous state machine that is driven
//! forward by different kinds of input.
//!
//! ## Frame or RTP level?
//!
//! Str0m defaults to the "frame level" which treats the RTP as an internal detail. The user
//! will thus mainly interact with:
//!
//! 1. [`Event::MediaData`][evmed] to receive full frames (audio frames or video frames).
//! 2. [`Writer::write`][writer] to write full frames.
//! 3. [`Writer::request_keyframe`][reqkey] to request keyframes.
//!
//! ### Frame level
//!
//! All codecs such as h264, vp8, vp9 and opus outputs what we call
//! "Frames". A frame has a very specific meaning for video, but this
//! project uses it in a broader sense, where a frame is either a video
//! or audio time stamped chunk of encoded data that typically represents
//! a chunk of audio, or _one single frame for video_.
//!
//! Frames are not suitable to use directly in UDP (RTP) packets - for
//! one they are too big. Frames are therefore further chunked up by
//! codec specific payloaders into RTP packets.
//!
//! ### RTP mode
//!
//! Str0m also provides an RTP level API. This would be similar to many other
//! RTP libraries where the RTP packets themselves are the API surface
//! towards the user (when building an SFU one would often talk about "forwarding
//! RTP packets", while with str0m we can also "forward frames"). Using
//! this API requires a deeper knowledge of RTP and WebRTC.
//!
//! To enable RTP mode
//!
//! ```
//! # use std::time::Instant;
//! # use str0m::Rtc;
//! let rtc = Rtc::builder()
//! // Enable RTP mode for this Rtc instance.
//! // This disables `MediaEvent` and the `Writer::write` API.
//! .set_rtp_mode(true)
//! .build(Instant::now());
//! ```
//!
//! RTP mode gives us some new API points.
//!
//! 1. [`Event::RtpPacket`][rtppak] emitted for every incoming RTP packet. Empty packets for bandwidth
//! estimation are silently discarded.
//! 2. [`StreamTx::write_rtp`][wrtrtp] to write outgoing RTP packets.
//! 3. [`StreamRx::request_keyframe`][reqkey2] to request keyframes from remote.
//!
//! ## NIC enumeration and TURN (and STUN)
//!
//! The [ICE RFC][ice] talks about "gathering ice candidates". This means
//! inspecting the local network interfaces and potentially binding UDP
//! sockets on each usable interface. Since str0m is Sans I/O, this part
//! is outside the scope of what str0m does. How the user figures out
//! local IP addresses, via config or via looking up local NICs is not
//! something str0m cares about.
//!
//! TURN is a way of obtaining IP addresses that can be used as fallback
//! in case direct connections fail. We consider TURN similar to
//! enumerating local network interfaces – it's a way of obtaining
//! sockets.
//!
//! All discovered candidates, be they local (NIC) or remote sockets
//! (TURN), are added to str0m and str0m will perform the task of ICE
//! agent, forming "candidate pairs" and figuring out the best connection
//! while the actual task of sending the network traffic is left to the
//! user.
//!
//! ## The importance of `&mut self`
//!
//! Rust shines when we can eschew locks and heavily rely `&mut` for data
//! write access. Since str0m has no internal threads, we never have to
//! deal with shared data. Furthermore the the internals of the library is
//! organized such that we don't need multiple references to the same
//! entities. In str0m there are no `Rc`, `Mutex`, `mpsc`, `Arc`(*), or
//! other locks.
//!
//! This means all input to the lib can be modelled as
//! `handle_something(&mut self, something)`.
//!
//! (*) Ok. There is one `Arc` if you use Windows where we also require openssl.
//!
//! ## Not a standard WebRTC "Peer Connection" API
//!
//! The library deliberately steps away from the "standard" WebRTC API as
//! seen in JavaScript and/or [webrtc-rs][webrtc-rs] (or [Pion][pion] in Go).
//! There are few reasons for this.
//!
//! First, in the standard API, events are callbacks, which are not a
//! great fit for Rust. Callbacks require some kind of reference
//! (ownership?) over the entity the callback is being dispatched
//! upon. I.e. if in Rust we want `pc.addEventListener(x)`, `x` needs
//! to be wholly owned by `pc`, or have some shared reference (like
//! `Arc`). Shared references means shared data, and to get mutable shared
//! data, we will need some kind of lock. i.e. `Arc>`
//! or similar.
//!
//! As an alternative we could turn all events into `mpsc` channels, but
//! listening to multiple channels is awkward without async.
//!
//! Second, in the standard API, entities like `RTCPeerConnection` and
//! `RTCRtpTransceiver`, are easily clonable and/or long lived
//! references. I.e. `pc.getTranscievers()` returns objects that can be
//! retained and owned by the caller. This pattern is fine for garbage
//! collected or reference counted languages, but not great with Rust.
//!
//! ## Panics, Errors and unwraps
//!
//! Str0m adheres to [fail-fast][ff]. That means rather than brushing state
//! bugs under the carpet, it panics. We make a distinction between errors and
//! bugs.
//!
//! * Errors are as a result of incorrect or impossible to understand user input.
//! * Bugs are broken internal invariants (assumptions).
//!
//! If you scan the str0m code you find a few `unwrap()` (or `expect()`). These
//! will (should) always be accompanied by a code comment that explains why the
//! unwrap is okay. This is an internal invariant, a state assumption that
//! str0m is responsible for maintaining.
//!
//! We do not believe it's correct to change every `unwrap()`/`expect()` into
//! `unwrap_or_else()`, `if let Some(x) = x { ... }` etc, because doing so
//! brushes an actual problem (an incorrect assumption) under the carpet. Trying
//! to hobble along with an incorrect state would at best result in broken
//! behavior, at worst a security risk!
//!
//! Panics are our friends: *panic means bug*
//!
//! And also: str0m should *never* panic on any user input. If you encounter a panic,
//! please report it!
//!
//! ### Catching panics
//!
//! Panics should be incredibly rare, or we have a serious problem as a project. For an SFU,
//! it might not be ideal if str0m encounters a bug and brings the entire server down with it.
//!
//! For those who want an extra level of safety, we recommend looking at [`catch_unwind`][catch]
//! to safely discard a faulty `Rtc` instance. Since `Rtc` has no internal threads, locks or async
//! tasks, discarding the instance never risk poisoning locks or other issues that can happen
//! when catching a panic.
//!
//! ## FAQ
//!
//! ### Features
//!
//! Below is a brief comparison of features between libWebRTC and str0m to help you determine
//! if str0m is suitable for your project.
//!
//! | Feature | str0m | libWebRTC |
//! | ------------------------ | ------------------ | ------------------ |
//! | Peer Connection API | :x: | :white_check_mark: |
//! | SDP | :white_check_mark: | :white_check_mark: |
//! | ICE | :white_check_mark: | :white_check_mark: |
//! | Data Channels | :white_check_mark: | :white_check_mark: |
//! | Send/Recv Reports | :white_check_mark: | :white_check_mark: |
//! | Transport Wide CC | :white_check_mark: | :white_check_mark: |
//! | Bandwidth Estimation | :white_check_mark: | :white_check_mark: |
//! | Simulcast | :white_check_mark: | :white_check_mark: |
//! | NACK | :white_check_mark: | :white_check_mark: |
//! | Packetize | :white_check_mark: | :white_check_mark: |
//! | Fixed Depacketize Buffer | :white_check_mark: | :white_check_mark: |
//! | Adaptive Jitter Buffer | :x: | :white_check_mark: |
//! | Video/audio capture | :x: | :white_check_mark: |
//! | Video/audio encode | :x: | :white_check_mark: |
//! | Video/audio decode | :x: | :white_check_mark: |
//! | Audio render | :x: | :white_check_mark: |
//! | Turn | :x: | :white_check_mark: |
//! | Network interface enum | :x: | :white_check_mark: |
//!
//! ### Platform Support
//!
//! Platforms str0m is compiled and tested on:
//!
//! | Platform | Compiled | Tested |
//! | ------------------------------ | ----------------- | ----------------- |
//! | `x86_64-pc-windows-msvc` | :white_check_mark:| :white_check_mark:|
//! | `x86_64-unknown-linux-gnu` | :white_check_mark:| :white_check_mark:|
//! | `x86_64-apple-darwin` | :white_check_mark:| :white_check_mark:|
//! | `aarch64-apple-darwin` | :white_check_mark:| :white_check_mark:|
//! | `aarch64-unknown-linux-gnu` | :white_check_mark:| :white_check_mark:|
//! | `aarch64-pc-windows-msvc` | :white_check_mark:| :white_check_mark:|
//! | `aarch64-apple-ios` | :white_check_mark:| :x: |
//! | `aarch64-linux-android` | :white_check_mark:| :x: |
//!
//! If your platform isn't listed but is supported by Rust, we'd love for you to give str0m a try and
//! share your experience. We greatly appreciate your feedback!
//!
//! ### Does str0m support IPv4, IPv6, UDP and TCP?
//!
//! Certainly! str0m fully support IPv4, IPv6, UDP and TCP protocols.
//!
//! ### Can I utilize str0m with any Rust async runtime?
//!
//! Absolutely! str0m is fully sync, ensuring that it integrates seamlessly with any Rust async
//! runtime you opt for.
//!
//! ### Can I create a client with str0m?
//!
//! Of course! You have the freedom to create a client with str0m. However, please note that some
//! common client features like media encoding, decoding, and capture are not included in str0m. But
//! don't let that stop you from building amazing applications!
//!
//! ### Can I use str0m in a media server?
//!
//! Yes! str0m excels as a server component with support for both RTP API and Frame API. You can
//! easily build that recording server or SFU you dreamt of in Rust!
//!
//! ### Can I deploy the chat example into production?
//!
//! While the chat example showcases how to use str0m's API, it's not intended for production use or
//! heavy load. Writing a full-featured SFU or MCU (Multipoint Control Unit) is a significant
//! undertaking, involving various design decisions based on production requirements.
//!
//! ### Discovered a bug? Here's how to share it with us
//!
//! We'd love to hear about it! Please submit an issue and consider joining our Discord community
//! to discuss further. For a seamless reporting experience, refer to this exemplary
//! bug report: . We appreciate your contribution
//! to making str0m better!
//!
//! ### I am allergic to SDP can you help me?
//!
//! Yes use the direct API!
//!
//! [sansio]: https://sans-io.readthedocs.io
//! [quinn]: https://github.com/quinn-rs/quinn
//! [pion]: https://github.com/pion/webrtc
//! [webrtc-rs]: https://github.com/webrtc-rs/webrtc
//! [discord]: https://discord.gg/e2CC8UYebP
//! [zulip]: https://str0m.zulipchat.com
//! [ice]: https://www.rfc-editor.org/rfc/rfc8445
//! [lookback]: https://www.lookback.com
//! [x-post]: https://github.com/algesten/str0m/blob/main/examples/http-post.rs
//! [x-chat]: https://github.com/algesten/str0m/blob/main/examples/chat.rs
//! [intg]: https://github.com/algesten/str0m/blob/main/tests/unidirectional.rs#L12
//! [ff]: https://en.wikipedia.org/wiki/Fail-fast
//! [catch]: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html
//! [evmed]: https://docs.rs/str0m/*/str0m/enum.Event.html#variant.MediaData
//! [writer]: https://docs.rs/str0m/*/str0m/media/struct.Writer.html#method.write
//! [reqkey]: https://docs.rs/str0m/*/str0m/media/struct.Writer.html#method.request_keyframe
//! [rtppak]: https://docs.rs/str0m/*/str0m/enum.Event.html#variant.RtpPacket
//! [wrtrtp]: https://docs.rs/str0m/*/str0m/rtp/struct.StreamTx.html#method.write_rtp
//! [reqkey2]: https://docs.rs/str0m/*/str0m/rtp/struct.StreamRx.html#method.request_keyframe
//! [bitwhip]: https://github.com/bitwhip/bitwhip
#![forbid(unsafe_code)]
#![allow(clippy::new_without_default)]
#![allow(clippy::bool_to_int_with_if)]
#![allow(clippy::assertions_on_constants)]
#![allow(clippy::manual_range_contains)]
#![allow(clippy::get_first)]
#![allow(clippy::needless_lifetimes)]
#![allow(clippy::precedence)]
#![allow(clippy::doc_overindented_list_items)]
#![allow(clippy::uninlined_format_args)]
#![allow(unknown_lints, mismatched_lifetime_syntaxes)]
#![deny(clippy::needless_pass_by_ref_mut)]
#![deny(missing_docs)]
#[macro_use]
extern crate tracing;
use bwe::{Bwe, BweKind};
use change::{DirectApi, SdpApi};
use rtp::RawPacket;
use std::fmt;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Instant;
use str0m_proto::Pii;
use streams::RtpPacket;
use streams::StreamPaused;
use util::InstantExt;
// Identity `drv::ToStatic` (`Static = Self`) for the `Copy` identity types.
// Used instead of `#[derive(drv::Input)]` because the derive's generated
// shadow type isn't `Eq`/`Hash`/`Borrow`, so it can't be a `HashMap`
// key inside a `#[drv::memo]` projection; `Static = Self` can. `eq_static`
// defers to each type's own `PartialEq`. Each module invokes this for its
// own types.
#[cfg(feature = "drv")]
macro_rules! drv_identity_copy {
($($t:ty),* $(,)?) => {
$(
impl drv::ToStatic for $t {
type Static = $t;
fn to_static(&self) -> $t { *self }
fn eq_static(&self, other: &$t) -> bool { self == other }
}
)*
};
}
#[cfg(feature = "drv")]
pub(crate) use drv_identity_copy;
/// Cryptographic provider traits and implementations.
///
/// This module provides the traits for pluggable cryptographic operations
/// used in DTLS, SRTP, and STUN.
pub mod crypto;
use crypto::Fingerprint;
mod dtls;
use crate::crypto::dtls::DtlsOutput;
use crate::crypto::{CryptoProvider, DtlsError, from_feature_flags};
use crate::dtls::is_would_block;
use dtls::Dtls;
use is::IceAgent;
use is::IceAgentEvent;
pub use is::{Candidate, CandidateBuilder, CandidateKind, IceConnectionState, IceCreds};
#[path = "config.rs"]
mod config_mod;
pub use config_mod::RtcConfig;
/// Default target MTU used when none is configured.
pub use io::DATAGRAM_MTU_TARGET;
/// Upper bound that the target endpoint of [`RtcConfig::set_mtu`] must satisfy.
pub use io::DATAGRAM_MTU_TARGET_MAX;
/// Lower bound that the target endpoint of [`RtcConfig::set_mtu`] must satisfy.
pub use io::DATAGRAM_MTU_TARGET_MIN;
/// Default warning threshold for over-sized packets.
pub use io::DATAGRAM_MTU_WARN;
/// Additional configuration.
pub mod config {
pub use super::crypto::dtls::{DtlsCert, DtlsVersion, KeyingMaterial};
pub use super::crypto::{CryptoProvider, Fingerprint};
}
/// Low level ICE access.
// The ICE API is not necessary to interact with directly for "regular"
// use of str0m. This is exported for other libraries that want to
// reuse str0m's ICE implementation. This is now in the `is` crate.
#[doc(hidden)]
pub mod ice {
pub use is::IceCreds;
pub use is::stun::{StunMessage, StunMessageBuilder, StunPacket, TransId};
pub use is::{IceAgent, IceAgentEvent};
pub use is::{LocalPreference, default_local_preference};
}
mod io;
use io::DatagramRecvInner;
mod packet;
#[path = "rtp/mod.rs"]
mod rtp_;
use rtp_::{Bitrate, DataSize};
/// Low level RTP access.
pub mod rtp {
/// Feedback for RTP.
pub mod rtcp {
pub use crate::rtp_::AppSpecificFeedback;
pub use crate::rtp_::{Descriptions, ExtendedReport, Fir, Goodbye, Nack, Pli};
pub use crate::rtp_::{Dlrr, NackEntry, ReceptionReport, ReportBlock};
pub use crate::rtp_::{FirEntry, ReceiverReport, SenderInfo, SenderReport, Twcc};
pub use crate::rtp_::{ReportList, Rrtr, Rtcp, Sdes, SdesType};
}
use self::rtcp::Rtcp;
/// Video Layers Allocation RTP Header Extension
pub mod vla;
pub use crate::rtp_::{AbsCaptureTime, ExtensionValues, UserExtensionValues};
pub use crate::rtp_::{Extension, ExtensionMap, ExtensionSerializer};
pub use crate::packet::{
Vp8Descriptor, Vp8DescriptorError, Vp8Patch, Vp8PatchBuilder, Vp8PatchError,
};
pub use crate::rtp_::{RtpHeader, SeqNo, Ssrc, VideoOrientation};
pub use crate::streams::{
RtpPacket, RtpWrite, StreamPaused, StreamRx, StreamTx, StreamTxQueueInfo,
};
/// Debug output of the unencrypted RTP and RTCP packets.
///
/// Enable using [`RtcConfig::enable_raw_packets()`][crate::RtcConfig::enable_raw_packets].
/// This clones data, and is therefore expensive.
/// Should not be enabled outside of tests and troubleshooting.
#[derive(Debug)]
pub enum RawPacket {
/// Sent RTCP.
RtcpTx(Rtcp),
/// Incoming RTCP.
RtcpRx(Rtcp),
/// Sent RTP.
RtpTx(RtpHeader, Vec),
/// Incoming RTP.
RtpRx(RtpHeader, Vec),
}
}
pub(crate) mod pacer;
#[path = "bwe/mod.rs"]
pub(crate) mod bwe_;
/// Bandwidth estimation.
pub mod bwe {
pub use crate::bwe_::api::*;
}
mod sctp;
use sctp::{RtcSctp, SctpEvent, SctpInitData};
mod sdp;
pub mod format;
use format::CodecConfig;
pub mod channel;
use channel::{Channel, ChannelData, ChannelHandler, ChannelId};
pub mod media;
use media::AppSpecificFeedback;
use media::SenderFeedback;
use media::{Direction, Media, Mid, Pt, Rid, Writer};
use media::{KeyframeRequest, KeyframeRequestKind};
use media::{MediaAdded, MediaChanged, MediaData};
pub mod change;
mod util;
use util::{Soonest, not_happening};
mod session;
use session::Session;
pub mod stats;
use stats::{CandidatePairStats, CandidateStats, MediaEgressStats, MediaIngressStats};
use stats::{PeerStats, Stats, StatsEvent, StatsSnapshot};
mod streams;
pub mod error;
/// Network related types to get socket data in/out of [`Rtc`].
pub mod net {
pub use crate::io::{DatagramRecv, DatagramSend, Protocol, Receive, TcpType, Transmit};
}
const VERSION: &str = env!("CARGO_PKG_VERSION");
pub use error::RtcError;
/// Instance that does WebRTC. Main struct of the entire library.
///
/// ## Usage
///
/// The canonical run loop is: perform exactly one mutation (typically
/// `handle_input` with a network packet or timeout), then drain
/// `poll_output` until it returns `Output::Timeout`, then wait for the
/// next event. **Every mutation must be followed by a full drain before
/// the next mutation.** See the [crate docs](crate#run-loop) for the full
/// explanation of the single-mutation invariant.
///
/// ```no_run
/// # use std::time::Instant;
/// # use str0m::{Rtc, Output, Input};
/// let mut rtc = Rtc::new(Instant::now());
///
/// loop {
/// // Drain poll_output until it returns the next timeout.
/// let timeout = loop {
/// match rtc.poll_output().unwrap() {
/// Output::Timeout(t) => break t,
/// Output::Transmit(t) => {
/// // TODO: Send t.contents to t.destination on a UDP socket.
/// }
/// Output::Event(e) => {
/// // TODO: Handle event.
/// }
/// }
/// };
///
/// // TODO: Wait for ONE of: `timeout` firing, an incoming network
/// // packet, or application-side data to send. Build an
/// // `Input` from whichever fires first.
/// let input: Input = todo!();
///
/// rtc.handle_input(input).unwrap();
/// }
/// ```
pub struct Rtc {
state: RtcState,
ice: IceAgent,
dtls: Dtls,
dtls_connected: bool,
dtls_buf: Vec,
next_dtls_timeout: Option,
sctp: RtcSctp,
chan: ChannelHandler,
stats: Option,
session: Session,
remote_fingerprint: Option,
remote_addrs: Vec,
send_addr: Option,
need_init_time: bool,
last_now: Instant,
peer_bytes_rx: u64,
peer_bytes_tx: u64,
change_counter: usize,
last_timeout_reason: Reason,
crypto_provider: Arc,
fingerprint_verification: bool,
close_dtls_started: bool,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum RtcState {
Alive,
Closing,
Closed,
}
struct SendAddr {
proto: net::Protocol,
source: SocketAddr,
destination: SocketAddr,
}
/// Events produced by [`Rtc::poll_output()`].
#[derive(Debug)]
#[non_exhaustive]
#[rustfmt::skip]
pub enum Event {
// =================== ICE related events ===================
/// Emitted when we got ICE connection and established DTLS.
Connected,
/// ICE connection state changes tells us whether the [`Rtc`] instance is
/// connected to the peer or not.
IceConnectionStateChange(IceConnectionState),
// =================== Media related events ==================
/// Upon detecting the remote side adding new media to the session.
///
/// For locally added media, this event never fires. Thus it can be thought of as an
/// "SDP only" event. If the direct API is used on both sides, the declaration is local \
/// to both sides and the event never fires.
///
/// The [`Media`] instance is available via [`Rtc::media()`].
MediaAdded(MediaAdded),
/// Incoming media data sent by the remote peer.
MediaData(MediaData),
/// Changes to the media may be emitted.
///
///. Currently only covers a change of direction.
MediaChanged(MediaChanged),
// =================== Data channel related events ===================
/// A data channel has opened.
///
/// The string is the channel label which is set by the opening peer and can
/// be used to identify the purpose of the channel when there are more than one.
///
/// The negotiation is to set up an SCTP association via DTLS. Subsequent data
/// channels reuse the same association.
///
/// Upon this event, the [`Channel`] can be obtained via [`Rtc::channel()`].
///
/// For [`SdpApi`]: The first ever data channel results in an SDP
/// negotiation, and this events comes at the end of that.
ChannelOpen(ChannelId, String),
/// Incoming data channel data from the remote peer.
ChannelData(ChannelData),
/// A data channel has been closed.
ChannelClose(ChannelId),
/// A data channel's buffered amount has dropped below the configured threshold.
ChannelBufferedAmountLow(ChannelId),
// =================== Statistics and BWE related events ===================
/// Statistics event for the Rtc instance
///
/// Includes both media traffic (rtp payload) as well as all traffic
PeerStats(PeerStats),
/// Aggregated statistics for each media (mid, rid) in the ingress direction
MediaIngressStats(MediaIngressStats),
/// Aggregated statistics for each media (mid, rid) in the egress direction
MediaEgressStats(MediaEgressStats),
/// A new estimate from the bandwidth estimation subsystem.
EgressBitrateEstimate(BweKind),
// =================== RTP related events ===================
/// Incoming keyframe request for media that we are sending to the remote peer.
///
/// The request is either PLI (Picture Loss Indication) or FIR (Full Intra Request).
KeyframeRequest(KeyframeRequest),
/// Whether an incoming encoded stream is paused.
///
/// This means the stream has not received any data for some time (default 1.5 seconds).
StreamPaused(StreamPaused),
/// Sender feedback for an incoming stream, derived from RTCP SR.
SenderFeedback(SenderFeedback),
/// Incoming RTP data.
RtpPacket(RtpPacket),
/// Incoming application-specific Payload-Specific Feedback (PSFB FMT=15, PT=206).
///
/// Emitted when a non-REMB FMT=15 RTCP PSFB message is received. The payload
/// is opaque and application-defined (RFC 4585 Section 6.4).
AppSpecificFeedback(AppSpecificFeedback),
/// The remote DTLS connection or SCTP association has closed.
///
/// When this event is emitted, `Rtc` starts local shutdown signals and
/// remains alive only while [`Rtc::poll_output()`] drains pending local
/// close output such as RTCP BYE and the DTLS close_notify response.
Closed,
/// Debug output of incoming and outgoing RTCP/RTP packets.
///
/// Enable using [`RtcConfig::enable_raw_packets()`].
/// This clones data, and is therefore expensive.
/// Should not be enabled outside of tests and troubleshooting.
RawPacket(Box),
/// For internal testing only.
///
/// The probe cluster config when a probe fires.
#[cfg(feature = "_internal_test_exports")]
Probe(crate::bwe_::ProbeClusterConfig),
}
impl Event {
/// Reference to the [`RawPacket`] if this is indeed an `Event::RawPacket`.
pub fn as_raw_packet(&self) -> Option<&RawPacket> {
if let Self::RawPacket(boxed) = &self {
Some(&**boxed)
} else {
None
}
}
}
/// Input as expected by [`Rtc::handle_input()`]. Either network data or a timeout.
#[derive(Debug)]
#[allow(clippy::large_enum_variant)] // We purposely don't want to allocate.
pub enum Input<'a> {
/// A timeout without any network input.
Timeout(Instant),
/// Network input. The [`Instant`] is the time the packet was received from the socket -
/// not necessarily "now" (e.g. when UDP demultiplexing runs on a separate thread). It
/// drives time forward when more recent than the last "now" the instance has seen.
Receive(Instant, net::Receive<'a>),
}
/// Output produced by [`Rtc::poll_output()`]
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum Output {
/// When the [`Rtc`] instance expects an [`Input::Timeout`].
Timeout(Instant),
/// Network data that is to be sent.
Transmit(net::Transmit),
/// Some event such as media data arriving from the remote peer or connection events.
Event(Event),
}
pub use crate::pacer::PacerReason;
/// The reason for the next [`Output::Timeout`].
///
/// This enum is not considered stable API and may change in minor revisions.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum Reason {
/// No timeout scheduled.
///
/// The timeout value is in the distant future.
#[default]
NotHappening,
/// The DTLS subsystem.
///
/// Only relevant during handshaking.
DTLS,
/// The ICE agent.
///
/// Includes checking candidate pairs and various cleanups.
Ice,
/// The SCTP subsystem.
///
/// Things like handling retransmissions and keep-alive checks.
Sctp,
/// Data channels.
///
/// Scheduled when we need to open allocations using SCTP.
Channel,
/// Stats gathering (if enabled).
///
/// Periodic gathering of statistics.
Stats,
/// Regular RTP feedback.
///
/// Receiver reports (RR) and sender reports (SR).
Feedback,
/// Sending of RTP NACK.
///
/// When missing packets are discovered, a NACK is scheduled.
Nack,
/// Reporting of TWCC (if enabled).
///
/// All incoming RTP packets are reported using TWCC. Enabled via SDP if both
/// sides support it.
Twcc,
/// RTP streams not receiving data goes into a paused state.
///
/// Whenever an RTP receive stream receives data, a new timeout is scheduled.
PauseCheck,
/// Preprocessing of RTP packets to be sent.
///
/// Housekeeping task in RTP send streams.
SendStream,
/// Packetizing of media into RTP data (if used).
///
/// Written media data needs packetizing. This is not used in RTP mode.
Packetize,
/// Pacer doing things.
Pacer(PacerReason),
/// The delay controller of the BWE subsystem.
BweDelayControl,
/// The probe controller of the BWE subsystem.
BweProbeControl,
/// The probe estimator of the BWE subsystem.
BweProbeEstimator,
}
impl Rtc {
/// Creates a new instance with default settings.
///
/// To configure the instance, use [`RtcConfig`].
///
/// ```
/// use std::time::Instant;
/// use str0m::Rtc;
///
/// let rtc = Rtc::new(Instant::now());
/// ```
pub fn new(start: Instant) -> Self {
let config = RtcConfig::default();
Self::new_from_config(config, start).expect("Failed to create Rtc from default config")
}
/// Creates a config builder that configures an [`Rtc`] instance.
///
/// ```
/// # use std::time::Instant;
/// # use str0m::Rtc;
/// let rtc = Rtc::builder()
/// .set_ice_lite(true)
/// .build(Instant::now());
/// ```
pub fn builder() -> RtcConfig {
RtcConfig::new()
}
pub(crate) fn new_from_config(config: RtcConfig, start: Instant) -> Result {
let crypto_provider = config
.crypto_provider
.clone()
// If crypto_provider is not set in config, check process default
.or_else(|| CryptoProvider::get_default().cloned().map(Arc::new))
// Or fall back on feature flags
.or_else(|| Some(Arc::new(from_feature_flags())))
// from_feature_flags panics already, so we should never see
// this expect message.
.expect("a crash earlier if no crypto provider was set");
let session = Session::new(&config);
// Capture before any partial moves of `config` below.
let mtu = config.mtu.clone();
let local_creds = config.local_ice_credentials.unwrap_or_else(IceCreds::new);
let mut ice = IceAgent::with_hmac(local_creds, crypto_provider.sha1_hmac_provider);
if config.ice_lite {
ice.set_ice_lite(config.ice_lite);
}
if let Some(initial_stun_rto) = config.initial_stun_rto {
ice.set_initial_stun_rto(initial_stun_rto);
}
if let Some(max_stun_rto) = config.max_stun_rto {
ice.set_max_stun_rto(max_stun_rto);
}
if let Some(max_stun_retransmits) = config.max_stun_retransmits {
ice.set_max_stun_retransmits(max_stun_retransmits);
}
ice.set_mtu(mtu.clone());
let dtls_cert = config
.dtls_cert
.or_else(|| crypto_provider.dtls_provider.generate_certificate())
.expect(
"No DTLS certificate provided and the crypto provider cannot generate one. \
Either provide a certificate via RtcConfig::set_dtls_cert or use a \
crypto provider that supports certificate generation.",
);
let mut sctp = RtcSctp::with_receive_limits(*mtu.start(), config.sctp_receive_limits);
if config.snap_enabled {
sctp.enable_snap();
}
Ok(Rtc {
state: RtcState::Alive,
ice,
dtls: Dtls::new(
&dtls_cert,
crypto_provider.dtls_provider,
crypto_provider.sha256_provider,
start,
config.dtls_version,
mtu,
)
.expect("DTLS to init without problem"),
dtls_connected: false,
dtls_buf: vec![0; 2000],
next_dtls_timeout: None,
session,
sctp,
chan: ChannelHandler::default(),
stats: config.stats_interval.map(Stats::new),
remote_fingerprint: None,
remote_addrs: vec![],
send_addr: None,
need_init_time: true,
last_now: start,
peer_bytes_rx: 0,
peer_bytes_tx: 0,
change_counter: 0,
last_timeout_reason: Reason::NotHappening,
crypto_provider,
fingerprint_verification: config.fingerprint_verification,
close_dtls_started: false,
})
}
/// Tests if this instance is still working.
///
/// Certain events will straight away disconnect the `Rtc` instance, such as
/// the DTLS fingerprint from the setup not matching that of the TLS negotiation
/// (since that would potentially indicate a MITM attack!).
///
/// The instance can be manually disconnected using [`Rtc::disconnect()`].
///
/// During shutdown, this remains `true` until [`Rtc::poll_output()`]
/// has drained pending local close output. App-facing operations such as
/// [`Rtc::writer()`] and [`Rtc::channel()`] are unavailable during that
/// closing drain.
///
/// ```
/// # use std::time::Instant;
/// # use str0m::Rtc;
/// let mut rtc = Rtc::new(Instant::now());
///
/// assert!(rtc.is_alive());
///
/// rtc.disconnect();
/// assert!(!rtc.is_alive());
/// ```
pub fn is_alive(&self) -> bool {
self.state != RtcState::Closed
}
/// Force disconnects the instance making [`Rtc::is_alive()`] return `false`.
///
/// This makes [`Rtc::poll_output`] and [`Rtc::handle_input`] go inert and not
/// produce anymore network output or events.
///
/// ```
/// # use std::time::Instant;
/// # use str0m::Rtc;
/// let mut rtc = Rtc::new(Instant::now());
///
/// rtc.disconnect();
/// assert!(!rtc.is_alive());
/// ```
pub fn disconnect(&mut self) {
self.state = RtcState::Closed;
}
/// Add a local ICE candidate. Local candidates are socket addresses the `Rtc` instance
/// use for communicating with the peer.
///
/// If the candidate is accepted by the `Rtc` instance, it will return `Some` with a reference
/// to it. You should then signal this candidate to the remote peer.
///
/// This library has no built-in discovery of local network addresses on the host
/// or NATed addresses via a STUN server or TURN server. The user of the library
/// is expected to add new local candidates as they are discovered.
///
/// In WebRTC lingo, the `Rtc` instance is permanently in a mode of [Trickle Ice][1]. It's
/// however advisable to add at least one local candidate before starting the instance.
///
/// ```
/// # use std::time::Instant;
/// # use str0m::{Rtc, Candidate};
/// let mut rtc = Rtc::new(Instant::now());
///
/// let a = "127.0.0.1:5000".parse().unwrap();
/// let c = Candidate::host(a, "udp").unwrap();
///
/// rtc.add_local_candidate(c);
/// ```
///
/// [1]: https://www.rfc-editor.org/rfc/rfc8838.txt
pub fn add_local_candidate(&mut self, c: Candidate) -> Option<&Candidate> {
self.ice.add_local_candidate(c)
}
/// Add a remote ICE candidate. Remote candidates are addresses of the peer.
///
/// For [`SdpApi`]: Remote candidates are typically added via
/// receiving a remote [`SdpOffer`][change::SdpOffer] or [`SdpAnswer`][change::SdpAnswer].
///
/// However for the case of [Trickle Ice][1], this is the way to add remote candidates
/// that are "trickled" from the other side.
///
/// ```
/// # use std::time::Instant;
/// # use str0m::{Rtc, Candidate};
/// let mut rtc = Rtc::new(Instant::now());
///
/// let a = "1.2.3.4:5000".parse().unwrap();
/// let c = Candidate::host(a, "udp").unwrap();
///
/// rtc.add_remote_candidate(c);
/// ```
///
/// [1]: https://www.rfc-editor.org/rfc/rfc8838.txt
pub fn add_remote_candidate(&mut self, c: Candidate) {
self.ice.add_remote_candidate(c);
}
/// Checks if we are connected.
///
/// This tests if we have ICE connection, DTLS and the SRTP crypto derived contexts are up.
pub fn is_connected(&self) -> bool {
self.ice.state().is_connected() && self.dtls_connected && self.session.is_connected()
}
/// Make changes to the Rtc session via SDP.
///
/// ```no_run
/// # use std::time::Instant;
/// # use str0m::Rtc;
/// # use str0m::media::{MediaKind, Direction};
/// # use str0m::change::SdpAnswer;
/// let mut rtc = Rtc::new(Instant::now());
///
/// let mut changes = rtc.sdp_api();
/// let mid_audio = changes.add_media(MediaKind::Audio, Direction::SendOnly, None, None, None);
/// let mid_video = changes.add_media(MediaKind::Video, Direction::SendOnly, None, None, None);
///
/// let (offer, pending) = changes.apply().unwrap();
/// let json = serde_json::to_vec(&offer).unwrap();
///
/// // Send json OFFER to remote peer. Receive an answer back.
/// let answer: SdpAnswer = todo!();
///
/// rtc.sdp_api().accept_answer(pending, answer).unwrap();
/// ```
pub fn sdp_api(&mut self) -> SdpApi {
SdpApi::new(self)
}
/// Makes direct changes to the Rtc session.
///
/// This is a low level API. For "normal" use via SDP, see [`Rtc::sdp_api()`].
pub fn direct_api(&mut self) -> DirectApi {
DirectApi::new(self)
}
/// Send outgoing media data (frames) or request keyframes.
///
/// Returns `None` if the direction isn't sending (`sendrecv` or `sendonly`).
///
/// ```no_run
/// # use std::time::Instant;
/// # use str0m::Rtc;
/// # use str0m::media::{MediaData, Mid};
/// # use str0m::format::PayloadParams;
/// let mut rtc = Rtc::new(Instant::now());
///
/// // add candidates, do SDP negotiation
/// let mid: Mid = todo!(); // obtain mid from Event::MediaAdded.
///
/// // Writer for this mid.
/// let writer = rtc.writer(mid).unwrap();
///
/// // Get incoming media data from another peer
/// let data: MediaData = todo!();
///
/// // Match incoming PT to an outgoing PT.
/// let pt = writer.match_params(data.params).unwrap();
///
/// writer.write(pt, data.network_time, data.time, data.data).unwrap();
/// ```
///
/// This is a frame level API: For RTP level see [`DirectApi::stream_tx()`]
/// and [`DirectApi::stream_rx()`].
///
pub fn writer(&mut self, mid: Mid) -> Option {
if self.session.rtp_mode {
panic!("In rtp_mode use direct_api().stream_tx().write_rtp()");
}
if self.state != RtcState::Alive {
return None;
}
// This does not catch potential RIDs required to send simulcast, but
// it's a good start. An error might arise later on RID mismatch.
self.session.media_by_mid_mut(mid)?;
Some(Writer::new(&mut self.session, mid))
}
/// Currently configured media.
///
/// Read only access. Changes are made via [`Rtc::sdp_api()`] or [`Rtc::direct_api()`].
pub fn media(&self, mid: Mid) -> Option<&Media> {
self.session.media_by_mid(mid)
}
fn init_dtls(&mut self, active: bool) -> Result<(), RtcError> {
if self.dtls.is_inited() {
return Ok(());
}
debug!("DTLS setup is: {:?}", active);
self.dtls.set_active(active);
// Initialize the DTLS state (client or server) before any operations
// This ensures internal state like random (client) or last_now (server) is initialized
self.dtls.handle_timeout(self.last_now)?;
if active {
// Drive handshake by sending an empty packet to trigger ClientHello
let _ = self.dtls.handle_receive(&[]);
}
Ok(())
}
fn try_init_sctp(
&mut self,
client: bool,
sctp_init_data: Option,
remote_max_message_size: Option,
) -> Result<(), RtcError> {
// If we got an m=application line, ensure we have negotiated the
// SCTP association with the other side.
if self.sctp.is_inited() {
return Ok(());
}
self.sctp.init(
client,
self.last_now,
sctp_init_data,
remote_max_message_size,
)?;
Ok(())
}
/// Creates a new Mid that is not in the session already.
pub(crate) fn new_mid(&self) -> Mid {
loop {
let mid = Mid::new();
if !self.session.has_mid(mid) {
break mid;
}
}
}
/// Poll the `Rtc` instance for output. Output can be three things, something to _Transmit_
/// via a UDP socket (maybe via a TURN server). An _Event_, such as receiving media data,
/// or a _Timeout_.
///
/// The user of the library is expected to continuously call this function and deal with
/// the output until it encounters an [`Output::Timeout`] at which point no further output
/// is produced (if polled again, it will result in just another timeout).
///
/// After exhausting the `poll_output`, the function will only produce more output again
/// when one of two things happen:
///
/// 1. The polled timeout is reached.
/// 2. New network input.
///
/// See [`Rtc`] instance documentation for how this is expected to be used in a loop.
pub fn poll_output(&mut self) -> Result