2 * Copyright 2018 The WebRTC project authors. All Rights Reserved.
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.
11 #include "pc/sdp_serializer.h"
15 #include <type_traits>
19 #include "absl/algorithm/container.h"
20 #include "absl/strings/string_view.h"
21 #include "absl/types/optional.h"
22 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
23 #include "rtc_base/checks.h"
24 #include "rtc_base/string_encode.h"
25 #include "rtc_base/string_to_number.h"
26 #include "rtc_base/strings/string_builder.h"
28 using cricket::RidDescription
;
29 using cricket::RidDirection
;
30 using cricket::SimulcastDescription
;
31 using cricket::SimulcastLayer
;
32 using cricket::SimulcastLayerList
;
39 const char kDelimiterComma
[] = ",";
40 const char kDelimiterCommaChar
= ',';
41 const char kDelimiterEqual
[] = "=";
42 const char kDelimiterEqualChar
= '=';
43 const char kDelimiterSemicolon
[] = ";";
44 const char kDelimiterSemicolonChar
= ';';
45 const char kDelimiterSpace
[] = " ";
46 const char kDelimiterSpaceChar
= ' ';
48 // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
49 // https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
50 const char kSimulcastPausedStream
[] = "~";
51 const char kSimulcastPausedStreamChar
= '~';
52 const char kSendDirection
[] = "send";
53 const char kReceiveDirection
[] = "recv";
54 const char kPayloadType
[] = "pt";
56 RTCError
ParseError(absl::string_view message
) {
57 return RTCError(RTCErrorType::SYNTAX_ERROR
, message
);
60 // These methods serialize simulcast according to the specification:
61 // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
62 rtc::StringBuilder
& operator<<(rtc::StringBuilder
& builder
,
63 const SimulcastLayer
& simulcast_layer
) {
64 if (simulcast_layer
.is_paused
) {
65 builder
<< kSimulcastPausedStream
;
67 builder
<< simulcast_layer
.rid
;
71 rtc::StringBuilder
& operator<<(
72 rtc::StringBuilder
& builder
,
73 const std::vector
<SimulcastLayer
>& layer_alternatives
) {
75 for (const SimulcastLayer
& rid
: layer_alternatives
) {
77 builder
<< kDelimiterComma
;
85 rtc::StringBuilder
& operator<<(rtc::StringBuilder
& builder
,
86 const SimulcastLayerList
& simulcast_layers
) {
88 for (const auto& alternatives
: simulcast_layers
) {
90 builder
<< kDelimiterSemicolon
;
92 builder
<< alternatives
;
97 // This method deserializes simulcast according to the specification:
98 // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
99 // sc-str-list = sc-alt-list *( ";" sc-alt-list )
100 // sc-alt-list = sc-id *( "," sc-id )
101 // sc-id-paused = "~"
102 // sc-id = [sc-id-paused] rid-id
103 // rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
104 RTCErrorOr
<SimulcastLayerList
> ParseSimulcastLayerList(const std::string
& str
) {
105 std::vector
<absl::string_view
> tokens
=
106 rtc::split(str
, kDelimiterSemicolonChar
);
107 if (tokens
.empty()) {
108 return ParseError("Layer list cannot be empty.");
111 SimulcastLayerList result
;
112 for (const absl::string_view
& token
: tokens
) {
114 return ParseError("Simulcast alternative layer list is empty.");
117 std::vector
<absl::string_view
> rid_tokens
=
118 rtc::split(token
, kDelimiterCommaChar
);
120 if (rid_tokens
.empty()) {
121 return ParseError("Simulcast alternative layer list is malformed.");
124 std::vector
<SimulcastLayer
> layers
;
125 for (const absl::string_view
& rid_token
: rid_tokens
) {
126 if (rid_token
.empty() || rid_token
== kSimulcastPausedStream
) {
127 return ParseError("Rid must not be empty.");
130 bool paused
= rid_token
[0] == kSimulcastPausedStreamChar
;
131 absl::string_view rid
= paused
? rid_token
.substr(1) : rid_token
;
132 layers
.push_back(SimulcastLayer(rid
, paused
));
135 result
.AddLayerWithAlternatives(layers
);
138 return std::move(result
);
141 webrtc::RTCError
ParseRidPayloadList(const std::string
& payload_list
,
142 RidDescription
* rid_description
) {
143 RTC_DCHECK(rid_description
);
144 std::vector
<int>& payload_types
= rid_description
->payload_types
;
145 // Check that the description doesn't have any payload types or restrictions.
146 // If the pt= field is specified, it must be first and must not repeat.
147 if (!payload_types
.empty()) {
148 return ParseError("Multiple pt= found in RID Description.");
150 if (!rid_description
->restrictions
.empty()) {
151 return ParseError("Payload list must appear first in the restrictions.");
154 // If the pt= field is specified, it must have a value.
155 if (payload_list
.empty()) {
156 return ParseError("Payload list must have at least one value.");
159 // Tokenize the ',' delimited list
160 std::vector
<std::string
> string_payloads
;
161 rtc::tokenize(payload_list
, kDelimiterCommaChar
, &string_payloads
);
162 if (string_payloads
.empty()) {
163 return ParseError("Payload list must have at least one value.");
166 for (const std::string
& payload_type
: string_payloads
) {
167 absl::optional
<int> value
= rtc::StringToNumber
<int>(payload_type
);
168 if (!value
.has_value()) {
169 return ParseError("Invalid payload type: " + payload_type
);
172 // Check if the value already appears in the payload list.
173 if (absl::c_linear_search(payload_types
, value
.value())) {
174 return ParseError("Duplicate payload type in list: " + payload_type
);
176 payload_types
.push_back(value
.value());
179 return RTCError::OK();
184 std::string
SdpSerializer::SerializeSimulcastDescription(
185 const cricket::SimulcastDescription
& simulcast
) const {
186 rtc::StringBuilder sb
;
187 std::string delimiter
;
189 if (!simulcast
.send_layers().empty()) {
190 sb
<< kSendDirection
<< kDelimiterSpace
<< simulcast
.send_layers();
191 delimiter
= kDelimiterSpace
;
194 if (!simulcast
.receive_layers().empty()) {
195 sb
<< delimiter
<< kReceiveDirection
<< kDelimiterSpace
196 << simulcast
.receive_layers();
202 // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
203 // a:simulcast:<send> <streams> <recv> <streams>
205 // sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] )
206 // sc-send = %s"send" SP sc-str-list
207 // sc-recv = %s"recv" SP sc-str-list
208 // sc-str-list = sc-alt-list *( ";" sc-alt-list )
209 // sc-alt-list = sc-id *( "," sc-id )
210 // sc-id-paused = "~"
211 // sc-id = [sc-id-paused] rid-id
212 // rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
213 RTCErrorOr
<SimulcastDescription
> SdpSerializer::DeserializeSimulcastDescription(
214 absl::string_view string
) const {
215 std::vector
<std::string
> tokens
;
216 rtc::tokenize(std::string(string
), kDelimiterSpaceChar
, &tokens
);
218 if (tokens
.size() != 2 && tokens
.size() != 4) {
219 return ParseError("Must have one or two <direction, streams> pairs.");
222 bool bidirectional
= tokens
.size() == 4; // indicates both send and recv
224 // Tokens 0, 2 (if exists) should be send / recv
225 if ((tokens
[0] != kSendDirection
&& tokens
[0] != kReceiveDirection
) ||
226 (bidirectional
&& tokens
[2] != kSendDirection
&&
227 tokens
[2] != kReceiveDirection
) ||
228 (bidirectional
&& tokens
[0] == tokens
[2])) {
229 return ParseError("Valid values: send / recv.");
232 // Tokens 1, 3 (if exists) should be alternative layer lists
233 RTCErrorOr
<SimulcastLayerList
> list1
, list2
;
234 list1
= ParseSimulcastLayerList(tokens
[1]);
236 return list1
.MoveError();
240 list2
= ParseSimulcastLayerList(tokens
[3]);
242 return list2
.MoveError();
246 // Set the layers so that list1 is for send and list2 is for recv
247 if (tokens
[0] != kSendDirection
) {
248 std::swap(list1
, list2
);
251 // Set the layers according to which pair is send and which is recv
252 // At this point if the simulcast is unidirectional then
253 // either `list1` or `list2` will be in 'error' state indicating that
254 // the value should not be used.
255 SimulcastDescription simulcast
;
257 simulcast
.send_layers() = list1
.MoveValue();
261 simulcast
.receive_layers() = list2
.MoveValue();
264 return std::move(simulcast
);
267 std::string
SdpSerializer::SerializeRidDescription(
268 const RidDescription
& rid_description
) const {
269 RTC_DCHECK(!rid_description
.rid
.empty());
270 RTC_DCHECK(rid_description
.direction
== RidDirection::kSend
||
271 rid_description
.direction
== RidDirection::kReceive
);
273 rtc::StringBuilder builder
;
274 builder
<< rid_description
.rid
<< kDelimiterSpace
275 << (rid_description
.direction
== RidDirection::kSend
277 : kReceiveDirection
);
279 const auto& payload_types
= rid_description
.payload_types
;
280 const auto& restrictions
= rid_description
.restrictions
;
282 // First property is separated by ' ', the next ones by ';'.
283 const char* propertyDelimiter
= kDelimiterSpace
;
285 // Serialize any codecs in the description.
286 if (!payload_types
.empty()) {
287 builder
<< propertyDelimiter
<< kPayloadType
<< kDelimiterEqual
;
288 propertyDelimiter
= kDelimiterSemicolon
;
289 const char* formatDelimiter
= "";
290 for (int payload_type
: payload_types
) {
291 builder
<< formatDelimiter
<< payload_type
;
292 formatDelimiter
= kDelimiterComma
;
296 // Serialize any restrictions in the description.
297 for (const auto& pair
: restrictions
) {
298 // Serialize key=val pairs. =val part is ommitted if val is empty.
299 builder
<< propertyDelimiter
<< pair
.first
;
300 if (!pair
.second
.empty()) {
301 builder
<< kDelimiterEqual
<< pair
.second
;
304 propertyDelimiter
= kDelimiterSemicolon
;
307 return builder
.str();
310 // https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
312 // rid-syntax = %s"a=rid:" rid-id SP rid-dir
313 // [ rid-pt-param-list / rid-param-list ]
314 // rid-id = 1*(alpha-numeric / "-" / "_")
315 // rid-dir = %s"send" / %s"recv"
316 // rid-pt-param-list = SP rid-fmt-list *( ";" rid-param )
317 // rid-param-list = SP rid-param *( ";" rid-param )
318 // rid-fmt-list = %s"pt=" fmt *( "," fmt )
319 // rid-param = 1*(alpha-numeric / "-") [ "=" param-val ]
320 // param-val = *( %x20-58 / %x60-7E )
321 // ; Any printable character except semicolon
322 RTCErrorOr
<RidDescription
> SdpSerializer::DeserializeRidDescription(
323 absl::string_view string
) const {
324 std::vector
<std::string
> tokens
;
325 rtc::tokenize(std::string(string
), kDelimiterSpaceChar
, &tokens
);
327 if (tokens
.size() < 2) {
328 return ParseError("RID Description must contain <RID> <direction>.");
331 if (tokens
.size() > 3) {
332 return ParseError("Invalid RID Description format. Too many arguments.");
335 if (!IsLegalRsidName(tokens
[0])) {
336 return ParseError("Invalid RID value: " + tokens
[0] + ".");
339 if (tokens
[1] != kSendDirection
&& tokens
[1] != kReceiveDirection
) {
340 return ParseError("Invalid RID direction. Supported values: send / recv.");
343 RidDirection direction
= tokens
[1] == kSendDirection
? RidDirection::kSend
344 : RidDirection::kReceive
;
346 RidDescription
rid_description(tokens
[0], direction
);
348 // If there is a third argument it is a payload list and/or restriction list.
349 if (tokens
.size() == 3) {
350 std::vector
<std::string
> restrictions
;
351 rtc::tokenize(tokens
[2], kDelimiterSemicolonChar
, &restrictions
);
353 // Check for malformed restriction list, such as ';' or ';;;' etc.
354 if (restrictions
.empty()) {
355 return ParseError("Invalid RID restriction list: " + tokens
[2]);
358 // Parse the restrictions. The payload indicator (pt) can only appear first.
359 for (const std::string
& restriction
: restrictions
) {
360 std::vector
<std::string
> parts
;
361 rtc::tokenize(restriction
, kDelimiterEqualChar
, &parts
);
362 if (parts
.empty() || parts
.size() > 2) {
363 return ParseError("Invalid format for restriction: " + restriction
);
366 // `parts` contains at least one value and it does not contain a space.
367 // Note: `parts` and other values might still contain tab, newline,
368 // unprintable characters, etc. which will not generate errors here but
369 // will (most-likely) be ignored by components down stream.
370 if (parts
[0] == kPayloadType
) {
371 RTCError error
= ParseRidPayloadList(
372 parts
.size() > 1 ? parts
[1] : std::string(), &rid_description
);
374 return std::move(error
);
380 // Parse `parts` as a key=value pair which allows unspecified values.
381 if (rid_description
.restrictions
.find(parts
[0]) !=
382 rid_description
.restrictions
.end()) {
383 return ParseError("Duplicate restriction specified: " + parts
[0]);
386 rid_description
.restrictions
[parts
[0]] =
387 parts
.size() > 1 ? parts
[1] : std::string();
391 return std::move(rid_description
);
394 } // namespace webrtc