Adding SDP parsing for Simulcast.
Parsing simulcast according to:
https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
Created SdpSerializer for making serialized components more testable.
Simulcast functionality is still not accessible to users.
Bug: webrtc:10055
Change-Id: Ia6e4cef756cb954521dd19e22911f8eb6498880e
Reviewed-on: https://webrtc-review.googlesource.com/c/112160
Commit-Queue: Amit Hilbuch <amithi@webrtc.org>
Reviewed-by: Seth Hampson <shampson@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#25883}
diff --git a/pc/sdpserializer.cc b/pc/sdpserializer.cc
new file mode 100644
index 0000000..b5a1d87
--- /dev/null
+++ b/pc/sdpserializer.cc
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2018 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "pc/sdpserializer.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "api/jsep.h"
+#include "rtc_base/strings/string_builder.h"
+
+using cricket::SimulcastDescription;
+using cricket::SimulcastLayer;
+using cricket::SimulcastLayerList;
+
+namespace webrtc {
+
+namespace {
+
+// delimiters
+const char kDelimiterComma[] = ",";
+const char kDelimiterCommaChar = ',';
+const char kDelimiterSemicolon[] = ";";
+const char kDelimiterSemicolonChar = ';';
+const char kDelimiterSpace[] = " ";
+const char kDelimiterSpaceChar = ' ';
+
+// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
+const char kSimulcastPausedStream[] = "~";
+const char kSimulcastPausedStreamChar = '~';
+const char kSimulcastSendStreams[] = "send";
+const char kSimulcastReceiveStreams[] = "recv";
+
+RTCError ParseError(const std::string& message) {
+ return RTCError(RTCErrorType::SYNTAX_ERROR, message);
+}
+
+// These methods serialize simulcast according to the specification:
+// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
+rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
+ const SimulcastLayer& simulcast_layer) {
+ if (simulcast_layer.is_paused) {
+ builder << kSimulcastPausedStream;
+ }
+ builder << simulcast_layer.rid;
+ return builder;
+}
+
+rtc::StringBuilder& operator<<(
+ rtc::StringBuilder& builder,
+ const std::vector<SimulcastLayer>& layer_alternatives) {
+ bool first = true;
+ for (const SimulcastLayer& rid : layer_alternatives) {
+ if (!first) {
+ builder << kDelimiterComma;
+ }
+ builder << rid;
+ first = false;
+ }
+ return builder;
+}
+
+rtc::StringBuilder& operator<<(rtc::StringBuilder& builder,
+ const SimulcastLayerList& simulcast_layers) {
+ bool first = true;
+ for (auto alternatives : simulcast_layers) {
+ if (!first) {
+ builder << kDelimiterSemicolon;
+ }
+ builder << alternatives;
+ first = false;
+ }
+ return builder;
+}
+
+// These methods deserialize simulcast according to the specification:
+// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
+// sc-str-list = sc-alt-list *( ";" sc-alt-list )
+// sc-alt-list = sc-id *( "," sc-id )
+// sc-id-paused = "~"
+// sc-id = [sc-id-paused] rid-id
+// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
+RTCErrorOr<SimulcastLayerList> ParseSimulcastLayerList(const std::string& str) {
+ std::vector<std::string> tokens;
+ rtc::tokenize_with_empty_tokens(str, kDelimiterSemicolonChar, &tokens);
+ if (tokens.empty()) {
+ return ParseError("Layer list cannot be empty.");
+ }
+
+ SimulcastLayerList result;
+ for (const std::string& token : tokens) {
+ if (token.empty()) {
+ return ParseError("Simulcast alternative layer list is empty.");
+ }
+
+ std::vector<std::string> rid_tokens;
+ rtc::tokenize_with_empty_tokens(token, kDelimiterCommaChar, &rid_tokens);
+ if (rid_tokens.empty()) {
+ return ParseError("Simulcast alternative layer list is malformed.");
+ }
+
+ std::vector<SimulcastLayer> layers;
+ for (const auto& rid_token : rid_tokens) {
+ if (rid_token.empty() || rid_token == kSimulcastPausedStream) {
+ return ParseError("Rid must not be empty.");
+ }
+
+ bool paused = rid_token[0] == kSimulcastPausedStreamChar;
+ std::string rid = paused ? rid_token.substr(1) : rid_token;
+
+ // TODO(amithi, bugs.webrtc.org/10073):
+ // Validate the rid format.
+ // See also: https://github.com/w3c/webrtc-pc/issues/2013
+ layers.push_back(SimulcastLayer(rid, paused));
+ }
+
+ result.AddLayerWithAlternatives(layers);
+ }
+
+ return std::move(result);
+}
+
+} // namespace
+
+std::string SdpSerializer::SerializeSimulcastDescription(
+ const cricket::SimulcastDescription& simulcast) const {
+ rtc::StringBuilder sb;
+ std::string delimiter;
+
+ if (!simulcast.send_layers().empty()) {
+ sb << kSimulcastSendStreams << kDelimiterSpace << simulcast.send_layers();
+ delimiter = kDelimiterSpace;
+ }
+
+ if (!simulcast.receive_layers().empty()) {
+ sb << delimiter << kSimulcastReceiveStreams << kDelimiterSpace
+ << simulcast.receive_layers();
+ }
+
+ return sb.str();
+}
+
+// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
+// a:simulcast:<send> <streams> <recv> <streams>
+// Formal Grammar
+// sc-value = ( sc-send [SP sc-recv] ) / ( sc-recv [SP sc-send] )
+// sc-send = %s"send" SP sc-str-list
+// sc-recv = %s"recv" SP sc-str-list
+// sc-str-list = sc-alt-list *( ";" sc-alt-list )
+// sc-alt-list = sc-id *( "," sc-id )
+// sc-id-paused = "~"
+// sc-id = [sc-id-paused] rid-id
+// rid-id = 1*(alpha-numeric / "-" / "_") ; see: I-D.ietf-mmusic-rid
+RTCErrorOr<SimulcastDescription> SdpSerializer::DeserializeSimulcastDescription(
+ absl::string_view string) const {
+ std::vector<std::string> tokens;
+ rtc::tokenize(std::string(string), kDelimiterSpaceChar, &tokens);
+
+ if (tokens.size() != 2 && tokens.size() != 4) {
+ return ParseError("Must have one or two <direction, streams> pairs.");
+ }
+
+ bool bidirectional = tokens.size() == 4; // indicates both send and recv
+
+ // Tokens 0, 2 (if exists) should be send / recv
+ if ((tokens[0] != kSimulcastSendStreams &&
+ tokens[0] != kSimulcastReceiveStreams) ||
+ (bidirectional && tokens[2] != kSimulcastSendStreams &&
+ tokens[2] != kSimulcastReceiveStreams) ||
+ (bidirectional && tokens[0] == tokens[2])) {
+ return ParseError("Valid values: send / recv.");
+ }
+
+ // Tokens 1, 3 (if exists) should be alternative layer lists
+ RTCErrorOr<SimulcastLayerList> list1, list2;
+ list1 = ParseSimulcastLayerList(tokens[1]);
+ if (!list1.ok()) {
+ return list1.MoveError();
+ }
+
+ if (bidirectional) {
+ list2 = ParseSimulcastLayerList(tokens[3]);
+ if (!list2.ok()) {
+ return list2.MoveError();
+ }
+ }
+
+ // Set the layers so that list1 is for send and list2 is for recv
+ if (tokens[0] != kSimulcastSendStreams) {
+ std::swap(list1, list2);
+ }
+
+ // Set the layers according to which pair is send and which is recv
+ // At this point if the simulcast is unidirectional then
+ // either |list1| or |list2| will be in 'error' state indicating that
+ // the value should not be used.
+ SimulcastDescription simulcast;
+ if (list1.ok()) {
+ simulcast.send_layers() = list1.MoveValue();
+ }
+
+ if (list2.ok()) {
+ simulcast.receive_layers() = list2.MoveValue();
+ }
+
+ return std::move(simulcast);
+}
+
+} // namespace webrtc