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}