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/BUILD.gn b/pc/BUILD.gn
index 6999ddd..89c4df8 100644
--- a/pc/BUILD.gn
+++ b/pc/BUILD.gn
@@ -56,6 +56,8 @@
     "rtptransportinternaladapter.h",
     "sessiondescription.cc",
     "sessiondescription.h",
+    "simulcastdescription.cc",
+    "simulcastdescription.h",
     "srtpfilter.cc",
     "srtpfilter.h",
     "srtpsession.cc",
@@ -163,6 +165,8 @@
     "rtptransceiver.h",
     "sctputils.cc",
     "sctputils.h",
+    "sdpserializer.cc",
+    "sdpserializer.h",
     "sdputils.cc",
     "sdputils.h",
     "statscollector.cc",
@@ -456,6 +460,7 @@
       "rtpsenderreceiver_unittest.cc",
       "rtptransceiver_unittest.cc",
       "sctputils_unittest.cc",
+      "sdpserializer_unittest.cc",
       "statscollector_unittest.cc",
       "test/fakeaudiocapturemodule_unittest.cc",
       "test/testsdpstrings.h",
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
diff --git a/pc/sdpserializer.h b/pc/sdpserializer.h
new file mode 100644
index 0000000..428b420
--- /dev/null
+++ b/pc/sdpserializer.h
@@ -0,0 +1,49 @@
+/*
+ *  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.
+ */
+
+#ifndef PC_SDPSERIALIZER_H_
+#define PC_SDPSERIALIZER_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "api/rtcerror.h"
+#include "pc/sessiondescription.h"
+
+namespace webrtc {
+
+// This class should serialize components of the SDP (and not the SDP itself).
+// Example:
+//     SimulcastDescription can be serialized and deserialized by this class.
+//     The serializer will know how to translate the data to spec-compliant
+//     format without knowing about the SDP attribute details (a=simulcast:)
+// Usage:
+//     Consider the SDP attribute for simulcast a=simulcast:<configuration>.
+//     The SDP serializtion code (webrtcsdp.h) should use |SdpSerializer| to
+//     serialize and deserialize the <configuration> section.
+// This class will allow testing the serialization of components without
+// having to serialize the entire SDP while hiding implementation details
+// from callers of sdp serialization (webrtcsdp.h).
+class SdpSerializer {
+ public:
+  // Serialization for the Simulcast description according to
+  // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
+  std::string SerializeSimulcastDescription(
+      const cricket::SimulcastDescription& simulcast) const;
+
+  // Deserialization for the SimulcastDescription according to
+  // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
+  RTCErrorOr<cricket::SimulcastDescription> DeserializeSimulcastDescription(
+      absl::string_view string) const;
+};
+
+}  // namespace webrtc
+
+#endif  // PC_SDPSERIALIZER_H_
diff --git a/pc/sdpserializer_unittest.cc b/pc/sdpserializer_unittest.cc
new file mode 100644
index 0000000..8b10da1
--- /dev/null
+++ b/pc/sdpserializer_unittest.cc
@@ -0,0 +1,235 @@
+/*
+ *  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 <string>
+#include <vector>
+
+#include "pc/sdpserializer.h"
+#include "rtc_base/gunit.h"
+
+using ::testing::ValuesIn;
+using cricket::SimulcastDescription;
+using cricket::SimulcastLayer;
+using cricket::SimulcastLayerList;
+
+namespace webrtc {
+
+class SdpSerializerTest : public ::testing::TestWithParam<const char*> {
+ public:
+  // Runs a test for deserializing Simulcast.
+  // |str| - The serialized Simulcast to parse.
+  // |expected| - The expected output Simulcast to compare to.
+  void TestSimulcastDeserialization(
+      const std::string& str,
+      const SimulcastDescription& expected) const {
+    SdpSerializer deserializer;
+    auto result = deserializer.DeserializeSimulcastDescription(str);
+    EXPECT_TRUE(result.ok());
+    ExpectEqual(expected, result.value());
+  }
+
+  // Runs a test for serializing Simulcast.
+  // |simulcast| - The Simulcast to serialize.
+  // |expected| - The expected output string to compare to.
+  void TestSimulcastSerialization(const SimulcastDescription& simulcast,
+                                  const std::string& expected) const {
+    SdpSerializer serializer;
+    auto result = serializer.SerializeSimulcastDescription(simulcast);
+    EXPECT_EQ(expected, result);
+  }
+
+  // Checks that the two vectors of SimulcastLayer objects are equal.
+  void ExpectEqual(const std::vector<SimulcastLayer>& expected,
+                   const std::vector<SimulcastLayer>& actual) const {
+    EXPECT_EQ(expected.size(), actual.size());
+    for (size_t i = 0; i < expected.size(); i++) {
+      EXPECT_EQ(expected[i].rid, actual[i].rid);
+      EXPECT_EQ(expected[i].is_paused, actual[i].is_paused);
+    }
+  }
+
+  // Checks that the two SimulcastLayerLists are equal.
+  void ExpectEqual(const SimulcastLayerList& expected,
+                   const SimulcastLayerList& actual) const {
+    EXPECT_EQ(expected.size(), actual.size());
+    for (size_t i = 0; i < expected.size(); i++) {
+      ExpectEqual(expected[i], actual[i]);
+    }
+  }
+
+  // Checks that the two SimulcastDescriptions are equal.
+  void ExpectEqual(const SimulcastDescription& expected,
+                   const SimulcastDescription& actual) const {
+    ExpectEqual(expected.send_layers(), actual.send_layers());
+    ExpectEqual(expected.receive_layers(), actual.receive_layers());
+  }
+};
+
+// Test Cases
+
+// Test simple deserialization with no alternative streams.
+TEST_F(SdpSerializerTest, DeserializeSimulcast_SimpleCaseNoAlternatives) {
+  std::string simulcast_str = "send 1;2 recv 3;4";
+  SimulcastDescription expected;
+  expected.send_layers().AddLayer(SimulcastLayer("1", false));
+  expected.send_layers().AddLayer(SimulcastLayer("2", false));
+  expected.receive_layers().AddLayer(SimulcastLayer("3", false));
+  expected.receive_layers().AddLayer(SimulcastLayer("4", false));
+  TestSimulcastDeserialization(simulcast_str, expected);
+}
+
+// Test simulcast deserialization with alternative streams.
+TEST_F(SdpSerializerTest, DeserializeSimulcast_SimpleCaseWithAlternatives) {
+  std::string simulcast_str = "send 1,5;2,6 recv 3,7;4,8";
+  SimulcastDescription expected;
+  expected.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("1", false), SimulcastLayer("5", false)});
+  expected.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("2", false), SimulcastLayer("6", false)});
+  expected.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("3", false), SimulcastLayer("7", false)});
+  expected.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("4", false), SimulcastLayer("8", false)});
+  TestSimulcastDeserialization(simulcast_str, expected);
+}
+
+// Test simulcast deserialization when only some streams have alternatives.
+TEST_F(SdpSerializerTest, DeserializeSimulcast_WithSomeAlternatives) {
+  std::string simulcast_str = "send 1;2,6 recv 3,7;4";
+  SimulcastDescription expected;
+  expected.send_layers().AddLayer(SimulcastLayer("1", false));
+  expected.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("2", false), SimulcastLayer("6", false)});
+  expected.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("3", false), SimulcastLayer("7", false)});
+  expected.receive_layers().AddLayer(SimulcastLayer("4", false));
+  TestSimulcastDeserialization(simulcast_str, expected);
+}
+
+// Test simulcast deserialization when only send streams are specified.
+TEST_F(SdpSerializerTest, DeserializeSimulcast_OnlySendStreams) {
+  std::string simulcast_str = "send 1;2,6;3,7;4";
+  SimulcastDescription expected;
+  expected.send_layers().AddLayer(SimulcastLayer("1", false));
+  expected.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("2", false), SimulcastLayer("6", false)});
+  expected.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("3", false), SimulcastLayer("7", false)});
+  expected.send_layers().AddLayer(SimulcastLayer("4", false));
+  TestSimulcastDeserialization(simulcast_str, expected);
+}
+
+// Test simulcast deserialization when only receive streams are specified.
+TEST_F(SdpSerializerTest, DeserializeSimulcast_OnlyReceiveStreams) {
+  std::string simulcast_str = "recv 1;2,6;3,7;4";
+  SimulcastDescription expected;
+  expected.receive_layers().AddLayer(SimulcastLayer("1", false));
+  expected.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("2", false), SimulcastLayer("6", false)});
+  expected.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("3", false), SimulcastLayer("7", false)});
+  expected.receive_layers().AddLayer(SimulcastLayer("4", false));
+  TestSimulcastDeserialization(simulcast_str, expected);
+}
+
+// Test simulcast deserialization with receive streams before send streams.
+TEST_F(SdpSerializerTest, DeserializeSimulcast_SendReceiveReversed) {
+  std::string simulcast_str = "recv 1;2,6 send 3,7;4";
+  SimulcastDescription expected;
+  expected.receive_layers().AddLayer(SimulcastLayer("1", false));
+  expected.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("2", false), SimulcastLayer("6", false)});
+  expected.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("3", false), SimulcastLayer("7", false)});
+  expected.send_layers().AddLayer(SimulcastLayer("4", false));
+  TestSimulcastDeserialization(simulcast_str, expected);
+}
+
+// Test simulcast deserialization with some streams set to paused state.
+TEST_F(SdpSerializerTest, DeserializeSimulcast_PausedStreams) {
+  std::string simulcast_str = "recv 1;~2,6 send 3,7;~4";
+  SimulcastDescription expected;
+  expected.receive_layers().AddLayer(SimulcastLayer("1", false));
+  expected.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("2", true), SimulcastLayer("6", false)});
+  expected.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("3", false), SimulcastLayer("7", false)});
+  expected.send_layers().AddLayer(SimulcastLayer("4", true));
+  TestSimulcastDeserialization(simulcast_str, expected);
+}
+
+// Parameterized negative test case for deserialization with invalid inputs.
+TEST_P(SdpSerializerTest, SimulcastDeserializationFailed) {
+  SdpSerializer deserializer;
+  auto result = deserializer.DeserializeSimulcastDescription(GetParam());
+  EXPECT_FALSE(result.ok());
+}
+
+// The malformed Simulcast inputs to use in the negative test case.
+const char* kSimulcastMalformedStrings[] = {
+    "send ",
+    "recv ",
+    "recv 1 send",
+    "receive 1",
+    "recv 1;~2,6 recv 3,7;~4",
+    "send 1;~2,6 send 3,7;~4",
+    "send ~;~2,6",
+    "send 1; ;~2,6",
+    "send 1,;~2,6",
+    "recv 1 send 2 3",
+    "",
+};
+
+INSTANTIATE_TEST_CASE_P(SimulcastDeserializationErrors,
+                        SdpSerializerTest,
+                        ValuesIn(kSimulcastMalformedStrings));
+
+// Test a simple serialization scenario.
+TEST_F(SdpSerializerTest, SerializeSimulcast_SimpleCase) {
+  SimulcastDescription simulcast;
+  simulcast.send_layers().AddLayer(SimulcastLayer("1", false));
+  simulcast.receive_layers().AddLayer(SimulcastLayer("2", false));
+  TestSimulcastSerialization(simulcast, "send 1 recv 2");
+}
+
+// Test serialization with only send streams.
+TEST_F(SdpSerializerTest, SerializeSimulcast_OnlySend) {
+  SimulcastDescription simulcast;
+  simulcast.send_layers().AddLayer(SimulcastLayer("1", false));
+  simulcast.send_layers().AddLayer(SimulcastLayer("2", false));
+  TestSimulcastSerialization(simulcast, "send 1;2");
+}
+
+// Test serialization with only receive streams
+TEST_F(SdpSerializerTest, SerializeSimulcast_OnlyReceive) {
+  SimulcastDescription simulcast;
+  simulcast.receive_layers().AddLayer(SimulcastLayer("1", false));
+  simulcast.receive_layers().AddLayer(SimulcastLayer("2", false));
+  TestSimulcastSerialization(simulcast, "recv 1;2");
+}
+
+// Test a complex serialization with multiple streams, alternatives and states.
+TEST_F(SdpSerializerTest, SerializeSimulcast_ComplexSerialization) {
+  SimulcastDescription simulcast;
+  simulcast.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("2", false), SimulcastLayer("1", true)});
+  simulcast.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("4", false), SimulcastLayer("3", false)});
+
+  simulcast.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("6", false), SimulcastLayer("7", false)});
+  simulcast.receive_layers().AddLayer(SimulcastLayer("8", true));
+  simulcast.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("9", false), SimulcastLayer("10", true),
+       SimulcastLayer("11", false)});
+  TestSimulcastSerialization(simulcast, "send 2,~1;4,3 recv 6,7;~8;9,~10,11");
+}
+
+}  // namespace webrtc
diff --git a/pc/sessiondescription.h b/pc/sessiondescription.h
index 691046f..ff6cd2f 100644
--- a/pc/sessiondescription.h
+++ b/pc/sessiondescription.h
@@ -25,6 +25,7 @@
 #include "media/base/streamparams.h"
 #include "p2p/base/transportdescription.h"
 #include "p2p/base/transportinfo.h"
+#include "pc/simulcastdescription.h"
 #include "rtc_base/socketaddress.h"
 
 namespace cricket {
@@ -203,6 +204,17 @@
   }
   bool extmap_allow_mixed() const { return extmap_allow_mixed_enum_ != kNo; }
 
+  // Simulcast functionality.
+  virtual bool HasSimulcast() const { return !simulcast_.empty(); }
+  virtual SimulcastDescription& simulcast_description() { return simulcast_; }
+  virtual const SimulcastDescription& simulcast_description() const {
+    return simulcast_;
+  }
+  virtual void set_simulcast_description(
+      const SimulcastDescription& simulcast) {
+    simulcast_ = simulcast;
+  }
+
  protected:
   bool rtcp_mux_ = false;
   bool rtcp_reduced_size_ = false;
@@ -220,6 +232,8 @@
   // session level, but we will respond that we support it. The plan is to add
   // it to our offer on session level. See todo in SessionDescription.
   ExtmapAllowMixed extmap_allow_mixed_enum_ = kNo;
+
+  SimulcastDescription simulcast_;
 };
 
 // TODO(bugs.webrtc.org/8620): Remove this alias once downstream projects have
diff --git a/pc/simulcastdescription.cc b/pc/simulcastdescription.cc
new file mode 100644
index 0000000..eca67a7
--- /dev/null
+++ b/pc/simulcastdescription.cc
@@ -0,0 +1,44 @@
+/*
+ *  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 <utility>
+
+#include "pc/simulcastdescription.h"
+#include "rtc_base/checks.h"
+
+namespace cricket {
+
+SimulcastLayer::SimulcastLayer(const std::string& rid, bool is_paused)
+    : rid{rid}, is_paused{is_paused} {
+  // TODO(amithi, bugs.webrtc.org/10073): Validate rid format.
+  RTC_DCHECK(!rid.empty());
+}
+
+void SimulcastLayerList::AddLayer(const SimulcastLayer& layer) {
+  list_.push_back({layer});
+}
+
+void SimulcastLayerList::AddLayerWithAlternatives(
+    const std::vector<SimulcastLayer>& rids) {
+  RTC_DCHECK(!rids.empty());
+  list_.push_back(rids);
+}
+
+const std::vector<SimulcastLayer>& SimulcastLayerList::operator[](
+    size_t index) const {
+  RTC_DCHECK_LT(index, list_.size());
+  return list_[index];
+}
+
+bool SimulcastDescription::empty() const {
+  return send_layers_.empty() && receive_layers_.empty();
+}
+
+}  // namespace cricket
diff --git a/pc/simulcastdescription.h b/pc/simulcastdescription.h
new file mode 100644
index 0000000..f4708ff
--- /dev/null
+++ b/pc/simulcastdescription.h
@@ -0,0 +1,104 @@
+/*
+ *  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.
+ */
+
+#ifndef PC_SIMULCASTDESCRIPTION_H_
+#define PC_SIMULCASTDESCRIPTION_H_
+
+#include <string>
+#include <vector>
+
+namespace cricket {
+
+// Describes a Simulcast Layer.
+// Each simulcast layer has a rid as the identifier and a paused flag.
+// See also: https://tools.ietf.org/html/draft-ietf-mmusic-rid-15 for
+// an explanation about rids.
+struct SimulcastLayer final {
+  SimulcastLayer(const std::string& rid, bool is_paused);
+
+  SimulcastLayer(const SimulcastLayer& other) = default;
+  SimulcastLayer& operator=(const SimulcastLayer& other) = default;
+
+  std::string rid;
+  bool is_paused;
+};
+
+// Describes a list of Simulcast layers.
+// Simulcast layers are specified in order of preference.
+// Each layer can have a list of alternatives (in order of preference).
+// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
+// Example Usage:
+//   To populate a list that specifies the following:
+//     1. Layer 1 or Layer 2
+//     2. Layer 3
+//     3. Layer 4 or Layer 5
+//   Use the following code:
+//     SimulcastLayerList list;
+//     list.AddLayerWithAlternatives(
+//            {SimulcastLayer("1", false), SimulcastLayer("2", false});
+//     list.AddLayer("3");
+//     list.AddLayerWithAlternatives(
+//            {SimulcastLayer("4", false), SimulcastLayer("5", false});
+class SimulcastLayerList final {
+ public:
+  // Use to add a layer when there will be no alternatives.
+  void AddLayer(const SimulcastLayer& layer);
+
+  // Use to add a list of alternatives.
+  // The alternatives should be specified in order of preference.
+  void AddLayerWithAlternatives(const std::vector<SimulcastLayer>& layers);
+
+  // Read-only access to the contents.
+  // Note: This object does not allow removal of layers.
+  std::vector<std::vector<SimulcastLayer>>::const_iterator begin() const {
+    return list_.begin();
+  }
+
+  std::vector<std::vector<SimulcastLayer>>::const_iterator end() const {
+    return list_.end();
+  }
+
+  const std::vector<SimulcastLayer>& operator[](size_t index) const;
+
+  size_t size() const { return list_.size(); }
+  bool empty() const { return list_.empty(); }
+
+ private:
+  // TODO(amithi, bugs.webrtc.org/10075):
+  // Validate that rids do not repeat in the list.
+  std::vector<std::vector<SimulcastLayer>> list_;
+};
+
+// Describes the simulcast options of a video media section.
+// This will list the send and receive layers (along with their alternatives).
+// Each simulcast layer has an identifier (rid) and can optionally be paused.
+// The order of the layers (as well as alternates) indicates user preference
+// from first to last (most preferred to least preferred).
+// https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
+class SimulcastDescription final {
+ public:
+  const SimulcastLayerList& send_layers() const { return send_layers_; }
+  SimulcastLayerList& send_layers() { return send_layers_; }
+
+  const SimulcastLayerList& receive_layers() const { return receive_layers_; }
+  SimulcastLayerList& receive_layers() { return receive_layers_; }
+
+  bool empty() const;
+
+ private:
+  // TODO(amithi, bugs.webrtc.org/10075):
+  // Validate that rids do not repeat in send and receive layers.
+  SimulcastLayerList send_layers_;
+  SimulcastLayerList receive_layers_;
+};
+
+}  // namespace cricket
+
+#endif  // PC_SIMULCASTDESCRIPTION_H_
diff --git a/pc/webrtcsdp.cc b/pc/webrtcsdp.cc
index 0cd6bec..37d7654 100644
--- a/pc/webrtcsdp.cc
+++ b/pc/webrtcsdp.cc
@@ -38,6 +38,7 @@
 #include "p2p/base/p2pconstants.h"
 #include "p2p/base/port.h"
 #include "pc/mediasession.h"
+#include "pc/sdpserializer.h"
 #include "rtc_base/arraysize.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
@@ -73,6 +74,7 @@
 using cricket::MediaType;
 using cricket::RtpHeaderExtensions;
 using cricket::MediaProtocolType;
+using cricket::SimulcastDescription;
 using cricket::SsrcGroup;
 using cricket::StreamParams;
 using cricket::StreamParamsVec;
@@ -162,6 +164,9 @@
 // draft-ietf-mmusic-sctp-sdp-07
 // a=sctp-port
 static const char kAttributeSctpPort[] = "sctp-port";
+// draft-ietf-mmusic-sdp-simulcast-13
+// a=simulcast
+static const char kAttributeSimulcast[] = "simulcast";
 
 // Experimental flags
 static const char kAttributeXGoogleFlag[] = "x-google-flag";
@@ -1652,6 +1657,17 @@
       }
     }
   }
+
+  // Simulcast (a=simulcast)
+  // https://tools.ietf.org/html/draft-ietf-mmusic-sdp-simulcast-13#section-5.1
+  if (media_desc->as_video() && media_desc->as_video()->HasSimulcast()) {
+    const auto& simulcast = media_desc->as_video()->simulcast_description();
+    InitAttrLine(kAttributeSimulcast, &os);
+    SdpSerializer serializer;
+    os << kSdpDelimiterColon
+       << serializer.SerializeSimulcastDescription(simulcast);
+    AddLine(os.str(), message);
+  }
 }
 
 void WriteFmtpHeader(int payload_type, rtc::StringBuilder* os) {
@@ -2750,6 +2766,7 @@
   std::string ptime_as_string;
   std::vector<std::string> stream_ids;
   std::string track_id;
+  SimulcastDescription simulcast;
 
   // Loop until the next m line
   while (!IsLineType(message, kLineTypeMedia, *pos)) {
@@ -2954,6 +2971,34 @@
           return false;
         }
         *msid_signaling |= cricket::kMsidSignalingMediaSection;
+      } else if (HasAttribute(line, kAttributeSimulcast)) {
+        const size_t kSimulcastPrefixLength =
+            kLinePrefixLength + arraysize(kAttributeSimulcast);
+        if (line.size() <= kSimulcastPrefixLength) {
+          return ParseFailed(line, "Simulcast attribute is empty.", error);
+        }
+
+        if (!simulcast.empty()) {
+          return ParseFailed(line, "Multiple Simulcast attributes specified.",
+                             error);
+        }
+
+        SdpSerializer deserializer;
+        RTCErrorOr<SimulcastDescription> error_or_simulcast =
+            deserializer.DeserializeSimulcastDescription(
+                line.substr(kSimulcastPrefixLength));
+        if (!error_or_simulcast.ok()) {
+          return ParseFailed(line,
+                             std::string("Malformed simulcast line: ") +
+                                 error_or_simulcast.error().message(),
+                             error);
+        }
+
+        simulcast = error_or_simulcast.value();
+      } else {
+        // Unrecognized attribute in RTP protocol.
+        RTC_LOG(LS_INFO) << "Ignored line: " << line;
+        continue;
       }
     } else {
       // Only parse lines that we are interested of.
@@ -3030,6 +3075,13 @@
     candidates->push_back(
         new JsepIceCandidate(mline_id, mline_index, candidate));
   }
+
+  if (!simulcast.empty()) {
+    // TODO(amithi, bugs.webrtc.org/10073):
+    // Verify that the rids in simulcast match rids in sdp.
+    media_desc->set_simulcast_description(simulcast);
+  }
+
   return true;
 }
 
diff --git a/pc/webrtcsdp_unittest.cc b/pc/webrtcsdp_unittest.cc
index 2002230..d8b6811 100644
--- a/pc/webrtcsdp_unittest.cc
+++ b/pc/webrtcsdp_unittest.cc
@@ -60,6 +60,8 @@
 using cricket::RELAY_PORT_TYPE;
 using cricket::SessionDescription;
 using cricket::MediaProtocolType;
+using cricket::SimulcastDescription;
+using cricket::SimulcastLayer;
 using cricket::StreamParams;
 using cricket::STUN_PORT_TYPE;
 using cricket::TransportDescription;
@@ -1314,6 +1316,13 @@
     }
   }
 
+  void CompareSimulcastDescription(const SimulcastDescription& simulcast1,
+                                   const SimulcastDescription& simulcast2) {
+    EXPECT_EQ(simulcast1.send_layers().size(), simulcast2.send_layers().size());
+    EXPECT_EQ(simulcast1.receive_layers().size(),
+              simulcast2.receive_layers().size());
+  }
+
   void CompareDataContentDescription(const DataContentDescription* dcd1,
                                      const DataContentDescription* dcd2) {
     EXPECT_EQ(dcd1->use_sctpmap(), dcd2->use_sctpmap());
@@ -1360,6 +1369,10 @@
         const DataContentDescription* dcd2 = c2.media_description()->as_data();
         CompareDataContentDescription(dcd1, dcd2);
       }
+
+      CompareSimulcastDescription(
+          c1.media_description()->simulcast_description(),
+          c2.media_description()->simulcast_description());
     }
 
     // group
@@ -3918,3 +3931,55 @@
   Replace("s=-\r\n", "s= \r\n", &sdp);
   EXPECT_TRUE(SdpDeserialize(sdp, &jsep_desc));
 }
+
+// Simulcast malformed input test for invalid format.
+TEST_F(WebRtcSdpTest, DeserializeSimulcastNegative_EmptyAttribute) {
+  ExpectParseFailureWithNewLines("a=ssrc:3 label:video_track_id_1\r\n",
+                                 "a=simulcast:\r\n", "a=simulcast:");
+}
+
+// Tests that duplicate simulcast entries in the SDP triggers a parse failure.
+TEST_F(WebRtcSdpTest, DeserializeSimulcastNegative_DuplicateAttribute) {
+  ExpectParseFailureWithNewLines("a=ssrc:3 label:video_track_id_1\r\n",
+                                 "a=simulcast:send 1\r\na=simulcast:recv 2\r\n",
+                                 "a=simulcast:");
+}
+
+// Validates that deserialization uses the a=simulcast: attribute
+TEST_F(WebRtcSdpTest, TestDeserializeSimulcastAttribute) {
+  std::string sdp = kSdpFullString;
+  sdp += "a=simulcast:send 1,2;3 recv 4;5;6\r\n";
+  JsepSessionDescription output(kDummyType);
+  SdpParseError error;
+  EXPECT_TRUE(webrtc::SdpDeserialize(sdp, &output, &error));
+  const cricket::ContentInfos& contents = output.description()->contents();
+  const cricket::MediaContentDescription* media =
+      contents.back().media_description();
+  EXPECT_TRUE(media->HasSimulcast());
+  EXPECT_EQ(2ul, media->simulcast_description().send_layers().size());
+  EXPECT_EQ(3ul, media->simulcast_description().receive_layers().size());
+}
+
+// Simulcast serialization integration test.
+// This test will serialize and deserialize the description and compare.
+// More detailed tests for parsing simulcast can be found in
+// unit tests for SdpSerializer.
+TEST_F(WebRtcSdpTest, SerializeSimulcast_ComplexSerialization) {
+  MakeUnifiedPlanDescription();
+  auto description = jdesc_.description();
+  auto media = description->GetContentDescriptionByName(kVideoContentName3);
+  SimulcastDescription& simulcast = media->simulcast_description();
+  simulcast.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("2", false), SimulcastLayer("1", true)});
+  simulcast.send_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("4", false), SimulcastLayer("3", false)});
+
+  simulcast.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("6", false), SimulcastLayer("7", false)});
+  simulcast.receive_layers().AddLayer(SimulcastLayer("8", true));
+  simulcast.receive_layers().AddLayerWithAlternatives(
+      {SimulcastLayer("9", false), SimulcastLayer("10", true),
+       SimulcastLayer("11", false)});
+
+  TestSerialize(jdesc_);
+}