Add the multicast DNS message format.

This CL adds the utilities to generate and parse mDNS messages (RFC 1035
and RFC 6762).

TBR=phoglund@webrtc.org

Bug: webrtc:9605
Change-Id: Id6121c17926887cd3a41a2dfc829462fd15f3a4c
Reviewed-on: https://webrtc-review.googlesource.com/93241
Commit-Queue: Qingsi Wang <qingsi@google.com>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Qingsi Wang <qingsi@webrtc.org>
Reviewed-by: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Alex Loiko <aleloi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#24505}
diff --git a/p2p/BUILD.gn b/p2p/BUILD.gn
index f8f70d9..74a124c 100644
--- a/p2p/BUILD.gn
+++ b/p2p/BUILD.gn
@@ -31,6 +31,8 @@
     "base/dtlstransportinternal.h",
     "base/icetransportinternal.cc",
     "base/icetransportinternal.h",
+    "base/mdns_message.cc",
+    "base/mdns_message.h",
     "base/p2pconstants.cc",
     "base/p2pconstants.h",
     "base/p2ptransportchannel.cc",
@@ -150,6 +152,7 @@
       "base/asyncstuntcpsocket_unittest.cc",
       "base/basicasyncresolverfactory_unittest.cc",
       "base/dtlstransport_unittest.cc",
+      "base/mdns_message_unittest.cc",
       "base/p2ptransportchannel_unittest.cc",
       "base/packetlossestimator_unittest.cc",
       "base/port_unittest.cc",
diff --git a/p2p/base/mdns_message.cc b/p2p/base/mdns_message.cc
new file mode 100644
index 0000000..61af6f3
--- /dev/null
+++ b/p2p/base/mdns_message.cc
@@ -0,0 +1,395 @@
+/*
+ *  Copyright 2018 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 "p2p/base/mdns_message.h"
+#include "rtc_base/logging.h"
+#include "rtc_base/nethelpers.h"
+#include "rtc_base/stringencode.h"
+
+namespace webrtc {
+
+namespace {
+// RFC 1035, Section 4.1.1.
+//
+// QR bit.
+constexpr uint16_t kMDnsFlagMaskQueryOrResponse = 0x8000;
+// AA bit.
+constexpr uint16_t kMDnsFlagMaskAuthoritative = 0x0400;
+// RFC 1035, Section 4.1.2, QCLASS and RFC 6762, Section 18.12, repurposing of
+// top bit of QCLASS as the unicast response bit.
+constexpr uint16_t kMDnsQClassMaskUnicastResponse = 0x8000;
+constexpr size_t kMDnsHeaderSizeBytes = 12;
+
+bool ReadDomainName(MessageBufferReader* buf, std::string* name) {
+  size_t name_start_pos = buf->CurrentOffset();
+  uint8_t label_length;
+  if (!buf->ReadUInt8(&label_length)) {
+    return false;
+  }
+  // RFC 1035, Section 4.1.4.
+  //
+  // If the first two bits of the length octet are ones, the name is compressed
+  // and the rest six bits with the next octet denotes its position in the
+  // message by the offset from the start of the message.
+  auto is_pointer = [](uint8_t octet) {
+    return (octet & 0x80) && (octet & 0x40);
+  };
+  while (label_length && !is_pointer(label_length)) {
+    // RFC 1035, Section 2.3.1, labels are restricted to 63 octets or less.
+    if (label_length > 63) {
+      return false;
+    }
+    std::string label;
+    if (!buf->ReadString(&label, label_length)) {
+      return false;
+    }
+    (*name) += label + ".";
+    if (!buf->ReadUInt8(&label_length)) {
+      return false;
+    }
+  }
+  if (is_pointer(label_length)) {
+    uint8_t next_octet;
+    if (!buf->ReadUInt8(&next_octet)) {
+      return false;
+    }
+    size_t pos_jump_to = ((label_length & 0x3f) << 8) | next_octet;
+    // A legitimate pointer only refers to a prior occurrence of the same name,
+    // and we should only move strictly backward to a prior name field after the
+    // header.
+    if (pos_jump_to >= name_start_pos || pos_jump_to < kMDnsHeaderSizeBytes) {
+      return false;
+    }
+    MessageBufferReader new_buf(buf->MessageData(), buf->MessageLength());
+    if (!new_buf.Consume(pos_jump_to)) {
+      return false;
+    }
+    return ReadDomainName(&new_buf, name);
+  }
+  return true;
+}
+
+void WriteDomainName(rtc::ByteBufferWriter* buf, const std::string& name) {
+  std::vector<std::string> labels;
+  rtc::tokenize(name, '.', &labels);
+  for (const auto& label : labels) {
+    buf->WriteUInt8(label.length());
+    buf->WriteString(label);
+  }
+  buf->WriteUInt8(0);
+}
+
+}  // namespace
+
+void MDnsHeader::SetQueryOrResponse(bool is_query) {
+  if (is_query) {
+    flags &= ~kMDnsFlagMaskQueryOrResponse;
+  } else {
+    flags |= kMDnsFlagMaskQueryOrResponse;
+  }
+}
+
+void MDnsHeader::SetAuthoritative(bool is_authoritative) {
+  if (is_authoritative) {
+    flags |= kMDnsFlagMaskAuthoritative;
+  } else {
+    flags &= ~kMDnsFlagMaskAuthoritative;
+  }
+}
+
+bool MDnsHeader::IsAuthoritative() const {
+  return flags & kMDnsFlagMaskAuthoritative;
+}
+
+bool MDnsHeader::Read(MessageBufferReader* buf) {
+  if (!buf->ReadUInt16(&id) || !buf->ReadUInt16(&flags) ||
+      !buf->ReadUInt16(&qdcount) || !buf->ReadUInt16(&ancount) ||
+      !buf->ReadUInt16(&nscount) || !buf->ReadUInt16(&arcount)) {
+    RTC_LOG(LS_ERROR) << "Invalid mDNS header.";
+    return false;
+  }
+  return true;
+}
+
+void MDnsHeader::Write(rtc::ByteBufferWriter* buf) const {
+  buf->WriteUInt16(id);
+  buf->WriteUInt16(flags);
+  buf->WriteUInt16(qdcount);
+  buf->WriteUInt16(ancount);
+  buf->WriteUInt16(nscount);
+  buf->WriteUInt16(arcount);
+}
+
+bool MDnsHeader::IsQuery() const {
+  return !(flags & kMDnsFlagMaskQueryOrResponse);
+}
+
+MDnsSectionEntry::MDnsSectionEntry() = default;
+MDnsSectionEntry::~MDnsSectionEntry() = default;
+MDnsSectionEntry::MDnsSectionEntry(const MDnsSectionEntry& other) = default;
+
+void MDnsSectionEntry::SetType(SectionEntryType type) {
+  switch (type) {
+    case SectionEntryType::kA:
+      type_ = 1;
+      return;
+    case SectionEntryType::kAAAA:
+      type_ = 28;
+      return;
+    default:
+      RTC_NOTREACHED();
+  }
+}
+
+SectionEntryType MDnsSectionEntry::GetType() const {
+  switch (type_) {
+    case 1:
+      return SectionEntryType::kA;
+    case 28:
+      return SectionEntryType::kAAAA;
+    default:
+      return SectionEntryType::kUnsupported;
+  }
+}
+
+void MDnsSectionEntry::SetClass(SectionEntryClass cls) {
+  switch (cls) {
+    case SectionEntryClass::kIN:
+      class_ = 1;
+      return;
+    default:
+      RTC_NOTREACHED();
+  }
+}
+
+SectionEntryClass MDnsSectionEntry::GetClass() const {
+  switch (class_) {
+    case 1:
+      return SectionEntryClass::kIN;
+    default:
+      return SectionEntryClass::kUnsupported;
+  }
+}
+
+MDnsQuestion::MDnsQuestion() = default;
+MDnsQuestion::MDnsQuestion(const MDnsQuestion& other) = default;
+MDnsQuestion::~MDnsQuestion() = default;
+
+bool MDnsQuestion::Read(MessageBufferReader* buf) {
+  if (!ReadDomainName(buf, &name_)) {
+    RTC_LOG(LS_ERROR) << "Invalid name.";
+    return false;
+  }
+  if (!buf->ReadUInt16(&type_) || !buf->ReadUInt16(&class_)) {
+    RTC_LOG(LS_ERROR) << "Invalid type and class.";
+    return false;
+  }
+  return true;
+}
+
+bool MDnsQuestion::Write(rtc::ByteBufferWriter* buf) const {
+  WriteDomainName(buf, name_);
+  buf->WriteUInt16(type_);
+  buf->WriteUInt16(class_);
+  return true;
+}
+
+void MDnsQuestion::SetUnicastResponse(bool should_unicast) {
+  if (should_unicast) {
+    class_ |= kMDnsQClassMaskUnicastResponse;
+  } else {
+    class_ &= ~kMDnsQClassMaskUnicastResponse;
+  }
+}
+
+bool MDnsQuestion::ShouldUnicastResponse() const {
+  return class_ & kMDnsQClassMaskUnicastResponse;
+}
+
+MDnsResourceRecord::MDnsResourceRecord() = default;
+MDnsResourceRecord::MDnsResourceRecord(const MDnsResourceRecord& other) =
+    default;
+MDnsResourceRecord::~MDnsResourceRecord() = default;
+
+bool MDnsResourceRecord::Read(MessageBufferReader* buf) {
+  if (!ReadDomainName(buf, &name_)) {
+    return false;
+  }
+  if (!buf->ReadUInt16(&type_) || !buf->ReadUInt16(&class_) ||
+      !buf->ReadUInt32(&ttl_seconds_) || !buf->ReadUInt16(&rdlength_)) {
+    return false;
+  }
+
+  switch (GetType()) {
+    case SectionEntryType::kA:
+      return ReadARData(buf);
+    case SectionEntryType::kAAAA:
+      return ReadQuadARData(buf);
+    case SectionEntryType::kUnsupported:
+      return false;
+    default:
+      RTC_NOTREACHED();
+  }
+  return false;
+}
+bool MDnsResourceRecord::ReadARData(MessageBufferReader* buf) {
+  // A RDATA contains a 32-bit IPv4 address.
+  return buf->ReadString(&rdata_, 4);
+}
+
+bool MDnsResourceRecord::ReadQuadARData(MessageBufferReader* buf) {
+  // AAAA RDATA contains a 128-bit IPv6 address.
+  return buf->ReadString(&rdata_, 16);
+}
+
+bool MDnsResourceRecord::Write(rtc::ByteBufferWriter* buf) const {
+  WriteDomainName(buf, name_);
+  buf->WriteUInt16(type_);
+  buf->WriteUInt16(class_);
+  buf->WriteUInt32(ttl_seconds_);
+  buf->WriteUInt16(rdlength_);
+  switch (GetType()) {
+    case SectionEntryType::kA:
+      WriteARData(buf);
+      return true;
+    case SectionEntryType::kAAAA:
+      WriteQuadARData(buf);
+      return true;
+    case SectionEntryType::kUnsupported:
+      return false;
+    default:
+      RTC_NOTREACHED();
+  }
+  return true;
+}
+
+void MDnsResourceRecord::WriteARData(rtc::ByteBufferWriter* buf) const {
+  buf->WriteString(rdata_);
+}
+
+void MDnsResourceRecord::WriteQuadARData(rtc::ByteBufferWriter* buf) const {
+  buf->WriteString(rdata_);
+}
+
+bool MDnsResourceRecord::SetIPAddressInRecordData(
+    const rtc::IPAddress& address) {
+  int af = address.family();
+  if (af != AF_INET && af != AF_INET6) {
+    return false;
+  }
+  char out[16] = {0};
+  if (!rtc::inet_pton(af, address.ToString().c_str(), out)) {
+    return false;
+  }
+  rdlength_ = (af == AF_INET) ? 4 : 16;
+  rdata_ = std::string(out, rdlength_);
+  return true;
+}
+
+bool MDnsResourceRecord::GetIPAddressFromRecordData(
+    rtc::IPAddress* address) const {
+  if (GetType() != SectionEntryType::kA &&
+      GetType() != SectionEntryType::kAAAA) {
+    return false;
+  }
+  if (rdata_.size() != 4 && rdata_.size() != 16) {
+    return false;
+  }
+  char out[INET6_ADDRSTRLEN] = {0};
+  int af = (GetType() == SectionEntryType::kA) ? AF_INET : AF_INET6;
+  if (!rtc::inet_ntop(af, rdata_.data(), out, sizeof(out))) {
+    return false;
+  }
+  return rtc::IPFromString(std::string(out), address);
+}
+
+MDnsMessage::MDnsMessage() = default;
+MDnsMessage::~MDnsMessage() = default;
+
+bool MDnsMessage::Read(MessageBufferReader* buf) {
+  RTC_DCHECK_EQ(0u, buf->CurrentOffset());
+  if (!header_.Read(buf)) {
+    return false;
+  }
+
+  auto read_question = [&buf](std::vector<MDnsQuestion>* section,
+                              uint16_t count) {
+    section->resize(count);
+    for (auto& question : (*section)) {
+      if (!question.Read(buf)) {
+        return false;
+      }
+    }
+    return true;
+  };
+  auto read_rr = [&buf](std::vector<MDnsResourceRecord>* section,
+                        uint16_t count) {
+    section->resize(count);
+    for (auto& rr : (*section)) {
+      if (!rr.Read(buf)) {
+        return false;
+      }
+    }
+    return true;
+  };
+
+  if (!read_question(&question_section_, header_.qdcount) ||
+      !read_rr(&answer_section_, header_.ancount) ||
+      !read_rr(&authority_section_, header_.nscount) ||
+      !read_rr(&additional_section_, header_.arcount)) {
+    return false;
+  }
+  return true;
+}
+
+bool MDnsMessage::Write(rtc::ByteBufferWriter* buf) const {
+  header_.Write(buf);
+
+  auto write_rr = [&buf](const std::vector<MDnsResourceRecord>& section) {
+    for (auto rr : section) {
+      if (!rr.Write(buf)) {
+        return false;
+      }
+    }
+    return true;
+  };
+
+  for (auto question : question_section_) {
+    if (!question.Write(buf)) {
+      return false;
+    }
+  }
+  if (!write_rr(answer_section_) || !write_rr(authority_section_) ||
+      !write_rr(additional_section_)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool MDnsMessage::ShouldUnicastResponse() const {
+  bool should_unicast = false;
+  for (const auto& question : question_section_) {
+    should_unicast |= question.ShouldUnicastResponse();
+  }
+  return should_unicast;
+}
+
+void MDnsMessage::AddQuestion(const MDnsQuestion& question) {
+  question_section_.push_back(question);
+  header_.qdcount = question_section_.size();
+}
+
+void MDnsMessage::AddAnswerRecord(const MDnsResourceRecord& answer) {
+  answer_section_.push_back(answer);
+  header_.ancount = answer_section_.size();
+}
+
+}  // namespace webrtc
diff --git a/p2p/base/mdns_message.h b/p2p/base/mdns_message.h
new file mode 100644
index 0000000..f6dec20
--- /dev/null
+++ b/p2p/base/mdns_message.h
@@ -0,0 +1,207 @@
+/*
+ *  Copyright 2018 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.
+ */
+
+#ifndef P2P_BASE_MDNS_MESSAGE_H_
+#define P2P_BASE_MDNS_MESSAGE_H_
+
+// This file contains classes to read and write mDNSs message defined in RFC
+// 6762 and RFC 1025 (DNS messages). Note that it is recommended by RFC 6762 to
+// use the name compression scheme defined in RFC 1035 whenever possible. We
+// currently only implement the capability of reading compressed names in mDNS
+// messages in MDnsMessage::Read(); however, the MDnsMessage::Write() does not
+// support name compression yet.
+//
+// Fuzzer tests (test/fuzzers/mdns_parser_fuzzer.cc) MUST always be performed
+// after changes made to this file.
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "rtc_base/bytebuffer.h"
+#include "rtc_base/ipaddress.h"
+#include "rtc_base/message_buffer_reader.h"
+
+namespace webrtc {
+
+// We use "section entry" to denote either a question or a resource record.
+//
+// RFC 1035 Section 3.2.2.
+enum class SectionEntryType {
+  kA,
+  kAAAA,
+  // Only the above types are processed in the current implementation.
+  kUnsupported,
+};
+
+// RFC 1035 Section 3.2.4.
+enum class SectionEntryClass {
+  kIN,
+  kUnsupported,
+};
+
+// RFC 1035, Section 4.1.1.
+class MDnsHeader final {
+ public:
+  bool Read(MessageBufferReader* buf);
+  void Write(rtc::ByteBufferWriter* buf) const;
+
+  void SetQueryOrResponse(bool is_query);
+  bool IsQuery() const;
+  void SetAuthoritative(bool is_authoritative);
+  bool IsAuthoritative() const;
+
+  uint16_t id = 0;
+  uint16_t flags = 0;
+  // Number of entries in the question section.
+  uint16_t qdcount = 0;
+  // Number of resource records in the answer section.
+  uint16_t ancount = 0;
+  // Number of name server resource records in the authority records section.
+  uint16_t nscount = 0;
+  // Number of resource records in the additional records section.
+  uint16_t arcount = 0;
+};
+
+// Entries in each section after the header share a common structure. Note that
+// this is not a concept defined in RFC 1035.
+class MDnsSectionEntry {
+ public:
+  MDnsSectionEntry();
+  MDnsSectionEntry(const MDnsSectionEntry& other);
+  virtual ~MDnsSectionEntry();
+  virtual bool Read(MessageBufferReader* buf) = 0;
+  virtual bool Write(rtc::ByteBufferWriter* buf) const = 0;
+
+  void SetName(const std::string& name) { name_ = name; }
+  // Returns the fully qualified domain name in the section entry, i.e., QNAME
+  // in a question or NAME in a resource record.
+  std::string GetName() const { return name_; }
+
+  void SetType(SectionEntryType type);
+  SectionEntryType GetType() const;
+  void SetClass(SectionEntryClass cls);
+  SectionEntryClass GetClass() const;
+
+ protected:
+  std::string name_;  // Fully qualified domain name.
+  uint16_t type_ = 0;
+  uint16_t class_ = 0;
+};
+
+// RFC 1035, Section 4.1.2.
+class MDnsQuestion final : public MDnsSectionEntry {
+ public:
+  MDnsQuestion();
+  MDnsQuestion(const MDnsQuestion& other);
+  ~MDnsQuestion() override;
+
+  bool Read(MessageBufferReader* buf) override;
+  bool Write(rtc::ByteBufferWriter* buf) const override;
+
+  void SetUnicastResponse(bool should_unicast);
+  bool ShouldUnicastResponse() const;
+};
+
+// RFC 1035, Section 4.1.3.
+class MDnsResourceRecord final : public MDnsSectionEntry {
+ public:
+  MDnsResourceRecord();
+  MDnsResourceRecord(const MDnsResourceRecord& other);
+  ~MDnsResourceRecord() override;
+
+  bool Read(MessageBufferReader* buf) override;
+  bool Write(rtc::ByteBufferWriter* buf) const override;
+
+  void SetTtlSeconds(uint32_t ttl_seconds) { ttl_seconds_ = ttl_seconds; }
+  uint32_t GetTtlSeconds() const { return ttl_seconds_; }
+  // Returns true if |address| is in the address family AF_INET or AF_INET6 and
+  // |address| has a valid IPv4 or IPv6 address; false otherwise.
+  bool SetIPAddressInRecordData(const rtc::IPAddress& address);
+  // Returns true if the record is of type A or AAAA and the record has a valid
+  // IPv4 or IPv6 address; false otherwise. Stores the valid IP in |address|.
+  bool GetIPAddressFromRecordData(rtc::IPAddress* address) const;
+
+ private:
+  // The list of methods reading and writing rdata can grow as we support more
+  // types of rdata.
+  bool ReadARData(MessageBufferReader* buf);
+  void WriteARData(rtc::ByteBufferWriter* buf) const;
+
+  bool ReadQuadARData(MessageBufferReader* buf);
+  void WriteQuadARData(rtc::ByteBufferWriter* buf) const;
+
+  uint32_t ttl_seconds_ = 0;
+  uint16_t rdlength_ = 0;
+  std::string rdata_;
+};
+
+class MDnsMessage final {
+ public:
+  // RFC 1035, Section 4.1.
+  enum class Section { kQuestion, kAnswer, kAuthority, kAdditional };
+
+  MDnsMessage();
+  ~MDnsMessage();
+  // Reads the mDNS message in |buf| and populates the corresponding fields in
+  // MDnsMessage.
+  bool Read(MessageBufferReader* buf);
+  // Write an mDNS message to |buf| based on the fields in MDnsMessage.
+  //
+  // TODO(qingsi): Implement name compression when writing mDNS messages.
+  bool Write(rtc::ByteBufferWriter* buf) const;
+
+  void SetId(uint16_t id) { header_.id = id; }
+  uint16_t GetId() const { return header_.id; }
+
+  void SetQueryOrResponse(bool is_query) {
+    header_.SetQueryOrResponse(is_query);
+  }
+  bool IsQuery() const { return header_.IsQuery(); }
+
+  void SetAuthoritative(bool is_authoritative) {
+    header_.SetAuthoritative(is_authoritative);
+  }
+  bool IsAuthoritative() const { return header_.IsAuthoritative(); }
+
+  // Returns true if the message is a query and the unicast response is
+  // preferred. False otherwise.
+  bool ShouldUnicastResponse() const;
+
+  void AddQuestion(const MDnsQuestion& question);
+  // TODO(qingsi): Implement AddXRecord for name server and additional records.
+  void AddAnswerRecord(const MDnsResourceRecord& answer);
+
+  const std::vector<MDnsQuestion>& question_section() const {
+    return question_section_;
+  }
+  const std::vector<MDnsResourceRecord>& answer_section() const {
+    return answer_section_;
+  }
+  const std::vector<MDnsResourceRecord>& authority_section() const {
+    return authority_section_;
+  }
+  const std::vector<MDnsResourceRecord>& additional_section() const {
+    return additional_section_;
+  }
+
+ private:
+  MDnsHeader header_;
+  std::vector<MDnsQuestion> question_section_;
+  std::vector<MDnsResourceRecord> answer_section_;
+  std::vector<MDnsResourceRecord> authority_section_;
+  std::vector<MDnsResourceRecord> additional_section_;
+};
+
+}  // namespace webrtc
+
+#endif  // P2P_BASE_MDNS_MESSAGE_H_
diff --git a/p2p/base/mdns_message_unittest.cc b/p2p/base/mdns_message_unittest.cc
new file mode 100644
index 0000000..fb4a3b1
--- /dev/null
+++ b/p2p/base/mdns_message_unittest.cc
@@ -0,0 +1,570 @@
+/*
+ *  Copyright 2018 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 <map>
+#include <set>
+#include <string>
+
+#include "p2p/base/mdns_message.h"
+#include "rtc_base/bytebuffer.h"
+#include "rtc_base/gunit.h"
+#include "rtc_base/ipaddress.h"
+#include "rtc_base/socketaddress.h"
+#include "test/gmock.h"
+
+#define ReadMDnsMessage(X, Y) ReadMDnsMessageTestCase(X, Y, sizeof(Y))
+#define WriteMDnsMessageAndCompare(X, Y) \
+  WriteMDnsMessageAndCompareWithTestCast(X, Y, sizeof(Y))
+
+using ::testing::ElementsAre;
+using ::testing::Pair;
+using ::testing::UnorderedElementsAre;
+
+namespace webrtc {
+
+namespace {
+
+const uint8_t kSingleQuestionForIPv4AddrWithUnicastResponse[] = {
+    0x12, 0x34,                                // ID
+    0x00, 0x00,                                // flags
+    0x00, 0x01,                                // number of questions
+    0x00, 0x00,                                // number of answer rr
+    0x00, 0x00,                                // number of name server rr
+    0x00, 0x00,                                // number of additional rr
+    0x06, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63,  // webrtc
+    0x03, 0x6f, 0x72, 0x67,                    // org
+    0x00,                                      // null label
+    0x00, 0x01,                                // type A Record
+    0x80, 0x01,                                // class IN, unicast response
+};
+
+const uint8_t kTwoQuestionsForIPv4AndIPv6AddrWithMulticastResponse[] = {
+    0x12, 0x34,                                      // ID
+    0x00, 0x00,                                      // flags
+    0x00, 0x02,                                      // number of questions
+    0x00, 0x00,                                      // number of answer rr
+    0x00, 0x00,                                      // number of name server rr
+    0x00, 0x00,                                      // number of additional rr
+    0x07, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x34,  // webrtc4
+    0x03, 0x6f, 0x72, 0x67,                          // org
+    0x00,                                            // null label
+    0x00, 0x01,                                      // type A Record
+    0x00, 0x01,  // class IN, multicast response
+    0x07, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x36,  // webrtc6
+    0x03, 0x6f, 0x72, 0x67,                          // org
+    0x00,                                            // null label
+    0x00, 0x1C,                                      // type AAAA Record
+    0x00, 0x01,  // class IN, multicast response
+};
+
+const uint8_t
+    kTwoQuestionsForIPv4AndIPv6AddrWithMulticastResponseAndNameCompression[] = {
+        0x12, 0x34,                                // ID
+        0x00, 0x00,                                // flags
+        0x00, 0x02,                                // number of questions
+        0x00, 0x00,                                // number of answer rr
+        0x00, 0x00,                                // number of name server rr
+        0x00, 0x00,                                // number of additional rr
+        0x03, 0x77, 0x77, 0x77,                    // www
+        0x06, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63,  // webrtc
+        0x03, 0x6f, 0x72, 0x67,                    // org
+        0x00,                                      // null label
+        0x00, 0x01,                                // type A Record
+        0x00, 0x01,                    // class IN, multicast response
+        0x04, 0x6d, 0x64, 0x6e, 0x73,  // mdns
+        0xc0, 0x10,                    // offset 16, webrtc.org.
+        0x00, 0x1C,                    // type AAAA Record
+        0x00, 0x01,                    // class IN, multicast response
+};
+
+const uint8_t kThreeQuestionsWithTwoPointersToTheSameNameSuffix[] = {
+    0x12, 0x34,                                // ID
+    0x00, 0x00,                                // flags
+    0x00, 0x03,                                // number of questions
+    0x00, 0x00,                                // number of answer rr
+    0x00, 0x00,                                // number of name server rr
+    0x00, 0x00,                                // number of additional rr
+    0x03, 0x77, 0x77, 0x77,                    // www
+    0x06, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63,  // webrtc
+    0x03, 0x6f, 0x72, 0x67,                    // org
+    0x00,                                      // null label
+    0x00, 0x01,                                // type A Record
+    0x00, 0x01,                                // class IN, multicast response
+    0x04, 0x6d, 0x64, 0x6e, 0x73,              // mdns
+    0xc0, 0x10,                                // offset 16, webrtc.org.
+    0x00, 0x1C,                                // type AAAA Record
+    0x00, 0x01,                                // class IN, multicast response
+    0xc0, 0x10,                                // offset 16, webrtc.org.
+    0x00, 0x01,                                // type A Record
+    0x00, 0x01,                                // class IN, multicast response
+};
+
+const uint8_t kThreeQuestionsWithPointerToNameSuffixContainingAnotherPointer[] =
+    {
+        0x12, 0x34,                                // ID
+        0x00, 0x00,                                // flags
+        0x00, 0x03,                                // number of questions
+        0x00, 0x00,                                // number of answer rr
+        0x00, 0x00,                                // number of name server rr
+        0x00, 0x00,                                // number of additional rr
+        0x03, 0x77, 0x77, 0x77,                    // www
+        0x06, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63,  // webrtc
+        0x03, 0x6f, 0x72, 0x67,                    // org
+        0x00,                                      // null label
+        0x00, 0x01,                                // type A Record
+        0x00, 0x01,                    // class IN, multicast response
+        0x04, 0x6d, 0x64, 0x6e, 0x73,  // mdns
+        0xc0, 0x10,                    // offset 16, webrtc.org.
+        0x00, 0x1C,                    // type AAAA Record
+        0x00, 0x01,                    // class IN, multicast response
+        0x03, 0x77, 0x77, 0x77,        // www
+        0xc0, 0x20,                    // offset 32, mdns.webrtc.org.
+        0x00, 0x01,                    // type A Record
+        0x00, 0x01,                    // class IN, multicast response
+};
+
+const uint8_t kCorruptedQuestionWithNameCompression1[] = {
+    0x12, 0x34,  // ID
+    0x84, 0x00,  // flags
+    0x00, 0x01,  // number of questions
+    0x00, 0x00,  // number of answer rr
+    0x00, 0x00,  // number of name server rr
+    0x00, 0x00,  // number of additional rr
+    0xc0, 0x0c,  // offset 12,
+    0x00, 0x01,  // type A Record
+    0x00, 0x01,  // class IN
+};
+
+const uint8_t kCorruptedQuestionWithNameCompression2[] = {
+    0x12, 0x34,  // ID
+    0x84, 0x00,  // flags
+    0x00, 0x01,  // number of questions
+    0x00, 0x00,  // number of answer rr
+    0x00, 0x00,  // number of name server rr
+    0x00, 0x00,  // number of additional rr
+    0x01, 0x77,  // w
+    0xc0, 0x0c,  // offset 12,
+    0x00, 0x01,  // type A Record
+    0x00, 0x01,  // class IN
+};
+
+const uint8_t kSingleAuthoritativeAnswerWithIPv4Addr[] = {
+    0x12, 0x34,                                // ID
+    0x84, 0x00,                                // flags
+    0x00, 0x00,                                // number of questions
+    0x00, 0x01,                                // number of answer rr
+    0x00, 0x00,                                // number of name server rr
+    0x00, 0x00,                                // number of additional rr
+    0x06, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63,  // webrtc
+    0x03, 0x6f, 0x72, 0x67,                    // org
+    0x00,                                      // null label
+    0x00, 0x01,                                // type A Record
+    0x00, 0x01,                                // class IN
+    0x00, 0x00, 0x00, 0x78,                    // TTL, 120 seconds
+    0x00, 0x04,                                // rdlength, 32 bits
+    0xC0, 0xA8, 0x00, 0x01,                    // 192.168.0.1
+};
+
+const uint8_t kTwoAuthoritativeAnswersWithIPv4AndIPv6Addr[] = {
+    0x12, 0x34,                                      // ID
+    0x84, 0x00,                                      // flags
+    0x00, 0x00,                                      // number of questions
+    0x00, 0x02,                                      // number of answer rr
+    0x00, 0x00,                                      // number of name server rr
+    0x00, 0x00,                                      // number of additional rr
+    0x07, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x34,  // webrtc4
+    0x03, 0x6f, 0x72, 0x67,                          // org
+    0x00,                                            // null label
+    0x00, 0x01,                                      // type A Record
+    0x00, 0x01,                                      // class IN
+    0x00, 0x00, 0x00, 0x3c,                          // TTL, 60 seconds
+    0x00, 0x04,                                      // rdlength, 32 bits
+    0xC0, 0xA8, 0x00, 0x01,                          // 192.168.0.1
+    0x07, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63, 0x36,  // webrtc6
+    0x03, 0x6f, 0x72, 0x67,                          // org
+    0x00,                                            // null label
+    0x00, 0x1C,                                      // type AAAA Record
+    0x00, 0x01,                                      // class IN
+    0x00, 0x00, 0x00, 0x78,                          // TTL, 120 seconds
+    0x00, 0x10,                                      // rdlength, 128 bits
+    0xfd, 0x12, 0x34, 0x56, 0x78, 0x9a, 0x00, 0x01,  // fd12:3456:789a:1::1
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+};
+
+const uint8_t kTwoAuthoritativeAnswersWithIPv4AndIPv6AddrWithNameCompression[] =
+    {
+        0x12, 0x34,                                // ID
+        0x84, 0x00,                                // flags
+        0x00, 0x00,                                // number of questions
+        0x00, 0x02,                                // number of answer rr
+        0x00, 0x00,                                // number of name server rr
+        0x00, 0x00,                                // number of additional rr
+        0x03, 0x77, 0x77, 0x77,                    // www
+        0x06, 0x77, 0x65, 0x62, 0x72, 0x74, 0x63,  // webrtc
+        0x03, 0x6f, 0x72, 0x67,                    // org
+        0x00,                                      // null label
+        0x00, 0x01,                                // type A Record
+        0x00, 0x01,                                // class IN
+        0x00, 0x00, 0x00, 0x3c,                    // TTL, 60 seconds
+        0x00, 0x04,                                // rdlength, 32 bits
+        0xc0, 0xA8, 0x00, 0x01,                    // 192.168.0.1
+        0xc0, 0x10,                                // offset 16, webrtc.org.
+        0x00, 0x1C,                                // type AAAA Record
+        0x00, 0x01,                                // class IN
+        0x00, 0x00, 0x00, 0x78,                    // TTL, 120 seconds
+        0x00, 0x10,                                // rdlength, 128 bits
+        0xfd, 0x12, 0x34, 0x56, 0x78, 0x9a, 0x00, 0x01,  // fd12:3456:789a:1::1
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+};
+
+const uint8_t kCorruptedAnswerWithNameCompression1[] = {
+    0x12, 0x34,              // ID
+    0x84, 0x00,              // flags
+    0x00, 0x00,              // number of questions
+    0x00, 0x01,              // number of answer rr
+    0x00, 0x00,              // number of name server rr
+    0x00, 0x00,              // number of additional rr
+    0xc0, 0x0c,              // offset 12,
+    0x00, 0x01,              // type A Record
+    0x00, 0x01,              // class IN
+    0x00, 0x00, 0x00, 0x3c,  // TTL, 60 seconds
+    0x00, 0x04,              // rdlength, 32 bits
+    0xc0, 0xA8, 0x00, 0x01,  // 192.168.0.1
+};
+
+const uint8_t kCorruptedAnswerWithNameCompression2[] = {
+    0x12, 0x34,              // ID
+    0x84, 0x00,              // flags
+    0x00, 0x00,              // number of questions
+    0x00, 0x01,              // number of answer rr
+    0x00, 0x00,              // number of name server rr
+    0x00, 0x00,              // number of additional rr
+    0x01, 0x77,              // w
+    0xc0, 0x0c,              // offset 12,
+    0x00, 0x01,              // type A Record
+    0x00, 0x01,              // class IN
+    0x00, 0x00, 0x00, 0x3c,  // TTL, 60 seconds
+    0x00, 0x04,              // rdlength, 32 bits
+    0xc0, 0xA8, 0x00, 0x01,  // 192.168.0.1
+};
+
+bool ReadMDnsMessageTestCase(MDnsMessage* msg,
+                             const uint8_t* testcase,
+                             size_t size) {
+  MessageBufferReader buf(reinterpret_cast<const char*>(testcase), size);
+  return msg->Read(&buf);
+}
+
+void WriteMDnsMessageAndCompareWithTestCast(MDnsMessage* msg,
+                                            const uint8_t* testcase,
+                                            size_t size) {
+  rtc::ByteBufferWriter out;
+  EXPECT_TRUE(msg->Write(&out));
+  EXPECT_EQ(size, out.Length());
+  int len = static_cast<int>(out.Length());
+  rtc::ByteBufferReader read_buf(out);
+  std::string bytes;
+  read_buf.ReadString(&bytes, len);
+  std::string testcase_bytes(reinterpret_cast<const char*>(testcase), size);
+  EXPECT_EQ(testcase_bytes, bytes);
+}
+
+bool GetQueriedNames(MDnsMessage* msg, std::set<std::string>* names) {
+  if (!msg->IsQuery() || msg->question_section().empty()) {
+    return false;
+  }
+  for (const auto& question : msg->question_section()) {
+    names->insert(question.GetName());
+  }
+  return true;
+}
+
+bool GetResolution(MDnsMessage* msg,
+                   std::map<std::string, rtc::IPAddress>* names) {
+  if (msg->IsQuery() || msg->answer_section().empty()) {
+    return false;
+  }
+  for (const auto& answer : msg->answer_section()) {
+    rtc::IPAddress resolved_addr;
+    if (!answer.GetIPAddressFromRecordData(&resolved_addr)) {
+      return false;
+    }
+    (*names)[answer.GetName()] = resolved_addr;
+  }
+  return true;
+}
+
+}  // namespace
+
+TEST(MDnsMessageTest, ReadSingleQuestionForIPv4Address) {
+  MDnsMessage msg;
+  ASSERT_TRUE(
+      ReadMDnsMessage(&msg, kSingleQuestionForIPv4AddrWithUnicastResponse));
+  EXPECT_TRUE(msg.IsQuery());
+  EXPECT_EQ(0x1234, msg.GetId());
+  ASSERT_EQ(1u, msg.question_section().size());
+  EXPECT_EQ(0u, msg.answer_section().size());
+  EXPECT_EQ(0u, msg.authority_section().size());
+  EXPECT_EQ(0u, msg.additional_section().size());
+  EXPECT_TRUE(msg.ShouldUnicastResponse());
+
+  const auto& question = msg.question_section()[0];
+  EXPECT_EQ(SectionEntryType::kA, question.GetType());
+
+  std::set<std::string> queried_names;
+  EXPECT_TRUE(GetQueriedNames(&msg, &queried_names));
+  EXPECT_THAT(queried_names, ElementsAre("webrtc.org."));
+}
+
+TEST(MDnsMessageTest, ReadTwoQuestionsForIPv4AndIPv6Addr) {
+  MDnsMessage msg;
+  ASSERT_TRUE(ReadMDnsMessage(
+      &msg, kTwoQuestionsForIPv4AndIPv6AddrWithMulticastResponse));
+  EXPECT_TRUE(msg.IsQuery());
+  EXPECT_EQ(0x1234, msg.GetId());
+  ASSERT_EQ(2u, msg.question_section().size());
+  EXPECT_EQ(0u, msg.answer_section().size());
+  EXPECT_EQ(0u, msg.authority_section().size());
+  EXPECT_EQ(0u, msg.additional_section().size());
+
+  const auto& question1 = msg.question_section()[0];
+  const auto& question2 = msg.question_section()[1];
+  EXPECT_EQ(SectionEntryType::kA, question1.GetType());
+  EXPECT_EQ(SectionEntryType::kAAAA, question2.GetType());
+
+  std::set<std::string> queried_names;
+  EXPECT_TRUE(GetQueriedNames(&msg, &queried_names));
+  EXPECT_THAT(queried_names,
+              UnorderedElementsAre("webrtc4.org.", "webrtc6.org."));
+}
+
+TEST(MDnsMessageTest, ReadTwoQuestionsForIPv4AndIPv6AddrWithNameCompression) {
+  MDnsMessage msg;
+  ASSERT_TRUE(ReadMDnsMessage(
+      &msg,
+      kTwoQuestionsForIPv4AndIPv6AddrWithMulticastResponseAndNameCompression));
+
+  ASSERT_EQ(2u, msg.question_section().size());
+  const auto& question1 = msg.question_section()[0];
+  const auto& question2 = msg.question_section()[1];
+  EXPECT_EQ(SectionEntryType::kA, question1.GetType());
+  EXPECT_EQ(SectionEntryType::kAAAA, question2.GetType());
+
+  std::set<std::string> queried_names;
+  EXPECT_TRUE(GetQueriedNames(&msg, &queried_names));
+  EXPECT_THAT(queried_names,
+              UnorderedElementsAre("www.webrtc.org.", "mdns.webrtc.org."));
+}
+
+TEST(MDnsMessageTest, ReadThreeQuestionsWithTwoPointersToTheSameNameSuffix) {
+  MDnsMessage msg;
+  ASSERT_TRUE(
+      ReadMDnsMessage(&msg, kThreeQuestionsWithTwoPointersToTheSameNameSuffix));
+
+  ASSERT_EQ(3u, msg.question_section().size());
+  const auto& question1 = msg.question_section()[0];
+  const auto& question2 = msg.question_section()[1];
+  const auto& question3 = msg.question_section()[2];
+  EXPECT_EQ(SectionEntryType::kA, question1.GetType());
+  EXPECT_EQ(SectionEntryType::kAAAA, question2.GetType());
+  EXPECT_EQ(SectionEntryType::kA, question3.GetType());
+
+  std::set<std::string> queried_names;
+  EXPECT_TRUE(GetQueriedNames(&msg, &queried_names));
+  EXPECT_THAT(queried_names,
+              UnorderedElementsAre("www.webrtc.org.", "mdns.webrtc.org.",
+                                   "webrtc.org."));
+}
+
+TEST(MDnsMessageTest,
+     ReadThreeQuestionsWithPointerToNameSuffixContainingAnotherPointer) {
+  MDnsMessage msg;
+  ASSERT_TRUE(ReadMDnsMessage(
+      &msg, kThreeQuestionsWithPointerToNameSuffixContainingAnotherPointer));
+
+  ASSERT_EQ(3u, msg.question_section().size());
+  const auto& question1 = msg.question_section()[0];
+  const auto& question2 = msg.question_section()[1];
+  const auto& question3 = msg.question_section()[2];
+  EXPECT_EQ(SectionEntryType::kA, question1.GetType());
+  EXPECT_EQ(SectionEntryType::kAAAA, question2.GetType());
+  EXPECT_EQ(SectionEntryType::kA, question3.GetType());
+
+  std::set<std::string> queried_names;
+  EXPECT_TRUE(GetQueriedNames(&msg, &queried_names));
+  EXPECT_THAT(queried_names,
+              UnorderedElementsAre("www.webrtc.org.", "mdns.webrtc.org.",
+                                   "www.mdns.webrtc.org."));
+}
+
+TEST(MDnsMessageTest,
+     ReadQuestionWithCorruptedPointerInNameCompressionShouldFail) {
+  MDnsMessage msg;
+  EXPECT_FALSE(ReadMDnsMessage(&msg, kCorruptedQuestionWithNameCompression1));
+  EXPECT_FALSE(ReadMDnsMessage(&msg, kCorruptedQuestionWithNameCompression2));
+}
+
+TEST(MDnsMessageTest, ReadSingleAnswerForIPv4Addr) {
+  MDnsMessage msg;
+  ASSERT_TRUE(ReadMDnsMessage(&msg, kSingleAuthoritativeAnswerWithIPv4Addr));
+  EXPECT_FALSE(msg.IsQuery());
+  EXPECT_TRUE(msg.IsAuthoritative());
+  EXPECT_EQ(0x1234, msg.GetId());
+  EXPECT_EQ(0u, msg.question_section().size());
+  ASSERT_EQ(1u, msg.answer_section().size());
+  EXPECT_EQ(0u, msg.authority_section().size());
+  EXPECT_EQ(0u, msg.additional_section().size());
+
+  const auto& answer = msg.answer_section()[0];
+  EXPECT_EQ(SectionEntryType::kA, answer.GetType());
+  EXPECT_EQ(120u, answer.GetTtlSeconds());
+
+  std::map<std::string, rtc::IPAddress> resolution;
+  EXPECT_TRUE(GetResolution(&msg, &resolution));
+  rtc::IPAddress expected_addr(rtc::SocketAddress("192.168.0.1", 0).ipaddr());
+  EXPECT_THAT(resolution, ElementsAre(Pair("webrtc.org.", expected_addr)));
+}
+
+TEST(MDnsMessageTest, ReadTwoAnswersForIPv4AndIPv6Addr) {
+  MDnsMessage msg;
+  ASSERT_TRUE(
+      ReadMDnsMessage(&msg, kTwoAuthoritativeAnswersWithIPv4AndIPv6Addr));
+  EXPECT_FALSE(msg.IsQuery());
+  EXPECT_TRUE(msg.IsAuthoritative());
+  EXPECT_EQ(0x1234, msg.GetId());
+  EXPECT_EQ(0u, msg.question_section().size());
+  ASSERT_EQ(2u, msg.answer_section().size());
+  EXPECT_EQ(0u, msg.authority_section().size());
+  EXPECT_EQ(0u, msg.additional_section().size());
+
+  const auto& answer1 = msg.answer_section()[0];
+  const auto& answer2 = msg.answer_section()[1];
+  EXPECT_EQ(SectionEntryType::kA, answer1.GetType());
+  EXPECT_EQ(SectionEntryType::kAAAA, answer2.GetType());
+  EXPECT_EQ(60u, answer1.GetTtlSeconds());
+  EXPECT_EQ(120u, answer2.GetTtlSeconds());
+
+  std::map<std::string, rtc::IPAddress> resolution;
+  EXPECT_TRUE(GetResolution(&msg, &resolution));
+  rtc::IPAddress expected_addr_ipv4(
+      rtc::SocketAddress("192.168.0.1", 0).ipaddr());
+  rtc::IPAddress expected_addr_ipv6(
+      rtc::SocketAddress("fd12:3456:789a:1::1", 0).ipaddr());
+  EXPECT_THAT(resolution,
+              UnorderedElementsAre(Pair("webrtc4.org.", expected_addr_ipv4),
+                                   Pair("webrtc6.org.", expected_addr_ipv6)));
+}
+
+TEST(MDnsMessageTest, ReadTwoAnswersForIPv4AndIPv6AddrWithNameCompression) {
+  MDnsMessage msg;
+  ASSERT_TRUE(ReadMDnsMessage(
+      &msg, kTwoAuthoritativeAnswersWithIPv4AndIPv6AddrWithNameCompression));
+
+  std::map<std::string, rtc::IPAddress> resolution;
+  EXPECT_TRUE(GetResolution(&msg, &resolution));
+  rtc::IPAddress expected_addr_ipv4(
+      rtc::SocketAddress("192.168.0.1", 0).ipaddr());
+  rtc::IPAddress expected_addr_ipv6(
+      rtc::SocketAddress("fd12:3456:789a:1::1", 0).ipaddr());
+  EXPECT_THAT(resolution,
+              UnorderedElementsAre(Pair("www.webrtc.org.", expected_addr_ipv4),
+                                   Pair("webrtc.org.", expected_addr_ipv6)));
+}
+
+TEST(MDnsMessageTest,
+     ReadAnswerWithCorruptedPointerInNameCompressionShouldFail) {
+  MDnsMessage msg;
+  EXPECT_FALSE(ReadMDnsMessage(&msg, kCorruptedAnswerWithNameCompression1));
+  EXPECT_FALSE(ReadMDnsMessage(&msg, kCorruptedAnswerWithNameCompression2));
+}
+
+TEST(MDnsMessageTest, WriteSingleQuestionForIPv4Addr) {
+  MDnsMessage msg;
+  msg.SetId(0x1234);
+  msg.SetQueryOrResponse(true);
+
+  MDnsQuestion question;
+  question.SetName("webrtc.org.");
+  question.SetType(SectionEntryType::kA);
+  question.SetClass(SectionEntryClass::kIN);
+  question.SetUnicastResponse(true);
+  msg.AddQuestion(question);
+
+  WriteMDnsMessageAndCompare(&msg,
+                             kSingleQuestionForIPv4AddrWithUnicastResponse);
+}
+
+TEST(MDnsMessageTest, WriteTwoQuestionsForIPv4AndIPv6Addr) {
+  MDnsMessage msg;
+  msg.SetId(0x1234);
+  msg.SetQueryOrResponse(true);
+
+  MDnsQuestion question1;
+  question1.SetName("webrtc4.org.");
+  question1.SetType(SectionEntryType::kA);
+  question1.SetClass(SectionEntryClass::kIN);
+  msg.AddQuestion(question1);
+
+  MDnsQuestion question2;
+  question2.SetName("webrtc6.org.");
+  question2.SetType(SectionEntryType::kAAAA);
+  question2.SetClass(SectionEntryClass::kIN);
+  msg.AddQuestion(question2);
+
+  WriteMDnsMessageAndCompare(
+      &msg, kTwoQuestionsForIPv4AndIPv6AddrWithMulticastResponse);
+}
+
+TEST(MDnsMessageTest, WriteSingleAnswerToIPv4Addr) {
+  MDnsMessage msg;
+  msg.SetId(0x1234);
+  msg.SetQueryOrResponse(false);
+  msg.SetAuthoritative(true);
+
+  MDnsResourceRecord answer;
+  answer.SetName("webrtc.org.");
+  answer.SetType(SectionEntryType::kA);
+  answer.SetClass(SectionEntryClass::kIN);
+  EXPECT_TRUE(answer.SetIPAddressInRecordData(
+      rtc::SocketAddress("192.168.0.1", 0).ipaddr()));
+  answer.SetTtlSeconds(120);
+  msg.AddAnswerRecord(answer);
+
+  WriteMDnsMessageAndCompare(&msg, kSingleAuthoritativeAnswerWithIPv4Addr);
+}
+
+TEST(MDnsMessageTest, WriteTwoAnswersToIPv4AndIPv6Addr) {
+  MDnsMessage msg;
+  msg.SetId(0x1234);
+  msg.SetQueryOrResponse(false);
+  msg.SetAuthoritative(true);
+
+  MDnsResourceRecord answer1;
+  answer1.SetName("webrtc4.org.");
+  answer1.SetType(SectionEntryType::kA);
+  answer1.SetClass(SectionEntryClass::kIN);
+  answer1.SetIPAddressInRecordData(
+      rtc::SocketAddress("192.168.0.1", 0).ipaddr());
+  answer1.SetTtlSeconds(60);
+  msg.AddAnswerRecord(answer1);
+
+  MDnsResourceRecord answer2;
+  answer2.SetName("webrtc6.org.");
+  answer2.SetType(SectionEntryType::kAAAA);
+  answer2.SetClass(SectionEntryClass::kIN);
+  answer2.SetIPAddressInRecordData(
+      rtc::SocketAddress("fd12:3456:789a:1::1", 0).ipaddr());
+  answer2.SetTtlSeconds(120);
+  msg.AddAnswerRecord(answer2);
+
+  WriteMDnsMessageAndCompare(&msg, kTwoAuthoritativeAnswersWithIPv4AndIPv6Addr);
+}
+
+}  // namespace webrtc
diff --git a/rtc_base/BUILD.gn b/rtc_base/BUILD.gn
index 7a03b3b..2ef79fa 100644
--- a/rtc_base/BUILD.gn
+++ b/rtc_base/BUILD.gn
@@ -413,6 +413,7 @@
     "ignore_wundef.h",
     "location.cc",
     "location.h",
+    "message_buffer_reader.h",
     "numerics/histogram_percentile_counter.cc",
     "numerics/histogram_percentile_counter.h",
     "numerics/mod_ops.h",
diff --git a/rtc_base/bytebuffer.h b/rtc_base/bytebuffer.h
index 9036bcd..9e08f02 100644
--- a/rtc_base/bytebuffer.h
+++ b/rtc_base/bytebuffer.h
@@ -185,7 +185,7 @@
   // after this call.
   bool Consume(size_t size);
 
- private:
+ protected:
   void Construct(const char* bytes, size_t size);
 
   const char* bytes_;
@@ -193,6 +193,7 @@
   size_t start_;
   size_t end_;
 
+ private:
   RTC_DISALLOW_COPY_AND_ASSIGN(ByteBufferReader);
 };
 
diff --git a/rtc_base/message_buffer_reader.h b/rtc_base/message_buffer_reader.h
new file mode 100644
index 0000000..baba89e
--- /dev/null
+++ b/rtc_base/message_buffer_reader.h
@@ -0,0 +1,37 @@
+/*
+ *  Copyright 2018 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.
+ */
+
+#ifndef RTC_BASE_MESSAGE_BUFFER_READER_H_
+#define RTC_BASE_MESSAGE_BUFFER_READER_H_
+
+#include "rtc_base/bytebuffer.h"
+
+namespace webrtc {
+
+// A simple subclass of the ByteBufferReader that exposes the starting address
+// of the message and its length, so that we can recall previously parsed data.
+class MessageBufferReader : public rtc::ByteBufferReader {
+ public:
+  MessageBufferReader(const char* bytes, size_t len)
+      : rtc::ByteBufferReader(bytes, len) {}
+  ~MessageBufferReader() = default;
+
+  // Starting address of the message.
+  const char* MessageData() const { return bytes_; }
+  // Total length of the message. Note that this is different from Length(),
+  // which is the length of the remaining message from the current offset.
+  size_t MessageLength() const { return size_; }
+  // Current offset in the message.
+  size_t CurrentOffset() const { return start_; }
+};
+
+}  // namespace webrtc
+
+#endif  // RTC_BASE_MESSAGE_BUFFER_READER_H_
diff --git a/test/DEPS b/test/DEPS
index cd8b2e2..80f47b2 100644
--- a/test/DEPS
+++ b/test/DEPS
@@ -32,6 +32,9 @@
     "+modules/pacing/packet_router.h",
     "+modules/remote_bitrate_estimator/include/remote_bitrate_estimator.h",
   ],
+  ".*mdns_parser_fuzzer\.cc": [
+    "+p2p/base/mdns_message.h",
+  ],
   ".*pseudotcp_parser_fuzzer\.cc": [
     "+p2p/base/pseudotcp.h",
   ],
diff --git a/test/fuzzers/BUILD.gn b/test/fuzzers/BUILD.gn
index f361d59..e006c6a 100644
--- a/test/fuzzers/BUILD.gn
+++ b/test/fuzzers/BUILD.gn
@@ -438,6 +438,18 @@
   dict = "corpora/stun.tokens"
 }
 
+webrtc_fuzzer_test("mdns_parser_fuzzer") {
+  sources = [
+    "mdns_parser_fuzzer.cc",
+  ]
+  deps = [
+    "../../p2p:rtc_p2p",
+    "../../rtc_base:rtc_base_approved",
+    "//third_party/abseil-cpp/absl/memory",
+  ]
+  seed_corpus = "corpora/mdns-corpus"
+}
+
 webrtc_fuzzer_test("pseudotcp_parser_fuzzer") {
   sources = [
     "pseudotcp_parser_fuzzer.cc",
diff --git a/test/fuzzers/corpora/mdns-corpus/1.mdns b/test/fuzzers/corpora/mdns-corpus/1.mdns
new file mode 100644
index 0000000..6e1789a
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/1.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/10.mdns b/test/fuzzers/corpora/mdns-corpus/10.mdns
new file mode 100644
index 0000000..f5fa6f8
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/10.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/11.mdns b/test/fuzzers/corpora/mdns-corpus/11.mdns
new file mode 100644
index 0000000..f72f28c
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/11.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/12.mdns b/test/fuzzers/corpora/mdns-corpus/12.mdns
new file mode 100644
index 0000000..9efa76d
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/12.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/13.mdns b/test/fuzzers/corpora/mdns-corpus/13.mdns
new file mode 100644
index 0000000..538a5a6
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/13.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/14.mdns b/test/fuzzers/corpora/mdns-corpus/14.mdns
new file mode 100644
index 0000000..d4ce4ef
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/14.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/15.mdns b/test/fuzzers/corpora/mdns-corpus/15.mdns
new file mode 100644
index 0000000..ff28105
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/15.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/16.mdns b/test/fuzzers/corpora/mdns-corpus/16.mdns
new file mode 100644
index 0000000..a1a02d7
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/16.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/17.mdns b/test/fuzzers/corpora/mdns-corpus/17.mdns
new file mode 100644
index 0000000..ba30f7b
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/17.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/18.mdns b/test/fuzzers/corpora/mdns-corpus/18.mdns
new file mode 100644
index 0000000..7cbdd3e
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/18.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/19.mdns b/test/fuzzers/corpora/mdns-corpus/19.mdns
new file mode 100644
index 0000000..f70eaa3
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/19.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/2.mdns b/test/fuzzers/corpora/mdns-corpus/2.mdns
new file mode 100644
index 0000000..7d259c2
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/2.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/20.mdns b/test/fuzzers/corpora/mdns-corpus/20.mdns
new file mode 100644
index 0000000..6681f94
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/20.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/3.mdns b/test/fuzzers/corpora/mdns-corpus/3.mdns
new file mode 100644
index 0000000..3ac4fd6
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/3.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/4.mdns b/test/fuzzers/corpora/mdns-corpus/4.mdns
new file mode 100644
index 0000000..3207842
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/4.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/5.mdns b/test/fuzzers/corpora/mdns-corpus/5.mdns
new file mode 100644
index 0000000..871ea41
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/5.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/6.mdns b/test/fuzzers/corpora/mdns-corpus/6.mdns
new file mode 100644
index 0000000..ee2f8ec
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/6.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/7.mdns b/test/fuzzers/corpora/mdns-corpus/7.mdns
new file mode 100644
index 0000000..d37b935
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/7.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/8.mdns b/test/fuzzers/corpora/mdns-corpus/8.mdns
new file mode 100644
index 0000000..dd2f976
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/8.mdns
Binary files differ
diff --git a/test/fuzzers/corpora/mdns-corpus/9.mdns b/test/fuzzers/corpora/mdns-corpus/9.mdns
new file mode 100644
index 0000000..a01e729
--- /dev/null
+++ b/test/fuzzers/corpora/mdns-corpus/9.mdns
Binary files differ
diff --git a/test/fuzzers/mdns_parser_fuzzer.cc b/test/fuzzers/mdns_parser_fuzzer.cc
new file mode 100644
index 0000000..c3f765e
--- /dev/null
+++ b/test/fuzzers/mdns_parser_fuzzer.cc
@@ -0,0 +1,26 @@
+/*
+ *  Copyright 2018 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 <stddef.h>
+#include <stdint.h>
+
+#include "absl/memory/memory.h"
+#include "p2p/base/mdns_message.h"
+#include "rtc_base/message_buffer_reader.h"
+
+namespace webrtc {
+
+void FuzzOneInput(const uint8_t* data, size_t size) {
+  MessageBufferReader buf(reinterpret_cast<const char*>(data), size);
+  auto mdns_msg = absl::make_unique<MDnsMessage>();
+  mdns_msg->Read(&buf);
+}
+
+}  // namespace webrtc