| Index: webrtc/pc/mediasession.cc | 
| diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc | 
| index 000c289c186b9c2daa5f41b0730d96d55f2b2665..5e380883896728d14d285f5ad00070e273f28d89 100644 | 
| --- a/webrtc/pc/mediasession.cc | 
| +++ b/webrtc/pc/mediasession.cc | 
| @@ -22,6 +22,7 @@ | 
| #include "webrtc/base/checks.h" | 
| #include "webrtc/base/helpers.h" | 
| #include "webrtc/base/logging.h" | 
| +#include "webrtc/base/optional.h" | 
| #include "webrtc/base/stringutils.h" | 
| #include "webrtc/common_types.h" | 
| #include "webrtc/common_video/h264/profile_level_id.h" | 
| @@ -1430,6 +1431,9 @@ SessionDescription* MediaSessionDescriptionFactory::CreateOffer( | 
| SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( | 
| const SessionDescription* offer, const MediaSessionOptions& options, | 
| const SessionDescription* current_description) const { | 
| +  if (!offer) { | 
| +    return nullptr; | 
| +  } | 
| // The answer contains the intersection of the codecs in the offer with the | 
| // codecs we support. As indicated by XEP-0167, we retain the same payload ids | 
| // from the offer in the answer. | 
| @@ -1438,55 +1442,61 @@ SessionDescription* MediaSessionDescriptionFactory::CreateAnswer( | 
| StreamParamsVec current_streams; | 
| GetCurrentStreamParams(current_description, ¤t_streams); | 
|  | 
| -  if (offer) { | 
| -    ContentInfos::const_iterator it = offer->contents().begin(); | 
| -    for (; it != offer->contents().end(); ++it) { | 
| -      if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) { | 
| -        if (!AddAudioContentForAnswer(offer, options, current_description, | 
| -                                  ¤t_streams, answer.get())) { | 
| -          return NULL; | 
| -        } | 
| -      } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) { | 
| -        if (!AddVideoContentForAnswer(offer, options, current_description, | 
| -                                      ¤t_streams, answer.get())) { | 
| -          return NULL; | 
| -        } | 
| -      } else { | 
| -        RTC_DCHECK(IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)); | 
| -        if (!AddDataContentForAnswer(offer, options, current_description, | 
| -                                     ¤t_streams, answer.get())) { | 
| -          return NULL; | 
| -        } | 
| -      } | 
| -    } | 
| -  } | 
| - | 
| // If the offer supports BUNDLE, and we want to use it too, create a BUNDLE | 
| // group in the answer with the appropriate content names. | 
| -  if (offer->HasGroup(GROUP_TYPE_BUNDLE) && options.bundle_enabled) { | 
| -    const ContentGroup* offer_bundle = offer->GetGroupByName(GROUP_TYPE_BUNDLE); | 
| -    ContentGroup answer_bundle(GROUP_TYPE_BUNDLE); | 
| -    for (ContentInfos::const_iterator content = answer->contents().begin(); | 
| -       content != answer->contents().end(); ++content) { | 
| -      if (!content->rejected && offer_bundle->HasContentName(content->name)) { | 
| -        answer_bundle.AddContentName(content->name); | 
| +  const ContentGroup* offer_bundle = offer->GetGroupByName(GROUP_TYPE_BUNDLE); | 
| +  ContentGroup answer_bundle(GROUP_TYPE_BUNDLE); | 
| +  // Transport info shared by the bundle group. | 
| +  std::unique_ptr<TransportInfo> bundle_transport; | 
| + | 
| +  ContentInfos::const_iterator it = offer->contents().begin(); | 
| +  for (; it != offer->contents().end(); ++it) { | 
| +    if (IsMediaContentOfType(&*it, MEDIA_TYPE_AUDIO)) { | 
| +      if (!AddAudioContentForAnswer(offer, options, current_description, | 
| +                                    bundle_transport.get(), ¤t_streams, | 
| +                                    answer.get())) { | 
| +        return NULL; | 
| } | 
| -    } | 
| -    if (answer_bundle.FirstContentName()) { | 
| -      answer->AddGroup(answer_bundle); | 
| - | 
| -      // Share the same ICE credentials and crypto params across all contents, | 
| -      // as BUNDLE requires. | 
| -      if (!UpdateTransportInfoForBundle(answer_bundle, answer.get())) { | 
| -        LOG(LS_ERROR) << "CreateAnswer failed to UpdateTransportInfoForBundle."; | 
| +    } else if (IsMediaContentOfType(&*it, MEDIA_TYPE_VIDEO)) { | 
| +      if (!AddVideoContentForAnswer(offer, options, current_description, | 
| +                                    bundle_transport.get(), ¤t_streams, | 
| +                                    answer.get())) { | 
| return NULL; | 
| } | 
| - | 
| -      if (!UpdateCryptoParamsForBundle(answer_bundle, answer.get())) { | 
| -        LOG(LS_ERROR) << "CreateAnswer failed to UpdateCryptoParamsForBundle."; | 
| +    } else { | 
| +      RTC_DCHECK(IsMediaContentOfType(&*it, MEDIA_TYPE_DATA)); | 
| +      if (!AddDataContentForAnswer(offer, options, current_description, | 
| +                                   bundle_transport.get(), ¤t_streams, | 
| +                                   answer.get())) { | 
| return NULL; | 
| } | 
| } | 
| +    // See if we can add the newly generated m= section to the BUNDLE group in | 
| +    // the answer. | 
| +    ContentInfo& added = answer->contents().back(); | 
| +    if (!added.rejected && options.bundle_enabled && offer_bundle && | 
| +        offer_bundle->HasContentName(added.name)) { | 
| +      answer_bundle.AddContentName(added.name); | 
| +      bundle_transport.reset( | 
| +          new TransportInfo(*answer->GetTransportInfoByName(added.name))); | 
| +    } | 
| +  } | 
| + | 
| +  // Only put BUNDLE group in answer if nonempty. | 
| +  if (answer_bundle.FirstContentName()) { | 
| +    answer->AddGroup(answer_bundle); | 
| + | 
| +    // Share the same ICE credentials and crypto params across all contents, | 
| +    // as BUNDLE requires. | 
| +    if (!UpdateTransportInfoForBundle(answer_bundle, answer.get())) { | 
| +      LOG(LS_ERROR) << "CreateAnswer failed to UpdateTransportInfoForBundle."; | 
| +      return NULL; | 
| +    } | 
| + | 
| +    if (!UpdateCryptoParamsForBundle(answer_bundle, answer.get())) { | 
| +      LOG(LS_ERROR) << "CreateAnswer failed to UpdateCryptoParamsForBundle."; | 
| +      return NULL; | 
| +    } | 
| } | 
|  | 
| return answer.release(); | 
| @@ -1634,16 +1644,17 @@ TransportDescription* MediaSessionDescriptionFactory::CreateTransportAnswer( | 
| const std::string& content_name, | 
| const SessionDescription* offer_desc, | 
| const TransportOptions& transport_options, | 
| -    const SessionDescription* current_desc) const { | 
| +    const SessionDescription* current_desc, | 
| +    bool require_transport_attributes) const { | 
| if (!transport_desc_factory_) | 
| return NULL; | 
| const TransportDescription* offer_tdesc = | 
| GetTransportDescription(content_name, offer_desc); | 
| const TransportDescription* current_tdesc = | 
| GetTransportDescription(content_name, current_desc); | 
| -  return | 
| -      transport_desc_factory_->CreateAnswer(offer_tdesc, transport_options, | 
| -                                            current_tdesc); | 
| +  return transport_desc_factory_->CreateAnswer(offer_tdesc, transport_options, | 
| +                                               require_transport_attributes, | 
| +                                               current_tdesc); | 
| } | 
|  | 
| bool MediaSessionDescriptionFactory::AddTransportAnswer( | 
| @@ -1838,15 +1849,17 @@ bool MediaSessionDescriptionFactory::AddAudioContentForAnswer( | 
| const SessionDescription* offer, | 
| const MediaSessionOptions& options, | 
| const SessionDescription* current_description, | 
| +    const TransportInfo* bundle_transport, | 
| StreamParamsVec* current_streams, | 
| SessionDescription* answer) const { | 
| const ContentInfo* audio_content = GetFirstAudioContent(offer); | 
| const AudioContentDescription* offer_audio = | 
| static_cast<const AudioContentDescription*>(audio_content->description); | 
|  | 
| -  std::unique_ptr<TransportDescription> audio_transport(CreateTransportAnswer( | 
| -      audio_content->name, offer, | 
| -      GetTransportOptions(options, audio_content->name), current_description)); | 
| +  std::unique_ptr<TransportDescription> audio_transport( | 
| +      CreateTransportAnswer(audio_content->name, offer, | 
| +                            GetTransportOptions(options, audio_content->name), | 
| +                            current_description, bundle_transport != nullptr)); | 
| if (!audio_transport) { | 
| return false; | 
| } | 
| @@ -1885,10 +1898,11 @@ bool MediaSessionDescriptionFactory::AddAudioContentForAnswer( | 
| return false;  // Fails the session setup. | 
| } | 
|  | 
| +  bool secure = bundle_transport ? bundle_transport->description.secure() | 
| +                                 : audio_transport->secure(); | 
| bool rejected = !options.has_audio() || audio_content->rejected || | 
| -      !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO, | 
| -                                audio_answer->protocol(), | 
| -                                audio_transport->secure()); | 
| +                  !IsMediaProtocolSupported(MEDIA_TYPE_AUDIO, | 
| +                                            audio_answer->protocol(), secure); | 
| if (!rejected) { | 
| AddTransportAnswer(audio_content->name, *(audio_transport.get()), answer); | 
| } else { | 
| @@ -1906,12 +1920,14 @@ bool MediaSessionDescriptionFactory::AddVideoContentForAnswer( | 
| const SessionDescription* offer, | 
| const MediaSessionOptions& options, | 
| const SessionDescription* current_description, | 
| +    const TransportInfo* bundle_transport, | 
| StreamParamsVec* current_streams, | 
| SessionDescription* answer) const { | 
| const ContentInfo* video_content = GetFirstVideoContent(offer); | 
| -  std::unique_ptr<TransportDescription> video_transport(CreateTransportAnswer( | 
| -      video_content->name, offer, | 
| -      GetTransportOptions(options, video_content->name), current_description)); | 
| +  std::unique_ptr<TransportDescription> video_transport( | 
| +      CreateTransportAnswer(video_content->name, offer, | 
| +                            GetTransportOptions(options, video_content->name), | 
| +                            current_description, bundle_transport != nullptr)); | 
| if (!video_transport) { | 
| return false; | 
| } | 
| @@ -1937,10 +1953,11 @@ bool MediaSessionDescriptionFactory::AddVideoContentForAnswer( | 
| video_answer.get())) { | 
| return false; | 
| } | 
| +  bool secure = bundle_transport ? bundle_transport->description.secure() | 
| +                                 : video_transport->secure(); | 
| bool rejected = !options.has_video() || video_content->rejected || | 
| -      !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO, | 
| -                                video_answer->protocol(), | 
| -                                video_transport->secure()); | 
| +                  !IsMediaProtocolSupported(MEDIA_TYPE_VIDEO, | 
| +                                            video_answer->protocol(), secure); | 
| if (!rejected) { | 
| if (!AddTransportAnswer(video_content->name, *(video_transport.get()), | 
| answer)) { | 
| @@ -1961,12 +1978,14 @@ bool MediaSessionDescriptionFactory::AddDataContentForAnswer( | 
| const SessionDescription* offer, | 
| const MediaSessionOptions& options, | 
| const SessionDescription* current_description, | 
| +    const TransportInfo* bundle_transport, | 
| StreamParamsVec* current_streams, | 
| SessionDescription* answer) const { | 
| const ContentInfo* data_content = GetFirstDataContent(offer); | 
| -  std::unique_ptr<TransportDescription> data_transport(CreateTransportAnswer( | 
| -      data_content->name, offer, | 
| -      GetTransportOptions(options, data_content->name), current_description)); | 
| +  std::unique_ptr<TransportDescription> data_transport( | 
| +      CreateTransportAnswer(data_content->name, offer, | 
| +                            GetTransportOptions(options, data_content->name), | 
| +                            current_description, bundle_transport != nullptr)); | 
| if (!data_transport) { | 
| return false; | 
| } | 
| @@ -2002,10 +2021,12 @@ bool MediaSessionDescriptionFactory::AddDataContentForAnswer( | 
| bool offer_uses_sctpmap = offer_data_description->use_sctpmap(); | 
| data_answer->set_use_sctpmap(offer_uses_sctpmap); | 
|  | 
| +  bool secure = bundle_transport ? bundle_transport->description.secure() | 
| +                                 : data_transport->secure(); | 
| + | 
| bool rejected = !options.has_data() || data_content->rejected || | 
| -      !IsMediaProtocolSupported(MEDIA_TYPE_DATA, | 
| -                                data_answer->protocol(), | 
| -                                data_transport->secure()); | 
| +                  !IsMediaProtocolSupported(MEDIA_TYPE_DATA, | 
| +                                            data_answer->protocol(), secure); | 
| if (!rejected) { | 
| data_answer->set_bandwidth(options.data_bandwidth); | 
| if (!AddTransportAnswer(data_content->name, *(data_transport.get()), | 
|  |