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