libp2p_webrtc_utils/
sdp.rs

1// Copyright 2023 Doug Anderson
2// Copyright 2022 Parity Technologies (UK) Ltd.
3//
4// Permission is hereby granted, free of charge, to any person obtaining a
5// copy of this software and associated documentation files (the "Software"),
6// to deal in the Software without restriction, including without limitation
7// the rights to use, copy, modify, merge, publish, distribute, sublicense,
8// and/or sell copies of the Software, and to permit persons to whom the
9// Software is furnished to do so, subject to the following conditions:
10//
11// The above copyright notice and this permission notice shall be included in
12// all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20// DEALINGS IN THE SOFTWARE.
21use std::net::{IpAddr, SocketAddr};
22
23use rand::{distributions::Alphanumeric, thread_rng, Rng};
24use serde::Serialize;
25use tinytemplate::TinyTemplate;
26
27use crate::fingerprint::Fingerprint;
28
29pub fn answer(addr: SocketAddr, server_fingerprint: Fingerprint, client_ufrag: &str) -> String {
30    let answer = render_description(
31        SERVER_SESSION_DESCRIPTION,
32        addr,
33        server_fingerprint,
34        client_ufrag,
35    );
36
37    tracing::trace!(%answer, "Created SDP answer");
38
39    answer
40}
41
42// See [`CLIENT_SESSION_DESCRIPTION`].
43//
44// a=ice-lite
45//
46//     A lite implementation is only appropriate for devices that will *always* be connected to
47//     the public Internet and have a public IP address at which it can receive packets from any
48//     correspondent. ICE will not function when a lite implementation is placed behind a NAT
49//     (RFC8445).
50//
51// a=tls-id:<id>
52//
53//     "TLS ID" uniquely identifies a TLS association.
54//     The ICE protocol uses a "TLS ID" system to indicate whether a fresh DTLS connection
55//     must be reopened in case of ICE renegotiation. Considering that ICE renegotiations
56//     never happen in our use case, we can simply put a random value and not care about
57//     it. Note however that the TLS ID in the answer must be present if and only if the
58//     offer contains one. (RFC8842)
59//     TODO: is it true that renegotiations never happen? what about a connection closing?
60//     "tls-id" attribute MUST be present in the initial offer and respective answer (RFC8839).
61//     XXX: but right now browsers don't send it.
62//
63// a=setup:passive
64//
65//     "passive" indicates that the remote DTLS server will only listen for incoming
66//     connections. (RFC5763)
67//     The answerer (server) MUST not be located behind a NAT (RFC6135).
68//
69//     The answerer MUST use either a setup attribute value of setup:active or setup:passive.
70//     Note that if the answerer uses setup:passive, then the DTLS handshake will not begin until
71//     the answerer is received, which adds additional latency. setup:active allows the answer and
72//     the DTLS handshake to occur in parallel. Thus, setup:active is RECOMMENDED.
73//
74// a=candidate:<foundation> <component-id> <transport> <priority> <connection-address> <port>
75// <cand-type>
76//
77//     A transport address for a candidate that can be used for connectivity checks (RFC8839).
78//
79// a=end-of-candidates
80const SERVER_SESSION_DESCRIPTION: &str = "v=0
81o=- 0 0 IN {ip_version} {target_ip}
82s=-
83t=0 0
84a=ice-lite
85m=application {target_port} UDP/DTLS/SCTP webrtc-datachannel
86c=IN {ip_version} {target_ip}
87a=mid:0
88a=ice-options:ice2
89a=ice-ufrag:{ufrag}
90a=ice-pwd:{pwd}
91a=fingerprint:{fingerprint_algorithm} {fingerprint_value}
92a=setup:passive
93a=sctp-port:5000
94a=max-message-size:16384
95a=candidate:1467250027 1 UDP 1467250027 {target_ip} {target_port} typ host
96a=end-of-candidates
97";
98
99/// Indicates the IP version used in WebRTC: `IP4` or `IP6`.
100#[derive(Serialize)]
101enum IpVersion {
102    IP4,
103    IP6,
104}
105
106/// Context passed to the templating engine, which replaces the above placeholders (e.g.
107/// `{IP_VERSION}`) with real values.
108#[derive(Serialize)]
109struct DescriptionContext {
110    pub(crate) ip_version: IpVersion,
111    pub(crate) target_ip: IpAddr,
112    pub(crate) target_port: u16,
113    pub(crate) fingerprint_algorithm: String,
114    pub(crate) fingerprint_value: String,
115    pub(crate) ufrag: String,
116    pub(crate) pwd: String,
117}
118
119/// Renders a [`TinyTemplate`] description using the provided arguments.
120pub fn render_description(
121    description: &str,
122    addr: SocketAddr,
123    fingerprint: Fingerprint,
124    ufrag: &str,
125) -> String {
126    let mut tt = TinyTemplate::new();
127    tt.add_template("description", description).unwrap();
128
129    let context = DescriptionContext {
130        ip_version: {
131            if addr.is_ipv4() {
132                IpVersion::IP4
133            } else {
134                IpVersion::IP6
135            }
136        },
137        target_ip: addr.ip(),
138        target_port: addr.port(),
139        fingerprint_algorithm: fingerprint.algorithm(),
140        fingerprint_value: fingerprint.to_sdp_format(),
141        // NOTE: ufrag is equal to pwd.
142        ufrag: ufrag.to_owned(),
143        pwd: ufrag.to_owned(),
144    };
145    tt.render("description", &context).unwrap()
146}
147
148/// Generates a random ufrag and adds a prefix according to the spec.
149pub fn random_ufrag() -> String {
150    format!(
151        "libp2p+webrtc+v1/{}",
152        thread_rng()
153            .sample_iter(&Alphanumeric)
154            .take(64)
155            .map(char::from)
156            .collect::<String>()
157    )
158}