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}