rvoip-rtp-core 0.2.3

RTP/RTCP protocol implementation for the rvoip stack
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
//! Server transport module
//!
//! This module contains the implementation of server-side transport logic.

use async_trait::async_trait;
use std::collections::HashMap;
use std::net::SocketAddr;
use std::time::Duration;
use tokio::sync::broadcast;

use crate::api::client::transport::RtcpStats;
use crate::api::client::transport::VoipMetrics;
use crate::api::common::config::SecurityInfo;
use crate::api::common::error::MediaTransportError;
use crate::api::common::events::MediaEventCallback;
use crate::api::common::extension::ExtensionFormat;
use crate::api::common::frame::MediaFrame;
use crate::api::common::stats::MediaStats;
use crate::{CsrcMapping, RtpCsrc, RtpSsrc};

mod core;
pub mod default;
// media module moved to media-core
mod rtcp;
mod security;
mod ssrc;
mod stats;
#[cfg(test)]
mod tests;
mod util;

// Export implementations
pub use default::DefaultMediaTransportServer;

/// Client information
#[derive(Debug, Clone)]
pub struct ClientInfo {
    /// Client identifier
    pub id: String,
    /// Client address
    pub address: SocketAddr,
    /// Is the connection secure
    pub secure: bool,
    /// Security information (if secure)
    pub security_info: Option<SecurityInfo>,
    /// Is the client connected
    pub connected: bool,
}

/// RTP Header Extension Data
#[derive(Debug, Clone)]
pub struct HeaderExtension {
    /// The ID of the extension (1-14 for one-byte header, 1-255 for two-byte header)
    pub id: u8,

    /// The URI that identifies this extension type
    pub uri: String,

    /// The data of the extension
    pub data: Vec<u8>,
}

/// Server-side media transport: one RTP endpoint accepting media
/// streams from many connected clients.
///
/// # When to use this
///
/// This is the high-density RTP server interface — designed for
/// deployments where one process terminates RTP from many concurrent
/// clients on a small set of ports, demultiplexes them by SSRC /
/// source address, and routes / mixes / transcodes / forwards.
/// Concrete use cases:
///
/// - **MCU / SFU media servers** — one server endpoint accepts N
///   client RTP streams, mixes (MCU) or selectively forwards (SFU).
/// - **Media gateways** — terminating RTP on one side, transcoding,
///   re-emitting on another network leg.
/// - **Application servers** with shared-port RTP termination
///   (recording bridges, IVR farms).
///
/// # Relationship to `RtpSession` / `MediaSessionController`
///
/// The standard rvoip media plane today uses a *per-dialog*
/// [`RtpSession`](crate::session::RtpSession) — one UDP socket pair
/// per call, managed by `media-core::MediaSessionController`. That's
/// the right model for a typical softphone or B2BUA where each call
/// gets its own RTP endpoint.
///
/// `MediaTransportServer` is the *one-to-many* alternative, kept
/// available for the higher-density deployments above. It is wired
/// internally inside rtp-core (`DefaultMediaTransportServer` etc.)
/// but **not currently exercised by media-core or session-core in
/// the default call flow** — those still drive `RtpSession`
/// directly through [`UdpRtpTransport`](crate::transport::UdpRtpTransport).
///
/// As of Phase C22 the internal `clients` map is a `DashMap` (was
/// `Arc<RwLock<HashMap>>`), so the per-frame egress / RTCP /
/// security paths scale to N concurrent clients without an outer
/// async writer lock — see `frame.rs`, `connection.rs`,
/// `ssrc/demux.rs`, `rtcp/*`, `stats.rs` for the consumer sites.
#[async_trait]
pub trait MediaTransportServer: Send + Sync + Clone {
    /// Start the server
    ///
    /// This binds to the configured address and starts listening for
    /// incoming client connections.
    async fn start(&self) -> Result<(), MediaTransportError>;

    /// Stop the server
    ///
    /// This stops listening for new connections and disconnects all
    /// existing clients.
    async fn stop(&self) -> Result<(), MediaTransportError>;

    /// Get the local address currently bound to
    ///
    /// This returns the actual bound address of the transport, which may be different
    /// from the configured address if dynamic port allocation is used. When using
    /// dynamic port allocation, this method should be called after start() to
    /// get the allocated port.
    ///
    /// This information is needed for SDP exchange in signaling protocols.
    async fn get_local_address(&self) -> Result<SocketAddr, MediaTransportError>;

    /// Send a media frame to a specific client
    ///
    /// If the client is not connected, this will return an error.
    async fn send_frame_to(
        &self,
        client_id: &str,
        frame: MediaFrame,
    ) -> Result<(), MediaTransportError>;

    /// Broadcast a media frame to all connected clients
    async fn broadcast_frame(&self, frame: MediaFrame) -> Result<(), MediaTransportError>;

    /// Receive a media frame from any client
    ///
    /// This returns the client ID and the frame received.
    async fn receive_frame(&self) -> Result<(String, MediaFrame), MediaTransportError>;

    /// Get a persistent frame receiver for receiving multiple frames
    ///
    /// This returns a receiver that can be used multiple times without creating
    /// new broadcast subscribers. This is more efficient than calling receive_frame()
    /// repeatedly.
    fn get_frame_receiver(&self) -> broadcast::Receiver<(String, MediaFrame)>;

    /// Get a list of connected clients
    async fn get_clients(&self) -> Result<Vec<ClientInfo>, MediaTransportError>;

    /// Disconnect a specific client
    async fn disconnect_client(&self, client_id: &str) -> Result<(), MediaTransportError>;

    /// Register a callback for transport events
    async fn on_event(&self, callback: MediaEventCallback) -> Result<(), MediaTransportError>;

    /// Register a callback for client connection events
    async fn on_client_connected(
        &self,
        callback: Box<dyn Fn(ClientInfo) + Send + Sync>,
    ) -> Result<(), MediaTransportError>;

    /// Register a callback for client disconnection events
    async fn on_client_disconnected(
        &self,
        callback: Box<dyn Fn(ClientInfo) + Send + Sync>,
    ) -> Result<(), MediaTransportError>;

    /// Get statistics for all clients
    async fn get_stats(&self) -> Result<MediaStats, MediaTransportError>;

    /// Get statistics for a specific client
    async fn get_client_stats(&self, client_id: &str) -> Result<MediaStats, MediaTransportError>;

    /// Get security information for SDP exchange
    async fn get_security_info(&self) -> Result<SecurityInfo, MediaTransportError>;

    /// Send an RTCP Receiver Report to all clients
    ///
    /// This sends a Receiver Report RTCP packet to all connected clients. This can be
    /// useful to force an immediate quality report instead of waiting for the
    /// automatic interval-based reports.
    async fn send_rtcp_receiver_report(&self) -> Result<(), MediaTransportError>;

    /// Send an RTCP Sender Report to all clients
    ///
    /// This sends a Sender Report RTCP packet to all connected clients. This can be
    /// useful to force an immediate quality report instead of waiting for the
    /// automatic interval-based reports.
    async fn send_rtcp_sender_report(&self) -> Result<(), MediaTransportError>;

    /// Send an RTCP Receiver Report to a specific client
    ///
    /// This sends a Receiver Report RTCP packet to the specified client. This can be
    /// useful to force an immediate quality report for a specific client.
    async fn send_rtcp_receiver_report_to_client(
        &self,
        client_id: &str,
    ) -> Result<(), MediaTransportError>;

    /// Send an RTCP Sender Report to a specific client
    ///
    /// This sends a Sender Report RTCP packet to the specified client. This can be
    /// useful to force an immediate quality report for a specific client.
    async fn send_rtcp_sender_report_to_client(
        &self,
        client_id: &str,
    ) -> Result<(), MediaTransportError>;

    /// Get detailed RTCP statistics for all clients
    ///
    /// This returns detailed quality metrics gathered from RTCP reports
    /// including jitter, packet loss, and round-trip time, aggregated across all clients.
    async fn get_rtcp_stats(&self) -> Result<RtcpStats, MediaTransportError>;

    /// Get detailed RTCP statistics for a specific client
    ///
    /// This returns detailed quality metrics gathered from RTCP reports
    /// including jitter, packet loss, and round-trip time for a specific client.
    async fn get_client_rtcp_stats(
        &self,
        client_id: &str,
    ) -> Result<RtcpStats, MediaTransportError>;

    /// Set the RTCP report interval
    ///
    /// This sets how frequently RTCP reports are sent. The default is usually
    /// 5% of the session bandwidth, but this can be adjusted for more or less
    /// frequent reporting.
    async fn set_rtcp_interval(&self, interval: Duration) -> Result<(), MediaTransportError>;

    /// Send an RTCP Application-Defined (APP) packet to all clients
    ///
    /// This sends an RTCP APP packet with the specified name and application data
    /// to all connected clients. APP packets are used for application-specific
    /// purposes and allow custom data to be exchanged between endpoints.
    ///
    /// - `name`: A four-character ASCII name to identify the application
    /// - `data`: The application-specific data to send
    async fn send_rtcp_app(&self, name: &str, data: Vec<u8>) -> Result<(), MediaTransportError>;

    /// Send an RTCP Application-Defined (APP) packet to a specific client
    ///
    /// This sends an RTCP APP packet with the specified name and application data
    /// to the specified client. APP packets are used for application-specific
    /// purposes and allow custom data to be exchanged between endpoints.
    ///
    /// - `client_id`: The ID of the client to send the packet to
    /// - `name`: A four-character ASCII name to identify the application
    /// - `data`: The application-specific data to send
    async fn send_rtcp_app_to_client(
        &self,
        client_id: &str,
        name: &str,
        data: Vec<u8>,
    ) -> Result<(), MediaTransportError>;

    /// Send an RTCP Goodbye (BYE) packet to all clients
    ///
    /// This sends an RTCP BYE packet with an optional reason for leaving.
    /// BYE packets are used to indicate that a source is no longer active.
    ///
    /// - `reason`: An optional reason string for leaving
    async fn send_rtcp_bye(&self, reason: Option<String>) -> Result<(), MediaTransportError>;

    /// Send an RTCP Goodbye (BYE) packet to a specific client
    ///
    /// This sends an RTCP BYE packet with an optional reason for leaving
    /// to the specified client. BYE packets are used to indicate that a
    /// source is no longer active.
    ///
    /// - `client_id`: The ID of the client to send the packet to
    /// - `reason`: An optional reason string for leaving
    async fn send_rtcp_bye_to_client(
        &self,
        client_id: &str,
        reason: Option<String>,
    ) -> Result<(), MediaTransportError>;

    /// Send an RTCP Extended Report (XR) packet with VoIP metrics to all clients
    ///
    /// This sends an RTCP XR packet with VoIP metrics for the specified SSRC
    /// to all connected clients. XR packets are used to report extended
    /// statistics beyond what is available in standard Sender/Receiver Reports.
    ///
    /// - `metrics`: The VoIP metrics to include in the XR packet
    async fn send_rtcp_xr_voip_metrics(
        &self,
        metrics: VoipMetrics,
    ) -> Result<(), MediaTransportError>;

    /// Send an RTCP Extended Report (XR) packet with VoIP metrics to a specific client
    ///
    /// This sends an RTCP XR packet with VoIP metrics for the specified SSRC
    /// to the specified client. XR packets are used to report extended
    /// statistics beyond what is available in standard Sender/Receiver Reports.
    ///
    /// - `client_id`: The ID of the client to send the packet to
    /// - `metrics`: The VoIP metrics to include in the XR packet
    async fn send_rtcp_xr_voip_metrics_to_client(
        &self,
        client_id: &str,
        metrics: VoipMetrics,
    ) -> Result<(), MediaTransportError>;

    // CSRC Management API Methods

    /// Check if CSRC management is enabled
    ///
    /// Returns true if CSRC management is enabled for this server.
    async fn is_csrc_management_enabled(&self) -> Result<bool, MediaTransportError>;

    /// Enable CSRC management
    ///
    /// This enables the CSRC management feature if it was not enabled
    /// in the configuration. Returns true if successfully enabled.
    async fn enable_csrc_management(&self) -> Result<bool, MediaTransportError>;

    /// Add a CSRC mapping for contributing sources
    ///
    /// Maps an original SSRC to a CSRC value with optional metadata.
    /// This is used in conferencing scenarios where multiple sources
    /// contribute to a single mixed stream.
    ///
    /// - `mapping`: The CSRC mapping to add
    async fn add_csrc_mapping(&self, mapping: CsrcMapping) -> Result<(), MediaTransportError>;

    /// Add a simple SSRC to CSRC mapping
    ///
    /// Simplified version that just maps an SSRC to a CSRC without metadata.
    ///
    /// - `original_ssrc`: The original SSRC to map
    /// - `csrc`: The CSRC value to map to
    async fn add_simple_csrc_mapping(
        &self,
        original_ssrc: RtpSsrc,
        csrc: RtpCsrc,
    ) -> Result<(), MediaTransportError>;

    /// Remove a CSRC mapping by SSRC
    ///
    /// Removes a mapping for the specified SSRC.
    ///
    /// - `original_ssrc`: The original SSRC to remove mapping for
    async fn remove_csrc_mapping_by_ssrc(
        &self,
        original_ssrc: RtpSsrc,
    ) -> Result<Option<CsrcMapping>, MediaTransportError>;

    /// Get a CSRC mapping by SSRC
    ///
    /// Returns the mapping for the specified SSRC if it exists.
    ///
    /// - `original_ssrc`: The original SSRC to get mapping for
    async fn get_csrc_mapping_by_ssrc(
        &self,
        original_ssrc: RtpSsrc,
    ) -> Result<Option<CsrcMapping>, MediaTransportError>;

    /// Get all CSRC mappings
    ///
    /// Returns all CSRC mappings currently registered.
    async fn get_all_csrc_mappings(&self) -> Result<Vec<CsrcMapping>, MediaTransportError>;

    /// Get CSRC values for active sources
    ///
    /// Returns the CSRC values for the specified active SSRCs.
    ///
    /// - `active_ssrcs`: The list of active SSRCs to get CSRCs for
    async fn get_active_csrcs(
        &self,
        active_ssrcs: &[RtpSsrc],
    ) -> Result<Vec<RtpCsrc>, MediaTransportError>;

    // Header Extensions API Methods

    /// Check if header extensions are enabled
    ///
    /// Returns true if header extensions are enabled for this server.
    async fn is_header_extensions_enabled(&self) -> Result<bool, MediaTransportError>;

    /// Enable header extensions with the specified format
    ///
    /// This enables the header extensions feature if it was not enabled
    /// in the configuration. Returns true if successfully enabled.
    ///
    /// - `format`: The header extension format to use (One-byte or Two-byte)
    async fn enable_header_extensions(
        &self,
        format: ExtensionFormat,
    ) -> Result<bool, MediaTransportError>;

    /// Configure a header extension mapping
    ///
    /// Maps an extension ID to a URI that identifies its type.
    ///
    /// - `id`: The extension ID to map (1-14 for one-byte, 1-255 for two-byte)
    /// - `uri`: The URI that identifies this extension type
    async fn configure_header_extension(
        &self,
        id: u8,
        uri: String,
    ) -> Result<(), MediaTransportError>;

    /// Configure multiple header extension mappings
    ///
    /// Maps extension IDs to URIs that identify their types.
    ///
    /// - `mappings`: A HashMap of extension IDs to URIs
    async fn configure_header_extensions(
        &self,
        mappings: HashMap<u8, String>,
    ) -> Result<(), MediaTransportError>;

    /// Add a header extension to the next outgoing packet for a specific client
    ///
    /// - `client_id`: The ID of the client to add the extension for
    /// - `extension`: The header extension to add
    async fn add_header_extension_for_client(
        &self,
        client_id: &str,
        extension: HeaderExtension,
    ) -> Result<(), MediaTransportError>;

    /// Add a header extension to the next outgoing packet for all clients
    ///
    /// - `extension`: The header extension to add
    async fn add_header_extension_for_all_clients(
        &self,
        extension: HeaderExtension,
    ) -> Result<(), MediaTransportError>;

    /// Add audio level header extension for a specific client
    ///
    /// - `client_id`: The ID of the client to add the extension for
    /// - `voice_activity`: true if voice activity is detected, false otherwise
    /// - `level`: audio level in dB below full scale (0-127)
    async fn add_audio_level_extension_for_client(
        &self,
        client_id: &str,
        voice_activity: bool,
        level: u8,
    ) -> Result<(), MediaTransportError>;

    /// Add audio level header extension for all clients
    ///
    /// - `voice_activity`: true if voice activity is detected, false otherwise
    /// - `level`: audio level in dB below full scale (0-127)
    async fn add_audio_level_extension_for_all_clients(
        &self,
        voice_activity: bool,
        level: u8,
    ) -> Result<(), MediaTransportError>;

    /// Add video orientation header extension for a specific client
    ///
    /// - `client_id`: The ID of the client to add the extension for
    /// - `camera_front_facing`: true if camera is front-facing, false otherwise
    /// - `camera_flipped`: true if camera is flipped, false otherwise
    /// - `rotation`: rotation in degrees (0, 90, 180, or 270)
    async fn add_video_orientation_extension_for_client(
        &self,
        client_id: &str,
        camera_front_facing: bool,
        camera_flipped: bool,
        rotation: u16,
    ) -> Result<(), MediaTransportError>;

    /// Add video orientation header extension for all clients
    ///
    /// - `camera_front_facing`: true if camera is front-facing, false otherwise
    /// - `camera_flipped`: true if camera is flipped, false otherwise
    /// - `rotation`: rotation in degrees (0, 90, 180, or 270)
    async fn add_video_orientation_extension_for_all_clients(
        &self,
        camera_front_facing: bool,
        camera_flipped: bool,
        rotation: u16,
    ) -> Result<(), MediaTransportError>;

    /// Add transport-cc header extension for a specific client
    ///
    /// - `client_id`: The ID of the client to add the extension for
    /// - `sequence_number`: transport-wide sequence number
    async fn add_transport_cc_extension_for_client(
        &self,
        client_id: &str,
        sequence_number: u16,
    ) -> Result<(), MediaTransportError>;

    /// Add transport-cc header extension for all clients
    ///
    /// - `sequence_number`: transport-wide sequence number
    async fn add_transport_cc_extension_for_all_clients(
        &self,
        sequence_number: u16,
    ) -> Result<(), MediaTransportError>;

    /// Get all header extensions received from a specific client
    ///
    /// - `client_id`: The ID of the client to get extensions from
    async fn get_received_header_extensions(
        &self,
        client_id: &str,
    ) -> Result<Vec<HeaderExtension>, MediaTransportError>;

    /// Get audio level header extension from a specific client
    ///
    /// - `client_id`: The ID of the client to get the extension from
    ///
    /// Returns a tuple of (voice_activity, level) if the extension is found
    async fn get_received_audio_level(
        &self,
        client_id: &str,
    ) -> Result<Option<(bool, u8)>, MediaTransportError>;

    /// Get video orientation header extension from a specific client
    ///
    /// - `client_id`: The ID of the client to get the extension from
    ///
    /// Returns a tuple of (camera_front_facing, camera_flipped, rotation) if the extension is found
    async fn get_received_video_orientation(
        &self,
        client_id: &str,
    ) -> Result<Option<(bool, bool, u16)>, MediaTransportError>;

    /// Get transport-cc header extension from a specific client
    ///
    /// - `client_id`: The ID of the client to get the extension from
    ///
    /// Returns the transport-wide sequence number if the extension is found
    async fn get_received_transport_cc(
        &self,
        client_id: &str,
    ) -> Result<Option<u16>, MediaTransportError>;

    /// Check if SSRC demultiplexing is enabled
    async fn is_ssrc_demultiplexing_enabled(&self) -> Result<bool, MediaTransportError>;

    /// Enable SSRC demultiplexing
    async fn enable_ssrc_demultiplexing(&self) -> Result<bool, MediaTransportError>;

    /// Register a client SSRC
    async fn register_client_ssrc(
        &self,
        client_id: &str,
        ssrc: u32,
    ) -> Result<bool, MediaTransportError>;

    /// Get all SSRCs for a client
    async fn get_client_ssrcs(&self, client_id: &str) -> Result<Vec<u32>, MediaTransportError>;

    /// Update the CNAME for a source
    async fn update_csrc_cname(
        &self,
        original_ssrc: RtpSsrc,
        cname: String,
    ) -> Result<bool, MediaTransportError>;

    /// Update the display name for a source
    async fn update_csrc_display_name(
        &self,
        original_ssrc: RtpSsrc,
        name: String,
    ) -> Result<bool, MediaTransportError>;
}