| /* | 
 |  *  Copyright 2025 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/sdp_munging_detector.h" | 
 |  | 
 | #include <algorithm> | 
 | #include <cstddef> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include "absl/algorithm/container.h" | 
 | #include "absl/strings/str_split.h" | 
 | #include "absl/strings/string_view.h" | 
 | #include "api/field_trials_view.h" | 
 | #include "api/jsep.h" | 
 | #include "api/media_types.h" | 
 | #include "api/uma_metrics.h" | 
 | #include "media/base/codec.h" | 
 | #include "media/base/media_constants.h" | 
 | #include "media/base/stream_params.h" | 
 | #include "p2p/base/transport_description.h" | 
 | #include "p2p/base/transport_info.h" | 
 | #include "pc/session_description.h" | 
 | #include "rtc_base/checks.h" | 
 | #include "rtc_base/logging.h" | 
 |  | 
 | namespace webrtc { | 
 |  | 
 | namespace { | 
 |  | 
 | SdpMungingType DetermineTransportModification( | 
 |     const TransportInfos& last_created_transport_infos, | 
 |     const TransportInfos& transport_infos_to_set) { | 
 |   if (last_created_transport_infos.size() != transport_infos_to_set.size()) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: Number of transport-infos does not " | 
 |                            "match last created description."; | 
 |     // Number of transports should always match number of contents so this | 
 |     // should never happen. | 
 |     return SdpMungingType::kNumberOfContents; | 
 |   } | 
 |   for (size_t i = 0; i < last_created_transport_infos.size(); i++) { | 
 |     if (last_created_transport_infos[i].description.ice_ufrag != | 
 |         transport_infos_to_set[i].description.ice_ufrag) { | 
 |       RTC_LOG(LS_WARNING) | 
 |           << "SDP munging: ice-ufrag does not match last created description."; | 
 |       return SdpMungingType::kIceUfrag; | 
 |     } | 
 |     if (last_created_transport_infos[i].description.ice_pwd != | 
 |         transport_infos_to_set[i].description.ice_pwd) { | 
 |       RTC_LOG(LS_WARNING) | 
 |           << "SDP munging: ice-pwd does not match last created description."; | 
 |       return SdpMungingType::kIcePwd; | 
 |     } | 
 |     if (last_created_transport_infos[i].description.ice_mode != | 
 |         transport_infos_to_set[i].description.ice_mode) { | 
 |       RTC_LOG(LS_WARNING) | 
 |           << "SDP munging: ice mode does not match last created description."; | 
 |       return SdpMungingType::kIceMode; | 
 |     } | 
 |     if (last_created_transport_infos[i].description.connection_role != | 
 |         transport_infos_to_set[i].description.connection_role) { | 
 |       RTC_LOG(LS_WARNING) | 
 |           << "SDP munging: DTLS role does not match last created description."; | 
 |       return SdpMungingType::kDtlsSetup; | 
 |     } | 
 |     if (last_created_transport_infos[i].description.transport_options != | 
 |         transport_infos_to_set[i].description.transport_options) { | 
 |       RTC_LOG(LS_WARNING) << "SDP munging: ice_options does not match last " | 
 |                              "created description."; | 
 |       bool created_renomination = | 
 |           absl::c_find( | 
 |               last_created_transport_infos[i].description.transport_options, | 
 |               ICE_OPTION_RENOMINATION) != | 
 |           last_created_transport_infos[i].description.transport_options.end(); | 
 |       bool set_renomination = | 
 |           absl::c_find(transport_infos_to_set[i].description.transport_options, | 
 |                        ICE_OPTION_RENOMINATION) != | 
 |           transport_infos_to_set[i].description.transport_options.end(); | 
 |       if (!created_renomination && set_renomination) { | 
 |         return SdpMungingType::kIceOptionsRenomination; | 
 |       } | 
 |       return SdpMungingType::kIceOptions; | 
 |     } | 
 |   } | 
 |   return SdpMungingType::kNoModification; | 
 | } | 
 |  | 
 | SdpMungingType DetermineAudioSdpMungingType( | 
 |     const MediaContentDescription* last_created_media_description, | 
 |     const MediaContentDescription* media_description_to_set) { | 
 |   RTC_DCHECK(last_created_media_description); | 
 |   RTC_DCHECK(media_description_to_set); | 
 |   // Removing codecs should be done via setCodecPreferences or negotiation, not | 
 |   // munging. | 
 |   if (last_created_media_description->codecs().size() > | 
 |       media_description_to_set->codecs().size()) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: audio codecs removed."; | 
 |     return SdpMungingType::kAudioCodecsRemoved; | 
 |   } | 
 |   // Adding audio codecs is measured after the more specific multiopus and L16 | 
 |   // checks. | 
 |  | 
 |   // Opus stereo modification required to enabled stereo playout for opus. | 
 |   bool created_opus_stereo = | 
 |       absl::c_find_if(last_created_media_description->codecs(), | 
 |                       [](const Codec codec) { | 
 |                         std::string value; | 
 |                         return codec.name == kOpusCodecName && | 
 |                                codec.GetParam(kCodecParamStereo, &value) && | 
 |                                value == kParamValueTrue; | 
 |                       }) != last_created_media_description->codecs().end(); | 
 |   bool set_opus_stereo = | 
 |       absl::c_find_if( | 
 |           media_description_to_set->codecs(), [](const Codec codec) { | 
 |             std::string value; | 
 |             return codec.name == kOpusCodecName && | 
 |                    codec.GetParam(kCodecParamStereo, &value) && | 
 |                    value == kParamValueTrue; | 
 |           }) != media_description_to_set->codecs().end(); | 
 |   if (!created_opus_stereo && set_opus_stereo) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: Opus stereo enabled."; | 
 |     return SdpMungingType::kAudioCodecsFmtpOpusStereo; | 
 |   } | 
 |  | 
 |   // Nonstandard 5.1/7.1 opus variant. | 
 |   bool created_multiopus = | 
 |       absl::c_find_if(last_created_media_description->codecs(), | 
 |                       [](const Codec codec) { | 
 |                         return codec.name == "multiopus"; | 
 |                       }) != last_created_media_description->codecs().end(); | 
 |   bool set_multiopus = | 
 |       absl::c_find_if(media_description_to_set->codecs(), | 
 |                       [](const Codec codec) { | 
 |                         return codec.name == "multiopus"; | 
 |                       }) != media_description_to_set->codecs().end(); | 
 |   if (!created_multiopus && set_multiopus) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: multiopus enabled."; | 
 |     return SdpMungingType::kAudioCodecsAddedMultiOpus; | 
 |   } | 
 |  | 
 |   // L16. | 
 |   bool created_l16 = | 
 |       absl::c_find_if(last_created_media_description->codecs(), | 
 |                       [](const Codec codec) { | 
 |                         return codec.name == kL16CodecName; | 
 |                       }) != last_created_media_description->codecs().end(); | 
 |   bool set_l16 = absl::c_find_if(media_description_to_set->codecs(), | 
 |                                  [](const Codec codec) { | 
 |                                    return codec.name == kL16CodecName; | 
 |                                  }) != media_description_to_set->codecs().end(); | 
 |   if (!created_l16 && set_l16) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: L16 enabled."; | 
 |     return SdpMungingType::kAudioCodecsAddedL16; | 
 |   } | 
 |  | 
 |   if (last_created_media_description->codecs().size() < | 
 |       media_description_to_set->codecs().size()) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: audio codecs added."; | 
 |     return SdpMungingType::kAudioCodecsAdded; | 
 |   } | 
 |  | 
 |   // Audio NACK is not offered by default. | 
 |   bool created_nack = | 
 |       absl::c_find_if( | 
 |           last_created_media_description->codecs(), [](const Codec codec) { | 
 |             return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamNack)); | 
 |           }) != last_created_media_description->codecs().end(); | 
 |   bool set_nack = | 
 |       absl::c_find_if( | 
 |           media_description_to_set->codecs(), [](const Codec codec) { | 
 |             return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamNack)); | 
 |           }) != media_description_to_set->codecs().end(); | 
 |   if (!created_nack && set_nack) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: audio nack enabled."; | 
 |     return SdpMungingType::kAudioCodecsRtcpFbAudioNack; | 
 |   } | 
 |  | 
 |   // RRTR is not offered by default. | 
 |   bool created_rrtr = | 
 |       absl::c_find_if( | 
 |           last_created_media_description->codecs(), [](const Codec codec) { | 
 |             return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamRrtr)); | 
 |           }) != last_created_media_description->codecs().end(); | 
 |   bool set_rrtr = | 
 |       absl::c_find_if( | 
 |           media_description_to_set->codecs(), [](const Codec codec) { | 
 |             return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamRrtr)); | 
 |           }) != media_description_to_set->codecs().end(); | 
 |   if (!created_rrtr && set_rrtr) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: audio rrtr enabled."; | 
 |     return SdpMungingType::kAudioCodecsRtcpFbRrtr; | 
 |   } | 
 |  | 
 |   // Opus FEC is on by default. Should not be munged, can be controlled by | 
 |   // the other side. | 
 |   bool created_opus_fec = | 
 |       absl::c_find_if(last_created_media_description->codecs(), | 
 |                       [](const Codec codec) { | 
 |                         std::string value; | 
 |                         return codec.name == kOpusCodecName && | 
 |                                codec.GetParam(kCodecParamUseInbandFec, | 
 |                                               &value) && | 
 |                                value == kParamValueTrue; | 
 |                       }) != last_created_media_description->codecs().end(); | 
 |   bool set_opus_fec = | 
 |       absl::c_find_if( | 
 |           media_description_to_set->codecs(), [](const Codec codec) { | 
 |             std::string value; | 
 |             return codec.name == kOpusCodecName && | 
 |                    codec.GetParam(kCodecParamUseInbandFec, &value) && | 
 |                    value == kParamValueTrue; | 
 |           }) != media_description_to_set->codecs().end(); | 
 |   if (created_opus_fec && !set_opus_fec) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: Opus FEC disabled."; | 
 |     return SdpMungingType::kAudioCodecsFmtpOpusFec; | 
 |   } | 
 |   // Opus DTX is off by default. Should not be munged, can be controlled by | 
 |   // the other side. | 
 |   bool created_opus_dtx = | 
 |       absl::c_find_if(last_created_media_description->codecs(), | 
 |                       [](const Codec codec) { | 
 |                         std::string value; | 
 |                         return codec.name == kOpusCodecName && | 
 |                                codec.GetParam(kCodecParamUseDtx, &value) && | 
 |                                value == kParamValueTrue; | 
 |                       }) != last_created_media_description->codecs().end(); | 
 |   bool set_opus_dtx = | 
 |       absl::c_find_if( | 
 |           media_description_to_set->codecs(), [](const Codec codec) { | 
 |             std::string value; | 
 |             return codec.name == kOpusCodecName && | 
 |                    codec.GetParam(kCodecParamUseDtx, &value) && | 
 |                    value == kParamValueTrue; | 
 |           }) != media_description_to_set->codecs().end(); | 
 |   if (!created_opus_dtx && set_opus_dtx) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: Opus DTX enabled."; | 
 |     return SdpMungingType::kAudioCodecsFmtpOpusDtx; | 
 |   } | 
 |  | 
 |   // Opus CBR is off by default. Should not be munged, can be controlled by | 
 |   // the other side. | 
 |   bool created_opus_cbr = | 
 |       absl::c_find_if(last_created_media_description->codecs(), | 
 |                       [](const Codec codec) { | 
 |                         std::string value; | 
 |                         return codec.name == kOpusCodecName && | 
 |                                codec.GetParam(kCodecParamCbr, &value) && | 
 |                                value == kParamValueTrue; | 
 |                       }) != last_created_media_description->codecs().end(); | 
 |   bool set_opus_cbr = | 
 |       absl::c_find_if( | 
 |           media_description_to_set->codecs(), [](const Codec codec) { | 
 |             std::string value; | 
 |             return codec.name == kOpusCodecName && | 
 |                    codec.GetParam(kCodecParamCbr, &value) && | 
 |                    value == kParamValueTrue; | 
 |           }) != media_description_to_set->codecs().end(); | 
 |   if (!created_opus_cbr && set_opus_cbr) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: Opus CBR enabled."; | 
 |     return SdpMungingType::kAudioCodecsFmtpOpusCbr; | 
 |   } | 
 |   return SdpMungingType::kNoModification; | 
 | } | 
 |  | 
 | SdpMungingType DetermineVideoSdpMungingType( | 
 |     const MediaContentDescription* last_created_media_description, | 
 |     const MediaContentDescription* media_description_to_set) { | 
 |   RTC_DCHECK(last_created_media_description); | 
 |   RTC_DCHECK(media_description_to_set); | 
 |   // Removing codecs should be done via setCodecPreferences or negotiation, not | 
 |   // munging. | 
 |   if (last_created_media_description->codecs().size() > | 
 |       media_description_to_set->codecs().size()) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: video codecs removed."; | 
 |     return SdpMungingType::kVideoCodecsRemoved; | 
 |   } | 
 |   if (last_created_media_description->codecs().size() < | 
 |       media_description_to_set->codecs().size()) { | 
 |     // Nonstandard a=packetization:raw | 
 |     bool created_raw_packetization = | 
 |         absl::c_find_if(last_created_media_description->codecs(), | 
 |                         [](const Codec codec) { | 
 |                           return codec.packetization.has_value(); | 
 |                         }) != last_created_media_description->codecs().end(); | 
 |     bool set_raw_packetization = | 
 |         absl::c_find_if(media_description_to_set->codecs(), | 
 |                         [](const Codec codec) { | 
 |                           return codec.packetization.has_value(); | 
 |                         }) != media_description_to_set->codecs().end(); | 
 |     if (!created_raw_packetization && set_raw_packetization) { | 
 |       RTC_LOG(LS_WARNING) | 
 |           << "SDP munging: video codecs with raw packetization added."; | 
 |       return SdpMungingType::kVideoCodecsAddedWithRawPacketization; | 
 |     } | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: video codecs added."; | 
 |     return SdpMungingType::kVideoCodecsAdded; | 
 |   } | 
 |  | 
 |   // Simulcast munging. | 
 |   if (last_created_media_description->streams().size() == 1 && | 
 |       media_description_to_set->streams().size() == 1) { | 
 |     bool created_sim = | 
 |         absl::c_find_if( | 
 |             last_created_media_description->streams()[0].ssrc_groups, | 
 |             [](const SsrcGroup group) { | 
 |               return group.semantics == kSimSsrcGroupSemantics; | 
 |             }) != | 
 |         last_created_media_description->streams()[0].ssrc_groups.end(); | 
 |     bool set_sim = | 
 |         absl::c_find_if(media_description_to_set->streams()[0].ssrc_groups, | 
 |                         [](const SsrcGroup group) { | 
 |                           return group.semantics == kSimSsrcGroupSemantics; | 
 |                         }) != | 
 |         media_description_to_set->streams()[0].ssrc_groups.end(); | 
 |     if (!created_sim && set_sim) { | 
 |       RTC_LOG(LS_WARNING) << "SDP munging: legacy simulcast group created."; | 
 |       return SdpMungingType::kVideoCodecsLegacySimulcast; | 
 |     } | 
 |   } | 
 |  | 
 |   // sps-pps-idr-in-keyframe. | 
 |   bool created_sps_pps_idr_in_keyframe = | 
 |       absl::c_find_if(last_created_media_description->codecs(), | 
 |                       [](const Codec codec) { | 
 |                         std::string value; | 
 |                         return codec.name == kH264CodecName && | 
 |                                codec.GetParam(kH264FmtpSpsPpsIdrInKeyframe, | 
 |                                               &value) && | 
 |                                value == kParamValueTrue; | 
 |                       }) != last_created_media_description->codecs().end(); | 
 |   bool set_sps_pps_idr_in_keyframe = | 
 |       absl::c_find_if( | 
 |           media_description_to_set->codecs(), [](const Codec codec) { | 
 |             std::string value; | 
 |             return codec.name == kH264CodecName && | 
 |                    codec.GetParam(kH264FmtpSpsPpsIdrInKeyframe, &value) && | 
 |                    value == kParamValueTrue; | 
 |           }) != media_description_to_set->codecs().end(); | 
 |   if (!created_sps_pps_idr_in_keyframe && set_sps_pps_idr_in_keyframe) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: sps-pps-idr-in-keyframe enabled."; | 
 |     return SdpMungingType::kVideoCodecsFmtpH264SpsPpsIdrInKeyframe; | 
 |   } | 
 |  | 
 |   return SdpMungingType::kNoModification; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | // Determine if the SDP was modified between createOffer and | 
 | // setLocalDescription. | 
 | SdpMungingType DetermineSdpMungingType( | 
 |     const SessionDescriptionInterface* sdesc, | 
 |     const SessionDescriptionInterface* last_created_desc) { | 
 |   if (!sdesc || !sdesc->description()) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: Failed to parse session description."; | 
 |     return SdpMungingType::kUnknownModification; | 
 |   } | 
 |  | 
 |   if (!last_created_desc || !last_created_desc->description()) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: SetLocalDescription called without " | 
 |                            "CreateOffer or CreateAnswer."; | 
 |     if (sdesc->GetType() == SdpType::kOffer) { | 
 |       return SdpMungingType::kWithoutCreateOffer; | 
 |     } else {  // answer or pranswer. | 
 |       return SdpMungingType::kWithoutCreateAnswer; | 
 |     } | 
 |   } | 
 |  | 
 |   // TODO: crbug.com/40567530 - we currently allow answer->pranswer | 
 |   // so can not check sdesc->GetType() == last_created_desc->GetType(). | 
 |  | 
 |   SdpMungingType type; | 
 |  | 
 |   // TODO: crbug.com/40567530 - change Chromium so that pointer comparison works | 
 |   // at least for implicit local description. | 
 |   if (sdesc->description() == last_created_desc->description()) { | 
 |     return SdpMungingType::kNoModification; | 
 |   } | 
 |  | 
 |   // Validate contents. | 
 |   const auto& last_created_contents = | 
 |       last_created_desc->description()->contents(); | 
 |   const auto& contents_to_set = sdesc->description()->contents(); | 
 |   if (last_created_contents.size() != contents_to_set.size()) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: Number of m= sections does not match " | 
 |                            "last created description."; | 
 |     return SdpMungingType::kNumberOfContents; | 
 |   } | 
 |   for (size_t content_index = 0; content_index < last_created_contents.size(); | 
 |        content_index++) { | 
 |     // TODO: crbug.com/40567530 - more checks are needed here. | 
 |     if (last_created_contents[content_index].mid() != | 
 |         contents_to_set[content_index].mid()) { | 
 |       RTC_LOG(LS_WARNING) << "SDP munging: mid does not match " | 
 |                              "last created description."; | 
 |       return SdpMungingType::kMid; | 
 |     } | 
 |  | 
 |     auto* last_created_media_description = | 
 |         last_created_contents[content_index].media_description(); | 
 |     auto* media_description_to_set = | 
 |         contents_to_set[content_index].media_description(); | 
 |     if (!(last_created_media_description && media_description_to_set)) { | 
 |       continue; | 
 |     } | 
 |     // Validate video and audio contents. | 
 |     MediaType media_type = last_created_media_description->type(); | 
 |     if (media_type == MediaType::VIDEO) { | 
 |       type = DetermineVideoSdpMungingType(last_created_media_description, | 
 |                                           media_description_to_set); | 
 |       if (type != SdpMungingType::kNoModification) { | 
 |         return type; | 
 |       } | 
 |     } else if (media_type == MediaType::AUDIO) { | 
 |       type = DetermineAudioSdpMungingType(last_created_media_description, | 
 |                                           media_description_to_set); | 
 |       if (type != SdpMungingType::kNoModification) { | 
 |         return type; | 
 |       } | 
 |     } | 
 |  | 
 |     // rtcp-mux. | 
 |     if (last_created_media_description->rtcp_mux() != | 
 |         media_description_to_set->rtcp_mux()) { | 
 |       RTC_LOG(LS_WARNING) << "SDP munging: rtcp-mux modified."; | 
 |       return SdpMungingType::kRtcpMux; | 
 |     } | 
 |  | 
 |     // rtcp-rsize. | 
 |     if (last_created_media_description->rtcp_reduced_size() != | 
 |         media_description_to_set->rtcp_reduced_size()) { | 
 |       RTC_LOG(LS_WARNING) << "SDP munging: rtcp-rsize modified."; | 
 |       return media_type == MediaType::AUDIO | 
 |                  ? SdpMungingType::kAudioCodecsRtcpReducedSize | 
 |                  : SdpMungingType::kVideoCodecsRtcpReducedSize; | 
 |     } | 
 |  | 
 |     // Validate codecs. We should have bailed out earlier if codecs were added | 
 |     // or removed. | 
 |     auto last_created_codecs = last_created_media_description->codecs(); | 
 |     auto codecs_to_set = media_description_to_set->codecs(); | 
 |     if (last_created_codecs.size() == codecs_to_set.size()) { | 
 |       for (size_t i = 0; i < last_created_codecs.size(); i++) { | 
 |         if (last_created_codecs[i] == codecs_to_set[i]) { | 
 |           continue; | 
 |         } | 
 |         // Codec position swapped. | 
 |         for (size_t j = i + 1; j < last_created_codecs.size(); j++) { | 
 |           if (last_created_codecs[i] == codecs_to_set[j]) { | 
 |             return media_type == MediaType::AUDIO | 
 |                        ? SdpMungingType::kAudioCodecsReordered | 
 |                        : SdpMungingType::kVideoCodecsReordered; | 
 |           } | 
 |         } | 
 |         // Same codec but id changed. | 
 |         if (last_created_codecs[i].name == codecs_to_set[i].name && | 
 |             last_created_codecs[i].id != codecs_to_set[i].id) { | 
 |           return SdpMungingType::kPayloadTypes; | 
 |         } | 
 |         if (last_created_codecs[i].params != codecs_to_set[i].params) { | 
 |           return media_type == MediaType::AUDIO | 
 |                      ? SdpMungingType::kAudioCodecsFmtp | 
 |                      : SdpMungingType::kVideoCodecsFmtp; | 
 |         } | 
 |         if (last_created_codecs[i].feedback_params != | 
 |             codecs_to_set[i].feedback_params) { | 
 |           return media_type == MediaType::AUDIO | 
 |                      ? SdpMungingType::kAudioCodecsRtcpFb | 
 |                      : SdpMungingType::kVideoCodecsRtcpFb; | 
 |         } | 
 |         // Nonstandard a=packetization:raw added by munging | 
 |         if (media_type == MediaType::VIDEO && | 
 |             last_created_codecs[i].packetization != | 
 |                 codecs_to_set[i].packetization) { | 
 |           return SdpMungingType::kVideoCodecsModifiedWithRawPacketization; | 
 |         } | 
 |         // At this point clockrate or channels changed. This should already be | 
 |         // rejected later in the process so ignore for munging. | 
 |       } | 
 |     } | 
 |  | 
 |     // sendrecv et al. | 
 |     if (last_created_media_description->direction() != | 
 |         media_description_to_set->direction()) { | 
 |       RTC_LOG(LS_WARNING) << "SDP munging: transceiver direction modified."; | 
 |       return SdpMungingType::kDirection; | 
 |     } | 
 |  | 
 |     // Validate media streams. | 
 |     if (last_created_media_description->streams().size() != | 
 |         media_description_to_set->streams().size()) { | 
 |       RTC_LOG(LS_WARNING) << "SDP munging: streams size does not match last " | 
 |                              "created description."; | 
 |       return SdpMungingType::kSsrcs; | 
 |     } | 
 |     for (size_t i = 0; i < last_created_media_description->streams().size(); | 
 |          i++) { | 
 |       if (last_created_media_description->streams()[i].ssrcs != | 
 |           media_description_to_set->streams()[i].ssrcs) { | 
 |         RTC_LOG(LS_WARNING) | 
 |             << "SDP munging: SSRCs do not match last created description."; | 
 |         return SdpMungingType::kSsrcs; | 
 |       } | 
 |     } | 
 |  | 
 |     // Validate RTP header extensions. | 
 |     auto last_created_extensions = | 
 |         last_created_media_description->rtp_header_extensions(); | 
 |     auto extensions_to_set = media_description_to_set->rtp_header_extensions(); | 
 |     if (last_created_extensions.size() < extensions_to_set.size()) { | 
 |       RTC_LOG(LS_WARNING) << "SDP munging: RTP header extension added."; | 
 |       return SdpMungingType::kRtpHeaderExtensionAdded; | 
 |     } | 
 |     if (last_created_extensions.size() > extensions_to_set.size()) { | 
 |       RTC_LOG(LS_WARNING) << "SDP munging: RTP header extension removed."; | 
 |       return SdpMungingType::kRtpHeaderExtensionRemoved; | 
 |     } | 
 |     for (size_t i = 0; i < last_created_extensions.size(); i++) { | 
 |       if (!(last_created_extensions[i].id == extensions_to_set[i].id)) { | 
 |         RTC_LOG(LS_WARNING) << "SDP munging: header extension modified."; | 
 |         return SdpMungingType::kRtpHeaderExtensionModified; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   // Validate transport descriptions. | 
 |   type = DetermineTransportModification( | 
 |       last_created_desc->description()->transport_infos(), | 
 |       sdesc->description()->transport_infos()); | 
 |   if (type != SdpMungingType::kNoModification) { | 
 |     return type; | 
 |   } | 
 |  | 
 |   // TODO: crbug.com/40567530 - this serializes the descriptions back to a SDP | 
 |   // string which is very complex and we not should be be forced to rely on | 
 |   // string equality. | 
 |   std::string serialized_description; | 
 |   std::string serialized_last_description; | 
 |   if (sdesc->ToString(&serialized_description) && | 
 |       last_created_desc->ToString(&serialized_last_description) && | 
 |       serialized_description == serialized_last_description) { | 
 |     return SdpMungingType::kNoModification; | 
 |   } | 
 |   return SdpMungingType::kUnknownModification; | 
 | } | 
 |  | 
 | // Similar to DetermineSdpMungingType, but only checks whether the ICE ufrag or | 
 | // pwd of the SDP has been modified between createOffer and setLocalDescription. | 
 | bool HasUfragSdpMunging(const SessionDescriptionInterface* sdesc, | 
 |                         const SessionDescriptionInterface* last_created_desc) { | 
 |   if (!sdesc || !sdesc->description()) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: Failed to parse session description."; | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (!last_created_desc || !last_created_desc->description()) { | 
 |     RTC_LOG(LS_WARNING) << "SDP munging: SetLocalDescription called without " | 
 |                            "CreateOffer or CreateAnswer."; | 
 |     return false; | 
 |   } | 
 |   TransportInfos last_created_transport_infos = | 
 |       last_created_desc->description()->transport_infos(); | 
 |   TransportInfos transport_infos_to_set = | 
 |       sdesc->description()->transport_infos(); | 
 |   for (size_t i = 0; i < std::min(last_created_transport_infos.size(), | 
 |                                   transport_infos_to_set.size()); | 
 |        i++) { | 
 |     if (last_created_transport_infos[i].description.ice_ufrag != | 
 |         transport_infos_to_set[i].description.ice_ufrag) { | 
 |       return true; | 
 |     } | 
 |     if (last_created_transport_infos[i].description.ice_pwd != | 
 |         transport_infos_to_set[i].description.ice_pwd) { | 
 |       return true; | 
 |     } | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | bool IsSdpMungingAllowed(SdpMungingType sdp_munging_type, | 
 |                          const FieldTrialsView& trials) { | 
 |   if (sdp_munging_type == SdpMungingType::kNoModification) { | 
 |     return true; | 
 |   } | 
 |   std::string type_as_string = | 
 |       std::to_string(static_cast<int>(sdp_munging_type)); | 
 |  | 
 |   std::string trial; | 
 |   // NoSdpMangleReject is for rollout, disallowing specific types of munging | 
 |   // via Finch. It is a comma-separated list of SdpMungingTypes | 
 |   if (trials.IsEnabled("WebRTC-NoSdpMangleReject")) { | 
 |     trial = trials.Lookup("WebRTC-NoSdpMangleReject"); | 
 |     const std::vector<absl::string_view> rejected_types = | 
 |         absl::StrSplit(trial, ','); | 
 |     return absl::c_find(rejected_types, type_as_string) == rejected_types.end(); | 
 |   } | 
 |   // NoSdpMangleAllowForTesting is for running E2E tests which should reject | 
 |   // by default with a test-supplied list of exceptions as a comma-separated | 
 |   // list. | 
 |   if (!trials.IsEnabled("WebRTC-NoSdpMangleAllowForTesting")) { | 
 |     return true; | 
 |   } | 
 |   trial = trials.Lookup("WebRTC-NoSdpMangleAllowForTesting"); | 
 |   const std::vector<absl::string_view> allowed_types = | 
 |       absl::StrSplit(trial, ','); | 
 |   return absl::c_find(allowed_types, type_as_string) != allowed_types.end(); | 
 | } | 
 |  | 
 | }  // namespace webrtc |