blob: 7ebaffda868578bea278baa5e4f139f63726eaa5 [file] [log] [blame]
Amit Hilbucha2012042018-12-03 11:35:05 -08001/*
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 09:11:00 -080011#include "pc/sdp_serializer.h"
Amit Hilbucha2012042018-12-03 11:35:05 -080012
13#include <string>
14#include <utility>
15#include <vector>
16
Steve Anton64b626b2019-01-28 17:25:26 -080017#include "absl/algorithm/container.h"
Amit Hilbucha2012042018-12-03 11:35:05 -080018#include "api/jsep.h"
Amit Hilbuchf4770402019-04-08 14:11:57 -070019#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
Amit Hilbuchc57d5732018-12-11 15:30:11 -080020#include "rtc_base/checks.h"
Steve Anton10542f22019-01-11 09:11:00 -080021#include "rtc_base/string_encode.h"
Amit Hilbuchc57d5732018-12-11 15:30:11 -080022#include "rtc_base/string_to_number.h"
Amit Hilbucha2012042018-12-03 11:35:05 -080023#include "rtc_base/strings/string_builder.h"
24
Amit Hilbuchc57d5732018-12-11 15:30:11 -080025using cricket::RidDescription;
26using cricket::RidDirection;
Amit Hilbucha2012042018-12-03 11:35:05 -080027using cricket::SimulcastDescription;
28using cricket::SimulcastLayer;
29using cricket::SimulcastLayerList;
30
31namespace webrtc {
32
33namespace {
34
35// delimiters
36const char kDelimiterComma[] = ",";
37const char kDelimiterCommaChar = ',';
Amit Hilbuchc57d5732018-12-11 15:30:11 -080038const char kDelimiterEqual[] = "=";
39const char kDelimiterEqualChar = '=';
Amit Hilbucha2012042018-12-03 11:35:05 -080040const char kDelimiterSemicolon[] = ";";
41const char kDelimiterSemicolonChar = ';';
42const char kDelimiterSpace[] = " ";
43const char kDelimiterSpaceChar = ' ';
44
45// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
Amit Hilbuchc57d5732018-12-11 15:30:11 -080046// https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
Amit Hilbucha2012042018-12-03 11:35:05 -080047const char kSimulcastPausedStream[] = "~";
48const char kSimulcastPausedStreamChar = '~';
Amit Hilbuchc57d5732018-12-11 15:30:11 -080049const char kSendDirection[] = "send";
50const char kReceiveDirection[] = "recv";
51const char kPayloadType[] = "pt";
Amit Hilbucha2012042018-12-03 11:35:05 -080052
53RTCError ParseError(const std::string& message) {
54 return RTCError(RTCErrorType::SYNTAX_ERROR, message);
55}
56
57// These methods serialize simulcast according to the specification:
58// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
59rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
60 const SimulcastLayer& simulcast_layer) {
61 if (simulcast_layer.is_paused) {
62 builder << kSimulcastPausedStream;
63 }
64 builder << simulcast_layer.rid;
65 return builder;
66}
67
68rtc::StringBuilder& operator<<(
69 rtc::StringBuilder& builder,
70 const std::vector<SimulcastLayer>& layer_alternatives) {
71 bool first = true;
72 for (const SimulcastLayer& rid : layer_alternatives) {
73 if (!first) {
74 builder << kDelimiterComma;
75 }
76 builder << rid;
77 first = false;
78 }
79 return builder;
80}
81
82rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
83 const SimulcastLayerList& simulcast_layers) {
84 bool first = true;
Mirko Bonadei739baf02019-01-27 17:29:42 +010085 for (const auto& alternatives : simulcast_layers) {
Amit Hilbucha2012042018-12-03 11:35:05 -080086 if (!first) {
87 builder << kDelimiterSemicolon;
88 }
89 builder << alternatives;
90 first = false;
91 }
92 return builder;
93}
Amit Hilbuchc57d5732018-12-11 15:30:11 -080094// This method deserializes simulcast according to the specification:
Amit Hilbucha2012042018-12-03 11:35:05 -080095// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
96// sc-str-list = sc-alt-list *( ";" sc-alt-list )
97// sc-alt-list = sc-id *( "," sc-id )
98// sc-id-paused = "~"
99// sc-id = [sc-id-paused] rid-id
100// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
101RTCErrorOr<SimulcastLayerList> ParseSimulcastLayerList(const std::string& str) {
102 std::vector<std::string> tokens;
103 rtc::tokenize_with_empty_tokens(str, kDelimiterSemicolonChar, &tokens);
104 if (tokens.empty()) {
105 return ParseError("Layer list cannot be empty.");
106 }
107
108 SimulcastLayerList result;
109 for (const std::string& token : tokens) {
110 if (token.empty()) {
111 return ParseError("Simulcast alternative layer list is empty.");
112 }
113
114 std::vector<std::string> rid_tokens;
115 rtc::tokenize_with_empty_tokens(token, kDelimiterCommaChar, &rid_tokens);
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800116
Amit Hilbucha2012042018-12-03 11:35:05 -0800117 if (rid_tokens.empty()) {
118 return ParseError("Simulcast alternative layer list is malformed.");
119 }
120
121 std::vector<SimulcastLayer> layers;
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800122 for (const std::string& rid_token : rid_tokens) {
Amit Hilbucha2012042018-12-03 11:35:05 -0800123 if (rid_token.empty() || rid_token == kSimulcastPausedStream) {
124 return ParseError("Rid must not be empty.");
125 }
126
127 bool paused = rid_token[0] == kSimulcastPausedStreamChar;
128 std::string rid = paused ? rid_token.substr(1) : rid_token;
Amit Hilbucha2012042018-12-03 11:35:05 -0800129 layers.push_back(SimulcastLayer(rid, paused));
130 }
131
132 result.AddLayerWithAlternatives(layers);
133 }
134
135 return std::move(result);
136}
137
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800138webrtc::RTCError ParseRidPayloadList(const std::string& payload_list,
139 RidDescription* rid_description) {
140 RTC_DCHECK(rid_description);
141 std::vector<int>& payload_types = rid_description->payload_types;
142 // Check that the description doesn't have any payload types or restrictions.
143 // If the pt= field is specified, it must be first and must not repeat.
144 if (!payload_types.empty()) {
145 return ParseError("Multiple pt= found in RID Description.");
146 }
147 if (!rid_description->restrictions.empty()) {
148 return ParseError("Payload list must appear first in the restrictions.");
149 }
150
151 // If the pt= field is specified, it must have a value.
152 if (payload_list.empty()) {
153 return ParseError("Payload list must have at least one value.");
154 }
155
156 // Tokenize the ',' delimited list
157 std::vector<std::string> string_payloads;
158 rtc::tokenize(payload_list, kDelimiterCommaChar, &string_payloads);
159 if (string_payloads.empty()) {
160 return ParseError("Payload list must have at least one value.");
161 }
162
163 for (const std::string& payload_type : string_payloads) {
164 absl::optional<int> value = rtc::StringToNumber<int>(payload_type);
165 if (!value.has_value()) {
166 return ParseError("Invalid payload type: " + payload_type);
167 }
168
169 // Check if the value already appears in the payload list.
Steve Anton64b626b2019-01-28 17:25:26 -0800170 if (absl::c_linear_search(payload_types, value.value())) {
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800171 return ParseError("Duplicate payload type in list: " + payload_type);
172 }
173 payload_types.push_back(value.value());
174 }
175
176 return RTCError::OK();
177}
178
Amit Hilbucha2012042018-12-03 11:35:05 -0800179} // namespace
180
181std::string SdpSerializer::SerializeSimulcastDescription(
182 const cricket::SimulcastDescription& simulcast) const {
183 rtc::StringBuilder sb;
184 std::string delimiter;
185
186 if (!simulcast.send_layers().empty()) {
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800187 sb << kSendDirection << kDelimiterSpace << simulcast.send_layers();
Amit Hilbucha2012042018-12-03 11:35:05 -0800188 delimiter = kDelimiterSpace;
189 }
190
191 if (!simulcast.receive_layers().empty()) {
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800192 sb << delimiter << kReceiveDirection << kDelimiterSpace
Amit Hilbucha2012042018-12-03 11:35:05 -0800193 << simulcast.receive_layers();
194 }
195
196 return sb.str();
197}
198
199// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
200// a:simulcast:<send> <streams> <recv> <streams>
201// Formal Grammar
202// sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] )
203// sc-send = %s"send" SP sc-str-list
204// sc-recv = %s"recv" SP sc-str-list
205// sc-str-list = sc-alt-list *( ";" sc-alt-list )
206// sc-alt-list = sc-id *( "," sc-id )
207// sc-id-paused = "~"
208// sc-id = [sc-id-paused] rid-id
209// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
210RTCErrorOr<SimulcastDescription> SdpSerializer::DeserializeSimulcastDescription(
211 absl::string_view string) const {
212 std::vector<std::string> tokens;
213 rtc::tokenize(std::string(string), kDelimiterSpaceChar, &tokens);
214
215 if (tokens.size() != 2 && tokens.size() != 4) {
216 return ParseError("Must have one or two <direction, streams> pairs.");
217 }
218
219 bool bidirectional = tokens.size() == 4; // indicates both send and recv
220
221 // Tokens 0, 2 (if exists) should be send / recv
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800222 if ((tokens[0] != kSendDirection && tokens[0] != kReceiveDirection) ||
223 (bidirectional && tokens[2] != kSendDirection &&
224 tokens[2] != kReceiveDirection) ||
Amit Hilbucha2012042018-12-03 11:35:05 -0800225 (bidirectional && tokens[0] == tokens[2])) {
226 return ParseError("Valid values: send / recv.");
227 }
228
229 // Tokens 1, 3 (if exists) should be alternative layer lists
230 RTCErrorOr<SimulcastLayerList> list1, list2;
231 list1 = ParseSimulcastLayerList(tokens[1]);
232 if (!list1.ok()) {
233 return list1.MoveError();
234 }
235
236 if (bidirectional) {
237 list2 = ParseSimulcastLayerList(tokens[3]);
238 if (!list2.ok()) {
239 return list2.MoveError();
240 }
241 }
242
243 // Set the layers so that list1 is for send and list2 is for recv
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800244 if (tokens[0] != kSendDirection) {
Amit Hilbucha2012042018-12-03 11:35:05 -0800245 std::swap(list1, list2);
246 }
247
248 // Set the layers according to which pair is send and which is recv
249 // At this point if the simulcast is unidirectional then
250 // either |list1| or |list2| will be in 'error' state indicating that
251 // the value should not be used.
252 SimulcastDescription simulcast;
253 if (list1.ok()) {
254 simulcast.send_layers() = list1.MoveValue();
255 }
256
257 if (list2.ok()) {
258 simulcast.receive_layers() = list2.MoveValue();
259 }
260
261 return std::move(simulcast);
262}
263
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800264std::string SdpSerializer::SerializeRidDescription(
265 const RidDescription& rid_description) const {
266 RTC_DCHECK(!rid_description.rid.empty());
267 RTC_DCHECK(rid_description.direction == RidDirection::kSend ||
268 rid_description.direction == RidDirection::kReceive);
269
270 rtc::StringBuilder builder;
271 builder << rid_description.rid << kDelimiterSpace
272 << (rid_description.direction == RidDirection::kSend
273 ? kSendDirection
274 : kReceiveDirection);
275
276 const auto& payload_types = rid_description.payload_types;
277 const auto& restrictions = rid_description.restrictions;
278
279 // First property is separated by ' ', the next ones by ';'.
280 const char* propertyDelimiter = kDelimiterSpace;
281
282 // Serialize any codecs in the description.
283 if (!payload_types.empty()) {
284 builder << propertyDelimiter << kPayloadType << kDelimiterEqual;
285 propertyDelimiter = kDelimiterSemicolon;
286 const char* formatDelimiter = "";
287 for (int payload_type : payload_types) {
288 builder << formatDelimiter << payload_type;
289 formatDelimiter = kDelimiterComma;
290 }
291 }
292
293 // Serialize any restrictions in the description.
294 for (const auto& pair : restrictions) {
295 // Serialize key=val pairs. =val part is ommitted if val is empty.
296 builder << propertyDelimiter << pair.first;
297 if (!pair.second.empty()) {
298 builder << kDelimiterEqual << pair.second;
299 }
300
301 propertyDelimiter = kDelimiterSemicolon;
302 }
303
304 return builder.str();
305}
306
307// https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
308// Formal Grammar
309// rid-syntax = %s"a=rid:" rid-id SP rid-dir
310// [ rid-pt-param-list / rid-param-list ]
311// rid-id = 1*(alpha-numeric / "-" / "_")
312// rid-dir = %s"send" / %s"recv"
313// rid-pt-param-list = SP rid-fmt-list *( ";" rid-param )
314// rid-param-list = SP rid-param *( ";" rid-param )
315// rid-fmt-list = %s"pt=" fmt *( "," fmt )
316// rid-param = 1*(alpha-numeric / "-") [ "=" param-val ]
317// param-val = *( %x20-58 / %x60-7E )
318// ; Any printable character except semicolon
319RTCErrorOr<RidDescription> SdpSerializer::DeserializeRidDescription(
320 absl::string_view string) const {
321 std::vector<std::string> tokens;
322 rtc::tokenize(std::string(string), kDelimiterSpaceChar, &tokens);
323
324 if (tokens.size() < 2) {
325 return ParseError("RID Description must contain <RID> <direction>.");
326 }
327
328 if (tokens.size() > 3) {
329 return ParseError("Invalid RID Description format. Too many arguments.");
330 }
331
Amit Hilbuchf4770402019-04-08 14:11:57 -0700332 if (!IsLegalRsidName(tokens[0])) {
333 return ParseError("Invalid RID value: " + tokens[0] + ".");
334 }
335
Amit Hilbuchc57d5732018-12-11 15:30:11 -0800336 if (tokens[1] != kSendDirection && tokens[1] != kReceiveDirection) {
337 return ParseError("Invalid RID direction. Supported values: send / recv.");
338 }
339
340 RidDirection direction = tokens[1] == kSendDirection ? RidDirection::kSend
341 : RidDirection::kReceive;
342
343 RidDescription rid_description(tokens[0], direction);
344
345 // If there is a third argument it is a payload list and/or restriction list.
346 if (tokens.size() == 3) {
347 std::vector<std::string> restrictions;
348 rtc::tokenize(tokens[2], kDelimiterSemicolonChar, &restrictions);
349
350 // Check for malformed restriction list, such as ';' or ';;;' etc.
351 if (restrictions.empty()) {
352 return ParseError("Invalid RID restriction list: " + tokens[2]);
353 }
354
355 // Parse the restrictions. The payload indicator (pt) can only appear first.
356 for (const std::string& restriction : restrictions) {
357 std::vector<std::string> parts;
358 rtc::tokenize(restriction, kDelimiterEqualChar, &parts);
359 if (parts.empty() || parts.size() > 2) {
360 return ParseError("Invalid format for restriction: " + restriction);
361 }
362
363 // |parts| contains at least one value and it does not contain a space.
364 // Note: |parts| and other values might still contain tab, newline,
365 // unprintable characters, etc. which will not generate errors here but
366 // will (most-likely) be ignored by components down stream.
367 if (parts[0] == kPayloadType) {
368 RTCError error = ParseRidPayloadList(
369 parts.size() > 1 ? parts[1] : std::string(), &rid_description);
370 if (!error.ok()) {
371 return std::move(error);
372 }
373
374 continue;
375 }
376
377 // Parse |parts| as a key=value pair which allows unspecified values.
378 if (rid_description.restrictions.find(parts[0]) !=
379 rid_description.restrictions.end()) {
380 return ParseError("Duplicate restriction specified: " + parts[0]);
381 }
382
383 rid_description.restrictions[parts[0]] =
384 parts.size() > 1 ? parts[1] : std::string();
385 }
386 }
387
388 return std::move(rid_description);
389}
390
Amit Hilbucha2012042018-12-03 11:35:05 -0800391} // namespace webrtc