blob: 6d405d07a9dfcd7aa49e79ad4e8964a7b242c157 [file] [log] [blame]
Amit Hilbucha2012042018-12-03 19:35:051/*
2 * Copyright 2018 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Steve Anton10542f22019-01-11 17:11:0011#include "pc/sdp_serializer.h"
Amit Hilbucha2012042018-12-03 19:35:0512
Harald Alvestrand5761e7b2021-01-29 14:45:0813#include <map>
Amit Hilbucha2012042018-12-03 19:35:0514#include <string>
Harald Alvestrandc24a2182022-02-23 13:44:5915#include <type_traits>
Amit Hilbucha2012042018-12-03 19:35:0516#include <utility>
17#include <vector>
18
Steve Anton64b626b2019-01-29 01:25:2619#include "absl/algorithm/container.h"
Harald Alvestrand5761e7b2021-01-29 14:45:0820#include "absl/types/optional.h"
Amit Hilbuchf4770402019-04-08 21:11:5721#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
Amit Hilbuchc57d5732018-12-11 23:30:1122#include "rtc_base/checks.h"
Steve Anton10542f22019-01-11 17:11:0023#include "rtc_base/string_encode.h"
Amit Hilbuchc57d5732018-12-11 23:30:1124#include "rtc_base/string_to_number.h"
Amit Hilbucha2012042018-12-03 19:35:0525#include "rtc_base/strings/string_builder.h"
26
Amit Hilbuchc57d5732018-12-11 23:30:1127using cricket::RidDescription;
28using cricket::RidDirection;
Amit Hilbucha2012042018-12-03 19:35:0529using cricket::SimulcastDescription;
30using cricket::SimulcastLayer;
31using cricket::SimulcastLayerList;
32
33namespace webrtc {
34
35namespace {
36
37// delimiters
38const char kDelimiterComma[] = ",";
39const char kDelimiterCommaChar = ',';
Amit Hilbuchc57d5732018-12-11 23:30:1140const char kDelimiterEqual[] = "=";
41const char kDelimiterEqualChar = '=';
Amit Hilbucha2012042018-12-03 19:35:0542const char kDelimiterSemicolon[] = ";";
43const char kDelimiterSemicolonChar = ';';
44const char kDelimiterSpace[] = " ";
45const char kDelimiterSpaceChar = ' ';
46
47// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
Amit Hilbuchc57d5732018-12-11 23:30:1148// https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
Amit Hilbucha2012042018-12-03 19:35:0549const char kSimulcastPausedStream[] = "~";
50const char kSimulcastPausedStreamChar = '~';
Amit Hilbuchc57d5732018-12-11 23:30:1151const char kSendDirection[] = "send";
52const char kReceiveDirection[] = "recv";
53const char kPayloadType[] = "pt";
Amit Hilbucha2012042018-12-03 19:35:0554
55RTCError ParseError(const std::string& message) {
56 return RTCError(RTCErrorType::SYNTAX_ERROR, message);
57}
58
59// These methods serialize simulcast according to the specification:
60// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
61rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
62 const SimulcastLayer& simulcast_layer) {
63 if (simulcast_layer.is_paused) {
64 builder << kSimulcastPausedStream;
65 }
66 builder << simulcast_layer.rid;
67 return builder;
68}
69
70rtc::StringBuilder& operator<<(
71 rtc::StringBuilder& builder,
72 const std::vector<SimulcastLayer>& layer_alternatives) {
73 bool first = true;
74 for (const SimulcastLayer& rid : layer_alternatives) {
75 if (!first) {
76 builder << kDelimiterComma;
77 }
78 builder << rid;
79 first = false;
80 }
81 return builder;
82}
83
84rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
85 const SimulcastLayerList& simulcast_layers) {
86 bool first = true;
Mirko Bonadei739baf02019-01-27 16:29:4287 for (const auto& alternatives : simulcast_layers) {
Amit Hilbucha2012042018-12-03 19:35:0588 if (!first) {
89 builder << kDelimiterSemicolon;
90 }
91 builder << alternatives;
92 first = false;
93 }
94 return builder;
95}
Amit Hilbuchc57d5732018-12-11 23:30:1196// This method deserializes simulcast according to the specification:
Amit Hilbucha2012042018-12-03 19:35:0597// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
98// sc-str-list = sc-alt-list *( ";" sc-alt-list )
99// sc-alt-list = sc-id *( "," sc-id )
100// sc-id-paused = "~"
101// sc-id = [sc-id-paused] rid-id
102// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
103RTCErrorOr<SimulcastLayerList> ParseSimulcastLayerList(const std::string& str) {
Niels Möller2d3186e2022-01-24 13:15:03104 std::vector<absl::string_view> tokens =
105 rtc::split(str, kDelimiterSemicolonChar);
Amit Hilbucha2012042018-12-03 19:35:05106 if (tokens.empty()) {
107 return ParseError("Layer list cannot be empty.");
108 }
109
110 SimulcastLayerList result;
Niels Möller2d3186e2022-01-24 13:15:03111 for (const absl::string_view& token : tokens) {
Amit Hilbucha2012042018-12-03 19:35:05112 if (token.empty()) {
113 return ParseError("Simulcast alternative layer list is empty.");
114 }
115
Niels Möller2d3186e2022-01-24 13:15:03116 std::vector<absl::string_view> rid_tokens =
117 rtc::split(token, kDelimiterCommaChar);
Amit Hilbuchc57d5732018-12-11 23:30:11118
Amit Hilbucha2012042018-12-03 19:35:05119 if (rid_tokens.empty()) {
120 return ParseError("Simulcast alternative layer list is malformed.");
121 }
122
123 std::vector<SimulcastLayer> layers;
Niels Möller2d3186e2022-01-24 13:15:03124 for (const absl::string_view& rid_token : rid_tokens) {
Amit Hilbucha2012042018-12-03 19:35:05125 if (rid_token.empty() || rid_token == kSimulcastPausedStream) {
126 return ParseError("Rid must not be empty.");
127 }
128
129 bool paused = rid_token[0] == kSimulcastPausedStreamChar;
Niels Möller2d3186e2022-01-24 13:15:03130 absl::string_view rid = paused ? rid_token.substr(1) : rid_token;
Amit Hilbucha2012042018-12-03 19:35:05131 layers.push_back(SimulcastLayer(rid, paused));
132 }
133
134 result.AddLayerWithAlternatives(layers);
135 }
136
137 return std::move(result);
138}
139
Amit Hilbuchc57d5732018-12-11 23:30:11140webrtc::RTCError ParseRidPayloadList(const std::string& payload_list,
141 RidDescription* rid_description) {
142 RTC_DCHECK(rid_description);
143 std::vector<int>& payload_types = rid_description->payload_types;
144 // Check that the description doesn't have any payload types or restrictions.
145 // If the pt= field is specified, it must be first and must not repeat.
146 if (!payload_types.empty()) {
147 return ParseError("Multiple pt= found in RID Description.");
148 }
149 if (!rid_description->restrictions.empty()) {
150 return ParseError("Payload list must appear first in the restrictions.");
151 }
152
153 // If the pt= field is specified, it must have a value.
154 if (payload_list.empty()) {
155 return ParseError("Payload list must have at least one value.");
156 }
157
158 // Tokenize the ',' delimited list
159 std::vector<std::string> string_payloads;
160 rtc::tokenize(payload_list, kDelimiterCommaChar, &string_payloads);
161 if (string_payloads.empty()) {
162 return ParseError("Payload list must have at least one value.");
163 }
164
165 for (const std::string& payload_type : string_payloads) {
166 absl::optional<int> value = rtc::StringToNumber<int>(payload_type);
167 if (!value.has_value()) {
168 return ParseError("Invalid payload type: " + payload_type);
169 }
170
171 // Check if the value already appears in the payload list.
Steve Anton64b626b2019-01-29 01:25:26172 if (absl::c_linear_search(payload_types, value.value())) {
Amit Hilbuchc57d5732018-12-11 23:30:11173 return ParseError("Duplicate payload type in list: " + payload_type);
174 }
175 payload_types.push_back(value.value());
176 }
177
178 return RTCError::OK();
179}
180
Amit Hilbucha2012042018-12-03 19:35:05181} // namespace
182
183std::string SdpSerializer::SerializeSimulcastDescription(
184 const cricket::SimulcastDescription& simulcast) const {
185 rtc::StringBuilder sb;
186 std::string delimiter;
187
188 if (!simulcast.send_layers().empty()) {
Amit Hilbuchc57d5732018-12-11 23:30:11189 sb << kSendDirection << kDelimiterSpace << simulcast.send_layers();
Amit Hilbucha2012042018-12-03 19:35:05190 delimiter = kDelimiterSpace;
191 }
192
193 if (!simulcast.receive_layers().empty()) {
Amit Hilbuchc57d5732018-12-11 23:30:11194 sb << delimiter << kReceiveDirection << kDelimiterSpace
Amit Hilbucha2012042018-12-03 19:35:05195 << simulcast.receive_layers();
196 }
197
198 return sb.str();
199}
200
201// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
202// a:simulcast:<send> <streams> <recv> <streams>
203// Formal Grammar
204// sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] )
205// sc-send = %s"send" SP sc-str-list
206// sc-recv = %s"recv" SP sc-str-list
207// sc-str-list = sc-alt-list *( ";" sc-alt-list )
208// sc-alt-list = sc-id *( "," sc-id )
209// sc-id-paused = "~"
210// sc-id = [sc-id-paused] rid-id
211// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
212RTCErrorOr<SimulcastDescription> SdpSerializer::DeserializeSimulcastDescription(
213 absl::string_view string) const {
214 std::vector<std::string> tokens;
215 rtc::tokenize(std::string(string), kDelimiterSpaceChar, &tokens);
216
217 if (tokens.size() != 2 && tokens.size() != 4) {
218 return ParseError("Must have one or two <direction, streams> pairs.");
219 }
220
221 bool bidirectional = tokens.size() == 4; // indicates both send and recv
222
223 // Tokens 0, 2 (if exists) should be send / recv
Amit Hilbuchc57d5732018-12-11 23:30:11224 if ((tokens[0] != kSendDirection && tokens[0] != kReceiveDirection) ||
225 (bidirectional && tokens[2] != kSendDirection &&
226 tokens[2] != kReceiveDirection) ||
Amit Hilbucha2012042018-12-03 19:35:05227 (bidirectional && tokens[0] == tokens[2])) {
228 return ParseError("Valid values: send / recv.");
229 }
230
231 // Tokens 1, 3 (if exists) should be alternative layer lists
232 RTCErrorOr<SimulcastLayerList> list1, list2;
233 list1 = ParseSimulcastLayerList(tokens[1]);
234 if (!list1.ok()) {
235 return list1.MoveError();
236 }
237
238 if (bidirectional) {
239 list2 = ParseSimulcastLayerList(tokens[3]);
240 if (!list2.ok()) {
241 return list2.MoveError();
242 }
243 }
244
245 // Set the layers so that list1 is for send and list2 is for recv
Amit Hilbuchc57d5732018-12-11 23:30:11246 if (tokens[0] != kSendDirection) {
Amit Hilbucha2012042018-12-03 19:35:05247 std::swap(list1, list2);
248 }
249
250 // Set the layers according to which pair is send and which is recv
251 // At this point if the simulcast is unidirectional then
Artem Titov880fa812021-07-30 20:30:23252 // either `list1` or `list2` will be in 'error' state indicating that
Amit Hilbucha2012042018-12-03 19:35:05253 // the value should not be used.
254 SimulcastDescription simulcast;
255 if (list1.ok()) {
256 simulcast.send_layers() = list1.MoveValue();
257 }
258
259 if (list2.ok()) {
260 simulcast.receive_layers() = list2.MoveValue();
261 }
262
263 return std::move(simulcast);
264}
265
Amit Hilbuchc57d5732018-12-11 23:30:11266std::string SdpSerializer::SerializeRidDescription(
267 const RidDescription& rid_description) const {
268 RTC_DCHECK(!rid_description.rid.empty());
269 RTC_DCHECK(rid_description.direction == RidDirection::kSend ||
270 rid_description.direction == RidDirection::kReceive);
271
272 rtc::StringBuilder builder;
273 builder << rid_description.rid << kDelimiterSpace
274 << (rid_description.direction == RidDirection::kSend
275 ? kSendDirection
276 : kReceiveDirection);
277
278 const auto& payload_types = rid_description.payload_types;
279 const auto& restrictions = rid_description.restrictions;
280
281 // First property is separated by ' ', the next ones by ';'.
282 const char* propertyDelimiter = kDelimiterSpace;
283
284 // Serialize any codecs in the description.
285 if (!payload_types.empty()) {
286 builder << propertyDelimiter << kPayloadType << kDelimiterEqual;
287 propertyDelimiter = kDelimiterSemicolon;
288 const char* formatDelimiter = "";
289 for (int payload_type : payload_types) {
290 builder << formatDelimiter << payload_type;
291 formatDelimiter = kDelimiterComma;
292 }
293 }
294
295 // Serialize any restrictions in the description.
296 for (const auto& pair : restrictions) {
297 // Serialize key=val pairs. =val part is ommitted if val is empty.
298 builder << propertyDelimiter << pair.first;
299 if (!pair.second.empty()) {
300 builder << kDelimiterEqual << pair.second;
301 }
302
303 propertyDelimiter = kDelimiterSemicolon;
304 }
305
306 return builder.str();
307}
308
309// https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
310// Formal Grammar
311// rid-syntax = %s"a=rid:" rid-id SP rid-dir
312// [ rid-pt-param-list / rid-param-list ]
313// rid-id = 1*(alpha-numeric / "-" / "_")
314// rid-dir = %s"send" / %s"recv"
315// rid-pt-param-list = SP rid-fmt-list *( ";" rid-param )
316// rid-param-list = SP rid-param *( ";" rid-param )
317// rid-fmt-list = %s"pt=" fmt *( "," fmt )
318// rid-param = 1*(alpha-numeric / "-") [ "=" param-val ]
319// param-val = *( %x20-58 / %x60-7E )
320// ; Any printable character except semicolon
321RTCErrorOr<RidDescription> SdpSerializer::DeserializeRidDescription(
322 absl::string_view string) const {
323 std::vector<std::string> tokens;
324 rtc::tokenize(std::string(string), kDelimiterSpaceChar, &tokens);
325
326 if (tokens.size() < 2) {
327 return ParseError("RID Description must contain <RID> <direction>.");
328 }
329
330 if (tokens.size() > 3) {
331 return ParseError("Invalid RID Description format. Too many arguments.");
332 }
333
Amit Hilbuchf4770402019-04-08 21:11:57334 if (!IsLegalRsidName(tokens[0])) {
335 return ParseError("Invalid RID value: " + tokens[0] + ".");
336 }
337
Amit Hilbuchc57d5732018-12-11 23:30:11338 if (tokens[1] != kSendDirection && tokens[1] != kReceiveDirection) {
339 return ParseError("Invalid RID direction. Supported values: send / recv.");
340 }
341
342 RidDirection direction = tokens[1] == kSendDirection ? RidDirection::kSend
343 : RidDirection::kReceive;
344
345 RidDescription rid_description(tokens[0], direction);
346
347 // If there is a third argument it is a payload list and/or restriction list.
348 if (tokens.size() == 3) {
349 std::vector<std::string> restrictions;
350 rtc::tokenize(tokens[2], kDelimiterSemicolonChar, &restrictions);
351
352 // Check for malformed restriction list, such as ';' or ';;;' etc.
353 if (restrictions.empty()) {
354 return ParseError("Invalid RID restriction list: " + tokens[2]);
355 }
356
357 // Parse the restrictions. The payload indicator (pt) can only appear first.
358 for (const std::string& restriction : restrictions) {
359 std::vector<std::string> parts;
360 rtc::tokenize(restriction, kDelimiterEqualChar, &parts);
361 if (parts.empty() || parts.size() > 2) {
362 return ParseError("Invalid format for restriction: " + restriction);
363 }
364
Artem Titov880fa812021-07-30 20:30:23365 // `parts` contains at least one value and it does not contain a space.
366 // Note: `parts` and other values might still contain tab, newline,
Amit Hilbuchc57d5732018-12-11 23:30:11367 // unprintable characters, etc. which will not generate errors here but
368 // will (most-likely) be ignored by components down stream.
369 if (parts[0] == kPayloadType) {
370 RTCError error = ParseRidPayloadList(
371 parts.size() > 1 ? parts[1] : std::string(), &rid_description);
372 if (!error.ok()) {
373 return std::move(error);
374 }
375
376 continue;
377 }
378
Artem Titov880fa812021-07-30 20:30:23379 // Parse `parts` as a key=value pair which allows unspecified values.
Amit Hilbuchc57d5732018-12-11 23:30:11380 if (rid_description.restrictions.find(parts[0]) !=
381 rid_description.restrictions.end()) {
382 return ParseError("Duplicate restriction specified: " + parts[0]);
383 }
384
385 rid_description.restrictions[parts[0]] =
386 parts.size() > 1 ? parts[1] : std::string();
387 }
388 }
389
390 return std::move(rid_description);
391}
392
Amit Hilbucha2012042018-12-03 19:35:05393} // namespace webrtc