Skip to content
File

Blob: firmware/vendor/str0m/src/change/direct.rs

rust426 lines
1use std::sync::Arc;
2 
3use crate::Candidate;
4use crate::IceCreds;
5use crate::Rtc;
6use crate::RtcError;
7use crate::channel::ChannelId;
8use crate::crypto::Fingerprint;
9use crate::crypto::dtls::ProtocolVersion;
10use crate::media::{Media, MediaKind};
11use crate::rtp_::MidRid;
12use crate::rtp_::{Mid, Rid, Ssrc};
13use crate::sctp::{ChannelConfig, SctpInitData};
14use crate::streams::{DEFAULT_RTX_CACHE_DURATION, DEFAULT_RTX_RATIO_CAP, StreamRx, StreamTx};
15 
16/// Direct change strategy.
17///
18/// Makes immediate changes to the Rtc session without any SDP OFFER/ANSWER. This
19/// is an alternative to [`Rtc::sdp_api()`] for use cases when you don’t want to use SDP
20/// (or when you want to write RTP directly).
21///
22/// To use the Direct API together with a browser client, you would need to make
23/// the equivalent changes on the browser side by manually generating the correct
24/// SDP OFFER/ANSWER to make the `RTCPeerConnection` match str0m's state.
25///
26/// To change str0m's state through the Direct API followed by the SDP API produce
27/// an SDP OFFER is not a supported use case. Either pick SDP API and let str0m handle
28/// the OFFER/ANSWER or use Direct API and deal with SDP manually. Not both.
29///
30/// <div class="warning"><b>This is a low level API.</b>
31///
32/// str0m normally guarantees that user input cannot cause panics.
33/// However as an exception, the Direct API does allow the user to configure the
34/// session in a way that is internally inconsistent. Such situations can
35/// result in panics.
36/// </div>
37pub struct DirectApi<'a> {
38 rtc: &'a mut Rtc,
39}
40 
41impl<'a> DirectApi<'a> {
42 /// Creates a new instance of the `DirectApi` struct with the specified `Rtc` instance.
43 ///
44 /// The `DirectApi` struct provides a high-level API for interacting with a WebRTC peer connection,
45 /// and the `Rtc` instance provides low-level access to the underlying WebRTC functionality.
46 pub fn new(rtc: &'a mut Rtc) -> Self {
47 DirectApi { rtc }
48 }
49 
50 /// Sets the ICE controlling flag for this peer connection.
51 ///
52 /// If `controlling` is `true`, this peer connection is set as the ICE controlling agent,
53 /// meaning it will take the initiative to send connectivity checks and control the pace of
54 /// connectivity checks sent between two peers during the ICE session.
55 ///
56 /// If `controlling` is `false`, this peer connection is set as the ICE controlled agent,
57 /// meaning it will respond to connectivity checks sent by the controlling agent.
58 pub fn set_ice_controlling(&mut self, controlling: bool) {
59 self.rtc.ice.set_controlling(controlling);
60 }
61 
62 /// Returns a reference to the local ICE credentials used by this peer connection.
63 ///
64 /// The ICE credentials consist of the username and password used by the ICE agent during
65 /// the ICE session to authenticate and exchange connectivity checks with the remote peer.
66 pub fn local_ice_credentials(&self) -> IceCreds {
67 self.rtc.ice.local_credentials().clone()
68 }
69 
70 /// Sets the local ICE credentials.
71 pub fn set_local_ice_credentials(&mut self, local_ice_credentials: IceCreds) {
72 self.rtc.ice.set_local_credentials(local_ice_credentials);
73 }
74 
75 /// Sets the remote ICE credentials.
76 pub fn set_remote_ice_credentials(&mut self, remote_ice_credentials: IceCreds) {
77 self.rtc.ice.set_remote_credentials(remote_ice_credentials);
78 }
79 
80 /// Invalidate a candidate and remove it from the connection.
81 ///
82 /// This is done for host candidates disappearing due to changes in the network
83 /// interfaces like a WiFi disconnecting or changing IPs.
84 ///
85 /// It can also be used to invalidate _remote_ candidates, i.e. if the remote
86 /// has signalled us that they have invalidated one of their candidates.
87 ///
88 /// Returns `true` if the candidate was found and invalidated.
89 pub fn invalidate_candidate(&mut self, c: &Candidate) -> bool {
90 self.rtc.ice.invalidate_candidate(c)
91 }
92 
93 /// Returns a reference to the local DTLS fingerprint used by this peer connection.
94 ///
95 /// The DTLS fingerprint is a hash of the local SSL/TLS certificate used to authenticate the
96 /// peer connection and establish a secure communication channel between the peers.
97 pub fn local_dtls_fingerprint(&self) -> &Fingerprint {
98 self.rtc.dtls.local_fingerprint()
99 }
100 
101 /// Returns a reference to the remote DTLS fingerprint used by this peer connection.
102 pub fn remote_dtls_fingerprint(&self) -> Option<&Fingerprint> {
103 self.rtc.dtls.remote_fingerprint()
104 }
105 
106 /// Returns the negotiated DTLS protocol version.
107 ///
108 /// Call this after receiving [`crate::Event::Connected`]
109 /// to learn the negotiated DTLS version. Before the handshake completes,
110 /// this may return `None`
111 pub fn dtls_protocol_version(&self) -> Option<ProtocolVersion> {
112 self.rtc.dtls.protocol_version()
113 }
114 
115 /// Sets the remote DTLS fingerprint.
116 pub fn set_remote_fingerprint(&mut self, dtls_fingerprint: Fingerprint) {
117 self.rtc.remote_fingerprint = Some(dtls_fingerprint);
118 }
119 
120 /// Start the DTLS subsystem.
121 pub fn start_dtls(&mut self, active: bool) -> Result<(), RtcError> {
122 self.rtc.init_dtls(active)
123 }
124 
125 /// Start the SCTP over DTLS.
126 ///
127 /// When `client` is `true`, this side initiates the SCTP association as the
128 /// connecting party.
129 pub fn start_sctp(&mut self, client: bool) {
130 self.rtc
131 .try_init_sctp(client, None, None)
132 .expect("starting SCTP should be infallible")
133 }
134 
135 /// Start SCTP over DTLS using out-of-band SCTP INIT data.
136 ///
137 /// This is used for SNAP (SCTP Negotiation Acceleration Protocol), which
138 /// allows skipping the 4-way SCTP handshake entirely once both peers have
139 /// exchanged their SCTP INIT chunks out of band.
140 ///
141 /// The `sctp_init_data` must contain:
142 ///
143 /// 1. A local INIT chunk generated by calling
144 /// [`SctpInitData::local_init_chunk()`].
145 /// 2. The remote INIT chunk set via [`SctpInitData::set_remote_init_chunk()`].
146 ///
147 /// This method returns an error unless both the local and remote INIT
148 /// chunks are present.
149 ///
150 /// This method is the SNAP-specific alternative to [`Self::start_sctp()`].
151 ///
152 /// # Example
153 /// ```ignore
154 /// use str0m::channel::SctpInitData;
155 ///
156 /// let mut init_data = SctpInitData::new();
157 /// let local_init = init_data.local_init_chunk().unwrap();
158 /// // ... exchange local_init via signaling, receive remote_init ...
159 /// init_data.set_remote_init_chunk(remote_init);
160 /// rtc.direct_api().start_sctp_with_snap(false, init_data)?;
161 /// ```
162 pub fn start_sctp_with_snap(
163 &mut self,
164 client: bool,
165 sctp_init_data: SctpInitData,
166 ) -> Result<(), RtcError> {
167 self.rtc.try_init_sctp(client, Some(sctp_init_data), None)
168 }
169 
170 /// Create a new data channel.
171 pub fn create_data_channel(&mut self, config: ChannelConfig) -> ChannelId {
172 let id = self.rtc.chan.new_channel(&config);
173 self.rtc.chan.confirm(id, config);
174 id
175 }
176 
177 /// Close a data channel.
178 pub fn close_data_channel(&mut self, channel_id: ChannelId) {
179 self.rtc.chan.close_channel(channel_id, &mut self.rtc.sctp);
180 }
181 
182 /// Set whether to enable ice-lite.
183 pub fn set_ice_lite(&mut self, ice_lite: bool) {
184 self.rtc.ice.set_ice_lite(ice_lite);
185 }
186 
187 /// Enable twcc feedback.
188 pub fn enable_twcc_feedback(&mut self) {
189 self.rtc.session.enable_twcc_feedback()
190 }
191 
192 /// Generate a ssrc that is not already used in session
193 pub fn new_ssrc(&self) -> Ssrc {
194 self.rtc.session.streams.new_ssrc()
195 }
196 
197 /// Get the str0m `ChannelId` by an `sctp_stream_id`.
198 ///
199 /// This is useful when using out of band negotiated sctp stream id in
200 /// [`Self::create_data_channel()`]
201 pub fn channel_id_by_sctp_stream_id(&self, id: u16) -> Option<ChannelId> {
202 self.rtc.chan.channel_id_by_stream_id(id)
203 }
204 
205 /// Get the `sctp_stream_id` from a str0m `ChannelId`.
206 ///
207 /// This is useful when using out of band negotiated sctp stream id in
208 /// [`Self::create_data_channel()`]
209 pub fn sctp_stream_id_by_channel_id(&self, id: ChannelId) -> Option<u16> {
210 self.rtc.chan.stream_id_by_channel_id(id)
211 }
212 
213 /// Create a new `Media`.
214 ///
215 /// All streams belong to a media identified by a `mid`. This creates the media without
216 /// doing any SDP dance.
217 pub fn declare_media(&mut self, mid: Mid, kind: MediaKind) -> &mut Media {
218 let max_index = self.rtc.session.medias.iter().map(|m| m.index()).max();
219 
220 let next_index = if let Some(max_index) = max_index {
221 max_index + 1
222 } else {
223 0
224 };
225 
226 let exts = self.rtc.session.exts.cloned_with_type(kind.is_audio());
227 let m = Media::from_direct_api(mid, next_index, kind, exts);
228 
229 self.rtc.session.medias.push(m);
230 self.rtc.session.medias.last_mut().unwrap()
231 }
232 
233 /// Remove `Media`.
234 ///
235 /// Removes media and all streams belong to a media identified by a `mid`.
236 pub fn remove_media(&mut self, mid: Mid) {
237 self.rtc.session.remove_media(mid);
238 }
239 
240 /// Allow incoming traffic from remote peer for the given SSRC.
241 ///
242 /// Can be called multiple times if the `rtx` is discovered later via RTP header extensions.
243 pub fn expect_stream_rx(
244 &mut self,
245 ssrc: Ssrc,
246 rtx: Option<Ssrc>,
247 mid: Mid,
248 rid: Option<Rid>,
249 ) -> &mut StreamRx {
250 let Some(_media) = self.rtc.session.media_by_mid(mid) else {
251 panic!("No media declared for mid: {}", mid);
252 };
253 
254 // By default we do not suppress nacks, this has to be called explicitly by the user of direct API.
255 let suppress_nack = false;
256 
257 let midrid = MidRid(mid, rid);
258 
259 self.rtc
260 .session
261 .streams
262 .expect_stream_rx(ssrc, rtx, midrid, suppress_nack)
263 }
264 
265 /// Remove the receive stream for the given SSRC.
266 ///
267 /// Returns true if stream existed and was removed.
268 pub fn remove_stream_rx(&mut self, ssrc: Ssrc) -> bool {
269 self.rtc.session.streams.remove_stream_rx(ssrc)
270 }
271 
272 /// Obtain a receive stream.
273 ///
274 /// In RTP mode, the receive stream is used to signal keyframe requests.
275 ///
276 /// The stream must first be declared using [`DirectApi::expect_stream_rx`].
277 pub fn stream_rx(&mut self, ssrc: &Ssrc) -> Option<&mut StreamRx> {
278 self.rtc.session.streams.stream_rx(ssrc)
279 }
280 
281 /// Obtain a recv stream by looking it up via mid/rid.
282 pub fn stream_rx_by_mid(&mut self, mid: Mid, rid: Option<Rid>) -> Option<&mut StreamRx> {
283 let midrid = MidRid(mid, rid);
284 self.rtc.session.streams.stream_rx_by_midrid(midrid, true)
285 }
286 
287 /// Declare the intention to send data using the given SSRC.
288 ///
289 /// * The resend RTX is optional but necessary to do resends. str0m does not do
290 /// resends without RTX.
291 ///
292 /// Can be called multiple times without changing any internal state. However
293 /// the RTX value is only picked up the first ever time we see a new SSRC.
294 pub fn declare_stream_tx(
295 &mut self,
296 ssrc: Ssrc,
297 rtx: Option<Ssrc>,
298 mid: Mid,
299 rid: Option<Rid>,
300 ) -> &mut StreamTx {
301 let Some(media) = self.rtc.session.media_by_mid_mut(mid) else {
302 panic!("No media declared for mid: {}", mid);
303 };
304 
305 let is_audio = media.kind().is_audio();
306 
307 let midrid = MidRid(mid, rid);
308 
309 // If there is a RID tx, declare it so we an use it in Writer API
310 if let Some(rid) = rid {
311 media.add_to_rid_tx(rid);
312 }
313 
314 let stream = self
315 .rtc
316 .session
317 .streams
318 .declare_stream_tx(ssrc, rtx, midrid);
319 
320 let size = if is_audio {
321 self.rtc.session.send_buffer_audio
322 } else {
323 self.rtc.session.send_buffer_video
324 };
325 
326 stream.set_rtx_cache(size, DEFAULT_RTX_CACHE_DURATION, DEFAULT_RTX_RATIO_CAP);
327 
328 stream
329 }
330 
331 /// Remove the transmit stream for the given SSRC.
332 ///
333 /// Returns true if stream existed and was removed.
334 pub fn remove_stream_tx(&mut self, ssrc: Ssrc) -> bool {
335 self.rtc.session.streams.remove_stream_tx(ssrc)
336 }
337 
338 /// Obtain a send stream to write RTP data directly.
339 ///
340 /// The stream must first be declared using [`DirectApi::declare_stream_tx`].
341 pub fn stream_tx(&mut self, ssrc: &Ssrc) -> Option<&mut StreamTx> {
342 self.rtc.session.streams.stream_tx(ssrc)
343 }
344 
345 /// Obtain a send stream by looking it up via mid/rid.
346 pub fn stream_tx_by_mid(&mut self, mid: Mid, rid: Option<Rid>) -> Option<&mut StreamTx> {
347 let midrid = MidRid(mid, rid);
348 self.rtc.session.streams.stream_tx_by_midrid(midrid)
349 }
350 
351 /// Reset a transmit stream to use a new SSRC and optionally a new RTX SSRC.
352 ///
353 /// This changes the SSRC of an existing stream and resets all relevant state.
354 /// Use this when you need to change the SSRC of an existing stream without creating a new one.
355 ///
356 /// If the stream has an RTX SSRC, `new_rtx` must be provided. If the stream doesn't
357 /// have an RTX SSRC, `new_rtx` is ignored.
358 ///
359 /// Returns a reference to the updated stream or None if:
360 /// - No stream was found for the given mid/rid
361 /// - The new SSRC is the same as the current one (no change needed)
362 /// - The new RTX SSRC is the same as the current one (no change needed)
363 pub fn reset_stream_tx(
364 &mut self,
365 mid: Mid,
366 rid: Option<Rid>,
367 new_ssrc: Ssrc,
368 new_rtx: Option<Ssrc>,
369 ) -> Option<&mut StreamTx> {
370 let midrid = MidRid(mid, rid);
371 
372 // Find the stream by mid/rid
373 let stream = self.rtc.session.streams.stream_tx_by_midrid(midrid)?;
374 
375 // Don't change to the same SSRC
376 if stream.ssrc() == new_ssrc {
377 return None;
378 }
379 
380 // If the stream has an RTX SSRC, New RTX must be provided and differ.
381 // But it is allowed to start or turn off RTX.
382 if stream.rtx().is_some() && stream.rtx() == new_rtx {
383 return None;
384 }
385 
386 // Reset the stream with the new SSRC and RTX
387 stream.reset_ssrc(new_ssrc, new_rtx);
388 
389 // Return a reference to the updated stream
390 Some(stream)
391 }
392 
393 /// Send an application-specific feedback message (PSFB FMT=15, PT=206).
394 ///
395 /// The message is sent in its own standalone SRTCP compound datagram, prefixed
396 /// with a Receiver Report for RFC 3550 compliance. The `sender_ssrc` is used as
397 /// the RR's sender SSRC, which allows remote RTCP demuxers that route compound
398 /// packets by the first SSRC to deliver the feedback to the correct media session.
399 ///
400 /// The `payload` is opaque and application-defined.
401 pub fn send_app_specific_feedback(
402 &mut self,
403 sender_ssrc: Ssrc,
404 media_ssrc: Ssrc,
405 payload: impl Into<Arc<[u8]>>,
406 ) {
407 self.rtc
408 .session
409 .send_app_specific_feedback(sender_ssrc, media_ssrc, payload);
410 }
411 
412 /// Send a Picture Loss Indication (PLI, PT=206 FMT=1) for a media stream,
413 /// choosing the `sender_ssrc` of the outgoing RTCP feedback.
414 ///
415 /// The PLI is queued into the regular RTCP compound packet. The `sender_ssrc`
416 /// sets the feedback packet's sender SSRC. `media_ssrc` is the SSRC of the
417 /// stream a keyframe is requested for.
418 ///
419 /// Unlike requesting a keyframe on a local receive stream, this does not require a
420 /// receive stream for `media_ssrc` and lets the caller choose the sender SSRC, so it
421 /// can relay a keyframe request for a stream sourced by the remote peer.
422 pub fn send_pli_feedback(&mut self, sender_ssrc: Ssrc, media_ssrc: Ssrc) {
423 self.rtc.session.send_pli_feedback(sender_ssrc, media_ssrc);
424 }
425}