• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
11 #include "pc/sdp_serializer.h"
12 
13 #include <string>
14 #include <utility>
15 #include <vector>
16 
17 #include "absl/algorithm/container.h"
18 #include "api/jsep.h"
19 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
20 #include "rtc_base/checks.h"
21 #include "rtc_base/string_encode.h"
22 #include "rtc_base/string_to_number.h"
23 #include "rtc_base/strings/string_builder.h"
24 
25 using cricket::RidDescription;
26 using cricket::RidDirection;
27 using cricket::SimulcastDescription;
28 using cricket::SimulcastLayer;
29 using cricket::SimulcastLayerList;
30 
31 namespace webrtc {
32 
33 namespace {
34 
35 // delimiters
36 const char kDelimiterComma[] = ",";
37 const char kDelimiterCommaChar = ',';
38 const char kDelimiterEqual[] = "=";
39 const char kDelimiterEqualChar = '=';
40 const char kDelimiterSemicolon[] = ";";
41 const char kDelimiterSemicolonChar = ';';
42 const char kDelimiterSpace[] = " ";
43 const char kDelimiterSpaceChar = ' ';
44 
45 // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
46 // https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
47 const char kSimulcastPausedStream[] = "~";
48 const char kSimulcastPausedStreamChar = '~';
49 const char kSendDirection[] = "send";
50 const char kReceiveDirection[] = "recv";
51 const char kPayloadType[] = "pt";
52 
ParseError(const std::string & message)53 RTCError 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
operator <<(rtc::StringBuilder & builder,const SimulcastLayer & simulcast_layer)59 rtc::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 
operator <<(rtc::StringBuilder & builder,const std::vector<SimulcastLayer> & layer_alternatives)68 rtc::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 
operator <<(rtc::StringBuilder & builder,const SimulcastLayerList & simulcast_layers)82 rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
83                                const SimulcastLayerList& simulcast_layers) {
84   bool first = true;
85   for (const auto& alternatives : simulcast_layers) {
86     if (!first) {
87       builder << kDelimiterSemicolon;
88     }
89     builder << alternatives;
90     first = false;
91   }
92   return builder;
93 }
94 // This method deserializes simulcast according to the specification:
95 // 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
ParseSimulcastLayerList(const std::string & str)101 RTCErrorOr<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);
116 
117     if (rid_tokens.empty()) {
118       return ParseError("Simulcast alternative layer list is malformed.");
119     }
120 
121     std::vector<SimulcastLayer> layers;
122     for (const std::string& rid_token : rid_tokens) {
123       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;
129       layers.push_back(SimulcastLayer(rid, paused));
130     }
131 
132     result.AddLayerWithAlternatives(layers);
133   }
134 
135   return std::move(result);
136 }
137 
ParseRidPayloadList(const std::string & payload_list,RidDescription * rid_description)138 webrtc::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.
170     if (absl::c_linear_search(payload_types, value.value())) {
171       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 
179 }  // namespace
180 
SerializeSimulcastDescription(const cricket::SimulcastDescription & simulcast) const181 std::string SdpSerializer::SerializeSimulcastDescription(
182     const cricket::SimulcastDescription& simulcast) const {
183   rtc::StringBuilder sb;
184   std::string delimiter;
185 
186   if (!simulcast.send_layers().empty()) {
187     sb << kSendDirection << kDelimiterSpace << simulcast.send_layers();
188     delimiter = kDelimiterSpace;
189   }
190 
191   if (!simulcast.receive_layers().empty()) {
192     sb << delimiter << kReceiveDirection << kDelimiterSpace
193        << 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
DeserializeSimulcastDescription(absl::string_view string) const210 RTCErrorOr<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
222   if ((tokens[0] != kSendDirection && tokens[0] != kReceiveDirection) ||
223       (bidirectional && tokens[2] != kSendDirection &&
224        tokens[2] != kReceiveDirection) ||
225       (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
244   if (tokens[0] != kSendDirection) {
245     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 
SerializeRidDescription(const RidDescription & rid_description) const264 std::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
DeserializeRidDescription(absl::string_view string) const319 RTCErrorOr<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 
332   if (!IsLegalRsidName(tokens[0])) {
333     return ParseError("Invalid RID value: " + tokens[0] + ".");
334   }
335 
336   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 
391 }  // namespace webrtc
392