datachannel_facade/
lib.rs

1//! datachannel-facade is a library that abstracts over platform-specific WebRTC DataChannel implementations.
2//!
3//! It works both in the browser and natively (via [libdatachannel](https://libdatachannel.org)).
4//!
5//! The following docs are from [MDN](https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API).
6
7mod sys;
8
9pub mod platform;
10
11/// The property RTCSessionDescription.type is a read-only string value which describes the description's type.
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13pub enum SdpType {
14    /// The session description object describes the initial proposal in an offer/answer exchange. The session
15    /// negotiation process begins with an offer being sent from the caller to the callee.
16    Offer,
17
18    /// The SDP contained in the sdp property is the definitive choice in the exchange. In other words, this session
19    /// description describes the agreed-upon configuration, and is being sent to finalize negotiation.
20    Answer,
21
22    /// The session description object describes a provisional answer; that is, a response to a previous offer that is
23    /// not the final answer. It is usually employed by legacy hardware.
24    Pranswer,
25
26    /// This special type with an empty session description is used to roll back to the previous stable state.
27    Rollback,
28}
29
30/// The RTCSessionDescription interface describes one end of a connection—or potential connection—and how it's
31/// configured. Each RTCSessionDescription consists of a description type indicating which part of the offer/answer
32/// negotiation process it describes and of the SDP descriptor of the session.
33///
34/// The process of negotiating a connection between two peers involves exchanging RTCSessionDescription objects back and
35/// forth, with each description suggesting one combination of connection configuration options that the sender of the
36/// description supports. Once the two peers agree upon a configuration for the connection, negotiation is complete.
37#[derive(Clone, Debug)]
38pub struct Description {
39    /// An enum describing the session description's type.
40    pub type_: SdpType,
41
42    /// A string containing the SDP describing the session.
43    pub sdp: String,
44}
45
46/// An object providing configuration options for the data channel. It can contain the following fields:
47pub struct DataChannelOptions {
48    /// Indicates whether or not messages sent on the RTCDataChannel are required to arrive at their destination in the
49    /// same order in which they were sent (true), or if they're allowed to arrive out-of-order (false). Default: true.
50    pub ordered: bool,
51
52    /// The maximum number of milliseconds that attempts to transfer a message may take in unreliable mode. While this
53    /// value is a 16-bit unsigned number, each user agent may clamp it to whatever maximum it deems appropriate.
54    /// Default: null.
55    pub max_packet_life_time: Option<u16>,
56
57    /// The maximum number of times the user agent should attempt to retransmit a message which fails the first time in
58    /// unreliable mode. While this value is a 16-bit unsigned number, each user agent may clamp it to whatever maximum
59    // it deems appropriate. Default: null.
60    pub max_retransmits: Option<u16>,
61
62    /// The name of the sub-protocol being used on the RTCDataChannel, if any; otherwise, the empty string ("").
63    /// Default: empty string (""). This string may not be longer than 65,535 bytes.
64    pub protocol: String,
65
66    /// By default (false), data channels are negotiated in-band, where one side calls createDataChannel, and the other
67    /// side listens to the RTCDataChannelEvent event using the ondatachannel event handler. Alternatively (true), they
68    /// can be negotiated out of-band, where both sides call createDataChannel with an agreed-upon ID. Default: false.
69    pub negotiated: bool,
70
71    /// A 16-bit numeric ID for the channel; permitted values are 0 to 65534. If you don't include this option, the user
72    /// agent will select an ID for you.
73    pub id: Option<u16>,
74}
75
76impl Default for DataChannelOptions {
77    fn default() -> Self {
78        Self {
79            ordered: true,
80            max_packet_life_time: None,
81            max_retransmits: None,
82            protocol: "".to_string(),
83            negotiated: false,
84            id: None,
85        }
86    }
87}
88
89/// The read-only connectionState property of the RTCPeerConnection interface indicates the current state of the peer
90/// connection by returning one of the following string values: new, connecting, connected, disconnected, failed, or
91/// closed.
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub enum PeerConnectionState {
94    /// At least one of the connection's ICE transports (RTCIceTransport or RTCDtlsTransport objects) is in the new
95    /// state, and none of them are in one of the following states: connecting, checking, failed, disconnected, or all
96    // of the connection's transports are in the closed state.
97    New,
98
99    /// One or more of the ICE transports are currently in the process of establishing a connection; that is, their
100    /// iceConnectionState is either checking or connected, and no transports are in the failed state.
101    Connecting,
102
103    /// Every ICE transport used by the connection is either in use (state connected or completed) or is closed (state
104    /// closed); in addition, at least one transport is either connected or completed.
105    Connected,
106
107    /// At least one of the ICE transports for the connection is in the disconnected state and none of the other
108    /// transports are in the states: failed, connecting, or checking.
109    Disconnected,
110
111    /// One or more of the ICE transports on the connection is in the failed state.
112    Failed,
113
114    /// The RTCPeerConnection is closed.
115    Closed,
116}
117
118/// The read-only property RTCPeerConnection.iceGatheringState returns a string that describes the connection's ICE
119/// gathering state. This lets you detect, for example, when collection of ICE candidates has finished.
120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
121pub enum IceGatheringState {
122    /// The peer connection was just created and hasn't done any networking yet.
123    New,
124
125    /// The ICE agent is in the process of gathering candidates for the connection.
126    Gathering,
127
128    /// The ICE agent has finished gathering candidates. If something happens that requires collecting new candidates,
129    /// such as a new interface being added or the addition of a new ICE server, the state will revert to gathering to
130    /// gather those candidates.
131    Complete,
132}
133
134/// A string representing the current ICE transport policy. Possible values are:
135#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
136pub enum IceTransportPolicy {
137    /// All ICE candidates will be considered. This is the default value.
138    #[default]
139    All,
140
141    /// Only ICE candidates whose IP addresses are being relayed, such as those being passed through a TURN server, will
142    /// be considered.
143    Relay,
144}
145
146/// A server which may be used by the ICE agent; these are typically STUN and/or TURN servers.
147#[derive(Debug, Clone)]
148pub struct IceServer {
149    /// This required property is either a single string or an array of strings, each specifying a URL which can be used
150    /// to connect to the server.
151    pub urls: Vec<String>,
152
153    /// If the object represents a TURN server, then this is the username to use during the authentication.
154    pub username: Option<String>,
155
156    /// The credential to use when logging into the server. This is only used if the object represents a TURN server.
157    pub credential: Option<String>,
158}
159
160/// An object providing options to configure the new connection:
161#[derive(Debug, Default, Clone)]
162pub struct Configuration {
163    /// An array of objects, each describing one server which may be used by the ICE agent; these are typically STUN
164    /// and/or TURN servers. If this isn't specified, the connection attempt will be made with no STUN or TURN server
165    /// available, which limits the connection to local peers. Each object may have the following properties:
166    pub ice_servers: Vec<IceServer>,
167
168    /// A string representing the current ICE transport policy. Possible values are:
169    pub ice_transport_policy: IceTransportPolicy,
170
171    sys: sys::Configuration,
172}
173
174/// An underlying platform error.
175#[derive(thiserror::Error, Debug)]
176#[error("{0}")]
177pub struct Error(Box<dyn std::error::Error + Send + Sync + 'static>);
178
179/// The RTCPeerConnection interface represents a WebRTC connection between the local computer and a remote peer. It
180/// provides methods to connect to a remote peer, maintain and monitor the connection, and close the connection once
181/// it's no longer needed.
182pub struct PeerConnection {
183    inner: sys::PeerConnection,
184}
185
186impl PeerConnection {
187    /// Returns a new RTCPeerConnection, representing a connection between the local device and a remote peer.
188    pub fn new(config: Configuration) -> Result<Self, Error> {
189        Ok(Self {
190            inner: sys::PeerConnection::new(config)?,
191        })
192    }
193
194    /// Closes the current peer connection.
195    pub fn close(&self) -> Result<(), Error> {
196        self.inner.close()
197    }
198
199    /// The RTCPeerConnection method setLocalDescription() changes the local description associated with the connection.
200    /// This description specifies the properties of the local end of the connection, including the media format. The
201    /// method takes a single parameter—the session description—and it returns a Promise which is fulfilled once the
202    /// description has been changed, asynchronously.
203    ///
204    /// <div class="warning">
205    /// In datachannel-facade, this will also perform createOffer/createAnswer behind the scenes. The resulting
206    /// description can be retrieved by calling PeerConnection::local_description.
207    /// </div>
208    pub async fn set_local_description(&self, type_: SdpType) -> Result<(), Error> {
209        self.inner.set_local_description(type_).await
210    }
211
212    /// The RTCPeerConnection method setRemoteDescription() sets the specified session description as the remote peer's
213    /// current offer or answer. The description specifies the properties of the remote end of the connection, including
214    /// the media format. The method takes a single parameter—the session description—and it returns a Promise which is
215    /// fulfilled once the description has been changed, asynchronously.
216    ///
217    /// This is typically called after receiving an offer or answer from another peer over the signaling server. Keep in
218    /// mind that if setRemoteDescription() is called while a connection is already in place, it means renegotiation is
219    /// underway (possibly to adapt to changing network conditions).
220    ///
221    // Because descriptions will be exchanged until the two peers agree on a configuration, the description submitted by
222    // calling setRemoteDescription() does not immediately take effect. Instead, the current connection configuration
223    // remains in place until negotiation is complete. Only then does the agreed-upon configuration take effect.
224    pub async fn set_remote_description(&self, description: &Description) -> Result<(), Error> {
225        self.inner.set_remote_description(description).await
226    }
227
228    /// The read-only property RTCPeerConnection.localDescription returns an RTCSessionDescription describing the
229    /// session for the local end of the connection. If it has not yet been set, this is null.
230    pub fn local_description(&self) -> Result<Option<Description>, Error> {
231        self.inner.local_description()
232    }
233
234    /// The read-only property RTCPeerConnection.remoteDescription returns a RTCSessionDescription describing the
235    /// session (which includes configuration and media information) for the remote end of the connection. If this
236    /// hasn't been set yet, this is null.
237    ///
238    /// The returned value typically reflects a remote description which has been received over the signaling server (as
239    /// either an offer or an answer) and then put into effect by your code calling
240    /// RTCPeerConnection.setRemoteDescription() in response.
241    pub fn remote_description(&self) -> Result<Option<Description>, Error> {
242        self.inner.remote_description()
243    }
244
245    /// Adds a new remote candidate to the RTCPeerConnection's remote description, which describes the state of the
246    /// remote end of the connection.
247    pub async fn add_ice_candidate(&self, cand: Option<&str>) -> Result<(), Error> {
248        self.inner.add_ice_candidate(cand).await
249    }
250
251    /// An icecandidate event is sent to an RTCPeerConnection when:
252    ///
253    /// - An RTCIceCandidate has been identified and added to the local peer by a call to
254    ///   RTCPeerConnection.setLocalDescription(),
255    ///
256    /// - Every RTCIceCandidate correlated with a particular username fragment and password combination (a generation)
257    ///   has been so identified and added, and
258    ///
259    /// - All ICE gathering on all transports is complete.
260    ///
261    /// In the first two cases, the event handler should transmit the candidate to the remote peer over the signaling
262    /// channel so the remote peer can add it to its set of remote candidates.
263    pub fn set_on_ice_candidate(
264        &mut self,
265        cb: Option<impl Fn(Option<&str>) + Send + Sync + 'static>,
266    ) {
267        self.inner.set_on_ice_candidate(cb)
268    }
269
270    /// The icegatheringstatechange event is sent to the onicegatheringstatechange event handler on an RTCPeerConnection
271    /// when the state of the ICE candidate gathering process changes. This signifies that the value of the connection's
272    /// iceGatheringState property has changed.
273    ///
274    /// When ICE first starts to gather connection candidates, the value changes from new to gathering to indicate that
275    /// the process of collecting candidate configurations for the connection has begun. When the value changes to
276    /// complete, all of the transports that make up the RTCPeerConnection have finished gathering ICE candidates.
277    pub fn set_on_ice_gathering_state_change(
278        &mut self,
279        cb: Option<impl Fn(IceGatheringState) + Send + Sync + 'static>,
280    ) {
281        self.inner.set_on_ice_gathering_state_change(cb)
282    }
283
284    /// The connectionstatechange event is sent to the onconnectionstatechange event handler on an RTCPeerConnection
285    /// object after a new track has been added to an RTCRtpReceiver which is part of the connection. The new connection
286    /// state can be found in connectionState, and is one of the string values: new, connecting, connected,
287    /// disconnected, failed, or closed.
288    pub fn set_on_connection_state_change(
289        &mut self,
290        cb: Option<impl Fn(PeerConnectionState) + Send + Sync + 'static>,
291    ) {
292        self.inner.set_on_connection_state_change(cb)
293    }
294
295    /// A datachannel event is sent to an RTCPeerConnection instance when an RTCDataChannel has been added to the
296    /// connection, as a result of the remote peer calling RTCPeerConnection.createDataChannel().
297    pub fn set_on_data_channel(
298        &mut self,
299        cb: Option<impl Fn(DataChannel) + Send + Sync + 'static>,
300    ) {
301        self.inner
302            .set_on_data_channel(cb.map(|cb| move |dc| cb(DataChannel { inner: dc })))
303    }
304
305    /// The createDataChannel() method on the RTCPeerConnection interface creates a new channel linked with the remote
306    /// peer, over which any kind of data may be transmitted. This can be useful for back-channel content, such as
307    /// images, file transfer, text chat, game update packets, and so forth.
308    pub fn create_data_channel(
309        &self,
310        label: &str,
311        options: DataChannelOptions,
312    ) -> Result<DataChannel, Error> {
313        Ok(DataChannel {
314            inner: self.inner.create_data_channel(label, options)?,
315        })
316    }
317}
318
319/// The RTCDataChannel interface represents a network channel which can be used for bidirectional peer-to-peer transfers
320/// of arbitrary data. Every data channel is associated with an RTCPeerConnection, and each peer connection can have up
321/// to a theoretical maximum of 65,534 data channels (the actual limit may vary from browser to browser).
322///
323/// To create a data channel and ask a remote peer to join you, call the RTCPeerConnection's createDataChannel() method.
324/// The peer being invited to exchange data receives a datachannel event (which has type RTCDataChannelEvent) to let it
325/// know the data channel has been added to the connection.
326pub struct DataChannel {
327    inner: sys::DataChannel,
328}
329
330impl DataChannel {
331    /// The WebRTC open event is sent to an RTCDataChannel object's onopen event handler when the underlying transport
332    /// used to send and receive the data channel's messages is opened or reopened.
333    pub fn set_on_open(&mut self, cb: Option<impl Fn() + Send + Sync + 'static>) {
334        self.inner.set_on_open(cb)
335    }
336
337    /// The close event is sent to the onclose event handler on an RTCDataChannel instance when the data transport for
338    /// the data channel has closed. Before any further data can be transferred using RTCDataChannel, a new
339    /// 'RTCDataChannel' instance must be created.
340    pub fn set_on_close(&mut self, cb: Option<impl Fn() + Send + Sync + 'static>) {
341        self.inner.set_on_close(cb)
342    }
343
344    /// A bufferedamountlow event is sent to an RTCDataChannel when the number of bytes currently in the outbound data
345    /// transfer buffer falls below the threshold specified in bufferedAmountLowThreshold. bufferedamountlow events
346    /// aren't sent if bufferedAmountLowThreshold is 0.
347    pub fn set_on_buffered_amount_low(&mut self, cb: Option<impl Fn() + Send + Sync + 'static>) {
348        self.inner.set_on_buffered_amount_low(cb)
349    }
350
351    /// A WebRTC error event is sent to an RTCDataChannel object's onerror event handler when an error occurs on the
352    /// data channel.
353    pub fn set_on_error(&mut self, cb: Option<impl Fn(Error) + Send + Sync + 'static>) {
354        self.inner.set_on_error(cb)
355    }
356
357    /// The WebRTC message event is sent to the onmessage event handler on an RTCDataChannel object when a message has
358    /// been received from the remote peer.
359    pub fn set_on_message(&mut self, cb: Option<impl Fn(&[u8]) + Send + Sync + 'static>) {
360        self.inner.set_on_message(cb)
361    }
362
363    /// The RTCDataChannel property bufferedAmountLowThreshold is used to specify the number of bytes of buffered
364    /// outgoing data that is considered "low." The default value is 0. When the number of buffered outgoing bytes, as
365    /// indicated by the bufferedAmount property, falls to or below this value, a bufferedamountlow event is fired. This
366    /// event may be used, for example, to implement code which queues more messages to be sent whenever there's room to
367    /// buffer them. Listeners may be added with onbufferedamountlow or addEventListener().
368    ///
369    /// The user agent may implement the process of actually sending data in any way it chooses; this may be done
370    /// periodically during the event loop or truly asynchronously. As messages are actually sent, this value is reduced
371    /// accordingly.
372    pub fn set_buffered_amount_low_threshold(&self, value: u32) -> Result<(), crate::Error> {
373        self.inner.set_buffered_amount_low_threshold(value)
374    }
375
376    /// The read-only RTCDataChannel property bufferedAmount returns the number of bytes of data currently queued to be
377    /// sent over the data channel. The queue may build up as a result of calls to the send() method. This only includes
378    /// data buffered by the user agent itself; it doesn't include any framing overhead or buffering done by the
379    /// operating system or network hardware.
380    ///
381    /// The user agent may implement the process of actually sending data in any way it chooses; this may be done
382    /// periodically during the event loop or truly asynchronously. As messages are actually sent, this value is reduced
383    /// accordingly.
384    pub fn buffered_amount(&self) -> Result<u32, crate::Error> {
385        self.inner.buffered_amount()
386    }
387
388    /// The RTCDataChannel.close() method closes the RTCDataChannel. Either peer is permitted to call this method to
389    /// initiate closure of the channel.
390    ///
391    /// Closure of the data channel is not instantaneous. Most of the process of closing the connection is handled
392    /// asynchronously; you can detect when the channel has finished closing by watching for a close event on the data
393    /// channel.
394    pub fn close(&self) -> Result<(), crate::Error> {
395        self.inner.close()
396    }
397
398    /// The send() method of the RTCDataChannel interface sends data across the data channel to the remote peer. This
399    /// can be done any time except during the initial process of creating the underlying transport channel. Data sent
400    /// before connecting is buffered if possible (or an error occurs if it's not possible), and is also buffered if
401    /// sent while the connection is closing or closed.
402    pub fn send(&self, buf: &[u8]) -> Result<(), crate::Error> {
403        self.inner.send(buf)
404    }
405}
406
407#[cfg(test)]
408mod test {
409    use super::*;
410
411    cfg_if::cfg_if! {
412        if #[cfg(target_arch = "wasm32")] {
413            wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
414        }
415    }
416
417    #[cfg_attr(not(target_arch = "wasm32"), pollster::test)]
418    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
419    pub async fn test_peer_connection_new() {
420        let pc = PeerConnection::new(Default::default()).unwrap();
421        pc.create_data_channel("test", Default::default()).unwrap();
422    }
423
424    #[cfg_attr(not(target_arch = "wasm32"), pollster::test)]
425    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
426    pub async fn test_peer_connection_communicate() {
427        let mut pc1 = PeerConnection::new(Default::default()).unwrap();
428        let pc1_gathered = std::sync::Arc::new(async_notify::Notify::new());
429        pc1.set_on_ice_gathering_state_change(Some({
430            let pc1_gathered = std::sync::Arc::clone(&pc1_gathered);
431            move |ice_gathering_state| {
432                if ice_gathering_state == IceGatheringState::Complete {
433                    pc1_gathered.notify();
434                }
435            }
436        }));
437
438        let mut dc1 = pc1
439            .create_data_channel(
440                "test",
441                DataChannelOptions {
442                    negotiated: true,
443                    id: Some(1),
444                    ..Default::default()
445                },
446            )
447            .unwrap();
448        let dc1_open = std::sync::Arc::new(async_notify::Notify::new());
449        dc1.set_on_open(Some({
450            let dc1_open = std::sync::Arc::clone(&dc1_open);
451            move || {
452                dc1_open.notify();
453            }
454        }));
455        pc1.set_local_description(SdpType::Offer).await.unwrap();
456        pc1_gathered.notified().await;
457
458        let mut pc2 = PeerConnection::new(Default::default()).unwrap();
459        let pc2_gathered = std::sync::Arc::new(async_notify::Notify::new());
460        pc2.set_on_ice_gathering_state_change(Some({
461            let pc2_gathered = std::sync::Arc::clone(&pc2_gathered);
462            move |ice_gathering_state| {
463                if ice_gathering_state == IceGatheringState::Complete {
464                    pc2_gathered.notify();
465                }
466            }
467        }));
468
469        let mut dc2 = pc2
470            .create_data_channel(
471                "test",
472                DataChannelOptions {
473                    negotiated: true,
474                    id: Some(1),
475                    ..Default::default()
476                },
477            )
478            .unwrap();
479        let dc2_open = std::sync::Arc::new(async_notify::Notify::new());
480        dc2.set_on_open(Some({
481            let dc2_open = std::sync::Arc::clone(&dc2_open);
482            move || {
483                dc2_open.notify();
484            }
485        }));
486
487        let (tx1, rx1) = async_channel::bounded(1);
488        dc1.set_on_message(Some(move |msg: &[u8]| {
489            tx1.try_send(msg.to_vec()).unwrap();
490        }));
491
492        let (tx2, rx2) = async_channel::bounded(1);
493        dc2.set_on_message(Some(move |msg: &[u8]| {
494            tx2.try_send(msg.to_vec()).unwrap();
495        }));
496
497        pc2.set_remote_description(&pc1.local_description().unwrap().unwrap())
498            .await
499            .unwrap();
500
501        pc2.set_local_description(SdpType::Answer).await.unwrap();
502        pc2_gathered.notified().await;
503
504        pc1.set_remote_description(&pc2.local_description().unwrap().unwrap())
505            .await
506            .unwrap();
507
508        dc1_open.notified().await;
509        dc2_open.notified().await;
510
511        dc1.send(b"hello world!").unwrap();
512        assert_eq!(rx2.recv().await.unwrap(), b"hello world!");
513
514        dc2.send(b"goodbye world!").unwrap();
515        assert_eq!(rx1.recv().await.unwrap(), b"goodbye world!");
516    }
517}