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 <algorithm>
14 #include <map>
15 #include <string>
16 #include <utility>
17 #include <vector>
18 
19 #include "absl/algorithm/container.h"
20 #include "absl/types/optional.h"
21 #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
22 #include "rtc_base/checks.h"
23 #include "rtc_base/string_encode.h"
24 #include "rtc_base/string_to_number.h"
25 #include "rtc_base/strings/string_builder.h"
26 
27 using cricket::RidDescription;
28 using cricket::RidDirection;
29 using cricket::SimulcastDescription;
30 using cricket::SimulcastLayer;
31 using cricket::SimulcastLayerList;
32 
33 namespace webrtc {
34 
35 namespace {
36 
37 // delimiters
38 const char kDelimiterComma[] = ",";
39 const char kDelimiterCommaChar = ',';
40 const char kDelimiterEqual[] = "=";
41 const char kDelimiterEqualChar = '=';
42 const char kDelimiterSemicolon[] = ";";
43 const char kDelimiterSemicolonChar = ';';
44 const char kDelimiterSpace[] = " ";
45 const char kDelimiterSpaceChar = ' ';
46 
47 // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
48 // https://tools.ietf.org/html/draft-ietf-mmusic-rid-15#section-10
49 const char kSimulcastPausedStream[] = "~";
50 const char kSimulcastPausedStreamChar = '~';
51 const char kSendDirection[] = "send";
52 const char kReceiveDirection[] = "recv";
53 const char kPayloadType[] = "pt";
54 
ParseError(const std::string & message)55 RTCError 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
operator <<(rtc::StringBuilder & builder,const SimulcastLayer & simulcast_layer)61 rtc::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 
operator <<(rtc::StringBuilder & builder,const std::vector<SimulcastLayer> & layer_alternatives)70 rtc::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 
operator <<(rtc::StringBuilder & builder,const SimulcastLayerList & simulcast_layers)84 rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
85                                const SimulcastLayerList& simulcast_layers) {
86   bool first = true;
87   for (const auto& alternatives : simulcast_layers) {
88     if (!first) {
89       builder << kDelimiterSemicolon;
90     }
91     builder << alternatives;
92     first = false;
93   }
94   return builder;
95 }
96 // This method deserializes simulcast according to the specification:
97 // 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
ParseSimulcastLayerList(const std::string & str)103 RTCErrorOr<SimulcastLayerList> ParseSimulcastLayerList(const std::string& str) {
104   std::vector<std::string> tokens;
105   rtc::tokenize_with_empty_tokens(str, kDelimiterSemicolonChar, &tokens);
106   if (tokens.empty()) {
107     return ParseError("Layer list cannot be empty.");
108   }
109 
110   SimulcastLayerList result;
111   for (const std::string& token : tokens) {
112     if (token.empty()) {
113       return ParseError("Simulcast alternative layer list is empty.");
114     }
115 
116     std::vector<std::string> rid_tokens;
117     rtc::tokenize_with_empty_tokens(token, kDelimiterCommaChar, &rid_tokens);
118 
119     if (rid_tokens.empty()) {
120       return ParseError("Simulcast alternative layer list is malformed.");
121     }
122 
123     std::vector<SimulcastLayer> layers;
124     for (const std::string& rid_token : rid_tokens) {
125       if (rid_token.empty() || rid_token == kSimulcastPausedStream) {
126         return ParseError("Rid must not be empty.");
127       }
128 
129       bool paused = rid_token[0] == kSimulcastPausedStreamChar;
130       std::string rid = paused ? rid_token.substr(1) : rid_token;
131       layers.push_back(SimulcastLayer(rid, paused));
132     }
133 
134     result.AddLayerWithAlternatives(layers);
135   }
136 
137   return std::move(result);
138 }
139 
ParseRidPayloadList(const std::string & payload_list,RidDescription * rid_description)140 webrtc::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.
172     if (absl::c_linear_search(payload_types, value.value())) {
173       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 
181 }  // namespace
182 
SerializeSimulcastDescription(const cricket::SimulcastDescription & simulcast) const183 std::string SdpSerializer::SerializeSimulcastDescription(
184     const cricket::SimulcastDescription& simulcast) const {
185   rtc::StringBuilder sb;
186   std::string delimiter;
187 
188   if (!simulcast.send_layers().empty()) {
189     sb << kSendDirection << kDelimiterSpace << simulcast.send_layers();
190     delimiter = kDelimiterSpace;
191   }
192 
193   if (!simulcast.receive_layers().empty()) {
194     sb << delimiter << kReceiveDirection << kDelimiterSpace
195        << 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
DeserializeSimulcastDescription(absl::string_view string) const212 RTCErrorOr<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
224   if ((tokens[0] != kSendDirection && tokens[0] != kReceiveDirection) ||
225       (bidirectional && tokens[2] != kSendDirection &&
226        tokens[2] != kReceiveDirection) ||
227       (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
246   if (tokens[0] != kSendDirection) {
247     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
252   // either |list1| or |list2| will be in 'error' state indicating that
253   // 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 
SerializeRidDescription(const RidDescription & rid_description) const266 std::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
DeserializeRidDescription(absl::string_view string) const321 RTCErrorOr<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 
334   if (!IsLegalRsidName(tokens[0])) {
335     return ParseError("Invalid RID value: " + tokens[0] + ".");
336   }
337 
338   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 
365       // |parts| contains at least one value and it does not contain a space.
366       // Note: |parts| and other values might still contain tab, newline,
367       // 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 
379       // Parse |parts| as a key=value pair which allows unspecified values.
380       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 
393 }  // namespace webrtc
394