rtc/statistics/
mod.rs

1//! Statistics Model (WIP)
2
3/*use std::collections::HashMap;
4use std::time::SystemTime;
5
6use ice::agent::agent_stats::{CandidatePairStats, CandidateStats};
7//TODO:use ice::agent::Agent;
8use ice::candidate::{candidate_pair::CandidatePairState, CandidateType};
9use ice::network_type::NetworkType;
10use serde::{Deserialize, Deserializer, Serialize, Serializer};
11use stats_collector::StatsCollector;
12use std::time::Instant;
13
14use crate::peer_connection::certificate::RTCCertificate;
15use crate::peer_connection::transport::dtls_transport::dtls_fingerprint::RTCDtlsFingerprint;
16/*
17use crate::data_channel::data_channel_state::RTCDataChannelState;
18use crate::data_channel::RTCDataChannel;
19use crate::rtp_transceiver::rtp_codec::RTCRtpCodecParameters;
20use crate::rtp_transceiver::{PayloadType, SSRC};
21use crate::sctp_transport::RTCSctpTransport;*/
22
23mod serialize;
24pub mod stats_collector;
25
26/// SSRC represents a synchronization source
27/// A synchronization source is a randomly chosen
28/// value meant to be globally unique within a particular
29/// RTP session. Used to identify a single stream of media.
30/// <https://tools.ietf.org/html/rfc3550#section-3>
31#[allow(clippy::upper_case_acronyms)]
32pub type SSRC = u32;
33
34/// PayloadType identifies the format of the RTP payload and determines
35/// its interpretation by the application. Each codec in a RTP Session
36/// will have a different PayloadType
37/// <https://tools.ietf.org/html/rfc3550#section-3>
38pub type PayloadType = u8;
39
40#[derive(Debug, Serialize, Deserialize)]
41pub enum RTCStatsType {
42    #[serde(rename = "candidate-pair")]
43    CandidatePair,
44    #[serde(rename = "certificate")]
45    Certificate,
46    #[serde(rename = "codec")]
47    Codec,
48    #[serde(rename = "csrc")]
49    CSRC,
50    #[serde(rename = "data-channel")]
51    DataChannel,
52    #[serde(rename = "inbound-rtp")]
53    InboundRTP,
54    #[serde(rename = "local-candidate")]
55    LocalCandidate,
56    #[serde(rename = "outbound-rtp")]
57    OutboundRTP,
58    #[serde(rename = "peer-connection")]
59    PeerConnection,
60    #[serde(rename = "receiver")]
61    Receiver,
62    #[serde(rename = "remote-candidate")]
63    RemoteCandidate,
64    #[serde(rename = "remote-inbound-rtp")]
65    RemoteInboundRTP,
66    #[serde(rename = "remote-outbound-rtp")]
67    RemoteOutboundRTP,
68    #[serde(rename = "sender")]
69    Sender,
70    #[serde(rename = "transport")]
71    Transport,
72}
73
74pub enum SourceStatsType {
75    LocalCandidate(CandidateStats),
76    RemoteCandidate(CandidateStats),
77}
78
79#[derive(Debug)]
80pub enum StatsReportType {
81    CandidatePair(ICECandidatePairStats),
82    CertificateStats(CertificateStats),
83    Codec(CodecStats),
84    DataChannel(DataChannelStats),
85    LocalCandidate(ICECandidateStats),
86    PeerConnection(PeerConnectionStats),
87    RemoteCandidate(ICECandidateStats),
88    //TODO: SCTPTransport(ICETransportStats),
89    //TODO: Transport(ICETransportStats),
90    InboundRTP(InboundRTPStats),
91    OutboundRTP(OutboundRTPStats),
92    RemoteInboundRTP(RemoteInboundRTPStats),
93    RemoteOutboundRTP(RemoteOutboundRTPStats),
94}
95
96impl From<SourceStatsType> for StatsReportType {
97    fn from(stats: SourceStatsType) -> Self {
98        match stats {
99            SourceStatsType::LocalCandidate(stats) => StatsReportType::LocalCandidate(
100                ICECandidateStats::new(stats, RTCStatsType::LocalCandidate),
101            ),
102            SourceStatsType::RemoteCandidate(stats) => StatsReportType::RemoteCandidate(
103                ICECandidateStats::new(stats, RTCStatsType::RemoteCandidate),
104            ),
105        }
106    }
107}
108
109impl Serialize for StatsReportType {
110    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
111    where
112        S: Serializer,
113    {
114        match self {
115            StatsReportType::CandidatePair(stats) => stats.serialize(serializer),
116            StatsReportType::CertificateStats(stats) => stats.serialize(serializer),
117            StatsReportType::Codec(stats) => stats.serialize(serializer),
118            StatsReportType::DataChannel(stats) => stats.serialize(serializer),
119            StatsReportType::LocalCandidate(stats) => stats.serialize(serializer),
120            StatsReportType::PeerConnection(stats) => stats.serialize(serializer),
121            StatsReportType::RemoteCandidate(stats) => stats.serialize(serializer),
122            StatsReportType::SCTPTransport(stats) => stats.serialize(serializer),
123            StatsReportType::Transport(stats) => stats.serialize(serializer),
124            StatsReportType::InboundRTP(stats) => stats.serialize(serializer),
125            StatsReportType::OutboundRTP(stats) => stats.serialize(serializer),
126            StatsReportType::RemoteInboundRTP(stats) => stats.serialize(serializer),
127            StatsReportType::RemoteOutboundRTP(stats) => stats.serialize(serializer),
128        }
129    }
130}
131
132impl<'de> Deserialize<'de> for StatsReportType {
133    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
134    where
135        D: Deserializer<'de>,
136    {
137        let value = serde_json::Value::deserialize(deserializer)?;
138        let type_field = value
139            .get("type")
140            .ok_or_else(|| serde::de::Error::missing_field("type"))?;
141        let rtc_type: RTCStatsType = serde_json::from_value(type_field.clone()).map_err(|e| {
142            serde::de::Error::custom(format!(
143                "failed to deserialize RTCStatsType from the `type` field ({type_field}): {e}"
144            ))
145        })?;
146
147        match rtc_type {
148            RTCStatsType::CandidatePair => {
149                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
150                Ok(StatsReportType::CandidatePair(stats))
151            }
152            RTCStatsType::Certificate => {
153                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
154                Ok(StatsReportType::CertificateStats(stats))
155            }
156            RTCStatsType::Codec => {
157                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
158                Ok(StatsReportType::Codec(stats))
159            }
160            RTCStatsType::CSRC => {
161                todo!()
162            }
163            RTCStatsType::DataChannel => {
164                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
165                Ok(StatsReportType::DataChannel(stats))
166            }
167            RTCStatsType::InboundRTP => {
168                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
169                Ok(StatsReportType::InboundRTP(stats))
170            }
171            RTCStatsType::LocalCandidate => {
172                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
173                Ok(StatsReportType::LocalCandidate(stats))
174            }
175            RTCStatsType::OutboundRTP => {
176                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
177                Ok(StatsReportType::OutboundRTP(stats))
178            }
179            RTCStatsType::PeerConnection => {
180                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
181                Ok(StatsReportType::PeerConnection(stats))
182            }
183            RTCStatsType::Receiver => {
184                todo!()
185            }
186            RTCStatsType::RemoteCandidate => {
187                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
188                Ok(StatsReportType::RemoteCandidate(stats))
189            }
190            RTCStatsType::RemoteInboundRTP => {
191                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
192                Ok(StatsReportType::RemoteInboundRTP(stats))
193            }
194            RTCStatsType::RemoteOutboundRTP => {
195                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
196                Ok(StatsReportType::RemoteOutboundRTP(stats))
197            }
198            RTCStatsType::Sender => {
199                todo!()
200            }
201            RTCStatsType::Transport => {
202                let stats = serde_json::from_value(value).map_err(serde::de::Error::custom)?;
203                Ok(StatsReportType::Transport(stats))
204            }
205        }
206    }
207}
208
209#[derive(Debug)]
210pub struct StatsReport {
211    pub reports: HashMap<String, StatsReportType>,
212}
213
214impl From<StatsCollector> for StatsReport {
215    fn from(collector: StatsCollector) -> Self {
216        StatsReport {
217            reports: collector.reports,
218        }
219    }
220}
221
222impl Serialize for StatsReport {
223    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224    where
225        S: Serializer,
226    {
227        self.reports.serialize(serializer)
228    }
229}
230
231impl<'de> Deserialize<'de> for StatsReport {
232    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233    where
234        D: Deserializer<'de>,
235    {
236        let value = serde_json::Value::deserialize(deserializer)?;
237        let root = value
238            .as_object()
239            .ok_or(serde::de::Error::custom("root object missing"))?;
240
241        let mut reports = HashMap::new();
242        for (key, value) in root {
243            let report = serde_json::from_value(value.clone()).map_err(|e| {
244                serde::de::Error::custom(format!(
245                    "failed to deserialize `StatsReportType` from key={key}, value={value}: {e}"
246                ))
247            })?;
248            reports.insert(key.clone(), report);
249        }
250        Ok(Self { reports })
251    }
252}
253
254#[derive(Debug, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase")]
256pub struct ICECandidatePairStats {
257    // RTCStats
258    #[serde(with = "serialize::instant_to_epoch_seconds")]
259    pub timestamp: Instant,
260    #[serde(rename = "type")]
261    pub stats_type: RTCStatsType,
262    pub id: String,
263
264    // RTCIceCandidatePairStats
265    // TODO: Add `transportId`
266    pub local_candidate_id: String,
267    pub remote_candidate_id: String,
268    pub state: CandidatePairState,
269    pub nominated: bool,
270    pub packets_sent: u32,
271    pub packets_received: u32,
272    pub bytes_sent: u64,
273    pub bytes_received: u64,
274    #[serde(with = "serialize::instant_to_epoch_seconds")]
275    pub last_packet_sent_timestamp: Instant,
276    #[serde(with = "serialize::instant_to_epoch_seconds")]
277    pub last_packet_received_timestamp: Instant,
278    pub total_round_trip_time: f64,
279    pub current_round_trip_time: f64,
280    pub available_outgoing_bitrate: f64,
281    pub available_incoming_bitrate: f64,
282    pub requests_received: u64,
283    pub requests_sent: u64,
284    pub responses_received: u64,
285    pub responses_sent: u64,
286    pub consent_requests_sent: u64,
287    // TODO: Add `packetsDiscardedOnSend`
288    // TODO: Add `bytesDiscardedOnSend`
289
290    // Non-canon
291    pub circuit_breaker_trigger_count: u32,
292    #[serde(with = "serialize::instant_to_epoch_seconds")]
293    pub consent_expired_timestamp: Instant,
294    #[serde(with = "serialize::instant_to_epoch_seconds")]
295    pub first_request_timestamp: Instant,
296    #[serde(with = "serialize::instant_to_epoch_seconds")]
297    pub last_request_timestamp: Instant,
298    pub retransmissions_sent: u64,
299}
300
301impl From<CandidatePairStats> for ICECandidatePairStats {
302    fn from(stats: CandidatePairStats) -> Self {
303        ICECandidatePairStats {
304            available_incoming_bitrate: stats.available_incoming_bitrate,
305            available_outgoing_bitrate: stats.available_outgoing_bitrate,
306            bytes_received: stats.bytes_received,
307            bytes_sent: stats.bytes_sent,
308            circuit_breaker_trigger_count: stats.circuit_breaker_trigger_count,
309            consent_expired_timestamp: stats.consent_expired_timestamp,
310            consent_requests_sent: stats.consent_requests_sent,
311            current_round_trip_time: stats.current_round_trip_time,
312            first_request_timestamp: stats.first_request_timestamp,
313            id: format!("{}-{}", stats.local_candidate_id, stats.remote_candidate_id),
314            last_packet_received_timestamp: stats.last_packet_received_timestamp,
315            last_packet_sent_timestamp: stats.last_packet_sent_timestamp,
316            last_request_timestamp: stats.last_request_timestamp,
317            local_candidate_id: stats.local_candidate_id,
318            nominated: stats.nominated,
319            packets_received: stats.packets_received,
320            packets_sent: stats.packets_sent,
321            remote_candidate_id: stats.remote_candidate_id,
322            requests_received: stats.requests_received,
323            requests_sent: stats.requests_sent,
324            responses_received: stats.responses_received,
325            responses_sent: stats.responses_sent,
326            retransmissions_sent: stats.retransmissions_sent,
327            state: stats.state,
328            stats_type: RTCStatsType::CandidatePair,
329            timestamp: stats.timestamp,
330            total_round_trip_time: stats.total_round_trip_time,
331        }
332    }
333}
334
335#[derive(Debug, Serialize, Deserialize)]
336#[serde(rename_all = "camelCase")]
337pub struct ICECandidateStats {
338    // RTCStats
339    #[serde(with = "serialize::instant_to_epoch_seconds")]
340    pub timestamp: Instant,
341    #[serde(rename = "type")]
342    pub stats_type: RTCStatsType,
343    pub id: String,
344
345    // RTCIceCandidateStats
346    pub candidate_type: CandidateType,
347    pub deleted: bool,
348    pub ip: String,
349    pub network_type: NetworkType,
350    pub port: u16,
351    pub priority: u32,
352    pub relay_protocol: String,
353    pub url: String,
354}
355
356impl ICECandidateStats {
357    fn new(stats: CandidateStats, stats_type: RTCStatsType) -> Self {
358        ICECandidateStats {
359            candidate_type: stats.candidate_type,
360            deleted: stats.deleted,
361            id: stats.id,
362            ip: stats.ip,
363            network_type: stats.network_type,
364            port: stats.port,
365            priority: stats.priority,
366            relay_protocol: stats.relay_protocol,
367            stats_type,
368            timestamp: stats.timestamp,
369            url: stats.url,
370        }
371    }
372}
373
374/*TODO:
375#[derive(Debug, Serialize, Deserialize)]
376#[serde(rename_all = "camelCase")]
377pub struct ICETransportStats {
378    // RTCStats
379    #[serde(with = "serialize::instant_to_epoch_seconds")]
380    pub timestamp: Instant,
381    #[serde(rename = "type")]
382    pub stats_type: RTCStatsType,
383    pub id: String,
384
385    // Non-canon
386    pub bytes_received: usize,
387    pub bytes_sent: usize,
388}
389
390impl ICETransportStats {
391    pub(crate) fn new(id: String, agent: Arc<Agent>) -> Self {
392        ICETransportStats {
393            id,
394            bytes_received: agent.get_bytes_received(),
395            bytes_sent: agent.get_bytes_sent(),
396            stats_type: RTCStatsType::Transport,
397            timestamp: Instant::now(),
398        }
399    }
400}*/
401
402#[derive(Debug, Serialize, Deserialize)]
403#[serde(rename_all = "camelCase")]
404pub struct CertificateStats {
405    // RTCStats
406    #[serde(with = "serialize::instant_to_epoch_seconds")]
407    pub timestamp: Instant,
408    #[serde(rename = "type")]
409    pub stats_type: RTCStatsType,
410    pub id: String,
411
412    // RTCCertificateStats
413    pub fingerprint: String,
414    pub fingerprint_algorithm: String,
415    // TODO: Add `base64Certificate` and `issuerCertificateId`.
416}
417
418impl CertificateStats {
419    pub(crate) fn new(cert: &RTCCertificate, fingerprint: RTCDtlsFingerprint) -> Self {
420        CertificateStats {
421            // TODO: base64_certificate
422            fingerprint: fingerprint.value,
423            fingerprint_algorithm: fingerprint.algorithm,
424            id: cert.stats_id.clone(),
425            // TODO: issuer_certificate_id
426            stats_type: RTCStatsType::Certificate,
427            timestamp: Instant::now(),
428        }
429    }
430}
431
432#[derive(Debug, Serialize, Deserialize)]
433#[serde(rename_all = "camelCase")]
434pub struct CodecStats {
435    // RTCStats
436    #[serde(with = "serialize::instant_to_epoch_seconds")]
437    pub timestamp: Instant,
438    #[serde(rename = "type")]
439    pub stats_type: RTCStatsType,
440    pub id: String,
441
442    // RTCCodecStats
443    pub payload_type: PayloadType,
444    pub mime_type: String,
445    pub channels: u16,
446    pub clock_rate: u32,
447    pub sdp_fmtp_line: String,
448    // TODO: Add `transportId`
449}
450
451impl From<&RTCRtpCodecParameters> for CodecStats {
452    fn from(codec: &RTCRtpCodecParameters) -> Self {
453        CodecStats {
454            channels: codec.capability.channels,
455            clock_rate: codec.capability.clock_rate,
456            id: codec.stats_id.clone(),
457            mime_type: codec.capability.mime_type.clone(),
458            payload_type: codec.payload_type,
459            sdp_fmtp_line: codec.capability.sdp_fmtp_line.clone(),
460            stats_type: RTCStatsType::Codec,
461            timestamp: Instant::now(),
462        }
463    }
464}
465
466#[derive(Debug, Serialize, Deserialize)]
467#[serde(rename_all = "camelCase")]
468pub struct DataChannelStats {
469    // RTCStats
470    #[serde(with = "serialize::instant_to_epoch_seconds")]
471    pub timestamp: Instant,
472    #[serde(rename = "type")]
473    pub stats_type: RTCStatsType,
474    pub id: String,
475
476    // RTCDataChannelStats
477    pub bytes_received: usize,
478    pub bytes_sent: usize,
479    pub data_channel_identifier: u16,
480    pub label: String,
481    pub messages_received: usize,
482    pub messages_sent: usize,
483    pub protocol: String,
484    pub state: RTCDataChannelState,
485}
486
487impl DataChannelStats {
488    pub(crate) async fn from(data_channel: &RTCDataChannel) -> Self {
489        let state = data_channel.ready_state();
490
491        let mut bytes_received = 0;
492        let mut bytes_sent = 0;
493        let mut messages_received = 0;
494        let mut messages_sent = 0;
495
496        let lock = data_channel.data_channel.lock().await;
497
498        if let Some(internal) = &*lock {
499            bytes_received = internal.bytes_received();
500            bytes_sent = internal.bytes_sent();
501            messages_received = internal.messages_received();
502            messages_sent = internal.messages_sent();
503        }
504
505        Self {
506            bytes_received,
507            bytes_sent,
508            data_channel_identifier: data_channel.id(), // TODO: "The value is initially null"
509            id: data_channel.stats_id.clone(),
510            label: data_channel.label.clone(),
511            messages_received,
512            messages_sent,
513            protocol: data_channel.protocol.clone(),
514            state,
515            stats_type: RTCStatsType::DataChannel,
516            timestamp: Instant::now(),
517        }
518    }
519}
520
521#[derive(Debug, Serialize, Deserialize)]
522#[serde(rename_all = "camelCase")]
523pub struct PeerConnectionStats {
524    // RTCStats
525    #[serde(with = "serialize::instant_to_epoch_seconds")]
526    pub timestamp: Instant,
527    #[serde(rename = "type")]
528    pub stats_type: RTCStatsType,
529    pub id: String,
530
531    // RTCPeerConnectionStats
532    pub data_channels_closed: u32,
533    pub data_channels_opened: u32,
534
535    // Non-canon
536    pub data_channels_accepted: u32,
537    pub data_channels_requested: u32,
538}
539
540impl PeerConnectionStats {
541    pub fn new(transport: &RTCSctpTransport, stats_id: String, data_channels_closed: u32) -> Self {
542        PeerConnectionStats {
543            data_channels_accepted: transport.data_channels_accepted(),
544            data_channels_closed,
545            data_channels_opened: transport.data_channels_opened(),
546            data_channels_requested: transport.data_channels_requested(),
547            id: stats_id,
548            stats_type: RTCStatsType::PeerConnection,
549            timestamp: Instant::now(),
550        }
551    }
552}
553
554#[derive(Debug, Serialize, Deserialize)]
555#[serde(rename_all = "camelCase")]
556pub struct InboundRTPStats {
557    // RTCStats
558    #[serde(with = "serialize::instant_to_epoch_seconds")]
559    pub timestamp: Instant,
560    #[serde(rename = "type")]
561    pub stats_type: RTCStatsType,
562    pub id: String,
563
564    // RTCRtpStreamStats
565    pub ssrc: SSRC,
566    pub kind: String, // Either "video" or "audio"
567    // TODO: Add transportId
568    // TODO: Add codecId
569
570    // RTCReceivedRtpStreamStats
571    pub packets_received: u64,
572    // TODO: packetsLost
573    // TODO: jitter(maybe, might be uattainable for the same reason as `framesDropped`)
574    // NB: `framesDropped` can't be produced since we aren't decoding, might be worth introducing a
575    // way for consumers to control this in the future.
576
577    // RTCInboundRtpStreamStats
578    pub track_identifier: String,
579    pub mid: String,
580    // TODO: `remoteId`
581    // NB: `framesDecoded`, `frameWidth`, frameHeight`, `framesPerSecond`, `qpSum`,
582    // `totalDecodeTime`, `totalInterFrameDelay`, and `totalSquaredInterFrameDelay` are all decoder
583    // specific values and can't be produced since we aren't decoding.
584    pub last_packet_received_timestamp: Option<SystemTime>,
585    pub header_bytes_received: u64,
586    // TODO: `packetsDiscarded`. This value only makes sense if we have jitter buffer, which we
587    // cannot assume.
588    // TODO: `fecPacketsReceived`, `fecPacketsDiscarded`
589    pub bytes_received: u64,
590    pub nack_count: u64,
591    pub fir_count: Option<u64>,
592    pub pli_count: Option<u64>,
593    // NB: `totalProcessingDelay`, `estimatedPlayoutTimestamp`, `jitterBufferDelay`,
594    // `jitterBufferTargetDelay`, `jitterBufferEmittedCount`, `jitterBufferMinimumDelay`,
595    // `totalSamplesReceived`, `concealedSamples`, `silentConcealedSamples`, `concealmentEvents`,
596    // `insertedSamplesForDeceleration`, `removedSamplesForAcceleration`, `audioLevel`,
597    // `totalAudioEneregy`, `totalSampleDuration`, `framesReceived, and `decoderImplementation` are
598    // all decoder specific and can't be produced since we aren't decoding.
599}
600
601#[derive(Debug, Serialize, Deserialize)]
602#[serde(rename_all = "camelCase")]
603pub struct OutboundRTPStats {
604    // RTCStats
605    #[serde(with = "serialize::instant_to_epoch_seconds")]
606    pub timestamp: Instant,
607    #[serde(rename = "type")]
608    pub stats_type: RTCStatsType,
609    pub id: String,
610
611    // RTCRtpStreamStats
612    pub ssrc: SSRC,
613    pub kind: String, // Either "video" or "audio"
614    // TODO: Add transportId
615    // TODO: Add codecId
616
617    // RTCSentRtpStreamStats
618    pub packets_sent: u64,
619    pub bytes_sent: u64,
620
621    // RTCOutboundRtpStreamStats
622    // NB: non-canon in browsers this is available via `RTCMediaSourceStats` which we are unlikely to implement
623    pub track_identifier: String,
624    pub mid: String,
625    // TODO: `mediaSourceId` and `remoteId`
626    pub rid: Option<String>,
627    pub header_bytes_sent: u64,
628    // TODO: `retransmittedPacketsSent` and `retransmittedPacketsSent`
629    // NB: `targetBitrate`, `totalEncodedBytesTarget`, `frameWidth` `frameHeight`, `framesPerSecond`, `framesSent`,
630    // `hugeFramesSent`, `framesEncoded`, `keyFramesEncoded`, `qpSum`, and `totalEncodeTime` are
631    // all encoder specific and can't be produced snce we aren't encoding.
632    // TODO: `totalPacketSendDelay` time from `TrackLocalWriter::write_rtp` to being written to
633    // socket.
634
635    // NB: `qualityLimitationReason`, `qualityLimitationDurations`, and `qualityLimitationResolutionChanges` are all
636    // encoder specific and can't be produced since we aren't encoding.
637    pub nack_count: u64,
638    pub fir_count: Option<u64>,
639    pub pli_count: Option<u64>,
640    // NB: `encoderImplementation` is encoder specific and can't be produced since we aren't
641    // encoding.
642}
643
644#[derive(Debug, Serialize, Deserialize)]
645#[serde(rename_all = "camelCase")]
646pub struct RemoteInboundRTPStats {
647    // RTCStats
648    #[serde(with = "serialize::instant_to_epoch_seconds")]
649    pub timestamp: Instant,
650    #[serde(rename = "type")]
651    pub stats_type: RTCStatsType,
652    pub id: String,
653
654    // RTCRtpStreamStats
655    pub ssrc: SSRC,
656    pub kind: String, // Either "video" or "audio"
657    // TODO: Add transportId
658    // TODO: Add codecId
659
660    // RTCReceivedRtpStreamStats
661    pub packets_received: u64,
662    pub packets_lost: i64,
663    // TODO: jitter(maybe, might be uattainable for the same reason as `framesDropped`)
664    // NB: `framesDropped` can't be produced since we aren't decoding, might be worth introducing a
665    // way for consumers to control this in the future.
666
667    // RTCRemoteInboundRtpStreamStats
668    pub local_id: String,
669    pub round_trip_time: Option<f64>,
670    pub total_round_trip_time: f64,
671    pub fraction_lost: f64,
672    pub round_trip_time_measurements: u64,
673}
674
675#[derive(Debug, Serialize, Deserialize)]
676#[serde(rename_all = "camelCase")]
677pub struct RemoteOutboundRTPStats {
678    // RTCStats
679    #[serde(with = "serialize::instant_to_epoch_seconds")]
680    pub timestamp: Instant,
681    #[serde(rename = "type")]
682    pub stats_type: RTCStatsType,
683    pub id: String,
684
685    // RTCRtpStreamStats
686    pub ssrc: SSRC,
687    pub kind: String, // Either "video" or "audio"
688    // TODO: Add transportId
689    // TODO: Add codecId
690
691    // RTCSentRtpStreamStats
692    pub packets_sent: u64,
693    pub bytes_sent: u64,
694
695    // RTCRemoteOutboundRtpStreamStats
696    pub local_id: String,
697    // TODO: `remote_timestamp`
698    pub round_trip_time: Option<f64>,
699    pub reports_sent: u64,
700    pub total_round_trip_time: f64,
701    pub round_trip_time_measurements: u64,
702}
703*/