/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef DOM_MEDIA_WEBRTC_SDP_SDPATTRIBUTE_H_
#define DOM_MEDIA_WEBRTC_SDP_SDPATTRIBUTE_H_

#include <algorithm>
#include <cctype>
#include <cstring>
#include <iomanip>
#include <ostream>
#include <span>
#include <sstream>
#include <string_view>
#include <vector>

#include "common/EncodingConstraints.h"
#include "mozilla/Assertions.h"
#include "mozilla/Maybe.h"
#include "mozilla/UniquePtr.h"
#include "nsString.h"
#include "sdp/SdpEnum.h"

namespace mozilla {

/**
 * Base class for SDP attributes
 */
class SdpAttribute {
 public:
  enum AttributeType {
    kFirstAttribute = 0,
    kBundleOnlyAttribute = 0,
    kCandidateAttribute,
    kConnectionAttribute,
    kDirectionAttribute,
    kDtlsMessageAttribute,
    kEndOfCandidatesAttribute,
    kExtmapAttribute,
    kExtmapAllowMixedAttribute,
    kFingerprintAttribute,
    kFmtpAttribute,
    kGroupAttribute,
    kIceLiteAttribute,
    kIceMismatchAttribute,
    kIceOptionsAttribute,
    kIcePwdAttribute,
    kIceUfragAttribute,
    kIdentityAttribute,
    kImageattrAttribute,
    kLabelAttribute,
    kMaxptimeAttribute,
    kMidAttribute,
    kMsidAttribute,
    kMsidSemanticAttribute,
    kPtimeAttribute,
    kRemoteCandidatesAttribute,
    kRidAttribute,
    kRtcpAttribute,
    kRtcpFbAttribute,
    kRtcpMuxAttribute,
    kRtcpRsizeAttribute,
    kRtpmapAttribute,
    kSctpmapAttribute,
    kSetupAttribute,
    kSimulcastAttribute,
    kSsrcAttribute,
    kSsrcGroupAttribute,
    kSctpPortAttribute,
    kMaxMessageSizeAttribute,
    kLastAttribute = kMaxMessageSizeAttribute
  };

  explicit SdpAttribute(const AttributeType type) : mType(type) {}
  virtual ~SdpAttribute() = default;

  virtual UniquePtr<SdpAttribute> Clone() const = 0;

  AttributeType GetType() const { return mType; }

  virtual void Serialize(std::ostream&) const = 0;

  static bool IsAllowedAtSessionLevel(const AttributeType type);
  static bool IsAllowedAtMediaLevel(const AttributeType type);
  static const std::string GetAttributeTypeString(const AttributeType type);

 protected:
  AttributeType mType;
};

inline std::ostream& operator<<(std::ostream& os, const SdpAttribute& attr) {
  attr.Serialize(os);
  return os;
}

inline std::ostream& operator<<(std::ostream& os,
                                const SdpAttribute::AttributeType type) {
  os << SdpAttribute::GetAttributeTypeString(type);
  return os;
}

///////////////////////////////////////////////////////////////////////////
// a=candidate, RFC5245
//-------------------------------------------------------------------------
//
// candidate-attribute   = "candidate" ":" foundation SP component-id SP
//                          transport SP
//                          priority SP
//                          connection-address SP     ;from RFC 4566
//                          port         ;port from RFC 4566
//                          SP cand-type
//                          [SP rel-addr]
//                          [SP rel-port]
//                          *(SP extension-att-name SP
//                               extension-att-value)
// foundation            = 1*32ice-char
// component-id          = 1*5DIGIT
// transport             = "UDP" / transport-extension
// transport-extension   = token              ; from RFC 3261
// priority              = 1*10DIGIT
// cand-type             = "typ" SP candidate-types
// candidate-types       = "host" / "srflx" / "prflx" / "relay" / token
// rel-addr              = "raddr" SP connection-address
// rel-port              = "rport" SP port
// extension-att-name    = byte-string    ;from RFC 4566
// extension-att-value   = byte-string
// ice-char              = ALPHA / DIGIT / "+" / "/"

// We use a SdpMultiStringAttribute for candidates

///////////////////////////////////////////////////////////////////////////
// a=connection, RFC4145
//-------------------------------------------------------------------------
//         connection-attr        = "a=connection:" conn-value
//         conn-value             = "new" / "existing"
class SdpConnectionAttribute : public SdpAttribute {
 public:
  enum ConnValue { kNew, kExisting };

  explicit SdpConnectionAttribute(const SdpConnectionAttribute::ConnValue value)
      : SdpAttribute(kConnectionAttribute), mValue(value) {}

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpConnectionAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  ConnValue mValue;
};

inline std::ostream& operator<<(std::ostream& os,
                                const SdpConnectionAttribute::ConnValue c) {
  switch (c) {
    case SdpConnectionAttribute::kNew:
      os << "new";
      break;
    case SdpConnectionAttribute::kExisting:
      os << "existing";
      break;
    default:
      MOZ_ASSERT(false);
      os << "?";
  }
  return os;
}

///////////////////////////////////////////////////////////////////////////
// a=sendrecv / a=sendonly / a=recvonly / a=inactive, RFC 4566
//-------------------------------------------------------------------------
class SdpDirectionAttribute : public SdpAttribute {
 public:
  enum Direction {
    kInactive = 0,
    kSendonly = sdp::kSend,
    kRecvonly = sdp::kRecv,
    kSendrecv = sdp::kSend | sdp::kRecv
  };

  explicit SdpDirectionAttribute(const Direction value)
      : SdpAttribute(kDirectionAttribute), mValue(value) {}

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpDirectionAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  Direction mValue;
};

inline std::ostream& operator<<(std::ostream& os,
                                const SdpDirectionAttribute::Direction d) {
  switch (d) {
    case SdpDirectionAttribute::kSendonly:
      os << "sendonly";
      break;
    case SdpDirectionAttribute::kRecvonly:
      os << "recvonly";
      break;
    case SdpDirectionAttribute::kSendrecv:
      os << "sendrecv";
      break;
    case SdpDirectionAttribute::kInactive:
      os << "inactive";
      break;
    default:
      MOZ_ASSERT(false);
      os << "?";
  }
  return os;
}

inline SdpDirectionAttribute::Direction reverse(
    const SdpDirectionAttribute::Direction d) {
  switch (d) {
    case SdpDirectionAttribute::Direction::kInactive:
      return SdpDirectionAttribute::Direction::kInactive;
    case SdpDirectionAttribute::Direction::kSendonly:
      return SdpDirectionAttribute::Direction::kRecvonly;
    case SdpDirectionAttribute::Direction::kRecvonly:
      return SdpDirectionAttribute::Direction::kSendonly;
    case SdpDirectionAttribute::Direction::kSendrecv:
      return SdpDirectionAttribute::Direction::kSendrecv;
  }
  MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Invalid direction!");
  MOZ_RELEASE_ASSERT(false);
}

inline SdpDirectionAttribute::Direction operator|(
    const SdpDirectionAttribute::Direction d1,
    const SdpDirectionAttribute::Direction d2) {
  return (SdpDirectionAttribute::Direction)((unsigned)d1 | (unsigned)d2);
}

inline SdpDirectionAttribute::Direction operator&(
    const SdpDirectionAttribute::Direction d1,
    const SdpDirectionAttribute::Direction d2) {
  return (SdpDirectionAttribute::Direction)((unsigned)d1 & (unsigned)d2);
}

inline SdpDirectionAttribute::Direction operator|=(
    SdpDirectionAttribute::Direction& d1,
    const SdpDirectionAttribute::Direction d2) {
  d1 = d1 | d2;
  return d1;
}

inline SdpDirectionAttribute::Direction operator&=(
    SdpDirectionAttribute::Direction& d1,
    const SdpDirectionAttribute::Direction d2) {
  d1 = d1 & d2;
  return d1;
}

///////////////////////////////////////////////////////////////////////////
// a=dtls-message, draft-rescorla-dtls-in-sdp
//-------------------------------------------------------------------------
//   attribute               =/   dtls-message-attribute
//
//   dtls-message-attribute  =    "dtls-message" ":" role SP value
//
//   role                    =    "client" / "server"
//
//   value                   =    1*(ALPHA / DIGIT / "+" / "/" / "=" )
//                                ; base64 encoded message
class SdpDtlsMessageAttribute : public SdpAttribute {
 public:
  enum Role { kClient, kServer };

  explicit SdpDtlsMessageAttribute(const Role role, const std::string& value)
      : SdpAttribute(kDtlsMessageAttribute), mRole(role), mValue(value) {}

  // TODO: remove this, Bug 1469702
  explicit SdpDtlsMessageAttribute(const std::string& unparsed)
      : SdpAttribute(kDtlsMessageAttribute), mRole(kClient) {
    std::istringstream is(unparsed);
    std::string error;
    // We're not really worried about errors here if we don't parse;
    // this attribute is a pure optimization.
    Parse(is, &error);
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpDtlsMessageAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  // TODO: remove this, Bug 1469702
  bool Parse(std::istream& is, std::string* error);

  Role mRole;
  std::string mValue;
};

inline std::ostream& operator<<(std::ostream& os,
                                const SdpDtlsMessageAttribute::Role r) {
  switch (r) {
    case SdpDtlsMessageAttribute::kClient:
      os << "client";
      break;
    case SdpDtlsMessageAttribute::kServer:
      os << "server";
      break;
    default:
      MOZ_ASSERT(false);
      os << "?";
  }
  return os;
}

///////////////////////////////////////////////////////////////////////////
// a=extmap, RFC5285
//-------------------------------------------------------------------------
// RFC5285
//        extmap = mapentry SP extensionname [SP extensionattributes]
//
//        extensionname = URI
//
//        direction = "sendonly" / "recvonly" / "sendrecv" / "inactive"
//
//        mapentry = "extmap:" 1*5DIGIT ["/" direction]
//
//        extensionattributes = byte-string
//
//        URI = <Defined in RFC 3986>
//
//        byte-string = <Defined in RFC 4566>
//
//        SP = <Defined in RFC 5234>
//
//        DIGIT = <Defined in RFC 5234>
class SdpExtmapAttributeList : public SdpAttribute {
 public:
  SdpExtmapAttributeList() : SdpAttribute(kExtmapAttribute) {}

  struct Extmap {
    uint16_t entry;
    SdpDirectionAttribute::Direction direction;
    bool direction_specified;
    std::string extensionname;
    std::string extensionattributes;
  };

  void PushEntry(const uint16_t entry,
                 const SdpDirectionAttribute::Direction direction,
                 const bool direction_specified,
                 const std::string& extensionname,
                 const std::string& extensionattributes = "") {
    Extmap value = {entry, direction, direction_specified, extensionname,
                    extensionattributes};
    mExtmaps.push_back(std::move(value));
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpExtmapAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<Extmap> mExtmaps;
};

///////////////////////////////////////////////////////////////////////////
// a=fingerprint, RFC4572
//-------------------------------------------------------------------------
//   fingerprint-attribute  =  "fingerprint" ":" hash-func SP fingerprint
//
//   hash-func              =  "sha-1" / "sha-224" / "sha-256" /
//                             "sha-384" / "sha-512" /
//                             "md5" / "md2" / token
//                             ; Additional hash functions can only come
//                             ; from updates to RFC 3279
//
//   fingerprint            =  2UHEX *(":" 2UHEX)
//                             ; Each byte in upper-case hex, separated
//                             ; by colons.
//
//   UHEX                   =  DIGIT / %x41-46 ; A-F uppercase
class SdpFingerprintAttributeList : public SdpAttribute {
 public:
  SdpFingerprintAttributeList() : SdpAttribute(kFingerprintAttribute) {}

  enum HashAlgorithm {
    kSha1,
    kSha224,
    kSha256,
    kSha384,
    kSha512,
    kMd5,
    kMd2,
    kUnknownAlgorithm
  };

  struct Fingerprint {
    HashAlgorithm hashFunc;
    std::vector<uint8_t> fingerprint;
  };

  // For use by application programmers. Enforces that it's a known and
  // reasonable algorithm.
  void PushEntry(std::string algorithm_str,
                 std::span<const uint8_t> fingerprint,
                 const bool enforcePlausible = true) {
    std::transform(algorithm_str.begin(), algorithm_str.end(),
                   algorithm_str.begin(), ::tolower);

    SdpFingerprintAttributeList::HashAlgorithm algorithm =
        SdpFingerprintAttributeList::kUnknownAlgorithm;

    if (algorithm_str == "sha-1") {
      algorithm = SdpFingerprintAttributeList::kSha1;
    } else if (algorithm_str == "sha-224") {
      algorithm = SdpFingerprintAttributeList::kSha224;
    } else if (algorithm_str == "sha-256") {
      algorithm = SdpFingerprintAttributeList::kSha256;
    } else if (algorithm_str == "sha-384") {
      algorithm = SdpFingerprintAttributeList::kSha384;
    } else if (algorithm_str == "sha-512") {
      algorithm = SdpFingerprintAttributeList::kSha512;
    } else if (algorithm_str == "md5") {
      algorithm = SdpFingerprintAttributeList::kMd5;
    } else if (algorithm_str == "md2") {
      algorithm = SdpFingerprintAttributeList::kMd2;
    }

    if ((algorithm == SdpFingerprintAttributeList::kUnknownAlgorithm) ||
        fingerprint.empty()) {
      if (enforcePlausible) {
        MOZ_ASSERT(false, "Unknown fingerprint algorithm");
      } else {
        return;
      }
    }

    PushEntry(algorithm, fingerprint);
  }

  void PushEntry(const HashAlgorithm hashFunc,
                 std::span<const uint8_t> fingerprint) {
    Fingerprint value = {
        hashFunc,
    };
    value.fingerprint.assign(fingerprint.begin(), fingerprint.end());
    mFingerprints.push_back(std::move(value));
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpFingerprintAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<Fingerprint> mFingerprints;

  static std::string FormatFingerprint(const std::vector<uint8_t>& fp);
  static std::vector<uint8_t> ParseFingerprint(const std::string& str);
};

inline nsLiteralCString ToString(
    const SdpFingerprintAttributeList::HashAlgorithm a) {
  static constexpr nsLiteralCString Values[] = {
      "sha-1"_ns,   "sha-224"_ns, "sha-256"_ns, "sha-384"_ns,
      "sha-512"_ns, "md5"_ns,     "md2"_ns,
  };
  if (a < std::size(Values)) return Values[a];
  MOZ_ASSERT(false);
  return "?"_ns;
}

inline std::ostream& operator<<(
    std::ostream& os, const SdpFingerprintAttributeList::HashAlgorithm a) {
  return os << ToString(a);
}

///////////////////////////////////////////////////////////////////////////
// a=group, RFC5888
//-------------------------------------------------------------------------
//         group-attribute     = "a=group:" semantics
//                               *(SP identification-tag)
//         semantics           = "LS" / "FID" / semantics-extension
//         semantics-extension = token
//         identification-tag  = token
class SdpGroupAttributeList : public SdpAttribute {
 public:
  SdpGroupAttributeList() : SdpAttribute(kGroupAttribute) {}

  enum Semantics {
    kLs,     // RFC5888
    kFid,    // RFC5888
    kSrf,    // RFC3524
    kAnat,   // RFC4091
    kFec,    // RFC5956
    kFecFr,  // RFC5956
    kCs,     // draft-mehta-rmt-flute-sdp-05
    kDdp,    // RFC5583
    kDup,    // RFC7104
    kBundle  // draft-ietf-mmusic-bundle
  };

  struct Group {
    Semantics semantics;
    std::vector<std::string> tags;
  };

  void PushEntry(const Semantics semantics,
                 const std::vector<std::string>& tags) {
    Group value = {semantics, tags};
    mGroups.push_back(std::move(value));
  }

  void RemoveMid(const std::string& mid) {
    for (auto i = mGroups.begin(); i != mGroups.end();) {
      auto tag = std::find(i->tags.begin(), i->tags.end(), mid);
      if (tag != i->tags.end()) {
        i->tags.erase(tag);
      }

      if (i->tags.empty()) {
        i = mGroups.erase(i);
      } else {
        ++i;
      }
    }
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpGroupAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<Group> mGroups;
};

inline std::ostream& operator<<(std::ostream& os,
                                const SdpGroupAttributeList::Semantics s) {
  switch (s) {
    case SdpGroupAttributeList::kLs:
      os << "LS";
      break;
    case SdpGroupAttributeList::kFid:
      os << "FID";
      break;
    case SdpGroupAttributeList::kSrf:
      os << "SRF";
      break;
    case SdpGroupAttributeList::kAnat:
      os << "ANAT";
      break;
    case SdpGroupAttributeList::kFec:
      os << "FEC";
      break;
    case SdpGroupAttributeList::kFecFr:
      os << "FEC-FR";
      break;
    case SdpGroupAttributeList::kCs:
      os << "CS";
      break;
    case SdpGroupAttributeList::kDdp:
      os << "DDP";
      break;
    case SdpGroupAttributeList::kDup:
      os << "DUP";
      break;
    case SdpGroupAttributeList::kBundle:
      os << "BUNDLE";
      break;
    default:
      MOZ_ASSERT(false);
      os << "?";
  }
  return os;
}

///////////////////////////////////////////////////////////////////////////
// a=identity, draft-ietf-rtcweb-security-arch
//-------------------------------------------------------------------------
//   identity-attribute  = "identity:" identity-assertion
//                         [ SP identity-extension
//                           *(";" [ SP ] identity-extension) ]
//   identity-assertion  = base64
//   base64              = 1*(ALPHA / DIGIT / "+" / "/" / "=" )
//   identity-extension  = extension-att-name [ "=" extension-att-value ]
//   extension-att-name  = token
//   extension-att-value = 1*(%x01-09 / %x0b-0c / %x0e-3a / %x3c-ff)
//                         ; byte-string from [RFC4566] omitting ";"

// We're just using an SdpStringAttribute for this right now
#if 0
class SdpIdentityAttribute : public SdpAttribute
{
public:
  explicit SdpIdentityAttribute(const std::string &assertion,
                                const std::vector<std::string> &extensions =
                                    std::vector<std::string>()) :
    SdpAttribute(kIdentityAttribute),
    mAssertion(assertion),
    mExtensions(extensions) {}

  virtual void Serialize(std::ostream& os) const override;

  std::string mAssertion;
  std::vector<std::string> mExtensions;
}
#endif

///////////////////////////////////////////////////////////////////////////
// a=imageattr, RFC6236
//-------------------------------------------------------------------------
//     image-attr = "imageattr:" PT 1*2( 1*WSP ( "send" / "recv" )
//                                       1*WSP attr-list )
//     PT = 1*DIGIT / "*"
//     attr-list = ( set *(1*WSP set) ) / "*"
//       ;  WSP and DIGIT defined in [RFC5234]
//
//     set= "[" "x=" xyrange "," "y=" xyrange *( "," key-value ) "]"
//                ; x is the horizontal image size range (pixel count)
//                ; y is the vertical image size range (pixel count)
//
//     key-value = ( "sar=" srange )
//               / ( "par=" prange )
//               / ( "q=" qvalue )
//                ; Key-value MAY be extended with other keyword
//                ;  parameters.
//                ; At most, one instance each of sar, par, or q
//                ;  is allowed in a set.
//                ;
//                ; sar (sample aspect ratio) is the sample aspect ratio
//                ;  associated with the set (optional, MAY be ignored)
//                ; par (picture aspect ratio) is the allowed
//                ;  ratio between the display's x and y physical
//                ;  size (optional)
//                ; q (optional, range [0.0..1.0], default value 0.5)
//                ;  is the preference for the given set,
//                ;  a higher value means a higher preference
//
//     onetonine = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9"
//                ; Digit between 1 and 9
//     xyvalue = onetonine *5DIGIT
//                ; Digit between 1 and 9 that is
//                ; followed by 0 to 5 other digits
//     step = xyvalue
//     xyrange = ( "[" xyvalue ":" [ step ":" ] xyvalue "]" )
//                ; Range between a lower and an upper value
//                ; with an optional step, default step = 1
//                ; The rightmost occurrence of xyvalue MUST have a
//                ; higher value than the leftmost occurrence.
//             / ( "[" xyvalue 1*( "," xyvalue ) "]" )
//                ; Discrete values separated by ','
//             / ( xyvalue )
//                ; A single value
//     spvalue = ( "0" "." onetonine *3DIGIT )
//                ; Values between 0.1000 and 0.9999
//             / ( onetonine "." 1*4DIGIT )
//                ; Values between 1.0000 and 9.9999
//     srange =  ( "[" spvalue 1*( "," spvalue ) "]" )
//                ; Discrete values separated by ','.
//                ; Each occurrence of spvalue MUST be
//                ; greater than the previous occurrence.
//             / ( "[" spvalue "-" spvalue "]" )
//                ; Range between a lower and an upper level (inclusive)
//                ; The second occurrence of spvalue MUST have a higher
//                ; value than the first
//             / ( spvalue )
//                ; A single value
//
//     prange =  ( "[" spvalue "-" spvalue "]" )
//                ; Range between a lower and an upper level (inclusive)
//                ; The second occurrence of spvalue MUST have a higher
//                ; value than the first
//
//     qvalue  = ( "0" "." 1*2DIGIT )
//             / ( "1" "." 1*2("0") )
//                ; Values between 0.00 and 1.00
//
//  XXX TBD -- We don't use this yet, and it's a project unto itself.
//

class SdpImageattrAttributeList : public SdpAttribute {
 public:
  SdpImageattrAttributeList() : SdpAttribute(kImageattrAttribute) {}

  class XYRange {
   public:
    XYRange() : min(0), max(0), step(1) {}
    void Serialize(std::ostream& os) const;
    // TODO: Remove this Bug 1469702
    bool Parse(std::istream& is, std::string* error);
    // TODO: Remove this Bug 1469702
    bool ParseAfterBracket(std::istream& is, std::string* error);
    // TODO: Remove this Bug 1469702
    bool ParseAfterMin(std::istream& is, std::string* error);
    // TODO: Remove this Bug 1469702
    bool ParseDiscreteValues(std::istream& is, std::string* error);
    std::vector<uint32_t> discreteValues;
    // min/max are used iff discreteValues is empty
    uint32_t min;
    uint32_t max;
    uint32_t step;
  };

  class SRange {
   public:
    SRange() : min(0), max(0) {}
    void Serialize(std::ostream& os) const;
    // TODO: Remove this Bug 1469702
    bool Parse(std::istream& is, std::string* error);
    // TODO: Remove this Bug 1469702
    bool ParseAfterBracket(std::istream& is, std::string* error);
    // TODO: Remove this Bug 1469702
    bool ParseAfterMin(std::istream& is, std::string* error);
    // TODO: Remove this Bug 1469702
    bool ParseDiscreteValues(std::istream& is, std::string* error);
    bool IsSet() const { return !discreteValues.empty() || (min && max); }
    std::vector<float> discreteValues;
    // min/max are used iff discreteValues is empty
    float min;
    float max;
  };

  class PRange {
   public:
    PRange() : min(0), max(0) {}
    void Serialize(std::ostream& os) const;
    // TODO: Remove this Bug 1469702
    bool Parse(std::istream& is, std::string* error);
    bool IsSet() const { return min && max; }
    float min;
    float max;
  };

  class Set {
   public:
    Set() : qValue(-1) {}
    void Serialize(std::ostream& os) const;
    // TODO: Remove this Bug 1469702
    bool Parse(std::istream& is, std::string* error);
    XYRange xRange;
    XYRange yRange;
    SRange sRange;
    PRange pRange;
    float qValue;
  };

  class Imageattr {
   public:
    Imageattr() : sendAll(false), recvAll(false) {}
    void Serialize(std::ostream& os) const;
    // TODO: Remove this Bug 1469702
    bool Parse(std::istream& is, std::string* error);
    // TODO: Remove this Bug 1469702
    bool ParseSets(std::istream& is, std::string* error);
    // If not set, this means all payload types
    Maybe<uint16_t> pt;
    bool sendAll;
    std::vector<Set> sendSets;
    bool recvAll;
    std::vector<Set> recvSets;
  };

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpImageattrAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  // TODO: Remove this Bug 1469702
  bool PushEntry(const std::string& raw, std::string* error, size_t* errorPos);

  std::vector<Imageattr> mImageattrs;
};

///////////////////////////////////////////////////////////////////////////
// a=msid, draft-ietf-mmusic-msid
//-------------------------------------------------------------------------
//   msid-attr = "msid:" identifier [ SP appdata ]
//   identifier = 1*64token-char ; see RFC 4566
//   appdata = 1*64token-char  ; see RFC 4566
class SdpMsidAttributeList : public SdpAttribute {
 public:
  SdpMsidAttributeList() : SdpAttribute(kMsidAttribute) {}

  struct Msid {
    std::string identifier;
    std::string appdata;
  };

  void PushEntry(const std::string& identifier,
                 const std::string& appdata = "") {
    Msid value = {identifier, appdata};
    mMsids.push_back(std::move(value));
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpMsidAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<Msid> mMsids;
};

///////////////////////////////////////////////////////////////////////////
// a=msid-semantic, draft-ietf-mmusic-msid
//-------------------------------------------------------------------------
//   msid-semantic-attr = "msid-semantic:" msid-semantic msid-list
//   msid-semantic = token ; see RFC 4566
//   msid-list = *(" " msid-id) / " *"
class SdpMsidSemanticAttributeList : public SdpAttribute {
 public:
  SdpMsidSemanticAttributeList() : SdpAttribute(kMsidSemanticAttribute) {}

  struct MsidSemantic {
    // TODO: Once we have some more of these, we might want to make an enum
    std::string semantic;
    std::vector<std::string> msids;
  };

  void PushEntry(const std::string& semantic,
                 const std::vector<std::string>& msids) {
    MsidSemantic value = {semantic, msids};
    mMsidSemantics.push_back(std::move(value));
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpMsidSemanticAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<MsidSemantic> mMsidSemantics;
};

///////////////////////////////////////////////////////////////////////////
// a=remote-candiate, RFC5245
//-------------------------------------------------------------------------
//   remote-candidate-att = "remote-candidates" ":" remote-candidate
//                           0*(SP remote-candidate)
//   remote-candidate = component-ID SP connection-address SP port
class SdpRemoteCandidatesAttribute : public SdpAttribute {
 public:
  struct Candidate {
    std::string id;
    std::string address;
    uint16_t port;
  };

  explicit SdpRemoteCandidatesAttribute(
      const std::vector<Candidate>& candidates)
      : SdpAttribute(kRemoteCandidatesAttribute), mCandidates(candidates) {}

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpRemoteCandidatesAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<Candidate> mCandidates;
};

/*
a=rid, draft-pthatcher-mmusic-rid-01

   rid-syntax        = "a=rid:" rid-identifier SP rid-dir
                       [ rid-pt-param-list / rid-param-list ]

   rid-identifier    = 1*(alpha-numeric / "-" / "_")

   rid-dir           = "send" / "recv"

   rid-pt-param-list = SP rid-fmt-list *(";" rid-param)

   rid-param-list    = SP rid-param *(";" rid-param)

   rid-fmt-list      = "pt=" fmt *( "," fmt )
                        ; fmt defined in {{RFC4566}}

   rid-param         = rid-width-param
                       / rid-height-param
                       / rid-fps-param
                       / rid-fs-param
                       / rid-br-param
                       / rid-pps-param
                       / rid-depend-param
                       / rid-param-other

   rid-width-param   = "max-width" [ "=" int-param-val ]

   rid-height-param  = "max-height" [ "=" int-param-val ]

   rid-fps-param     = "max-fps" [ "=" int-param-val ]

   rid-fs-param      = "max-fs" [ "=" int-param-val ]

   rid-br-param      = "max-br" [ "=" int-param-val ]

   rid-pps-param     = "max-pps" [ "=" int-param-val ]

   rid-depend-param  = "depend=" rid-list

   rid-param-other   = 1*(alpha-numeric / "-") [ "=" param-val ]

   rid-list          = rid-identifier *( "," rid-identifier )

   int-param-val     = 1*DIGIT

   param-val         = *( %x20-58 / %x60-7E )
                       ; Any printable character except semicolon
*/
class SdpRidAttributeList : public SdpAttribute {
 public:
  explicit SdpRidAttributeList() : SdpAttribute(kRidAttribute) {}

  struct Rid {
    Rid() : direction(sdp::kSend) {}

    // Remove this function. See Bug 1469702
    bool Parse(std::istream& is, std::string* error);
    // Remove this function. See Bug 1469702
    bool ParseParameters(std::istream& is, std::string* error);
    // Remove this function. See Bug 1469702
    bool ParseDepend(std::istream& is, std::string* error);
    // Remove this function. See Bug 1469702
    bool ParseFormats(std::istream& is, std::string* error);

    void Serialize(std::ostream& os) const;
    void SerializeParameters(std::ostream& os) const;
    bool HasFormat(const std::string& format) const;
    bool HasParameters() const {
      return !formats.empty() || constraints.maxWidth ||
             constraints.maxHeight || constraints.maxFps || constraints.maxFs ||
             constraints.maxBr || constraints.maxPps || !dependIds.empty();
    }

    std::string id;
    sdp::Direction direction;
    std::vector<uint16_t> formats;  // Empty implies all
    VideoEncodingConstraints constraints;
    std::vector<std::string> dependIds;
  };

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpRidAttributeList>(*this);
  }

  static bool CheckRidValidity(const std::string& aRid, std::string* aError);
  static size_t kMaxRidLength;

  virtual void Serialize(std::ostream& os) const override;

  // Remove this function. See Bug 1469702
  bool PushEntry(const std::string& raw, std::string* error, size_t* errorPos);

  void PushEntry(const std::string& id, const sdp::Direction dir,
                 const std::vector<uint16_t>& formats,
                 const VideoEncodingConstraints& constraints,
                 const std::vector<std::string>& dependIds);

  std::vector<Rid> mRids;
};

///////////////////////////////////////////////////////////////////////////
// a=rtcp, RFC3605
//-------------------------------------------------------------------------
//   rtcp-attribute =  "a=rtcp:" port  [nettype space addrtype space
//                         connection-address] CRLF
class SdpRtcpAttribute : public SdpAttribute {
 public:
  explicit SdpRtcpAttribute(const uint16_t port)
      : SdpAttribute(kRtcpAttribute),
        mPort(port),
        mNetType(sdp::kNetTypeNone),
        mAddrType(sdp::kAddrTypeNone) {}

  SdpRtcpAttribute(const uint16_t port, const sdp::NetType netType,
                   const sdp::AddrType addrType, const std::string& address)
      : SdpAttribute(kRtcpAttribute),
        mPort(port),
        mNetType(netType),
        mAddrType(addrType),
        mAddress(address) {
    MOZ_ASSERT(netType != sdp::kNetTypeNone);
    MOZ_ASSERT(addrType != sdp::kAddrTypeNone);
    MOZ_ASSERT(!address.empty());
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpRtcpAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  uint16_t mPort;
  sdp::NetType mNetType;
  sdp::AddrType mAddrType;
  std::string mAddress;
};

///////////////////////////////////////////////////////////////////////////
// a=rtcp-fb, RFC4585
//-------------------------------------------------------------------------
//    rtcp-fb-syntax = "a=rtcp-fb:" rtcp-fb-pt SP rtcp-fb-val CRLF
//
//    rtcp-fb-pt         = "*"   ; wildcard: applies to all formats
//                       / fmt   ; as defined in SDP spec
//
//    rtcp-fb-val        = "ack" rtcp-fb-ack-param
//                       / "nack" rtcp-fb-nack-param
//                       / "trr-int" SP 1*DIGIT
//                       / rtcp-fb-id rtcp-fb-param
//
//    rtcp-fb-id         = 1*(alpha-numeric / "-" / "_")
//
//    rtcp-fb-param      = SP "app" [SP byte-string]
//                       / SP token [SP byte-string]
//                       / ; empty
//
//    rtcp-fb-ack-param  = SP "rpsi"
//                       / SP "app" [SP byte-string]
//                       / SP token [SP byte-string]
//                       / ; empty
//
//    rtcp-fb-nack-param = SP "pli"
//                       / SP "sli"
//                       / SP "rpsi"
//                       / SP "app" [SP byte-string]
//                       / SP token [SP byte-string]
//                       / ; empty
//
class SdpRtcpFbAttributeList : public SdpAttribute {
 public:
  SdpRtcpFbAttributeList() : SdpAttribute(kRtcpFbAttribute) {}

  enum Type { kAck, kApp, kCcm, kNack, kTrrInt, kRemb, kTransportCC };

  static const char* pli;
  static const char* sli;
  static const char* rpsi;
  static const char* app;

  static const char* fir;
  static const char* tmmbr;
  static const char* tstr;
  static const char* vbcm;

  struct Feedback {
    std::string pt;
    Type type;
    std::string parameter;
    std::string extra;
    // TODO(bug 1744307): Use =default here once it is supported
    bool operator==(const Feedback& aOther) const {
      return pt == aOther.pt && type == aOther.type &&
             parameter == aOther.parameter && extra == aOther.extra;
    }
  };

  void PushEntry(const std::string& pt, const Type type,
                 const std::string& parameter = "",
                 const std::string& extra = "") {
    Feedback value = {pt, type, parameter, extra};
    mFeedbacks.push_back(std::move(value));
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpRtcpFbAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<Feedback> mFeedbacks;
};

inline std::ostream& operator<<(std::ostream& os,
                                const SdpRtcpFbAttributeList::Type type) {
  switch (type) {
    case SdpRtcpFbAttributeList::kAck:
      os << "ack";
      break;
    case SdpRtcpFbAttributeList::kApp:
      os << "app";
      break;
    case SdpRtcpFbAttributeList::kCcm:
      os << "ccm";
      break;
    case SdpRtcpFbAttributeList::kNack:
      os << "nack";
      break;
    case SdpRtcpFbAttributeList::kTrrInt:
      os << "trr-int";
      break;
    case SdpRtcpFbAttributeList::kRemb:
      os << "goog-remb";
      break;
    case SdpRtcpFbAttributeList::kTransportCC:
      os << "transport-cc";
      break;
    default:
      MOZ_ASSERT(false);
      os << "?";
  }
  return os;
}

///////////////////////////////////////////////////////////////////////////
// a=rtpmap, RFC4566
//-------------------------------------------------------------------------
// a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
class SdpRtpmapAttributeList : public SdpAttribute {
 public:
  SdpRtpmapAttributeList() : SdpAttribute(kRtpmapAttribute) {}

  // Minimal set to get going
  enum CodecType {
    kOpus,
    kG722,
    kPCMU,
    kPCMA,
    kVP8,
    kVP9,
    kiLBC,
    kiSAC,
    kH264,
    kAV1,
    kRed,
    kUlpfec,
    kTelephoneEvent,
    kRtx,
    kOtherCodec
  };

  struct Rtpmap {
    std::string pt;
    CodecType codec;
    std::string name;
    uint32_t clock;
    // Technically, this could mean something else in the future.
    // In practice, that's probably not going to happen.
    uint32_t channels;
  };

  void PushEntry(const std::string& pt, const CodecType codec,
                 const std::string& name, const uint32_t clock,
                 const uint32_t channels = 0) {
    Rtpmap value = {pt, codec, name, clock, channels};
    mRtpmaps.push_back(std::move(value));
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpRtpmapAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  bool HasEntry(const std::string& pt) const {
    for (auto it = mRtpmaps.begin(); it != mRtpmaps.end(); ++it) {
      if (it->pt == pt) {
        return true;
      }
    }
    return false;
  }

  const Rtpmap& GetEntry(const std::string& pt) const {
    for (auto it = mRtpmaps.begin(); it != mRtpmaps.end(); ++it) {
      if (it->pt == pt) {
        return *it;
      }
    }
    MOZ_CRASH();
  }

  std::vector<Rtpmap> mRtpmaps;
};

inline std::ostream& operator<<(std::ostream& os,
                                const SdpRtpmapAttributeList::CodecType c) {
  switch (c) {
    case SdpRtpmapAttributeList::kOpus:
      os << "opus";
      break;
    case SdpRtpmapAttributeList::kG722:
      os << "G722";
      break;
    case SdpRtpmapAttributeList::kPCMU:
      os << "PCMU";
      break;
    case SdpRtpmapAttributeList::kPCMA:
      os << "PCMA";
      break;
    case SdpRtpmapAttributeList::kVP8:
      os << "VP8";
      break;
    case SdpRtpmapAttributeList::kVP9:
      os << "VP9";
      break;
    case SdpRtpmapAttributeList::kiLBC:
      os << "iLBC";
      break;
    case SdpRtpmapAttributeList::kiSAC:
      os << "iSAC";
      break;
    case SdpRtpmapAttributeList::kH264:
      os << "H264";
      break;
    case SdpRtpmapAttributeList::kAV1:
      os << "AV1";
      break;
    case SdpRtpmapAttributeList::kRed:
      os << "red";
      break;
    case SdpRtpmapAttributeList::kUlpfec:
      os << "ulpfec";
      break;
    case SdpRtpmapAttributeList::kTelephoneEvent:
      os << "telephone-event";
      break;
    case SdpRtpmapAttributeList::kRtx:
      os << "rtx";
      break;
    default:
      MOZ_ASSERT(false);
      os << "?";
  }
  return os;
}

///////////////////////////////////////////////////////////////////////////
// a=fmtp, RFC4566, RFC5576
//-------------------------------------------------------------------------
//       a=fmtp:<format> <format specific parameters>
//
class SdpFmtpAttributeList : public SdpAttribute {
 public:
  SdpFmtpAttributeList() : SdpAttribute(kFmtpAttribute) {}

  // Base class for format parameters
  class Parameters {
   public:
    explicit Parameters(SdpRtpmapAttributeList::CodecType aCodec)
        : codec_type(aCodec) {}

    virtual ~Parameters() = default;
    virtual UniquePtr<Parameters> Clone() const = 0;
    virtual bool ShouldSerialize() const { return true; }
    virtual void Serialize(std::ostream& os) const = 0;
    virtual bool CompareEq(const Parameters& other) const = 0;

    bool operator==(const Parameters& other) const {
      return codec_type == other.codec_type && CompareEq(other);
    }
    SdpRtpmapAttributeList::CodecType codec_type;
  };

  class RedParameters : public Parameters {
   public:
    RedParameters() : Parameters(SdpRtpmapAttributeList::kRed) {}

    virtual UniquePtr<Parameters> Clone() const override {
      return MakeUnique<RedParameters>(*this);
    }

    virtual void Serialize(std::ostream& os) const override {
      for (size_t i = 0; i < encodings.size(); ++i) {
        os << (i != 0 ? "/" : "") << std::to_string(encodings[i]);
      }
    }

    virtual bool CompareEq(const Parameters& other) const override {
      return encodings == static_cast<const RedParameters&>(other).encodings;
    }

    std::vector<uint8_t> encodings;
  };

  struct Av1Parameters : public Parameters {
    // https://aomediacodec.github.io/av1-rtp-spec/#722-rid-restrictions-mapping-for-av1
    Maybe<uint8_t> profile;
    static constexpr uint8_t kDefaultProfile = 0;
    Maybe<uint8_t> levelIdx;
    static constexpr uint8_t kDefaultLevelIdx = 5;
    Maybe<uint8_t> tier;
    static constexpr uint8_t kDefaultTier = 0;

    Av1Parameters() : Parameters(SdpRtpmapAttributeList::kAV1) {}
    Av1Parameters(const Av1Parameters&) = default;

    virtual ~Av1Parameters() = default;

    virtual UniquePtr<Parameters> Clone() const override {
      return MakeUnique<Av1Parameters>(*this);
    }

    // Returns the profile parameter if set, or the spec mandated default of 0.
    auto profileValue() const -> uint8_t {
      return profile.valueOr(kDefaultProfile);
    }
    // Returns the level-idx parameter if set, or the spec mandated default of
    // 5.
    auto levelIdxValue() const -> uint8_t {
      return levelIdx.valueOr(kDefaultLevelIdx);
    }
    // Returns the tier parameter if set, or the spec mandated default of 0.
    auto tierValue() const -> uint8_t { return tier.valueOr(kDefaultTier); }

    virtual bool ShouldSerialize() const override {
      return profile.isSome() || levelIdx.isSome() || tier.isSome();
    };

    virtual void Serialize(std::ostream& os) const override {
      bool first = true;
      profile.apply([&](const auto& profileV) {
        os << "profile=" << static_cast<int>(profileV);
        first = false;
      });
      levelIdx.apply([&](const auto& levelIdxV) {
        os << (first ? "" : ";") << "level-idx=" << static_cast<int>(levelIdxV);
        first = false;
      });
      tier.apply([&](const auto& tierV) {
        os << (first ? "" : ";") << "tier=" << static_cast<int>(tierV);
      });
    }

    virtual bool CompareEq(const Parameters& aOther) const override {
      return aOther.codec_type == codec_type &&
             static_cast<const Av1Parameters&>(aOther).profile == profile &&
             static_cast<const Av1Parameters&>(aOther).levelIdx == levelIdx &&
             static_cast<const Av1Parameters&>(aOther).tier == tier;
    }
  };

  class RtxParameters : public Parameters {
   public:
    uint8_t apt = 255;  // Valid payload types are 0 - 127, use 255 to represent
                        // unset value.
    Maybe<uint32_t> rtx_time;

    RtxParameters() : Parameters(SdpRtpmapAttributeList::kRtx) {}

    virtual ~RtxParameters() = default;

    virtual UniquePtr<Parameters> Clone() const override {
      return MakeUnique<RtxParameters>(*this);
    }

    virtual void Serialize(std::ostream& os) const override {
      if (apt <= 127) {
        os << "apt=" << static_cast<uint32_t>(apt);
        rtx_time.apply([&](const auto& time) { os << ";rtx-time=" << time; });
      }
    }

    virtual bool CompareEq(const Parameters& aOther) const override {
      if (aOther.codec_type != codec_type) {
        return false;
      }
      auto other = static_cast<const RtxParameters&>(aOther);
      return other.apt == apt && other.rtx_time == rtx_time;
    }
  };

  class H264Parameters : public Parameters {
   public:
    // Baseline no constraints level 1
    static const uint32_t kDefaultProfileLevelId = 0x42000A;

    H264Parameters()
        : Parameters(SdpRtpmapAttributeList::kH264),
          packetization_mode(0),
          level_asymmetry_allowed(false),
          profile_level_id(kDefaultProfileLevelId),
          max_mbps(0),
          max_fs(0),
          max_cpb(0),
          max_dpb(0),
          max_br(0) {
      memset(sprop_parameter_sets, 0, sizeof(sprop_parameter_sets));
    }

    virtual UniquePtr<Parameters> Clone() const override {
      return MakeUnique<H264Parameters>(*this);
    }

    virtual void Serialize(std::ostream& os) const override {
      // Note: don't move this, since having an unconditional param up top
      // lets us avoid a whole bunch of conditional streaming of ';' below
      os << "profile-level-id=" << std::hex << std::setfill('0') << std::setw(6)
         << profile_level_id << std::dec << std::setfill(' ');

      os << ";level-asymmetry-allowed=" << (level_asymmetry_allowed ? 1 : 0);

      if (strlen(sprop_parameter_sets)) {
        os << ";sprop-parameter-sets=" << sprop_parameter_sets;
      }

      if (packetization_mode != 0) {
        os << ";packetization-mode=" << packetization_mode;
      }

      if (max_mbps != 0) {
        os << ";max-mbps=" << max_mbps;
      }

      if (max_fs != 0) {
        os << ";max-fs=" << max_fs;
      }

      if (max_cpb != 0) {
        os << ";max-cpb=" << max_cpb;
      }

      if (max_dpb != 0) {
        os << ";max-dpb=" << max_dpb;
      }

      if (max_br != 0) {
        os << ";max-br=" << max_br;
      }
    }

    virtual bool CompareEq(const Parameters& other) const override {
      const auto& otherH264 = static_cast<const H264Parameters&>(other);

      // sprop is not comapred here as it does not get parsed in the rsdparsa
      return packetization_mode == otherH264.packetization_mode &&
             level_asymmetry_allowed == otherH264.level_asymmetry_allowed &&
             profile_level_id == otherH264.profile_level_id &&
             max_mbps == otherH264.max_mbps && max_fs == otherH264.max_fs &&
             max_cpb == otherH264.max_cpb && max_dpb == otherH264.max_dpb &&
             max_br == otherH264.max_br;
    }

    static const size_t max_sprop_len = 128;
    char sprop_parameter_sets[max_sprop_len];
    unsigned int packetization_mode;
    bool level_asymmetry_allowed;
    unsigned int profile_level_id;
    unsigned int max_mbps;
    unsigned int max_fs;
    unsigned int max_cpb;
    unsigned int max_dpb;
    unsigned int max_br;
  };

  // Also used for VP9 since they share parameters
  class VP8Parameters : public Parameters {
   public:
    explicit VP8Parameters(const SdpRtpmapAttributeList::CodecType type)
        : Parameters(type), max_fs(0), max_fr(0) {}

    virtual UniquePtr<Parameters> Clone() const override {
      return MakeUnique<VP8Parameters>(*this);
    }

    virtual void Serialize(std::ostream& os) const override {
      // draft-ietf-payload-vp8-11 says these are mandatory, upper layer
      // needs to ensure they're set properly.
      os << "max-fs=" << max_fs;
      os << ";max-fr=" << max_fr;
    }

    virtual bool CompareEq(const Parameters& other) const override {
      const auto& otherVP8 = static_cast<const VP8Parameters&>(other);

      return max_fs == otherVP8.max_fs && max_fr == otherVP8.max_fr;
    }

    unsigned int max_fs;
    unsigned int max_fr;
  };

  class OpusParameters : public Parameters {
   public:
    enum {
      kDefaultMaxPlaybackRate = 48000,
      kDefaultStereo = 0,
      kDefaultUseInBandFec = 0,
      kDefaultMaxAverageBitrate = 0,
      kDefaultUseDTX = 0,
      kDefaultFrameSize = 0,
      kDefaultMinFrameSize = 0,
      kDefaultMaxFrameSize = 0,
      kDefaultUseCbr = 0
    };
    OpusParameters()
        : Parameters(SdpRtpmapAttributeList::kOpus),
          maxplaybackrate(kDefaultMaxPlaybackRate),
          stereo(kDefaultStereo),
          useInBandFec(kDefaultUseInBandFec),
          maxAverageBitrate(kDefaultMaxAverageBitrate),
          useDTX(kDefaultUseDTX),
          frameSizeMs(kDefaultFrameSize),
          minFrameSizeMs(kDefaultMinFrameSize),
          maxFrameSizeMs(kDefaultMaxFrameSize),
          useCbr(kDefaultUseCbr) {}

    UniquePtr<Parameters> Clone() const override {
      return MakeUnique<OpusParameters>(*this);
    }

    void Serialize(std::ostream& os) const override {
      os << "maxplaybackrate=" << maxplaybackrate << ";stereo=" << stereo
         << ";useinbandfec=" << useInBandFec;

      if (useDTX) {
        os << ";usedtx=1";
      }
      if (maxAverageBitrate) {
        os << ";maxaveragebitrate=" << maxAverageBitrate;
      }
      if (frameSizeMs) {
        os << ";ptime=" << frameSizeMs;
      }
      if (minFrameSizeMs) {
        os << ";minptime=" << minFrameSizeMs;
      }
      if (maxFrameSizeMs) {
        os << ";maxptime=" << maxFrameSizeMs;
      }
      if (useCbr) {
        os << ";cbr=1";
      }
    }

    virtual bool CompareEq(const Parameters& other) const override {
      const auto& otherOpus = static_cast<const OpusParameters&>(other);

      bool maxplaybackrateIsEq = (maxplaybackrate == otherOpus.maxplaybackrate);

      // This is due to a bug in sipcc that causes maxplaybackrate to
      // always be 0 if it appears in the fmtp
      if (((maxplaybackrate == 0) && (otherOpus.maxplaybackrate != 0)) ||
          ((maxplaybackrate != 0) && (otherOpus.maxplaybackrate == 0))) {
        maxplaybackrateIsEq = true;
      }

      return maxplaybackrateIsEq && stereo == otherOpus.stereo &&
             useInBandFec == otherOpus.useInBandFec &&
             maxAverageBitrate == otherOpus.maxAverageBitrate &&
             useDTX == otherOpus.useDTX &&
             frameSizeMs == otherOpus.frameSizeMs &&
             minFrameSizeMs == otherOpus.minFrameSizeMs &&
             maxFrameSizeMs == otherOpus.maxFrameSizeMs &&
             useCbr == otherOpus.useCbr;
    }

    unsigned int maxplaybackrate;
    unsigned int stereo;
    unsigned int useInBandFec;
    uint32_t maxAverageBitrate;
    bool useDTX;
    uint32_t frameSizeMs;
    uint32_t minFrameSizeMs;
    uint32_t maxFrameSizeMs;
    bool useCbr;
  };

  class TelephoneEventParameters : public Parameters {
   public:
    TelephoneEventParameters()
        : Parameters(SdpRtpmapAttributeList::kTelephoneEvent),
          dtmfTones("0-15") {}

    virtual UniquePtr<Parameters> Clone() const override {
      return MakeUnique<TelephoneEventParameters>(*this);
    }

    void Serialize(std::ostream& os) const override { os << dtmfTones; }

    virtual bool CompareEq(const Parameters& other) const override {
      return dtmfTones ==
             static_cast<const TelephoneEventParameters&>(other).dtmfTones;
    }

    std::string dtmfTones;
  };

  class Fmtp {
   public:
    Fmtp(const std::string& aFormat, const Parameters& aParameters)
        : format(aFormat), parameters(aParameters.Clone()) {}

    // TODO: Rip all of this out when we have move semantics in the stl.
    Fmtp(const Fmtp& orig) { *this = orig; }

    Fmtp& operator=(const Fmtp& rhs) {
      if (this != &rhs) {
        format = rhs.format;
        parameters = rhs.parameters ? rhs.parameters->Clone() : nullptr;
      }
      return *this;
    }

    bool operator==(const Fmtp& other) const {
      return format == other.format && *parameters == *other.parameters;
    }

    // The contract around these is as follows:
    // * |parameters| is only set if we recognized the media type and had
    //   a subclass of Parameters to represent that type of parameters
    // * |parameters| is a best-effort representation; it might be missing
    //   stuff
    // * Parameters::codec_type tells you the concrete class, eg
    //   kH264 -> H264Parameters
    std::string format;
    UniquePtr<Parameters> parameters;
  };

  bool operator==(const SdpFmtpAttributeList& other) const;

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpFmtpAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  void PushEntry(const std::string& format, const Parameters& parameters) {
    mFmtps.push_back(Fmtp(format, parameters));
  }

  std::vector<Fmtp> mFmtps;
};

///////////////////////////////////////////////////////////////////////////
// a=sctpmap, draft-ietf-mmusic-sctp-sdp-05
//-------------------------------------------------------------------------
//      sctpmap-attr        =  "a=sctpmap:" sctpmap-number media-subtypes
// [streams]
//      sctpmap-number      =  1*DIGIT
//      protocol            =  labelstring
//        labelstring         =  text
//        text                =  byte-string
//      streams      =  1*DIGIT
//
// We're going to pretend that there are spaces where they make sense.
class SdpSctpmapAttributeList : public SdpAttribute {
 public:
  SdpSctpmapAttributeList() : SdpAttribute(kSctpmapAttribute) {}

  struct Sctpmap {
    std::string pt;
    std::string name;
    uint32_t streams;
  };

  void PushEntry(const std::string& pt, const std::string& name,
                 const uint32_t streams = 0) {
    Sctpmap value = {pt, name, streams};
    mSctpmaps.push_back(std::move(value));
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpSctpmapAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  bool HasEntry(const std::string& pt) const {
    for (auto it = mSctpmaps.begin(); it != mSctpmaps.end(); ++it) {
      if (it->pt == pt) {
        return true;
      }
    }
    return false;
  }

  const Sctpmap& GetFirstEntry() const { return mSctpmaps[0]; }

  std::vector<Sctpmap> mSctpmaps;
};

///////////////////////////////////////////////////////////////////////////
// a=setup, RFC4145
//-------------------------------------------------------------------------
//       setup-attr           =  "a=setup:" role
//       role                 =  "active" / "passive" / "actpass" / "holdconn"
class SdpSetupAttribute : public SdpAttribute {
 public:
  enum Role { kActive, kPassive, kActpass, kHoldconn };

  explicit SdpSetupAttribute(const Role role)
      : SdpAttribute(kSetupAttribute), mRole(role) {}

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpSetupAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  Role mRole;
};

inline std::ostream& operator<<(std::ostream& os,
                                const SdpSetupAttribute::Role r) {
  switch (r) {
    case SdpSetupAttribute::kActive:
      os << "active";
      break;
    case SdpSetupAttribute::kPassive:
      os << "passive";
      break;
    case SdpSetupAttribute::kActpass:
      os << "actpass";
      break;
    case SdpSetupAttribute::kHoldconn:
      os << "holdconn";
      break;
    default:
      MOZ_ASSERT(false);
      os << "?";
  }
  return os;
}

// Old draft-04
// sc-attr     = "a=simulcast:" 1*2( WSP sc-str-list ) [WSP sc-pause-list]
// sc-str-list = sc-dir WSP sc-id-type "=" sc-alt-list *( ";" sc-alt-list )
// sc-pause-list = "paused=" sc-alt-list
// sc-dir      = "send" / "recv"
// sc-id-type  = "pt" / "rid" / token
// sc-alt-list = sc-id *( "," sc-id )
// sc-id       = fmt / rid-identifier / token
// ; WSP defined in [RFC5234]
// ; fmt, token defined in [RFC4566]
// ; rid-identifier defined in [I-D.pthatcher-mmusic-rid]
//
// New draft 14, need to parse this for now, will eventually emit it
// 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
// ; SP defined in [RFC5234]
// ; rid-id defined in [I-D.ietf-mmusic-rid]

class SdpSimulcastAttribute : public SdpAttribute {
 public:
  SdpSimulcastAttribute() : SdpAttribute(kSimulcastAttribute) {}

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpSimulcastAttribute>(*this);
  }

  void Serialize(std::ostream& os) const override;
  bool Parse(std::istream& is, std::string* error);

  class Encoding {
   public:
    Encoding(const std::string& aRid, const bool aPaused)
        : rid(aRid), paused(aPaused) {}
    std::string rid;
    bool paused = false;
  };

  class Version {
   public:
    void Serialize(std::ostream& os) const;
    bool IsSet() const { return !choices.empty(); }
    bool Parse(std::istream& is, std::string* error);

    std::vector<Encoding> choices;
  };

  class Versions : public std::vector<Version> {
   public:
    void Serialize(std::ostream& os) const;
    bool IsSet() const {
      if (empty()) {
        return false;
      }

      for (const Version& version : *this) {
        if (version.IsSet()) {
          return true;
        }
      }

      return false;
    }

    bool Parse(std::istream& is, std::string* error);
  };

  Versions sendVersions;
  Versions recvVersions;
};

///////////////////////////////////////////////////////////////////////////
// a=ssrc, RFC5576
//-------------------------------------------------------------------------
// ssrc-attr = "ssrc:" ssrc-id SP attribute
// ; The base definition of "attribute" is in RFC 4566.
// ; (It is the content of "a=" lines.)
//
// ssrc-id = integer ; 0 .. 2**32 - 1
//-------------------------------------------------------------------------
// TODO -- In the future, it might be nice if we ran a parse on the
// attribute section of this so that we could interpret it semantically.
// For WebRTC, the key use case for a=ssrc is assocaiting SSRCs with
// media sections, and we're not really going to care about the attribute
// itself. So we're just going to store it as a string for the time being.
// Issue 187.
class SdpSsrcAttributeList : public SdpAttribute {
 public:
  SdpSsrcAttributeList() : SdpAttribute(kSsrcAttribute) {}

  struct Ssrc {
    uint32_t ssrc;
    std::string attribute;
  };

  void PushEntry(const uint32_t ssrc, const std::string& attribute) {
    Ssrc value = {ssrc, attribute};
    mSsrcs.push_back(std::move(value));
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpSsrcAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<Ssrc> mSsrcs;
};

///////////////////////////////////////////////////////////////////////////
// a=ssrc-group, RFC5576
//-------------------------------------------------------------------------
// ssrc-group-attr = "ssrc-group:" semantics *(SP ssrc-id)
//
// semantics       = "FEC" / "FID" / token
//
// ssrc-id = integer ; 0 .. 2**32 - 1
class SdpSsrcGroupAttributeList : public SdpAttribute {
 public:
  enum Semantics {
    kFec,    // RFC5576
    kFid,    // RFC5576
    kFecFr,  // RFC5956
    kDup,    // RFC7104
    kSim     // non-standard, used by hangouts
  };

  struct SsrcGroup {
    Semantics semantics;
    std::vector<uint32_t> ssrcs;
  };

  SdpSsrcGroupAttributeList() : SdpAttribute(kSsrcGroupAttribute) {}

  void PushEntry(const Semantics semantics,
                 const std::vector<uint32_t>& ssrcs) {
    SsrcGroup value = {semantics, ssrcs};
    mSsrcGroups.push_back(std::move(value));
  }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpSsrcGroupAttributeList>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<SsrcGroup> mSsrcGroups;
};

inline std::ostream& operator<<(std::ostream& os,
                                const SdpSsrcGroupAttributeList::Semantics s) {
  switch (s) {
    case SdpSsrcGroupAttributeList::kFec:
      os << "FEC";
      break;
    case SdpSsrcGroupAttributeList::kFid:
      os << "FID";
      break;
    case SdpSsrcGroupAttributeList::kFecFr:
      os << "FEC-FR";
      break;
    case SdpSsrcGroupAttributeList::kDup:
      os << "DUP";
      break;
    case SdpSsrcGroupAttributeList::kSim:
      os << "SIM";
      break;
    default:
      MOZ_ASSERT(false);
      os << "?";
  }
  return os;
}

///////////////////////////////////////////////////////////////////////////
class SdpMultiStringAttribute : public SdpAttribute {
 public:
  explicit SdpMultiStringAttribute(const AttributeType type)
      : SdpAttribute(type) {}

  void PushEntry(const std::string& entry) { mValues.push_back(entry); }

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpMultiStringAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<std::string> mValues;
};

// otherwise identical to SdpMultiStringAttribute, this is used for
// ice-options and other places where the value is serialized onto
// a single line with space separating tokens
class SdpOptionsAttribute : public SdpAttribute {
 public:
  explicit SdpOptionsAttribute(const AttributeType type) : SdpAttribute(type) {}

  void PushEntry(std::string&& entry) { mValues.push_back(std::move(entry)); }

  void Load(const std::string& value);

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpOptionsAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::vector<std::string> mValues;
};

// Used for attributes that take no value (eg; a=ice-lite)
class SdpFlagAttribute : public SdpAttribute {
 public:
  explicit SdpFlagAttribute(const AttributeType type) : SdpAttribute(type) {}

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpFlagAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;
};

// Used for any other kind of single-valued attribute not otherwise specialized
class SdpStringAttribute : public SdpAttribute {
 public:
  explicit SdpStringAttribute(const AttributeType type, std::string_view value)
      : SdpAttribute(type), mValue(value) {}

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpStringAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  std::string mValue;
};

// Used for any purely (non-negative) numeric attribute
class SdpNumberAttribute : public SdpAttribute {
 public:
  explicit SdpNumberAttribute(const AttributeType type,
                              const uint32_t value = 0)
      : SdpAttribute(type), mValue(value) {}

  UniquePtr<SdpAttribute> Clone() const override {
    return MakeUnique<SdpNumberAttribute>(*this);
  }

  virtual void Serialize(std::ostream& os) const override;

  uint32_t mValue;
};

}  // namespace mozilla

#endif
