Skip to main content

oxpulse_sfu_kit/client/
dc_builder.rs

1//! DataChannel builder methods on [`Client`].
2//!
3//! `with_extra_dc` is the generic primitive; `with_chat_dcs` and
4//! `with_voice_dc` are thin convenience shims.
5
6use super::Client;
7use crate::dc::ChannelConfig;
8
9impl Client {
10    /// Register an additional DataChannel to open during SDP negotiation.
11    ///
12    /// The `label`, `id`, and channel `cfg` are stored in [`Client::extra_dcs`].
13    /// The application signalling layer reads these during offer/answer and
14    /// calls `Rtc::open_stream(id, str0m_channel_config)` for each entry.
15    ///
16    /// # Example
17    ///
18    /// ```rust,no_run
19    /// use oxpulse_sfu_kit::{ChannelConfig, Client, SfuRtcBuilder, SfuMetrics};
20    /// use std::sync::Arc;
21    ///
22    /// let rtc = SfuRtcBuilder::new().build();
23    /// let client = Client::new(rtc, Arc::new(SfuMetrics::new_default()))
24    ///     .with_extra_dc("telemetry", 20, ChannelConfig::reliable_ordered());
25    /// ```
26    #[must_use]
27    pub fn with_extra_dc(mut self, label: &str, id: u16, cfg: ChannelConfig) -> Self {
28        debug_assert!(
29            !self.extra_dcs.iter().any(|c| c.id() == id),
30            "DC id {} already registered (existing labels: {:?})",
31            id,
32            self.extra_dcs.iter().map(|c| c.label()).collect::<Vec<_>>()
33        );
34        debug_assert!(
35            cfg.max_packet_lifetime_ms.is_none() || cfg.max_retransmits.is_none(),
36            "ChannelConfig invariant violated: max_packet_lifetime_ms and max_retransmits \
37             cannot both be Some (label={label}, id={id})"
38        );
39        let mut entry = cfg;
40        entry.id = id;
41        entry.label = label.to_string();
42        self.extra_dcs.push(entry);
43        self
44    }
45
46    /// Register the standard OxPulse chat DataChannels (id=4 and id=5).
47    ///
48    /// - `"chat-data"` (id=4): reliable ordered — text messages.
49    /// - `"chat-ctrl"` (id=5): unreliable, 0 retransmits — typing / presence signals.
50    ///
51    /// This is a thin shim over [`with_extra_dc`][Self::with_extra_dc]; no
52    /// behaviour change relative to the pre-0.10.0 implementation.
53    #[must_use]
54    pub fn with_chat_dcs(self) -> Self {
55        self.with_extra_dc("chat-data", 4, ChannelConfig::reliable_ordered())
56            .with_extra_dc("chat-ctrl", 5, ChannelConfig::unreliable_max_retransmits(0))
57    }
58
59    /// Register the Phase 8 voice DataChannel (id=6).
60    ///
61    /// Uses unordered delivery with `Reliability::MaxPacketLifetime { lifetime: max_pkt_lifetime_ms }`.
62    /// For voice control signals, 200 ms is the recommended lifetime — packets older
63    /// than that are useless and should be discarded.
64    #[must_use]
65    pub fn with_voice_dc(self, max_pkt_lifetime_ms: u32) -> Self {
66        self.with_extra_dc(
67            "voice",
68            6,
69            ChannelConfig::unreliable_max_lifetime(max_pkt_lifetime_ms),
70        )
71    }
72}
73
74// ── Test seams ─────────────────────────────────────────────────────────────
75
76#[cfg(any(test, feature = "test-utils"))]
77impl Client {
78    /// Build a `Client` for integration tests without real ICE/DTLS setup.
79    ///
80    /// Identical to [`crate::client::test_seed::new_client`] but available as an associated
81    /// function on `Client` so tests can call `Client::new_for_test()` without
82    /// importing the internal module.
83    #[must_use]
84    pub fn new_for_test() -> Self {
85        crate::client::test_seed::new_client(crate::propagate::ClientId(u64::MAX))
86    }
87
88    /// Look up the [`ChannelConfig`] registered under `label` via `with_extra_dc`.
89    ///
90    /// Returns `None` if no DC with that label has been registered.
91    #[must_use]
92    pub fn dc_config_for(&self, label: &str) -> Option<&ChannelConfig> {
93        self.extra_dcs.iter().find(|dc| dc.label() == label)
94    }
95
96    /// Number of DataChannels registered via `with_extra_dc` (and its shims).
97    #[must_use]
98    pub fn dc_count(&self) -> usize {
99        self.extra_dcs.len()
100    }
101}