Thanks to visit codestin.com
Credit goes to webrtc.googlesource.com

blob: 2b838d93ab33bdc1c67d4ecc0387091dae5fcc92 [file] [log] [blame]
Philipp Hanckecfaba8f2025-01-15 01:16:391/*
2 * Copyright 2025 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "pc/sdp_munging_detector.h"
12
Tom Van Goethem84e40072025-04-28 22:14:1113#include <algorithm>
Philipp Hanckecfaba8f2025-01-15 01:16:3914#include <cstddef>
15#include <string>
Philipp Hancke84d07a32025-06-18 15:49:0616#include <vector>
Philipp Hanckecfaba8f2025-01-15 01:16:3917
18#include "absl/algorithm/container.h"
Philipp Hancke84d07a32025-06-18 15:49:0619#include "absl/strings/str_split.h"
20#include "absl/strings/string_view.h"
21#include "api/field_trials_view.h"
Philipp Hanckecfaba8f2025-01-15 01:16:3922#include "api/jsep.h"
Philipp Hancke9ec6f982025-02-21 22:42:0223#include "api/media_types.h"
Philipp Hanckecfaba8f2025-01-15 01:16:3924#include "api/uma_metrics.h"
25#include "media/base/codec.h"
26#include "media/base/media_constants.h"
27#include "media/base/stream_params.h"
Harald Alvestrandb2ee2402025-10-07 10:34:5928#include "p2p/base/p2p_constants.h"
Philipp Hancke699fe6f2025-03-19 18:26:2929#include "p2p/base/transport_description.h"
Philipp Hanckecfaba8f2025-01-15 01:16:3930#include "p2p/base/transport_info.h"
31#include "pc/session_description.h"
32#include "rtc_base/checks.h"
33#include "rtc_base/logging.h"
34
35namespace webrtc {
36
37namespace {
38
39SdpMungingType DetermineTransportModification(
Evan Shrubsolee6a1f702025-04-15 14:55:4240 const TransportInfos& last_created_transport_infos,
41 const TransportInfos& transport_infos_to_set) {
Philipp Hanckecfaba8f2025-01-15 01:16:3942 if (last_created_transport_infos.size() != transport_infos_to_set.size()) {
Philipp Hancke19b06442025-10-24 06:57:3843 RTC_LOG(LS_ERROR) << "SDP munging: Number of transport-infos does not "
44 "match last created description.";
Philipp Hanckecfaba8f2025-01-15 01:16:3945 // Number of transports should always match number of contents so this
46 // should never happen.
47 return SdpMungingType::kNumberOfContents;
48 }
49 for (size_t i = 0; i < last_created_transport_infos.size(); i++) {
50 if (last_created_transport_infos[i].description.ice_ufrag !=
51 transport_infos_to_set[i].description.ice_ufrag) {
52 RTC_LOG(LS_WARNING)
53 << "SDP munging: ice-ufrag does not match last created description.";
54 return SdpMungingType::kIceUfrag;
55 }
56 if (last_created_transport_infos[i].description.ice_pwd !=
57 transport_infos_to_set[i].description.ice_pwd) {
58 RTC_LOG(LS_WARNING)
59 << "SDP munging: ice-pwd does not match last created description.";
60 return SdpMungingType::kIcePwd;
61 }
62 if (last_created_transport_infos[i].description.ice_mode !=
63 transport_infos_to_set[i].description.ice_mode) {
64 RTC_LOG(LS_WARNING)
65 << "SDP munging: ice mode does not match last created description.";
66 return SdpMungingType::kIceMode;
67 }
68 if (last_created_transport_infos[i].description.connection_role !=
69 transport_infos_to_set[i].description.connection_role) {
70 RTC_LOG(LS_WARNING)
71 << "SDP munging: DTLS role does not match last created description.";
72 return SdpMungingType::kDtlsSetup;
73 }
74 if (last_created_transport_infos[i].description.transport_options !=
75 transport_infos_to_set[i].description.transport_options) {
76 RTC_LOG(LS_WARNING) << "SDP munging: ice_options does not match last "
77 "created description.";
Philipp Hancke699fe6f2025-03-19 18:26:2978 bool created_renomination =
79 absl::c_find(
80 last_created_transport_infos[i].description.transport_options,
Evan Shrubsole945e5172025-04-08 14:11:4581 ICE_OPTION_RENOMINATION) !=
Philipp Hancke699fe6f2025-03-19 18:26:2982 last_created_transport_infos[i].description.transport_options.end();
83 bool set_renomination =
84 absl::c_find(transport_infos_to_set[i].description.transport_options,
Evan Shrubsole945e5172025-04-08 14:11:4585 ICE_OPTION_RENOMINATION) !=
Philipp Hancke699fe6f2025-03-19 18:26:2986 transport_infos_to_set[i].description.transport_options.end();
87 if (!created_renomination && set_renomination) {
88 return SdpMungingType::kIceOptionsRenomination;
89 }
Philipp Hancke74e224e2025-06-27 22:54:0190 bool created_trickle =
91 absl::c_find(
92 last_created_transport_infos[i].description.transport_options,
93 ICE_OPTION_TRICKLE) !=
94 last_created_transport_infos[i].description.transport_options.end();
95 bool set_trickle =
96 absl::c_find(transport_infos_to_set[i].description.transport_options,
97 ICE_OPTION_TRICKLE) !=
98 transport_infos_to_set[i].description.transport_options.end();
99 if (created_trickle && !set_trickle) {
100 return SdpMungingType::kIceOptionsTrickle;
101 }
Philipp Hanckecfaba8f2025-01-15 01:16:39102 return SdpMungingType::kIceOptions;
103 }
104 }
105 return SdpMungingType::kNoModification;
106}
107
Philipp Hancke19061392025-08-15 16:51:12108SdpMungingType DetermineAudioSdpModification(
Evan Shrubsole080cdac2025-03-20 09:34:48109 const MediaContentDescription* last_created_media_description,
110 const MediaContentDescription* media_description_to_set) {
Philipp Hanckecfaba8f2025-01-15 01:16:39111 RTC_DCHECK(last_created_media_description);
112 RTC_DCHECK(media_description_to_set);
113 // Removing codecs should be done via setCodecPreferences or negotiation, not
114 // munging.
115 if (last_created_media_description->codecs().size() >
116 media_description_to_set->codecs().size()) {
117 RTC_LOG(LS_WARNING) << "SDP munging: audio codecs removed.";
118 return SdpMungingType::kAudioCodecsRemoved;
119 }
120 // Adding audio codecs is measured after the more specific multiopus and L16
121 // checks.
122
123 // Opus stereo modification required to enabled stereo playout for opus.
124 bool created_opus_stereo =
125 absl::c_find_if(last_created_media_description->codecs(),
Evan Shrubsole0b212e32025-04-07 13:43:01126 [](const Codec codec) {
Philipp Hanckecfaba8f2025-01-15 01:16:39127 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45128 return codec.name == kOpusCodecName &&
129 codec.GetParam(kCodecParamStereo, &value) &&
130 value == kParamValueTrue;
Philipp Hanckecfaba8f2025-01-15 01:16:39131 }) != last_created_media_description->codecs().end();
132 bool set_opus_stereo =
133 absl::c_find_if(
Evan Shrubsole0b212e32025-04-07 13:43:01134 media_description_to_set->codecs(), [](const Codec codec) {
Philipp Hanckecfaba8f2025-01-15 01:16:39135 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45136 return codec.name == kOpusCodecName &&
137 codec.GetParam(kCodecParamStereo, &value) &&
138 value == kParamValueTrue;
Philipp Hanckecfaba8f2025-01-15 01:16:39139 }) != media_description_to_set->codecs().end();
140 if (!created_opus_stereo && set_opus_stereo) {
141 RTC_LOG(LS_WARNING) << "SDP munging: Opus stereo enabled.";
142 return SdpMungingType::kAudioCodecsFmtpOpusStereo;
143 }
144
145 // Nonstandard 5.1/7.1 opus variant.
146 bool created_multiopus =
147 absl::c_find_if(last_created_media_description->codecs(),
Evan Shrubsole0b212e32025-04-07 13:43:01148 [](const Codec codec) {
Philipp Hanckecfaba8f2025-01-15 01:16:39149 return codec.name == "multiopus";
150 }) != last_created_media_description->codecs().end();
151 bool set_multiopus =
152 absl::c_find_if(media_description_to_set->codecs(),
Evan Shrubsole0b212e32025-04-07 13:43:01153 [](const Codec codec) {
Philipp Hanckecfaba8f2025-01-15 01:16:39154 return codec.name == "multiopus";
155 }) != media_description_to_set->codecs().end();
156 if (!created_multiopus && set_multiopus) {
157 RTC_LOG(LS_WARNING) << "SDP munging: multiopus enabled.";
158 return SdpMungingType::kAudioCodecsAddedMultiOpus;
159 }
160
161 // L16.
162 bool created_l16 =
163 absl::c_find_if(last_created_media_description->codecs(),
Evan Shrubsole0b212e32025-04-07 13:43:01164 [](const Codec codec) {
Evan Shrubsole945e5172025-04-08 14:11:45165 return codec.name == kL16CodecName;
Philipp Hanckecfaba8f2025-01-15 01:16:39166 }) != last_created_media_description->codecs().end();
167 bool set_l16 = absl::c_find_if(media_description_to_set->codecs(),
Evan Shrubsole0b212e32025-04-07 13:43:01168 [](const Codec codec) {
Evan Shrubsole945e5172025-04-08 14:11:45169 return codec.name == kL16CodecName;
Philipp Hanckecfaba8f2025-01-15 01:16:39170 }) != media_description_to_set->codecs().end();
171 if (!created_l16 && set_l16) {
172 RTC_LOG(LS_WARNING) << "SDP munging: L16 enabled.";
173 return SdpMungingType::kAudioCodecsAddedL16;
174 }
175
Philipp Hanckeeefd2ab2025-03-17 15:45:03176 if (last_created_media_description->codecs().size() <
177 media_description_to_set->codecs().size()) {
178 RTC_LOG(LS_WARNING) << "SDP munging: audio codecs added.";
179 return SdpMungingType::kAudioCodecsAdded;
180 }
181
Philipp Hanckeee5e7e52025-03-03 01:33:03182 // Audio NACK is not offered by default.
183 bool created_nack =
Evan Shrubsole945e5172025-04-08 14:11:45184 absl::c_find_if(
185 last_created_media_description->codecs(), [](const Codec codec) {
186 return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamNack));
187 }) != last_created_media_description->codecs().end();
Philipp Hanckeee5e7e52025-03-03 01:33:03188 bool set_nack =
Evan Shrubsole945e5172025-04-08 14:11:45189 absl::c_find_if(
190 media_description_to_set->codecs(), [](const Codec codec) {
191 return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamNack));
192 }) != media_description_to_set->codecs().end();
Philipp Hanckeee5e7e52025-03-03 01:33:03193 if (!created_nack && set_nack) {
194 RTC_LOG(LS_WARNING) << "SDP munging: audio nack enabled.";
195 return SdpMungingType::kAudioCodecsRtcpFbAudioNack;
196 }
197
Philipp Hanckeeefd2ab2025-03-17 15:45:03198 // RRTR is not offered by default.
199 bool created_rrtr =
Evan Shrubsole945e5172025-04-08 14:11:45200 absl::c_find_if(
201 last_created_media_description->codecs(), [](const Codec codec) {
202 return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamRrtr));
203 }) != last_created_media_description->codecs().end();
Philipp Hanckeeefd2ab2025-03-17 15:45:03204 bool set_rrtr =
Evan Shrubsole945e5172025-04-08 14:11:45205 absl::c_find_if(
206 media_description_to_set->codecs(), [](const Codec codec) {
207 return codec.HasFeedbackParam(FeedbackParam(kRtcpFbParamRrtr));
208 }) != media_description_to_set->codecs().end();
Philipp Hanckeeefd2ab2025-03-17 15:45:03209 if (!created_rrtr && set_rrtr) {
210 RTC_LOG(LS_WARNING) << "SDP munging: audio rrtr enabled.";
211 return SdpMungingType::kAudioCodecsRtcpFbRrtr;
Philipp Hanckecfaba8f2025-01-15 01:16:39212 }
Philipp Hanckeee5e7e52025-03-03 01:33:03213
214 // Opus FEC is on by default. Should not be munged, can be controlled by
215 // the other side.
216 bool created_opus_fec =
217 absl::c_find_if(last_created_media_description->codecs(),
Evan Shrubsole0b212e32025-04-07 13:43:01218 [](const Codec codec) {
Philipp Hanckeee5e7e52025-03-03 01:33:03219 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45220 return codec.name == kOpusCodecName &&
221 codec.GetParam(kCodecParamUseInbandFec,
Philipp Hanckeee5e7e52025-03-03 01:33:03222 &value) &&
Evan Shrubsole945e5172025-04-08 14:11:45223 value == kParamValueTrue;
Philipp Hanckeee5e7e52025-03-03 01:33:03224 }) != last_created_media_description->codecs().end();
225 bool set_opus_fec =
226 absl::c_find_if(
Evan Shrubsole0b212e32025-04-07 13:43:01227 media_description_to_set->codecs(), [](const Codec codec) {
Philipp Hanckeee5e7e52025-03-03 01:33:03228 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45229 return codec.name == kOpusCodecName &&
230 codec.GetParam(kCodecParamUseInbandFec, &value) &&
231 value == kParamValueTrue;
Philipp Hanckeee5e7e52025-03-03 01:33:03232 }) != media_description_to_set->codecs().end();
233 if (created_opus_fec && !set_opus_fec) {
234 RTC_LOG(LS_WARNING) << "SDP munging: Opus FEC disabled.";
235 return SdpMungingType::kAudioCodecsFmtpOpusFec;
236 }
237 // Opus DTX is off by default. Should not be munged, can be controlled by
238 // the other side.
239 bool created_opus_dtx =
240 absl::c_find_if(last_created_media_description->codecs(),
Evan Shrubsole0b212e32025-04-07 13:43:01241 [](const Codec codec) {
Philipp Hanckeee5e7e52025-03-03 01:33:03242 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45243 return codec.name == kOpusCodecName &&
244 codec.GetParam(kCodecParamUseDtx, &value) &&
245 value == kParamValueTrue;
Philipp Hanckeee5e7e52025-03-03 01:33:03246 }) != last_created_media_description->codecs().end();
247 bool set_opus_dtx =
248 absl::c_find_if(
Evan Shrubsole0b212e32025-04-07 13:43:01249 media_description_to_set->codecs(), [](const Codec codec) {
Philipp Hanckeee5e7e52025-03-03 01:33:03250 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45251 return codec.name == kOpusCodecName &&
252 codec.GetParam(kCodecParamUseDtx, &value) &&
253 value == kParamValueTrue;
Philipp Hanckeee5e7e52025-03-03 01:33:03254 }) != media_description_to_set->codecs().end();
255 if (!created_opus_dtx && set_opus_dtx) {
256 RTC_LOG(LS_WARNING) << "SDP munging: Opus DTX enabled.";
257 return SdpMungingType::kAudioCodecsFmtpOpusDtx;
258 }
259
260 // Opus CBR is off by default. Should not be munged, can be controlled by
261 // the other side.
262 bool created_opus_cbr =
263 absl::c_find_if(last_created_media_description->codecs(),
Evan Shrubsole0b212e32025-04-07 13:43:01264 [](const Codec codec) {
Philipp Hanckeee5e7e52025-03-03 01:33:03265 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45266 return codec.name == kOpusCodecName &&
267 codec.GetParam(kCodecParamCbr, &value) &&
268 value == kParamValueTrue;
Philipp Hanckeee5e7e52025-03-03 01:33:03269 }) != last_created_media_description->codecs().end();
270 bool set_opus_cbr =
271 absl::c_find_if(
Evan Shrubsole0b212e32025-04-07 13:43:01272 media_description_to_set->codecs(), [](const Codec codec) {
Philipp Hanckeee5e7e52025-03-03 01:33:03273 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45274 return codec.name == kOpusCodecName &&
275 codec.GetParam(kCodecParamCbr, &value) &&
276 value == kParamValueTrue;
Philipp Hanckeee5e7e52025-03-03 01:33:03277 }) != media_description_to_set->codecs().end();
278 if (!created_opus_cbr && set_opus_cbr) {
279 RTC_LOG(LS_WARNING) << "SDP munging: Opus CBR enabled.";
280 return SdpMungingType::kAudioCodecsFmtpOpusCbr;
281 }
Philipp Hanckecfaba8f2025-01-15 01:16:39282 return SdpMungingType::kNoModification;
283}
284
Philipp Hancke19061392025-08-15 16:51:12285SdpMungingType DetermineRtcpModification(
286 const MediaContentDescription* last_created_media_description,
287 const MediaContentDescription* media_description_to_set) {
288 // rtcp-mux.
289 if (last_created_media_description->rtcp_mux() !=
290 media_description_to_set->rtcp_mux()) {
291 RTC_LOG(LS_WARNING) << "SDP munging: rtcp-mux modified.";
292 return SdpMungingType::kRtcpMux;
293 }
294
295 // rtcp-rsize.
296 if (last_created_media_description->rtcp_reduced_size() !=
297 media_description_to_set->rtcp_reduced_size()) {
298 RTC_LOG(LS_WARNING) << "SDP munging: rtcp-rsize modified.";
299 return last_created_media_description->type() == MediaType::AUDIO
300 ? SdpMungingType::kAudioCodecsRtcpReducedSize
301 : SdpMungingType::kVideoCodecsRtcpReducedSize;
302 }
303 return SdpMungingType::kNoModification;
304}
305
306SdpMungingType DetermineCodecModification(
307 const MediaContentDescription* last_created_media_description,
308 const MediaContentDescription* media_description_to_set) {
309 MediaType media_type = last_created_media_description->type();
310 // Validate codecs. We should have bailed out earlier if codecs were added
311 // or removed.
312 auto last_created_codecs = last_created_media_description->codecs();
313 auto codecs_to_set = media_description_to_set->codecs();
314 if (last_created_codecs.size() == codecs_to_set.size()) {
315 for (size_t i = 0; i < last_created_codecs.size(); i++) {
316 if (last_created_codecs[i] == codecs_to_set[i]) {
317 continue;
318 }
319 // Codec position swapped.
320 for (size_t j = i + 1; j < last_created_codecs.size(); j++) {
321 if (last_created_codecs[i] == codecs_to_set[j]) {
322 return media_type == MediaType::AUDIO
323 ? SdpMungingType::kAudioCodecsReordered
324 : SdpMungingType::kVideoCodecsReordered;
325 }
326 }
327 // Same codec but id changed.
328 if (last_created_codecs[i].name == codecs_to_set[i].name &&
329 last_created_codecs[i].id != codecs_to_set[i].id) {
330 return SdpMungingType::kPayloadTypes;
331 }
332 if (last_created_codecs[i].params != codecs_to_set[i].params) {
333 return media_type == MediaType::AUDIO
334 ? SdpMungingType::kAudioCodecsFmtp
335 : SdpMungingType::kVideoCodecsFmtp;
336 }
337 if (last_created_codecs[i].feedback_params !=
338 codecs_to_set[i].feedback_params) {
339 return media_type == MediaType::AUDIO
340 ? SdpMungingType::kAudioCodecsRtcpFb
341 : SdpMungingType::kVideoCodecsRtcpFb;
342 }
343 // Nonstandard a=packetization:raw added by munging
344 if (media_type == MediaType::VIDEO &&
345 last_created_codecs[i].packetization !=
346 codecs_to_set[i].packetization) {
347 return SdpMungingType::kVideoCodecsModifiedWithRawPacketization;
348 }
349 // At this point clockrate or channels changed. This should already be
350 // rejected later in the process so ignore for munging.
351 }
352 }
353 return SdpMungingType::kNoModification;
354}
355
356SdpMungingType DetermineVideoSdpModification(
Evan Shrubsole080cdac2025-03-20 09:34:48357 const MediaContentDescription* last_created_media_description,
358 const MediaContentDescription* media_description_to_set) {
Philipp Hanckecfaba8f2025-01-15 01:16:39359 RTC_DCHECK(last_created_media_description);
360 RTC_DCHECK(media_description_to_set);
361 // Removing codecs should be done via setCodecPreferences or negotiation, not
362 // munging.
363 if (last_created_media_description->codecs().size() >
364 media_description_to_set->codecs().size()) {
365 RTC_LOG(LS_WARNING) << "SDP munging: video codecs removed.";
366 return SdpMungingType::kVideoCodecsRemoved;
367 }
368 if (last_created_media_description->codecs().size() <
369 media_description_to_set->codecs().size()) {
Philipp Hancke82aab162025-04-23 21:56:40370 // Nonstandard a=packetization:raw
371 bool created_raw_packetization =
372 absl::c_find_if(last_created_media_description->codecs(),
373 [](const Codec codec) {
374 return codec.packetization.has_value();
375 }) != last_created_media_description->codecs().end();
376 bool set_raw_packetization =
377 absl::c_find_if(media_description_to_set->codecs(),
378 [](const Codec codec) {
379 return codec.packetization.has_value();
380 }) != media_description_to_set->codecs().end();
381 if (!created_raw_packetization && set_raw_packetization) {
382 RTC_LOG(LS_WARNING)
383 << "SDP munging: video codecs with raw packetization added.";
384 return SdpMungingType::kVideoCodecsAddedWithRawPacketization;
385 }
Philipp Hanckecfaba8f2025-01-15 01:16:39386 RTC_LOG(LS_WARNING) << "SDP munging: video codecs added.";
387 return SdpMungingType::kVideoCodecsAdded;
388 }
389
390 // Simulcast munging.
391 if (last_created_media_description->streams().size() == 1 &&
392 media_description_to_set->streams().size() == 1) {
393 bool created_sim =
394 absl::c_find_if(
395 last_created_media_description->streams()[0].ssrc_groups,
Evan Shrubsolee6a1f702025-04-15 14:55:42396 [](const SsrcGroup group) {
Evan Shrubsole945e5172025-04-08 14:11:45397 return group.semantics == kSimSsrcGroupSemantics;
Philipp Hanckecfaba8f2025-01-15 01:16:39398 }) !=
399 last_created_media_description->streams()[0].ssrc_groups.end();
400 bool set_sim =
Evan Shrubsole945e5172025-04-08 14:11:45401 absl::c_find_if(media_description_to_set->streams()[0].ssrc_groups,
Evan Shrubsolee6a1f702025-04-15 14:55:42402 [](const SsrcGroup group) {
Evan Shrubsole945e5172025-04-08 14:11:45403 return group.semantics == kSimSsrcGroupSemantics;
404 }) !=
405 media_description_to_set->streams()[0].ssrc_groups.end();
Philipp Hanckecfaba8f2025-01-15 01:16:39406 if (!created_sim && set_sim) {
407 RTC_LOG(LS_WARNING) << "SDP munging: legacy simulcast group created.";
408 return SdpMungingType::kVideoCodecsLegacySimulcast;
409 }
410 }
411
412 // sps-pps-idr-in-keyframe.
413 bool created_sps_pps_idr_in_keyframe =
414 absl::c_find_if(last_created_media_description->codecs(),
Evan Shrubsole0b212e32025-04-07 13:43:01415 [](const Codec codec) {
Philipp Hanckecfaba8f2025-01-15 01:16:39416 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45417 return codec.name == kH264CodecName &&
418 codec.GetParam(kH264FmtpSpsPpsIdrInKeyframe,
419 &value) &&
420 value == kParamValueTrue;
Philipp Hanckecfaba8f2025-01-15 01:16:39421 }) != last_created_media_description->codecs().end();
422 bool set_sps_pps_idr_in_keyframe =
423 absl::c_find_if(
Evan Shrubsole0b212e32025-04-07 13:43:01424 media_description_to_set->codecs(), [](const Codec codec) {
Philipp Hanckecfaba8f2025-01-15 01:16:39425 std::string value;
Evan Shrubsole945e5172025-04-08 14:11:45426 return codec.name == kH264CodecName &&
427 codec.GetParam(kH264FmtpSpsPpsIdrInKeyframe, &value) &&
428 value == kParamValueTrue;
Philipp Hanckecfaba8f2025-01-15 01:16:39429 }) != media_description_to_set->codecs().end();
430 if (!created_sps_pps_idr_in_keyframe && set_sps_pps_idr_in_keyframe) {
431 RTC_LOG(LS_WARNING) << "SDP munging: sps-pps-idr-in-keyframe enabled.";
432 return SdpMungingType::kVideoCodecsFmtpH264SpsPpsIdrInKeyframe;
433 }
Philipp Hanckecfaba8f2025-01-15 01:16:39434 return SdpMungingType::kNoModification;
435}
436
Philipp Hancke19b06442025-10-24 06:57:38437SdpMungingType DetermineDataSdpModification(
438 const MediaContentDescription* last_created_media_description,
439 const MediaContentDescription* media_description_to_set) {
440 RTC_DCHECK(last_created_media_description);
441 RTC_DCHECK(media_description_to_set);
442 auto last_created_sctp_description =
443 last_created_media_description->as_sctp();
444 auto sctp_description_to_set = media_description_to_set->as_sctp();
445 RTC_DCHECK(last_created_sctp_description);
446 RTC_DCHECK(sctp_description_to_set);
447
448 if (last_created_sctp_description->sctp_init() !=
449 sctp_description_to_set->sctp_init()) {
450 RTC_LOG(LS_ERROR) << "SDP munging: sctp-init does not match "
451 "last created description.";
452 return SdpMungingType::kDataChannelSctpInit;
453 }
454
Philipp Hanckebd3046c2025-10-24 06:26:37455 if (last_created_sctp_description->max_message_size() !=
456 sctp_description_to_set->max_message_size()) {
457 RTC_LOG(LS_WARNING) << "SDP munging: max-message-size does not match "
458 "last created description.";
459 return SdpMungingType::kDataChannelMaxMessageSize;
460 }
461
462 if (last_created_sctp_description->port() !=
463 sctp_description_to_set->port()) {
464 RTC_LOG(LS_WARNING) << "SDP munging: sctp-port does not match "
465 "last created description.";
466 return SdpMungingType::kDataChannelSctpPort;
467 }
468
Philipp Hancke19b06442025-10-24 06:57:38469 return SdpMungingType::kNoModification;
470}
471
Philipp Hancke19061392025-08-15 16:51:12472SdpMungingType DetermineContentsModification(
473 const ContentInfos& last_created_contents,
474 const ContentInfos& contents_to_set) {
Philipp Hanckecfaba8f2025-01-15 01:16:39475 SdpMungingType type;
Philipp Hanckecfaba8f2025-01-15 01:16:39476 if (last_created_contents.size() != contents_to_set.size()) {
Philipp Hancke19b06442025-10-24 06:57:38477 RTC_LOG(LS_ERROR) << "SDP munging: Number of m= sections does not match "
478 "last created description.";
Philipp Hanckecfaba8f2025-01-15 01:16:39479 return SdpMungingType::kNumberOfContents;
480 }
Philipp Hancke19061392025-08-15 16:51:12481
Philipp Hanckea9038852025-03-15 18:40:46482 for (size_t content_index = 0; content_index < last_created_contents.size();
483 content_index++) {
Philipp Hanckecfaba8f2025-01-15 01:16:39484 // TODO: crbug.com/40567530 - more checks are needed here.
Philipp Hanckea9038852025-03-15 18:40:46485 if (last_created_contents[content_index].mid() !=
486 contents_to_set[content_index].mid()) {
Philipp Hanckecfaba8f2025-01-15 01:16:39487 RTC_LOG(LS_WARNING) << "SDP munging: mid does not match "
488 "last created description.";
489 return SdpMungingType::kMid;
490 }
491
492 auto* last_created_media_description =
Philipp Hanckea9038852025-03-15 18:40:46493 last_created_contents[content_index].media_description();
494 auto* media_description_to_set =
495 contents_to_set[content_index].media_description();
Philipp Hanckecfaba8f2025-01-15 01:16:39496 if (!(last_created_media_description && media_description_to_set)) {
497 continue;
498 }
499 // Validate video and audio contents.
Evan Shrubsole94adaee2025-05-09 10:35:15500 MediaType media_type = last_created_media_description->type();
Philipp Hancke19b06442025-10-24 06:57:38501 if (media_type == MediaType::DATA) {
502 type = DetermineDataSdpModification(last_created_media_description,
503 media_description_to_set);
504 if (type != SdpMungingType::kNoModification) {
505 return type;
506 }
507 }
Philipp Hanckec6c628c2025-08-16 19:15:50508 bool is_rtp =
509 media_type == MediaType::AUDIO || media_type == MediaType::VIDEO;
510 if (!is_rtp) {
511 // The checks that follow only apply for RTP-based contents.
512 continue;
513 }
Evan Shrubsole94adaee2025-05-09 10:35:15514 if (media_type == MediaType::VIDEO) {
Philipp Hancke19061392025-08-15 16:51:12515 type = DetermineVideoSdpModification(last_created_media_description,
516 media_description_to_set);
Philipp Hanckecfaba8f2025-01-15 01:16:39517 if (type != SdpMungingType::kNoModification) {
518 return type;
519 }
Evan Shrubsole94adaee2025-05-09 10:35:15520 } else if (media_type == MediaType::AUDIO) {
Philipp Hancke19061392025-08-15 16:51:12521 type = DetermineAudioSdpModification(last_created_media_description,
522 media_description_to_set);
Philipp Hanckecfaba8f2025-01-15 01:16:39523 if (type != SdpMungingType::kNoModification) {
524 return type;
525 }
526 }
Philipp Hancke9ec6f982025-02-21 22:42:02527
Philipp Hancke19061392025-08-15 16:51:12528 type = DetermineRtcpModification(last_created_media_description,
529 media_description_to_set);
530 if (type != SdpMungingType::kNoModification) {
531 return type;
Philipp Hancke35891352025-06-23 20:06:06532 }
533
Philipp Hancke19061392025-08-15 16:51:12534 type = DetermineCodecModification(last_created_media_description,
535 media_description_to_set);
536 if (type != SdpMungingType::kNoModification) {
537 return type;
Philipp Hancke35891352025-06-23 20:06:06538 }
539
Philipp Hancke19061392025-08-15 16:51:12540 // Validate direction (sendrecv et al).
SamingLinb8404182025-04-18 10:48:38541 if (last_created_media_description->direction() !=
542 media_description_to_set->direction()) {
543 RTC_LOG(LS_WARNING) << "SDP munging: transceiver direction modified.";
544 return SdpMungingType::kDirection;
545 }
546
Philipp Hanckecfaba8f2025-01-15 01:16:39547 // Validate media streams.
548 if (last_created_media_description->streams().size() !=
549 media_description_to_set->streams().size()) {
550 RTC_LOG(LS_WARNING) << "SDP munging: streams size does not match last "
551 "created description.";
552 return SdpMungingType::kSsrcs;
553 }
554 for (size_t i = 0; i < last_created_media_description->streams().size();
555 i++) {
556 if (last_created_media_description->streams()[i].ssrcs !=
557 media_description_to_set->streams()[i].ssrcs) {
558 RTC_LOG(LS_WARNING)
559 << "SDP munging: SSRCs do not match last created description.";
560 return SdpMungingType::kSsrcs;
561 }
562 }
563
564 // Validate RTP header extensions.
565 auto last_created_extensions =
566 last_created_media_description->rtp_header_extensions();
567 auto extensions_to_set = media_description_to_set->rtp_header_extensions();
568 if (last_created_extensions.size() < extensions_to_set.size()) {
569 RTC_LOG(LS_WARNING) << "SDP munging: RTP header extension added.";
570 return SdpMungingType::kRtpHeaderExtensionAdded;
571 }
572 if (last_created_extensions.size() > extensions_to_set.size()) {
573 RTC_LOG(LS_WARNING) << "SDP munging: RTP header extension removed.";
574 return SdpMungingType::kRtpHeaderExtensionRemoved;
575 }
576 for (size_t i = 0; i < last_created_extensions.size(); i++) {
577 if (!(last_created_extensions[i].id == extensions_to_set[i].id)) {
578 RTC_LOG(LS_WARNING) << "SDP munging: header extension modified.";
579 return SdpMungingType::kRtpHeaderExtensionModified;
580 }
581 }
Philipp Hanckec955d2f2025-10-10 07:05:00582
583 // Validate b= (which does not have an effect in the local description).
584 if (last_created_media_description->bandwidth() !=
585 media_description_to_set->bandwidth()) {
586 RTC_LOG(LS_WARNING) << "SDP munging: modifying bandwidth in SLD does not "
587 "have an effect locally.";
588 return SdpMungingType::kBandwidth;
589 }
Philipp Hanckecfaba8f2025-01-15 01:16:39590 }
Philipp Hancke19061392025-08-15 16:51:12591 return SdpMungingType::kNoModification;
592}
593
594} // namespace
595
596// Determine if the SDP was modified between createOffer and
597// setLocalDescription.
598SdpMungingType DetermineSdpMungingType(
599 const SessionDescriptionInterface* sdesc,
600 const SessionDescriptionInterface* last_created_desc) {
601 if (!sdesc || !sdesc->description()) {
602 RTC_LOG(LS_WARNING) << "SDP munging: Failed to parse session description.";
603 // This is done to ensure the pointers are valid and should not happen at
604 // this point.
605 RTC_DCHECK_NOTREACHED();
606 return SdpMungingType::kCurrentDescriptionFailedToParse;
607 }
608
609 if (!last_created_desc || !last_created_desc->description()) {
610 RTC_LOG(LS_WARNING) << "SDP munging: SetLocalDescription called without "
611 "CreateOffer or CreateAnswer.";
612 if (sdesc->GetType() == SdpType::kOffer) {
613 return SdpMungingType::kWithoutCreateOffer;
614 } else { // answer or pranswer.
615 return SdpMungingType::kWithoutCreateAnswer;
616 }
617 }
618
619 // TODO: crbug.com/40567530 - we currently allow answer->pranswer
620 // so can not check sdesc->GetType() == last_created_desc->GetType().
621
622 SdpMungingType type;
623
624 // TODO: crbug.com/40567530 - change Chromium so that pointer comparison works
625 // at least for implicit local description.
626 if (sdesc->description() == last_created_desc->description()) {
627 return SdpMungingType::kNoModification;
628 }
629
630 // Validate contents.
631 type = DetermineContentsModification(
632 last_created_desc->description()->contents(),
633 sdesc->description()->contents());
634 if (type != SdpMungingType::kNoModification) {
635 return type;
636 }
Philipp Hanckecfaba8f2025-01-15 01:16:39637
638 // Validate transport descriptions.
639 type = DetermineTransportModification(
640 last_created_desc->description()->transport_infos(),
641 sdesc->description()->transport_infos());
642 if (type != SdpMungingType::kNoModification) {
643 return type;
644 }
645
Harald Alvestrand3d4bae02025-08-14 21:20:25646 // Validate number of candidates.
Philipp Hancke19061392025-08-15 16:51:12647 for (size_t content_index = 0;
648 content_index < last_created_desc->description()->contents().size();
Harald Alvestrand3d4bae02025-08-14 21:20:25649 content_index++) {
650 // All contents have a (possibly empty) candidate set.
651 // Check that this holds.
652 RTC_DCHECK(sdesc->candidates(content_index));
653 if (sdesc->candidates(content_index)->count() !=
654 last_created_desc->candidates(content_index)->count()) {
655 RTC_LOG(LS_WARNING)
656 << "SDP munging: media section " << content_index << " changed from "
657 << last_created_desc->candidates(content_index)->count() << " to "
658 << sdesc->candidates(content_index)->count() << " candidates";
659 return SdpMungingType::kIceCandidateCount;
660 }
661 }
662
Harald Alvestrandb2ee2402025-10-07 10:34:59663 // Validate Bundle fields
664 std::vector<const ContentGroup*> old_bundles =
665 last_created_desc->description()->GetGroupsByName(GROUP_TYPE_BUNDLE);
666 std::vector<const ContentGroup*> new_bundles =
667 sdesc->description()->GetGroupsByName(GROUP_TYPE_BUNDLE);
668 if (old_bundles.size() != new_bundles.size()) {
669 RTC_LOG(LS_WARNING) << "SDP munging: number of bundle groups changed from "
670 << old_bundles.size() << " to " << new_bundles.size();
671 return SdpMungingType::kBundle;
672 }
673 for (size_t i = 0; i < old_bundles.size(); ++i) {
674 if (*new_bundles[i] != *old_bundles[i]) {
675 RTC_LOG(LS_WARNING) << "SDP munging: Content of bundle group " << i
676 << " changed from " << old_bundles[i]->ToString()
677 << " to " << new_bundles[i]->ToString();
678 return SdpMungingType::kBundle;
679 }
680 }
681
Philipp Hanckecfaba8f2025-01-15 01:16:39682 // TODO: crbug.com/40567530 - this serializes the descriptions back to a SDP
683 // string which is very complex and we not should be be forced to rely on
684 // string equality.
685 std::string serialized_description;
686 std::string serialized_last_description;
687 if (sdesc->ToString(&serialized_description) &&
688 last_created_desc->ToString(&serialized_last_description) &&
689 serialized_description == serialized_last_description) {
690 return SdpMungingType::kNoModification;
691 }
692 return SdpMungingType::kUnknownModification;
693}
694
Tom Van Goethem84e40072025-04-28 22:14:11695// Similar to DetermineSdpMungingType, but only checks whether the ICE ufrag or
696// pwd of the SDP has been modified between createOffer and setLocalDescription.
697bool HasUfragSdpMunging(const SessionDescriptionInterface* sdesc,
698 const SessionDescriptionInterface* last_created_desc) {
699 if (!sdesc || !sdesc->description()) {
700 RTC_LOG(LS_WARNING) << "SDP munging: Failed to parse session description.";
701 return false;
702 }
703
704 if (!last_created_desc || !last_created_desc->description()) {
705 RTC_LOG(LS_WARNING) << "SDP munging: SetLocalDescription called without "
706 "CreateOffer or CreateAnswer.";
707 return false;
708 }
709 TransportInfos last_created_transport_infos =
710 last_created_desc->description()->transport_infos();
711 TransportInfos transport_infos_to_set =
712 sdesc->description()->transport_infos();
713 for (size_t i = 0; i < std::min(last_created_transport_infos.size(),
714 transport_infos_to_set.size());
715 i++) {
716 if (last_created_transport_infos[i].description.ice_ufrag !=
717 transport_infos_to_set[i].description.ice_ufrag) {
718 return true;
719 }
720 if (last_created_transport_infos[i].description.ice_pwd !=
721 transport_infos_to_set[i].description.ice_pwd) {
722 return true;
723 }
724 }
725 return false;
726}
727
Philipp Hancke84d07a32025-06-18 15:49:06728bool IsSdpMungingAllowed(SdpMungingType sdp_munging_type,
729 const FieldTrialsView& trials) {
Philipp Hancke5e8d17b2025-08-01 18:22:26730 switch (sdp_munging_type) {
731 case SdpMungingType::kNoModification:
732 return true;
733 case SdpMungingType::kNumberOfContents:
734 return false;
Philipp Hancke19b06442025-10-24 06:57:38735 case kDataChannelSctpInit:
736 return false;
Philipp Hancke5e8d17b2025-08-01 18:22:26737 default:
738 // Handled below.
739 break;
Philipp Hancke84d07a32025-06-18 15:49:06740 }
741 std::string type_as_string =
742 std::to_string(static_cast<int>(sdp_munging_type));
743
744 std::string trial;
745 // NoSdpMangleReject is for rollout, disallowing specific types of munging
746 // via Finch. It is a comma-separated list of SdpMungingTypes
747 if (trials.IsEnabled("WebRTC-NoSdpMangleReject")) {
748 trial = trials.Lookup("WebRTC-NoSdpMangleReject");
749 const std::vector<absl::string_view> rejected_types =
750 absl::StrSplit(trial, ',');
751 return absl::c_find(rejected_types, type_as_string) == rejected_types.end();
752 }
753 // NoSdpMangleAllowForTesting is for running E2E tests which should reject
754 // by default with a test-supplied list of exceptions as a comma-separated
755 // list.
756 if (!trials.IsEnabled("WebRTC-NoSdpMangleAllowForTesting")) {
757 return true;
758 }
759 trial = trials.Lookup("WebRTC-NoSdpMangleAllowForTesting");
760 const std::vector<absl::string_view> allowed_types =
761 absl::StrSplit(trial, ',');
762 return absl::c_find(allowed_types, type_as_string) != allowed_types.end();
763}
764
Philipp Hanckecfaba8f2025-01-15 01:16:39765} // namespace webrtc