#![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)]
#![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::time::{Duration, Instant};
use streams::RtpPacket;
use streams::StreamPaused;
use thiserror::Error;
use util::InstantExt;
mod crypto;
use crypto::Fingerprint;
mod dtls;
use dtls::DtlsCert;
use dtls::{Dtls, DtlsEvent};
#[path = "ice/mod.rs"]
mod ice_;
use ice_::IceAgent;
use ice_::IceAgentEvent;
pub use ice_::{Candidate, CandidateKind, IceConnectionState, IceCreds};
#[doc(hidden)]
pub mod ice {
pub use crate::ice_::IceCreds;
pub use crate::ice_::{IceAgent, IceAgentEvent};
pub use crate::io::{StunMessage, StunPacket};
}
mod io;
use io::DatagramRecvInner;
mod packet;
#[path = "rtp/mod.rs"]
mod rtp_;
use rtp_::{Bitrate, DataSize};
use rtp_::{Extension, ExtensionMap};
pub mod rtp {
pub mod rtcp {
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;
pub mod vla;
pub use crate::rtp_::{Extension, ExtensionMap, ExtensionSerializer};
pub use crate::rtp_::{ExtensionValues, UserExtensionValues};
pub use crate::rtp_::{RtpHeader, SeqNo, Ssrc, VideoOrientation};
pub use crate::streams::{RtpPacket, StreamPaused, StreamRx, StreamTx};
#[derive(Debug)]
pub enum RawPacket {
RtcpTx(Rtcp),
RtcpRx(Rtcp),
RtpTx(RtpHeader, Vec<u8>),
RtpRx(RtpHeader, Vec<u8>),
}
}
pub mod bwe;
mod sctp;
use sctp::{RtcSctp, SctpEvent};
mod sdp;
pub mod format;
use format::CodecConfig;
pub mod channel;
use channel::{Channel, ChannelData, ChannelHandler, ChannelId};
pub mod media;
use media::{Direction, Media, Mid, Pt, Rid, Writer};
use media::{KeyframeRequest, KeyframeRequestKind};
use media::{MediaAdded, MediaChanged, MediaData};
pub mod change;
mod util;
use util::{already_happened, not_happening, Soonest};
mod session;
use session::Session;
pub mod stats;
use stats::{MediaEgressStats, MediaIngressStats, PeerStats, Stats, StatsEvent, StatsSnapshot};
mod streams;
pub mod net {
pub use crate::io::{DatagramRecv, DatagramSend, Protocol, Receive, Transmit};
}
pub mod error {
pub use crate::dtls::DtlsError;
pub use crate::ice_::IceError;
pub use crate::io::NetError;
pub use crate::packet::PacketError;
pub use crate::rtp_::RtpError;
pub use crate::sctp::{ProtoError, SctpError};
pub use crate::sdp::SdpError;
}
const VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum RtcError {
#[error("remote sdp: {0}")]
RemoteSdp(String),
#[error("{0}")]
Sdp(#[from] error::SdpError),
#[error("{0}")]
Rtp(#[from] error::RtpError),
#[error("{0}")]
Io(#[from] std::io::Error),
#[error("{0}")]
Dtls(#[from] error::DtlsError),
#[error("{0} {1} {2}")]
Packet(Mid, Pt, error::PacketError),
#[error("PT is unknown {0}")]
UnknownPt(Pt),
#[error("RID is unknown {0}")]
UnknownRid(Rid),
#[error("No sender source")]
NoSenderSource,
#[error("When outgoing stream has RTX, write_rtp must be called with rtp_pt set")]
ResendRequiresRtxPt,
#[error("Direction does not allow sending: {0}")]
NotSendingDirection(Direction),
#[error("Direction does not allow receiving")]
NotReceivingDirection,
#[error("No receiver source (rid: {0:?})")]
NoReceiverSource(Option<Rid>),
#[error("Requested feedback is not enabled: {0:?}")]
FeedbackNotEnabled(KeyframeRequestKind),
#[error("{0}")]
Net(#[from] error::NetError),
#[error("{0}")]
Ice(#[from] error::IceError),
#[error("{0}")]
Sctp(#[from] error::SctpError),
#[error("Changes made out of order")]
ChangesOutOfOrder,
#[error("Consecutive calls to write() without poll_output() in between")]
WriteWithoutPoll,
}
pub struct Rtc {
alive: bool,
ice: IceAgent,
dtls: Dtls,
sctp: RtcSctp,
chan: ChannelHandler,
stats: Option<Stats>,
session: Session,
remote_fingerprint: Option<Fingerprint>,
remote_addrs: Vec<SocketAddr>,
send_addr: Option<SendAddr>,
need_init_time: bool,
last_now: Instant,
peer_bytes_rx: u64,
peer_bytes_tx: u64,
change_counter: usize,
last_timeout_reason: Reason,
}
struct SendAddr {
proto: net::Protocol,
source: SocketAddr,
destination: SocketAddr,
}
#[derive(Debug)]
#[non_exhaustive]
#[rustfmt::skip]
pub enum Event {
Connected,
IceConnectionStateChange(IceConnectionState),
MediaAdded(MediaAdded),
MediaData(MediaData),
MediaChanged(MediaChanged),
ChannelOpen(ChannelId, String),
ChannelData(ChannelData),
ChannelClose(ChannelId),
PeerStats(PeerStats),
MediaIngressStats(MediaIngressStats),
MediaEgressStats(MediaEgressStats),
EgressBitrateEstimate(BweKind),
KeyframeRequest(KeyframeRequest),
StreamPaused(StreamPaused),
RtpPacket(RtpPacket),
RawPacket(Box<RawPacket>),
}
impl Event {
pub fn as_raw_packet(&self) -> Option<&RawPacket> {
if let Self::RawPacket(boxed) = &self {
Some(&**boxed)
} else {
None
}
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)] pub enum Input<'a> {
Timeout(Instant),
Receive(Instant, net::Receive<'a>),
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum Output {
Timeout(Instant),
Transmit(net::Transmit),
Event(Event),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Reason {
NotHappening,
DTLS,
Ice,
Sctp,
Channel,
Stats,
Feedback,
Nack,
Twcc,
PauseCheck,
SendStream,
Packetize,
Pacing,
Bwe,
}
impl Default for Reason {
fn default() -> Self {
Self::NotHappening
}
}
impl Rtc {
pub fn new() -> Self {
let config = RtcConfig::default();
Self::new_from_config(config)
}
pub fn builder() -> RtcConfig {
RtcConfig::new()
}
pub(crate) fn new_from_config(config: RtcConfig) -> Self {
let session = Session::new(&config);
let local_creds = config.local_ice_credentials.unwrap_or_else(IceCreds::new);
let mut ice = IceAgent::with_local_credentials(local_creds);
if config.ice_lite {
ice.set_ice_lite(config.ice_lite);
}
let dtls_cert = if let Some(c) = config.dtls_cert {
c
} else {
#[cfg(feature = "openssl")]
{
DtlsCert::new_openssl()
}
#[cfg(not(feature = "openssl"))]
{
panic!("No DTLS implementation. Enable openssl feature");
}
};
Rtc {
alive: true,
ice,
dtls: Dtls::new(dtls_cert).expect("DTLS to init without problem"),
session,
sctp: RtcSctp::new(),
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: already_happened(),
peer_bytes_rx: 0,
peer_bytes_tx: 0,
change_counter: 0,
last_timeout_reason: Reason::NotHappening,
}
}
pub fn is_alive(&self) -> bool {
self.alive
}
pub fn disconnect(&mut self) {
if self.alive {
info!("Set alive=false");
self.alive = false;
}
}
pub fn add_local_candidate(&mut self, c: Candidate) {
self.ice.add_local_candidate(c);
}
pub fn add_remote_candidate(&mut self, c: Candidate) {
self.ice.add_remote_candidate(c);
}
pub fn is_connected(&self) -> bool {
self.ice.state().is_connected() && self.dtls.is_connected()
}
pub fn sdp_api(&mut self) -> SdpApi {
SdpApi::new(self)
}
pub fn direct_api(&mut self) -> DirectApi {
DirectApi::new(self)
}
pub fn writer(&mut self, mid: Mid) -> Option<Writer> {
if self.session.rtp_mode {
panic!("In rtp_mode use direct_api().stream_tx().write_rtp()");
}
self.session.media_by_mid_mut(mid)?;
Some(Writer::new(&mut self.session, mid))
}
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(());
}
info!("DTLS setup is: {:?}", active);
self.dtls.set_active(active);
if active {
self.dtls.handle_handshake()?;
}
Ok(())
}
fn init_sctp(&mut self, client: bool) {
if self.sctp.is_inited() {
return;
}
self.sctp.init(client, self.last_now);
}
pub(crate) fn new_mid(&self) -> Mid {
loop {
let mid = Mid::new();
if !self.session.has_mid(mid) {
break mid;
}
}
}
pub fn poll_output(&mut self) -> Result<Output, RtcError> {
let o = self.do_poll_output()?;
match &o {
Output::Event(e) => match e {
Event::ChannelData(_) | Event::MediaData(_) | Event::RtpPacket(_) => {
trace!("{:?}", e)
}
_ => debug!("{:?}", e),
},
Output::Transmit(t) => {
self.peer_bytes_tx += t.contents.len() as u64;
trace!("OUT {:?}", t)
}
Output::Timeout(_t) => {}
}
Ok(o)
}
fn do_poll_output(&mut self) -> Result<Output, RtcError> {
if !self.alive {
self.last_timeout_reason = Reason::NotHappening;
return Ok(Output::Timeout(not_happening()));
}
while let Some(e) = self.ice.poll_event() {
match e {
IceAgentEvent::IceRestart(_) => {
}
IceAgentEvent::IceConnectionStateChange(v) => {
return Ok(Output::Event(Event::IceConnectionStateChange(v)))
}
IceAgentEvent::DiscoveredRecv { proto, source } => {
info!("ICE remote address: {:?}/{:?}", source, proto);
self.remote_addrs.push(source);
while self.remote_addrs.len() > 20 {
self.remote_addrs.remove(0);
}
}
IceAgentEvent::NominatedSend {
proto,
source,
destination,
} => {
info!(
"ICE nominated send from: {:?} to: {:?} with protocol {:?}",
source, destination, proto,
);
self.send_addr = Some(SendAddr {
proto,
source,
destination,
});
}
}
}
let mut dtls_connected = false;
while let Some(e) = self.dtls.poll_event() {
match e {
DtlsEvent::Connected => {
debug!("DTLS connected");
dtls_connected = true;
}
DtlsEvent::SrtpKeyingMaterial(mat, srtp_profile) => {
info!(
"DTLS set SRTP keying material and profile: {}",
srtp_profile
);
let active = self.dtls.is_active().expect("DTLS must be inited by now");
self.session.set_keying_material(mat, srtp_profile, active);
}
DtlsEvent::RemoteFingerprint(v1) => {
debug!("DTLS verify remote fingerprint");
if let Some(v2) = &self.remote_fingerprint {
if v1 != *v2 {
self.disconnect();
return Err(RtcError::RemoteSdp("remote fingerprint no match".into()));
}
} else {
self.disconnect();
return Err(RtcError::RemoteSdp("no a=fingerprint before dtls".into()));
}
}
DtlsEvent::Data(v) => {
self.sctp.handle_input(self.last_now, &v);
}
}
}
if dtls_connected {
return Ok(Output::Event(Event::Connected));
}
while let Some(e) = self.sctp.poll() {
match e {
SctpEvent::Transmit { mut packets } => {
if let Some(v) = packets.front() {
if let Err(e) = self.dtls.handle_input(v) {
if e.is_would_block() {
self.sctp.push_back_transmit(packets);
break;
} else {
return Err(e.into());
}
}
packets.pop_front();
if !packets.is_empty() {
self.sctp.push_back_transmit(packets);
}
break;
}
}
SctpEvent::Open { id, label } => {
self.chan.ensure_channel_id_for(id);
let id = self.chan.channel_id_by_stream_id(id).unwrap();
return Ok(Output::Event(Event::ChannelOpen(id, label)));
}
SctpEvent::Close { id } => {
let Some(id) = self.chan.channel_id_by_stream_id(id) else {
warn!("Drop ChannelClose event for id: {:?}", id);
continue;
};
self.chan.remove_channel(id);
return Ok(Output::Event(Event::ChannelClose(id)));
}
SctpEvent::Data { id, binary, data } => {
let Some(id) = self.chan.channel_id_by_stream_id(id) else {
warn!("Drop ChannelData event for id: {:?}", id);
continue;
};
let cd = ChannelData { id, binary, data };
return Ok(Output::Event(Event::ChannelData(cd)));
}
}
}
if let Some(ev) = self.session.poll_event() {
return Ok(Output::Event(ev));
}
if let Some(ev) = self.session.poll_event_fallible()? {
return Ok(Output::Event(ev));
}
if let Some(e) = self.stats.as_mut().and_then(|s| s.poll_output()) {
return Ok(match e {
StatsEvent::Peer(s) => Output::Event(Event::PeerStats(s)),
StatsEvent::MediaIngress(s) => Output::Event(Event::MediaIngressStats(s)),
StatsEvent::MediaEgress(s) => Output::Event(Event::MediaEgressStats(s)),
});
}
if let Some(v) = self.ice.poll_transmit() {
return Ok(Output::Transmit(v));
}
if let Some(send) = &self.send_addr {
let datagram = None
.or_else(|| self.dtls.poll_datagram())
.or_else(|| self.session.poll_datagram(self.last_now));
if let Some(contents) = datagram {
let t = net::Transmit {
proto: send.proto,
source: send.source,
destination: send.destination,
contents,
};
return Ok(Output::Transmit(t));
}
}
let stats = self.stats.as_mut();
let time_and_reason = (None, Reason::NotHappening)
.soonest((self.dtls.poll_timeout(self.last_now), Reason::DTLS))
.soonest((self.ice.poll_timeout(), Reason::Ice))
.soonest(self.session.poll_timeout())
.soonest((self.sctp.poll_timeout(), Reason::Sctp))
.soonest((self.chan.poll_timeout(&self.sctp), Reason::Channel))
.soonest((stats.and_then(|s| s.poll_timeout()), Reason::Stats));
let time = time_and_reason.0.unwrap_or_else(not_happening);
let reason = time_and_reason.1;
let next = if time < self.last_now {
self.last_now
} else {
time
};
self.last_timeout_reason = reason;
Ok(Output::Timeout(next))
}
pub fn last_timeout_reason(&self) -> Reason {
self.last_timeout_reason
}
pub fn accepts(&self, input: &Input) -> bool {
let Input::Receive(_, r) = input else {
return true;
};
if let Some(send_addr) = &self.send_addr {
if r.source == send_addr.destination {
return true;
}
}
if let DatagramRecvInner::Stun(v) = &r.contents.inner {
return self.ice.accepts_message(v);
}
if self.ice.has_viable_remote_candidate(r.source) {
return true;
}
false
}
pub fn handle_input(&mut self, input: Input) -> Result<(), RtcError> {
if !self.alive {
return Ok(());
}
match input {
Input::Timeout(now) => self.do_handle_timeout(now)?,
Input::Receive(now, r) => {
self.do_handle_receive(now, r)?;
self.do_handle_timeout(now)?;
}
}
Ok(())
}
fn init_time(&mut self, now: Instant) {
if !self.need_init_time {
return;
}
let _ = now.to_unix_duration();
self.need_init_time = false;
}
fn do_handle_timeout(&mut self, now: Instant) -> Result<(), RtcError> {
self.init_time(now);
self.last_now = now;
self.ice.handle_timeout(now);
self.sctp.handle_timeout(now);
self.chan.handle_timeout(now, &mut self.sctp);
self.session.handle_timeout(now)?;
if let Some(stats) = &mut self.stats {
if stats.wants_timeout(now) {
let mut snapshot = StatsSnapshot::new(now);
snapshot.peer_rx = self.peer_bytes_rx;
snapshot.peer_tx = self.peer_bytes_tx;
self.session.visit_stats(now, &mut snapshot);
stats.do_handle_timeout(&mut snapshot);
}
}
Ok(())
}
fn do_handle_receive(&mut self, now: Instant, r: net::Receive) -> Result<(), RtcError> {
self.init_time(now);
trace!("IN {:?}", r);
self.last_now = now;
use DatagramRecvInner::*;
let bytes_rx = match r.contents.inner {
Stun(_) => 0,
Dtls(v) | Rtp(v) | Rtcp(v) => v.len(),
};
self.peer_bytes_rx += bytes_rx as u64;
match r.contents.inner {
Stun(stun) => {
let packet = io::StunPacket {
proto: r.proto,
source: r.source,
destination: r.destination,
message: stun,
};
self.ice.handle_packet(now, packet);
}
Dtls(dtls) => self.dtls.handle_receive(dtls)?,
Rtp(rtp) => self.session.handle_rtp_receive(now, rtp),
Rtcp(rtcp) => self.session.handle_rtcp_receive(now, rtcp),
}
Ok(())
}
pub fn channel(&mut self, id: ChannelId) -> Option<Channel<'_>> {
if !self.alive {
return None;
}
let sctp_stream_id = self.chan.stream_id_by_channel_id(id)?;
if !self.sctp.is_open(sctp_stream_id) {
return None;
}
Some(Channel::new(sctp_stream_id, self))
}
pub fn bwe(&mut self) -> Bwe {
Bwe(self)
}
fn is_correct_change_id(&self, change_id: usize) -> bool {
self.change_counter == change_id + 1
}
fn next_change_id(&mut self) -> usize {
let n = self.change_counter;
self.change_counter += 1;
n
}
pub fn codec_config(&self) -> &CodecConfig {
&self.session.codec_config
}
}
#[derive(Debug, Clone)]
pub struct RtcConfig {
local_ice_credentials: Option<IceCreds>,
dtls_cert: Option<DtlsCert>,
fingerprint_verification: bool,
ice_lite: bool,
codec_config: CodecConfig,
exts: ExtensionMap,
stats_interval: Option<Duration>,
bwe_config: Option<BweConfig>,
reordering_size_audio: usize,
reordering_size_video: usize,
send_buffer_audio: usize,
send_buffer_video: usize,
rtp_mode: bool,
enable_raw_packets: bool,
}
#[derive(Debug, Clone)]
struct BweConfig {
initial_bitrate: Bitrate,
enable_loss_controller: bool,
}
impl RtcConfig {
pub fn new() -> Self {
RtcConfig::default()
}
pub fn local_ice_credentials(&self) -> &Option<IceCreds> {
&self.local_ice_credentials
}
pub fn set_local_ice_credentials(mut self, local_ice_credentials: IceCreds) -> Self {
self.local_ice_credentials = Some(local_ice_credentials);
self
}
pub fn dtls_cert(&self) -> Option<&DtlsCert> {
self.dtls_cert.as_ref()
}
pub fn set_dtls_cert(mut self, dtls_cert: DtlsCert) -> Self {
self.dtls_cert = Some(dtls_cert);
self
}
pub fn set_ice_lite(mut self, enabled: bool) -> Self {
self.ice_lite = enabled;
self
}
pub fn fingerprint_verification(&self) -> bool {
self.fingerprint_verification
}
pub fn set_fingerprint_verification(mut self, enabled: bool) -> Self {
self.fingerprint_verification = enabled;
self
}
pub fn ice_lite(&self) -> bool {
self.ice_lite
}
pub fn codec_config(&mut self) -> &mut CodecConfig {
&mut self.codec_config
}
pub fn clear_codecs(mut self) -> Self {
self.codec_config.clear();
self
}
pub fn enable_opus(mut self, enabled: bool) -> Self {
self.codec_config.enable_opus(enabled);
self
}
pub fn enable_vp8(mut self, enabled: bool) -> Self {
self.codec_config.enable_vp8(enabled);
self
}
pub fn enable_h264(mut self, enabled: bool) -> Self {
self.codec_config.enable_h264(enabled);
self
}
pub fn enable_vp9(mut self, enabled: bool) -> Self {
self.codec_config.enable_vp9(enabled);
self
}
pub fn extension_map(&mut self) -> &mut ExtensionMap {
&mut self.exts
}
pub fn set_extension_map(mut self, exts: ExtensionMap) -> Self {
self.exts = exts;
self
}
pub fn clear_extension_map(mut self) -> Self {
self.exts.clear();
self
}
pub fn set_extension(mut self, id: u8, ext: Extension) -> Self {
self.exts.set(id, ext);
self
}
pub fn set_stats_interval(mut self, interval: Option<Duration>) -> Self {
self.stats_interval = interval;
self
}
pub fn stats_interval(&self) -> Option<Duration> {
self.stats_interval
}
pub fn enable_bwe(mut self, initial_estimate: Option<Bitrate>) -> Self {
match initial_estimate {
Some(b) => {
let conf = self.bwe_config.get_or_insert(BweConfig::new(b));
conf.initial_bitrate = b;
}
None => {
self.bwe_config = None;
}
}
self
}
pub fn enable_experimental_loss_based_bwe(mut self, enabled: bool) -> Self {
if let Some(c) = &mut self.bwe_config {
c.enable_loss_controller = enabled;
}
self
}
pub fn bwe_initial_bitrate(&self) -> Option<Bitrate> {
self.bwe_config.as_ref().map(|c| c.initial_bitrate)
}
pub fn set_reordering_size_audio(mut self, size: usize) -> Self {
self.reordering_size_audio = size;
self
}
pub fn reordering_size_audio(&self) -> usize {
self.reordering_size_audio
}
pub fn set_reordering_size_video(mut self, size: usize) -> Self {
self.reordering_size_video = size;
self
}
pub fn reordering_size_video(&self) -> usize {
self.reordering_size_video
}
pub fn set_send_buffer_audio(mut self, size: usize) -> Self {
assert!(size > 0);
self.send_buffer_audio = size;
self
}
pub fn send_buffer_audio(&self) -> usize {
self.send_buffer_audio
}
pub fn set_send_buffer_video(mut self, size: usize) -> Self {
self.send_buffer_video = size;
self
}
pub fn send_buffer_video(&self) -> usize {
self.send_buffer_video
}
pub fn set_rtp_mode(mut self, enabled: bool) -> Self {
self.rtp_mode = enabled;
self
}
pub fn rtp_mode(&self) -> bool {
self.rtp_mode
}
pub fn enable_raw_packets(mut self, enabled: bool) -> Self {
self.enable_raw_packets = enabled;
self
}
pub fn build(self) -> Rtc {
Rtc::new_from_config(self)
}
}
impl BweConfig {
fn new(initial_bitrate: Bitrate) -> Self {
Self {
initial_bitrate,
enable_loss_controller: false,
}
}
}
impl Default for RtcConfig {
fn default() -> Self {
Self {
local_ice_credentials: None,
dtls_cert: None,
fingerprint_verification: true,
ice_lite: false,
codec_config: CodecConfig::new_with_defaults(),
exts: ExtensionMap::standard(),
stats_interval: None,
bwe_config: None,
reordering_size_audio: 15,
reordering_size_video: 30,
send_buffer_audio: 50,
send_buffer_video: 1000,
rtp_mode: false,
enable_raw_packets: false,
}
}
}
impl PartialEq for Event {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::IceConnectionStateChange(l0), Self::IceConnectionStateChange(r0)) => l0 == r0,
(Self::MediaAdded(m0), Self::MediaAdded(m1)) => m0 == m1,
(Self::MediaData(m1), Self::MediaData(m2)) => m1 == m2,
(Self::ChannelOpen(l0, l1), Self::ChannelOpen(r0, r1)) => l0 == r0 && l1 == r1,
(Self::ChannelData(l0), Self::ChannelData(r0)) => l0 == r0,
(Self::ChannelClose(l0), Self::ChannelClose(r0)) => l0 == r0,
_ => false,
}
}
}
impl Eq for Event {}
impl fmt::Debug for Rtc {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Rtc").finish()
}
}
macro_rules! log_stat {
($name:expr, $($arg:expr),+) => {
#[cfg(feature = "_internal_dont_use_log_stats")]
{
use std::time::SystemTime;
use std::io::{self, Write};
let now = SystemTime::now();
let since_epoch = now.duration_since(SystemTime::UNIX_EPOCH).unwrap();
let unix_time_ms = since_epoch.as_millis();
let mut lock = io::stdout().lock();
write!(lock, "{} ", $name).expect("Failed to write to stdout");
$(
write!(lock, "{},", $arg).expect("Failed to write to stdout");
)+
writeln!(lock, "{}", unix_time_ms).expect("Failed to write to stdout");
}
};
}
pub(crate) use log_stat;
#[cfg(test)]
mod test {
use std::panic::UnwindSafe;
use super::*;
#[test]
fn rtc_is_send() {
fn is_send<T: Send>(_t: T) {}
fn is_sync<T: Sync>(_t: T) {}
is_send(Rtc::new());
is_sync(Rtc::new());
}
#[test]
fn rtc_is_unwind_safe() {
fn is_unwind_safe<T: UnwindSafe>(_t: T) {}
is_unwind_safe(Rtc::new());
}
#[test]
fn event_is_reasonably_sized() {
let n = std::mem::size_of::<Event>();
assert!(n < 450);
}
}
#[cfg(feature = "_internal_test_exports")]
#[allow(missing_docs)]
pub mod _internal_test_exports;