/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /* 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/. */ #include "sdp/SipccSdpMediaSection.h" #include #include "sdp/SdpParser.h" extern "C" { #include "sipcc_sdp.h" } #ifdef CRLF # undef CRLF #endif #define CRLF "\r\n" namespace mozilla { SipccSdpMediaSection::SipccSdpMediaSection( const SipccSdpMediaSection& aOrig, const SipccSdpAttributeList* sessionLevel) : SdpMediaSection(aOrig), mMediaType(aOrig.mMediaType), mPort(aOrig.mPort), mPortCount(aOrig.mPortCount), mProtocol(aOrig.mProtocol), mFormats(aOrig.mFormats), mConnection(new SdpConnection(*aOrig.mConnection)), mBandwidths(aOrig.mBandwidths), mAttributeList(aOrig.mAttributeList, sessionLevel) {} unsigned int SipccSdpMediaSection::GetPort() const { return mPort; } void SipccSdpMediaSection::SetPort(unsigned int port) { mPort = port; } unsigned int SipccSdpMediaSection::GetPortCount() const { return mPortCount; } SdpMediaSection::Protocol SipccSdpMediaSection::GetProtocol() const { return mProtocol; } const SdpConnection& SipccSdpMediaSection::GetConnection() const { return *mConnection; } SdpConnection& SipccSdpMediaSection::GetConnection() { return *mConnection; } uint32_t SipccSdpMediaSection::GetBandwidth(const std::string& type) const { auto found = mBandwidths.find(type); if (found == mBandwidths.end()) { return 0; } return found->second; } const std::vector& SipccSdpMediaSection::GetFormats() const { return mFormats; } const SdpAttributeList& SipccSdpMediaSection::GetAttributeList() const { return mAttributeList; } SdpAttributeList& SipccSdpMediaSection::GetAttributeList() { return mAttributeList; } SdpDirectionAttribute SipccSdpMediaSection::GetDirectionAttribute() const { return SdpDirectionAttribute(mAttributeList.GetDirection()); } bool SipccSdpMediaSection::Load(sdp_t* sdp, uint16_t level, InternalResults& results) { switch (sdp_get_media_type(sdp, level)) { case SDP_MEDIA_AUDIO: mMediaType = kAudio; break; case SDP_MEDIA_VIDEO: mMediaType = kVideo; break; case SDP_MEDIA_APPLICATION: mMediaType = kApplication; break; case SDP_MEDIA_TEXT: mMediaType = kText; break; default: results.AddParseError(sdp_get_media_line_number(sdp, level), "Unsupported media section type"); return false; } mPort = sdp_get_media_portnum(sdp, level); int32_t pc = sdp_get_media_portcount(sdp, level); if (pc == SDP_INVALID_VALUE) { // SDP_INVALID_VALUE (ie; -2) is used when there is no port count. :( mPortCount = 0; } else if (pc > static_cast(UINT16_MAX) || pc < 0) { results.AddParseError(sdp_get_media_line_number(sdp, level), "Invalid port count"); return false; } else { mPortCount = pc; } if (!LoadProtocol(sdp, level, results)) { return false; } if (!LoadFormats(sdp, level, results)) { return false; } if (!mAttributeList.Load(sdp, level, results)) { return false; } if (!ValidateSimulcast(sdp, level, results)) { return false; } if (!mBandwidths.Load(sdp, level, results)) { return false; } return LoadConnection(sdp, level, results); } bool SipccSdpMediaSection::LoadProtocol(sdp_t* sdp, uint16_t level, InternalResults& results) { switch (sdp_get_media_transport(sdp, level)) { case SDP_TRANSPORT_RTPAVP: mProtocol = kRtpAvp; break; case SDP_TRANSPORT_RTPSAVP: mProtocol = kRtpSavp; break; case SDP_TRANSPORT_RTPAVPF: mProtocol = kRtpAvpf; break; case SDP_TRANSPORT_RTPSAVPF: mProtocol = kRtpSavpf; break; case SDP_TRANSPORT_UDPTLSRTPSAVP: mProtocol = kUdpTlsRtpSavp; break; case SDP_TRANSPORT_UDPTLSRTPSAVPF: mProtocol = kUdpTlsRtpSavpf; break; case SDP_TRANSPORT_TCPDTLSRTPSAVP: mProtocol = kTcpDtlsRtpSavp; break; case SDP_TRANSPORT_TCPDTLSRTPSAVPF: mProtocol = kTcpDtlsRtpSavpf; break; case SDP_TRANSPORT_DTLSSCTP: mProtocol = kDtlsSctp; break; case SDP_TRANSPORT_UDPDTLSSCTP: mProtocol = kUdpDtlsSctp; break; case SDP_TRANSPORT_TCPDTLSSCTP: mProtocol = kTcpDtlsSctp; break; default: results.AddParseError(sdp_get_media_line_number(sdp, level), "Unsupported media transport type"); return false; } return true; } bool SipccSdpMediaSection::LoadFormats(sdp_t* sdp, uint16_t level, InternalResults& results) { sdp_media_e mtype = sdp_get_media_type(sdp, level); if (mtype == SDP_MEDIA_APPLICATION) { sdp_transport_e ttype = sdp_get_media_transport(sdp, level); if ((ttype == SDP_TRANSPORT_UDPDTLSSCTP) || (ttype == SDP_TRANSPORT_TCPDTLSSCTP)) { if (sdp_get_media_sctp_fmt(sdp, level) == SDP_SCTP_MEDIA_FMT_WEBRTC_DATACHANNEL) { mFormats.push_back("webrtc-datachannel"); } } else { uint32_t ptype = sdp_get_media_sctp_port(sdp, level); std::ostringstream osPayloadType; osPayloadType << ptype; mFormats.push_back(osPayloadType.str()); } } else if (mtype == SDP_MEDIA_AUDIO || mtype == SDP_MEDIA_VIDEO) { uint16_t count = sdp_get_media_num_payload_types(sdp, level); for (uint16_t i = 0; i < count; ++i) { sdp_payload_ind_e indicator; // we ignore this, which is fine uint32_t ptype = sdp_get_media_payload_type(sdp, level, i + 1, &indicator); if (GET_DYN_PAYLOAD_TYPE_VALUE(ptype) > UINT8_MAX) { results.AddParseError(sdp_get_media_line_number(sdp, level), "Format is too large"); return false; } std::ostringstream osPayloadType; // sipcc stores payload types in a funny way. When sipcc and the SDP it // parsed differ on what payload type number should be used for a given // codec, sipcc's value goes in the lower byte, and the SDP's value in // the upper byte. When they do not differ, only the lower byte is used. // We want what was in the SDP, verbatim. osPayloadType << GET_DYN_PAYLOAD_TYPE_VALUE(ptype); mFormats.push_back(osPayloadType.str()); } } return true; } bool SipccSdpMediaSection::ValidateSimulcast(sdp_t* sdp, uint16_t level, InternalResults& results) const { if (!GetAttributeList().HasAttribute(SdpAttribute::kSimulcastAttribute)) { return true; } const SdpSimulcastAttribute& simulcast(GetAttributeList().GetSimulcast()); if (!ValidateSimulcastVersions(sdp, level, simulcast.sendVersions, sdp::kSend, results)) { return false; } if (!ValidateSimulcastVersions(sdp, level, simulcast.recvVersions, sdp::kRecv, results)) { return false; } return true; } bool SipccSdpMediaSection::ValidateSimulcastVersions( sdp_t* sdp, uint16_t level, const SdpSimulcastAttribute::Versions& versions, sdp::Direction direction, InternalResults& results) const { for (const SdpSimulcastAttribute::Version& version : versions) { for (const SdpSimulcastAttribute::Encoding& encoding : version.choices) { const SdpRidAttributeList::Rid* ridAttr = FindRid(encoding.rid); if (!ridAttr || (ridAttr->direction != direction)) { std::ostringstream os; os << "No rid attribute for \'" << encoding.rid << "\'"; results.AddParseError(sdp_get_media_line_number(sdp, level), os.str()); results.AddParseError(sdp_get_media_line_number(sdp, level), os.str()); return false; } } } return true; } bool SipccSdpMediaSection::LoadConnection(sdp_t* sdp, uint16_t level, InternalResults& results) { if (!sdp_connection_valid(sdp, level)) { level = SDP_SESSION_LEVEL; if (!sdp_connection_valid(sdp, level)) { results.AddParseError(sdp_get_media_line_number(sdp, level), "Missing c= line"); return false; } } sdp_nettype_e type = sdp_get_conn_nettype(sdp, level); if (type != SDP_NT_INTERNET) { results.AddParseError(sdp_get_media_line_number(sdp, level), "Unsupported network type"); return false; } sdp::AddrType addrType; switch (sdp_get_conn_addrtype(sdp, level)) { case SDP_AT_IP4: addrType = sdp::kIPv4; break; case SDP_AT_IP6: addrType = sdp::kIPv6; break; default: results.AddParseError(sdp_get_media_line_number(sdp, level), "Unsupported address type"); return false; } std::string address = sdp_get_conn_address(sdp, level); int16_t ttl = static_cast(sdp_get_mcast_ttl(sdp, level)); if (ttl < 0) { ttl = 0; } int32_t numAddr = static_cast(sdp_get_mcast_num_of_addresses(sdp, level)); if (numAddr < 0) { numAddr = 0; } mConnection = MakeUnique(addrType, address, ttl, numAddr); return true; } void SipccSdpMediaSection::AddCodec(const std::string& pt, const std::string& name, uint32_t clockrate, uint16_t channels) { mFormats.push_back(pt); SdpRtpmapAttributeList* rtpmap = new SdpRtpmapAttributeList(); if (mAttributeList.HasAttribute(SdpAttribute::kRtpmapAttribute)) { const SdpRtpmapAttributeList& old = mAttributeList.GetRtpmap(); for (auto it = old.mRtpmaps.begin(); it != old.mRtpmaps.end(); ++it) { rtpmap->mRtpmaps.push_back(*it); } } SdpRtpmapAttributeList::CodecType codec = SdpRtpmapAttributeList::kOtherCodec; if (name == "opus") { codec = SdpRtpmapAttributeList::kOpus; } else if (name == "G722") { codec = SdpRtpmapAttributeList::kG722; } else if (name == "PCMU") { codec = SdpRtpmapAttributeList::kPCMU; } else if (name == "PCMA") { codec = SdpRtpmapAttributeList::kPCMA; } else if (name == "VP8") { codec = SdpRtpmapAttributeList::kVP8; } else if (name == "VP9") { codec = SdpRtpmapAttributeList::kVP9; } else if (name == "H264") { codec = SdpRtpmapAttributeList::kH264; } rtpmap->PushEntry(pt, codec, name, clockrate, channels); mAttributeList.SetAttribute(rtpmap); } void SipccSdpMediaSection::ClearCodecs() { mFormats.clear(); mAttributeList.RemoveAttribute(SdpAttribute::kRtpmapAttribute); mAttributeList.RemoveAttribute(SdpAttribute::kFmtpAttribute); mAttributeList.RemoveAttribute(SdpAttribute::kSctpmapAttribute); mAttributeList.RemoveAttribute(SdpAttribute::kRtcpFbAttribute); } void SipccSdpMediaSection::AddDataChannel(const std::string& name, uint16_t port, uint16_t streams, uint32_t message_size) { // Only one allowed, for now. This may change as the specs (and deployments) // evolve. mFormats.clear(); if ((mProtocol == kUdpDtlsSctp) || (mProtocol == kTcpDtlsSctp)) { // new data channel format according to draft 21 mFormats.push_back(name); mAttributeList.SetAttribute( new SdpNumberAttribute(SdpAttribute::kSctpPortAttribute, port)); if (message_size) { mAttributeList.SetAttribute(new SdpNumberAttribute( SdpAttribute::kMaxMessageSizeAttribute, message_size)); } } else { // old data channels format according to draft 05 std::string port_str = std::to_string(port); mFormats.push_back(port_str); SdpSctpmapAttributeList* sctpmap = new SdpSctpmapAttributeList(); sctpmap->PushEntry(port_str, name, streams); mAttributeList.SetAttribute(sctpmap); if (message_size) { // This is a workaround to allow detecting Firefox's w/o EOR support mAttributeList.SetAttribute(new SdpNumberAttribute( SdpAttribute::kMaxMessageSizeAttribute, message_size)); } } } void SipccSdpMediaSection::Serialize(std::ostream& os) const { os << "m=" << mMediaType << " " << mPort; if (mPortCount) { os << "/" << mPortCount; } os << " " << mProtocol; for (auto i = mFormats.begin(); i != mFormats.end(); ++i) { os << " " << (*i); } os << CRLF; // We dont do i= if (mConnection) { os << *mConnection; } mBandwidths.Serialize(os); // We dont do k= because they're evil os << mAttributeList; } } // namespace mozilla