Use the same draft version in SDP data channel answers as used in the offer.
This change adds a flag, use_sctpmap, to DataContentDescription. The deserialization code sets the flag based on the format of the m= line.
There were already unit tests using SDP in the new format, so I just updated them to check use_sctpmap was set as expected.
The change to mediasession copies use_sctpmap from the offered DataContentDescription to the answer.
I haven't figured out how to test this change yet, but wanted to get feedback before continuing.
BUG=chromium:686212
Review-Url: https://codereview.webrtc.org/2690943011
Cr-Commit-Position: refs/heads/master@{#16686}
diff --git a/webrtc/pc/mediasession.cc b/webrtc/pc/mediasession.cc
index c596951..9a4dac9 100644
--- a/webrtc/pc/mediasession.cc
+++ b/webrtc/pc/mediasession.cc
@@ -1996,6 +1996,12 @@
return false; // Fails the session setup.
}
+ // Respond with sctpmap if the offer uses sctpmap.
+ const DataContentDescription* offer_data_description =
+ static_cast<const DataContentDescription*>(data_content->description);
+ bool offer_uses_sctpmap = offer_data_description->use_sctpmap();
+ data_answer->set_use_sctpmap(offer_uses_sctpmap);
+
bool rejected = !options.has_data() || data_content->rejected ||
!IsMediaProtocolSupported(MEDIA_TYPE_DATA,
data_answer->protocol(),
diff --git a/webrtc/pc/mediasession.h b/webrtc/pc/mediasession.h
index 06815ec..68f4c37 100644
--- a/webrtc/pc/mediasession.h
+++ b/webrtc/pc/mediasession.h
@@ -401,10 +401,18 @@
class DataContentDescription : public MediaContentDescriptionImpl<DataCodec> {
public:
+ DataContentDescription() {}
+
virtual ContentDescription* Copy() const {
return new DataContentDescription(*this);
}
virtual MediaType type() const { return MEDIA_TYPE_DATA; }
+
+ bool use_sctpmap() const { return use_sctpmap_; }
+ void set_use_sctpmap(bool enable) { use_sctpmap_ = enable; }
+
+ private:
+ bool use_sctpmap_ = true;
};
// Creates media session descriptions according to the supplied codecs and
@@ -456,9 +464,9 @@
const MediaSessionOptions& options,
const SessionDescription* current_description) const;
SessionDescription* CreateAnswer(
- const SessionDescription* offer,
- const MediaSessionOptions& options,
- const SessionDescription* current_description) const;
+ const SessionDescription* offer,
+ const MediaSessionOptions& options,
+ const SessionDescription* current_description) const;
private:
const AudioCodecs& GetAudioCodecsForOffer(
diff --git a/webrtc/pc/mediasession_unittest.cc b/webrtc/pc/mediasession_unittest.cc
index 6676240..6837e50 100644
--- a/webrtc/pc/mediasession_unittest.cc
+++ b/webrtc/pc/mediasession_unittest.cc
@@ -912,27 +912,27 @@
std::unique_ptr<SessionDescription> answer(
f2_.CreateAnswer(offer.get(), opts, NULL));
const ContentInfo* ac = answer->GetContentByName("audio");
- const ContentInfo* vc = answer->GetContentByName("data");
+ const ContentInfo* dc = answer->GetContentByName("data");
ASSERT_TRUE(ac != NULL);
- ASSERT_TRUE(vc != NULL);
+ ASSERT_TRUE(dc != NULL);
EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
- EXPECT_EQ(std::string(NS_JINGLE_RTP), vc->type);
+ EXPECT_EQ(std::string(NS_JINGLE_RTP), dc->type);
const AudioContentDescription* acd =
static_cast<const AudioContentDescription*>(ac->description);
- const DataContentDescription* vcd =
- static_cast<const DataContentDescription*>(vc->description);
+ const DataContentDescription* dcd =
+ static_cast<const DataContentDescription*>(dc->description);
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw
EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc
EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux
ASSERT_CRYPTO(acd, 1U, CS_AES_CM_128_HMAC_SHA1_32);
- EXPECT_EQ(MEDIA_TYPE_DATA, vcd->type());
- EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), vcd->codecs());
- EXPECT_NE(0U, vcd->first_ssrc()); // a random nonzero ssrc
- EXPECT_TRUE(vcd->rtcp_mux()); // negotiated rtcp-mux
- ASSERT_CRYPTO(vcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
- EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol());
+ EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
+ EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), dcd->codecs());
+ EXPECT_NE(0U, dcd->first_ssrc()); // a random nonzero ssrc
+ EXPECT_TRUE(dcd->rtcp_mux()); // negotiated rtcp-mux
+ ASSERT_CRYPTO(dcd, 1U, CS_AES_CM_128_HMAC_SHA1_80);
+ EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol());
}
TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerGcm) {
@@ -946,27 +946,70 @@
std::unique_ptr<SessionDescription> answer(
f2_.CreateAnswer(offer.get(), opts, NULL));
const ContentInfo* ac = answer->GetContentByName("audio");
- const ContentInfo* vc = answer->GetContentByName("data");
+ const ContentInfo* dc = answer->GetContentByName("data");
ASSERT_TRUE(ac != NULL);
- ASSERT_TRUE(vc != NULL);
+ ASSERT_TRUE(dc != NULL);
EXPECT_EQ(std::string(NS_JINGLE_RTP), ac->type);
- EXPECT_EQ(std::string(NS_JINGLE_RTP), vc->type);
+ EXPECT_EQ(std::string(NS_JINGLE_RTP), dc->type);
const AudioContentDescription* acd =
static_cast<const AudioContentDescription*>(ac->description);
- const DataContentDescription* vcd =
- static_cast<const DataContentDescription*>(vc->description);
+ const DataContentDescription* dcd =
+ static_cast<const DataContentDescription*>(dc->description);
EXPECT_EQ(MEDIA_TYPE_AUDIO, acd->type());
EXPECT_EQ(MAKE_VECTOR(kAudioCodecsAnswer), acd->codecs());
EXPECT_EQ(kAutoBandwidth, acd->bandwidth()); // negotiated auto bw
EXPECT_NE(0U, acd->first_ssrc()); // a random nonzero ssrc
EXPECT_TRUE(acd->rtcp_mux()); // negotiated rtcp-mux
ASSERT_CRYPTO(acd, 1U, CS_AEAD_AES_256_GCM);
- EXPECT_EQ(MEDIA_TYPE_DATA, vcd->type());
- EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), vcd->codecs());
- EXPECT_NE(0U, vcd->first_ssrc()); // a random nonzero ssrc
- EXPECT_TRUE(vcd->rtcp_mux()); // negotiated rtcp-mux
- ASSERT_CRYPTO(vcd, 1U, CS_AEAD_AES_256_GCM);
- EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), vcd->protocol());
+ EXPECT_EQ(MEDIA_TYPE_DATA, dcd->type());
+ EXPECT_EQ(MAKE_VECTOR(kDataCodecsAnswer), dcd->codecs());
+ EXPECT_NE(0U, dcd->first_ssrc()); // a random nonzero ssrc
+ EXPECT_TRUE(dcd->rtcp_mux()); // negotiated rtcp-mux
+ ASSERT_CRYPTO(dcd, 1U, CS_AEAD_AES_256_GCM);
+ EXPECT_EQ(std::string(cricket::kMediaProtocolSavpf), dcd->protocol());
+}
+
+// The use_sctpmap flag should be set in a DataContentDescription by default.
+// The answer's use_sctpmap flag should match the offer's.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerUsesSctpmap) {
+ MediaSessionOptions opts;
+ opts.data_channel_type = cricket::DCT_SCTP;
+ std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ ContentInfo* dc_offer = offer->GetContentByName("data");
+ ASSERT_TRUE(dc_offer != NULL);
+ DataContentDescription* dcd_offer =
+ static_cast<DataContentDescription*>(dc_offer->description);
+ EXPECT_TRUE(dcd_offer->use_sctpmap());
+
+ std::unique_ptr<SessionDescription> answer(
+ f2_.CreateAnswer(offer.get(), opts, NULL));
+ const ContentInfo* dc_answer = answer->GetContentByName("data");
+ ASSERT_TRUE(dc_answer != NULL);
+ const DataContentDescription* dcd_answer =
+ static_cast<const DataContentDescription*>(dc_answer->description);
+ EXPECT_TRUE(dcd_answer->use_sctpmap());
+}
+
+// The answer's use_sctpmap flag should match the offer's.
+TEST_F(MediaSessionDescriptionFactoryTest, TestCreateDataAnswerWithoutSctpmap) {
+ MediaSessionOptions opts;
+ opts.data_channel_type = cricket::DCT_SCTP;
+ std::unique_ptr<SessionDescription> offer(f1_.CreateOffer(opts, NULL));
+ ASSERT_TRUE(offer.get() != NULL);
+ ContentInfo* dc_offer = offer->GetContentByName("data");
+ ASSERT_TRUE(dc_offer != NULL);
+ DataContentDescription* dcd_offer =
+ static_cast<DataContentDescription*>(dc_offer->description);
+ dcd_offer->set_use_sctpmap(false);
+
+ std::unique_ptr<SessionDescription> answer(
+ f2_.CreateAnswer(offer.get(), opts, NULL));
+ const ContentInfo* dc_answer = answer->GetContentByName("data");
+ ASSERT_TRUE(dc_answer != NULL);
+ const DataContentDescription* dcd_answer =
+ static_cast<const DataContentDescription*>(dc_answer->description);
+ EXPECT_FALSE(dcd_answer->use_sctpmap());
}
// Verifies that the order of the media contents in the offer is preserved in
diff --git a/webrtc/pc/webrtcsdp.cc b/webrtc/pc/webrtcsdp.cc
index ed49c34..b749d6f 100644
--- a/webrtc/pc/webrtcsdp.cc
+++ b/webrtc/pc/webrtcsdp.cc
@@ -234,7 +234,9 @@
const std::vector<Candidate>& candidates,
bool unified_plan_sdp,
std::string* message);
-static void BuildSctpContentAttributes(std::string* message, int sctp_port);
+static void BuildSctpContentAttributes(std::string* message,
+ int sctp_port,
+ bool use_sctpmap);
static void BuildRtpContentAttributes(const MediaContentDescription* media_desc,
const MediaType media_type,
bool unified_plan_sdp,
@@ -1275,16 +1277,21 @@
if (IsDtlsSctp(media_desc->protocol())) {
fmt.append(" ");
- for (std::vector<cricket::DataCodec>::const_iterator it =
- data_desc->codecs().begin();
- it != data_desc->codecs().end(); ++it) {
- if (cricket::CodecNamesEq(it->name, cricket::kGoogleSctpDataCodecName)
- && it->GetParam(cricket::kCodecParamPort, &sctp_port)) {
- break;
+ if (data_desc->use_sctpmap()) {
+ for (std::vector<cricket::DataCodec>::const_iterator it =
+ data_desc->codecs().begin();
+ it != data_desc->codecs().end(); ++it) {
+ if (cricket::CodecNamesEq(it->name,
+ cricket::kGoogleSctpDataCodecName) &&
+ it->GetParam(cricket::kCodecParamPort, &sctp_port)) {
+ break;
+ }
}
- }
- fmt.append(rtc::ToString<int>(sctp_port));
+ fmt.append(rtc::ToString<int>(sctp_port));
+ } else {
+ fmt.append(kDefaultSctpmapProtocol);
+ }
} else {
for (std::vector<cricket::DataCodec>::const_iterator it =
data_desc->codecs().begin();
@@ -1407,23 +1414,34 @@
AddLine(os.str(), message);
if (IsDtlsSctp(media_desc->protocol())) {
- BuildSctpContentAttributes(message, sctp_port);
+ const DataContentDescription* data_desc =
+ static_cast<const DataContentDescription*>(media_desc);
+ bool use_sctpmap = data_desc->use_sctpmap();
+ BuildSctpContentAttributes(message, sctp_port, use_sctpmap);
} else if (IsRtp(media_desc->protocol())) {
BuildRtpContentAttributes(media_desc, media_type, unified_plan_sdp,
message);
}
}
-void BuildSctpContentAttributes(std::string* message, int sctp_port) {
- // draft-ietf-mmusic-sctp-sdp-04
- // a=sctpmap:sctpmap-number protocol [streams]
- // TODO(lally): switch this over to mmusic-sctp-sdp-12 (or later), with
- // 'a=sctp-port:'
+void BuildSctpContentAttributes(std::string* message,
+ int sctp_port,
+ bool use_sctpmap) {
std::ostringstream os;
- InitAttrLine(kAttributeSctpmap, &os);
- os << kSdpDelimiterColon << sctp_port << kSdpDelimiterSpace
- << kDefaultSctpmapProtocol << kSdpDelimiterSpace
- << cricket::kMaxSctpStreams;
+ if (use_sctpmap) {
+ // draft-ietf-mmusic-sctp-sdp-04
+ // a=sctpmap:sctpmap-number protocol [streams]
+ InitAttrLine(kAttributeSctpmap, &os);
+ os << kSdpDelimiterColon << sctp_port << kSdpDelimiterSpace
+ << kDefaultSctpmapProtocol << kSdpDelimiterSpace
+ << cricket::kMaxSctpStreams;
+ } else {
+ // draft-ietf-mmusic-sctp-sdp-23
+ // a=sctp-port:<port>
+ InitAttrLine(kAttributeSctpPort, &os);
+ os << kSdpDelimiterColon << sctp_port;
+ // TODO(zstein): emit max-message-size here
+ }
AddLine(os.str(), message);
}
@@ -2291,6 +2309,7 @@
std::vector<std::string> fields;
rtc::split(line.substr(kLinePrefixLength),
kSdpDelimiterSpace, &fields);
+
const size_t expected_min_fields = 4;
if (fields.size() < expected_min_fields) {
return ParseFailedExpectMinFieldNum(line, expected_min_fields, error);
@@ -2351,10 +2370,15 @@
candidates, error);
content.reset(data_desc);
- int p;
- if (data_desc && IsDtlsSctp(protocol) && rtc::FromString(fields[3], &p)) {
- if (!AddSctpDataCodec(data_desc, p))
- return false;
+ if (data_desc && IsDtlsSctp(protocol)) {
+ int p;
+ if (rtc::FromString(fields[3], &p)) {
+ if (!AddSctpDataCodec(data_desc, p)) {
+ return false;
+ }
+ } else if (fields[3] == kDefaultSctpmapProtocol) {
+ data_desc->set_use_sctpmap(false);
+ }
}
} else {
LOG(LS_WARNING) << "Unsupported media type: " << line;
diff --git a/webrtc/pc/webrtcsdp_unittest.cc b/webrtc/pc/webrtcsdp_unittest.cc
index 1a6a212..f5cac89 100644
--- a/webrtc/pc/webrtcsdp_unittest.cc
+++ b/webrtc/pc/webrtcsdp_unittest.cc
@@ -65,6 +65,9 @@
typedef std::vector<Candidate> Candidates;
static const uint32_t kDefaultSctpPort = 5000;
+static const char kDefaultSctpPortStr[] = "5000";
+static const uint16_t kUnusualSctpPort = 9556;
+static const char kUnusualSctpPortStr[] = "9556";
static const char kSessionTime[] = "t=0 0\r\n";
static const uint32_t kCandidatePriority = 2130706432U; // pref = 1.0
static const char kAttributeIceUfragVoice[] = "a=ice-ufrag:ufrag_voice\r\n";
@@ -1210,6 +1213,11 @@
}
}
+ void CompareDataContentDescription(const DataContentDescription* dcd1,
+ const DataContentDescription* dcd2) {
+ EXPECT_EQ(dcd1->use_sctpmap(), dcd2->use_sctpmap());
+ CompareMediaContentDescription<DataContentDescription>(dcd1, dcd2);
+ }
void CompareSessionDescription(const SessionDescription& desc1,
const SessionDescription& desc2) {
@@ -1251,7 +1259,7 @@
static_cast<const DataContentDescription*>(c1.description);
const DataContentDescription* dcd2 =
static_cast<const DataContentDescription*>(c2.description);
- CompareMediaContentDescription<DataContentDescription>(dcd1, dcd2);
+ CompareDataContentDescription(dcd1, dcd2);
}
}
@@ -1486,9 +1494,10 @@
return true;
}
- void AddSctpDataChannel() {
+ void AddSctpDataChannel(bool use_sctpmap) {
std::unique_ptr<DataContentDescription> data(new DataContentDescription());
data_desc_ = data.get();
+ data_desc_->set_use_sctpmap(use_sctpmap);
data_desc_->set_protocol(cricket::kMediaProtocolDtlsSctp);
DataCodec codec(cricket::kGoogleSctpDataCodecPlType,
cricket::kGoogleSctpDataCodecName);
@@ -2058,7 +2067,8 @@
}
TEST_F(WebRtcSdpTest, SerializeSessionDescriptionWithSctpDataChannel) {
- AddSctpDataChannel();
+ bool use_sctpmap = true;
+ AddSctpDataChannel(use_sctpmap);
JsepSessionDescription jsep_desc(kDummyString);
ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
@@ -2070,7 +2080,8 @@
}
TEST_F(WebRtcSdpTest, SerializeWithSctpDataChannelAndNewPort) {
- AddSctpDataChannel();
+ bool use_sctpmap = true;
+ AddSctpDataChannel(use_sctpmap);
JsepSessionDescription jsep_desc(kDummyString);
ASSERT_TRUE(jsep_desc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
@@ -2570,7 +2581,8 @@
}
TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannels) {
- AddSctpDataChannel();
+ bool use_sctpmap = true;
+ AddSctpDataChannel(use_sctpmap);
JsepSessionDescription jdesc(kDummyString);
ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
@@ -2596,7 +2608,8 @@
}
TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelsWithSctpPort) {
- AddSctpDataChannel();
+ bool use_sctpmap = false;
+ AddSctpDataChannel(use_sctpmap);
JsepSessionDescription jdesc(kDummyString);
ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
@@ -2622,7 +2635,8 @@
}
TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelsWithSctpColonPort) {
- AddSctpDataChannel();
+ bool use_sctpmap = false;
+ AddSctpDataChannel(use_sctpmap);
JsepSessionDescription jdesc(kDummyString);
ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
@@ -2650,7 +2664,8 @@
// Test to check the behaviour if sctp-port is specified
// on the m= line and in a=sctp-port.
TEST_F(WebRtcSdpTest, DeserializeSdpWithMultiSctpPort) {
- AddSctpDataChannel();
+ bool use_sctpmap = true;
+ AddSctpDataChannel(use_sctpmap);
JsepSessionDescription jdesc(kDummyString);
ASSERT_TRUE(jdesc.Initialize(desc_.Copy(), kSessionId, kSessionVersion));
@@ -2676,20 +2691,10 @@
// No crash is a pass.
}
-TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelAndNewPort) {
- AddSctpDataChannel();
- const uint16_t kUnusualSctpPort = 9556;
- char default_portstr[16];
- char unusual_portstr[16];
- rtc::sprintfn(default_portstr, sizeof(default_portstr), "%d",
- kDefaultSctpPort);
- rtc::sprintfn(unusual_portstr, sizeof(unusual_portstr), "%d",
- kUnusualSctpPort);
-
- // First setup the expected JsepSessionDescription.
- JsepSessionDescription jdesc(kDummyString);
+void MutateJsepSctpPort(JsepSessionDescription& jdesc,
+ const SessionDescription& desc) {
// take our pre-built session description and change the SCTP port.
- cricket::SessionDescription* mutant = desc_.Copy();
+ cricket::SessionDescription* mutant = desc.Copy();
DataContentDescription* dcdesc = static_cast<DataContentDescription*>(
mutant->GetContentDescriptionByName(kDataContentName));
std::vector<cricket::DataCodec> codecs(dcdesc->codecs());
@@ -2701,27 +2706,46 @@
// note: mutant's owned by jdesc now.
ASSERT_TRUE(jdesc.Initialize(mutant, kSessionId, kSessionVersion));
mutant = NULL;
+}
+
+TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelAndUnusualPort) {
+ bool use_sctpmap = true;
+ AddSctpDataChannel(use_sctpmap);
+
+ // First setup the expected JsepSessionDescription.
+ JsepSessionDescription jdesc(kDummyString);
+ MutateJsepSctpPort(jdesc, desc_);
// Then get the deserialized JsepSessionDescription.
std::string sdp_with_data = kSdpString;
sdp_with_data.append(kSdpSctpDataChannelString);
- rtc::replace_substrs(default_portstr, strlen(default_portstr),
- unusual_portstr, strlen(unusual_portstr),
- &sdp_with_data);
+ rtc::replace_substrs(kDefaultSctpPortStr, strlen(kDefaultSctpPortStr),
+ kUnusualSctpPortStr, strlen(kUnusualSctpPortStr),
+ &sdp_with_data);
JsepSessionDescription jdesc_output(kDummyString);
EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output));
EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output));
+}
+
+TEST_F(WebRtcSdpTest,
+ DeserializeSdpWithSctpDataChannelAndUnusualPortInAttribute) {
+ bool use_sctpmap = false;
+ AddSctpDataChannel(use_sctpmap);
+
+ JsepSessionDescription jdesc(kDummyString);
+ MutateJsepSctpPort(jdesc, desc_);
// We need to test the deserialized JsepSessionDescription from
// kSdpSctpDataChannelStringWithSctpPort for
// draft-ietf-mmusic-sctp-sdp-07
// a=sctp-port
- sdp_with_data = kSdpString;
+ std::string sdp_with_data = kSdpString;
sdp_with_data.append(kSdpSctpDataChannelStringWithSctpPort);
- rtc::replace_substrs(default_portstr, strlen(default_portstr),
- unusual_portstr, strlen(unusual_portstr),
- &sdp_with_data);
+ rtc::replace_substrs(kDefaultSctpPortStr, strlen(kDefaultSctpPortStr),
+ kUnusualSctpPortStr, strlen(kUnusualSctpPortStr),
+ &sdp_with_data);
+ JsepSessionDescription jdesc_output(kDummyString);
EXPECT_TRUE(SdpDeserialize(sdp_with_data, &jdesc_output));
EXPECT_TRUE(CompareSessionDescription(jdesc, jdesc_output));
@@ -2744,7 +2768,8 @@
}
TEST_F(WebRtcSdpTest, DeserializeSdpWithSctpDataChannelsAndBandwidth) {
- AddSctpDataChannel();
+ bool use_sctpmap = true;
+ AddSctpDataChannel(use_sctpmap);
JsepSessionDescription jdesc(kDummyString);
DataContentDescription* dcd = static_cast<DataContentDescription*>(
GetFirstDataContent(&desc_)->description);