Add NSEC record support for MDnsClient

Add support for NSEC records to MDnsClient, which can be used to deny the
existence of records.

BUG=255232

Review URL: https://chromiumcodereview.appspot.com/17747004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@209452 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/dns/mdns_cache.cc b/net/dns/mdns_cache.cc
index f210d0ab..b430f95 100644
--- a/net/dns/mdns_cache.cc
+++ b/net/dns/mdns_cache.cc
@@ -44,12 +44,12 @@
 }
 
 bool MDnsCache::Key::operator<(const MDnsCache::Key& key) const {
-  if (type_ != key.type_)
-    return type_ < key.type_;
-
   if (name_ != key.name_)
     return name_ < key.name_;
 
+  if (type_ != key.type_)
+    return type_ < key.type_;
+
   if (optional_ != key.optional_)
     return optional_ < key.optional_;
   return false;  // keys are equal
@@ -154,8 +154,8 @@
 
   RecordMap::const_iterator i = mdns_cache_.lower_bound(Key(type, name, ""));
   for (; i != mdns_cache_.end(); ++i) {
-    if (i->first.type() != type ||
-        (!name.empty() && i->first.name() != name)) {
+    if (i->first.name() != name ||
+        (type != 0 && i->first.type() != type)) {
       break;
     }
 
@@ -168,6 +168,19 @@
   }
 }
 
+scoped_ptr<const RecordParsed> MDnsCache::RemoveRecord(
+    const RecordParsed* record) {
+  Key key = Key::CreateFor(record);
+  RecordMap::iterator found = mdns_cache_.find(key);
+
+  if (found != mdns_cache_.end() && found->second == record) {
+    mdns_cache_.erase(key);
+    return scoped_ptr<const RecordParsed>(record);
+  }
+
+  return scoped_ptr<const RecordParsed>();
+}
+
 // static
 std::string MDnsCache::GetOptionalFieldForRecord(
     const RecordParsed* record) {
diff --git a/net/dns/mdns_cache.h b/net/dns/mdns_cache.h
index a73a50e..27a14d8 100644
--- a/net/dns/mdns_cache.h
+++ b/net/dns/mdns_cache.h
@@ -24,7 +24,7 @@
 // guaranteed not to return expired records. It also has facilities for timely
 // record expiration.
 class NET_EXPORT_PRIVATE MDnsCache {
-public:
+ public:
   // Key type for the record map. It is a 3-tuple of type, name and optional
   // value ordered by type, then name, then optional value. This allows us to
   // query for all records of a certain type and name, while also allowing us
@@ -71,7 +71,7 @@
   const RecordParsed* LookupKey(const Key& key);
 
   // Return records with type |type| and name |name|. Expired records will not
-  // be returned. If |name| is empty, return all records with type |type|.
+  // be returned. If |type| is zero, return all records with name |name|.
   void FindDnsRecords(unsigned type,
                       const std::string& name,
                       std::vector<const RecordParsed*>* records,
@@ -87,9 +87,13 @@
   // base::Time when the cache is empty.
   base::Time next_expiration() const { return next_expiration_; }
 
+  // Remove a record from the cache.  Returns a scoped version of the pointer
+  // passed in if it was removed, scoped null otherwise.
+  scoped_ptr<const RecordParsed> RemoveRecord(const RecordParsed* record);
+
   void Clear();
 
-private:
+ private:
   typedef std::map<Key, const RecordParsed*> RecordMap;
 
   // Get the effective expiration of a cache entry, based on its creation time
diff --git a/net/dns/mdns_cache_unittest.cc b/net/dns/mdns_cache_unittest.cc
index 69de38a..955fdbd 100644
--- a/net/dns/mdns_cache_unittest.cc
+++ b/net/dns/mdns_cache_unittest.cc
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <algorithm>
+
 #include "base/bind.h"
 #include "net/dns/dns_response.h"
 #include "net/dns/dns_test_util.h"
@@ -72,6 +74,39 @@
   0x5f, 0x79,
 };
 
+static const uint8 kTestResponseTwoRecords[] = {
+  // Answer 1
+  // ghs.l.google.com in DNS format. (A)
+  0x03, 'g', 'h', 's',
+  0x01, 'l',
+  0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+  0x03, 'c', 'o', 'm',
+  0x00,
+  0x00, 0x01,         // TYPE is A.
+  0x00, 0x01,         // CLASS is IN.
+  0x00, 0x00,         // TTL (4 bytes) is 53 seconds.
+  0x00, 0x35,
+  0x00, 0x04,         // RDLENGTH is 4 bytes.
+  0x4a, 0x7d,         // RDATA is the IP: 74.125.95.121
+  0x5f, 0x79,
+  // Answer 2
+  // ghs.l.google.com in DNS format. (AAAA)
+  0x03, 'g', 'h', 's',
+  0x01, 'l',
+  0x06, 'g', 'o', 'o', 'g', 'l', 'e',
+  0x03, 'c', 'o', 'm',
+  0x00,
+  0x00, 0x1c,         // TYPE is AAA.
+  0x00, 0x01,         // CLASS is IN.
+  0x00, 0x00,         // TTL (4 bytes) is 53 seconds.
+  0x00, 0x35,
+  0x00, 0x10,         // RDLENGTH is 4 bytes.
+  0x4a, 0x7d, 0x4a, 0x7d,
+  0x5f, 0x79, 0x5f, 0x79,
+  0x5f, 0x79, 0x5f, 0x79,
+  0x5f, 0x79, 0x5f, 0x79,
+};
+
 class RecordRemovalMock {
  public:
   MOCK_METHOD1(OnRecordRemoved, void(const RecordParsed*));
@@ -231,4 +266,58 @@
   EXPECT_EQ(default_time_ + ttl1, cache_.next_expiration());
 }
 
-} // namespace net
+TEST_F(MDnsCacheTest, AnyRRType) {
+  DnsRecordParser parser(kTestResponseTwoRecords,
+                         sizeof(kTestResponseTwoRecords),
+                         0);
+
+  scoped_ptr<const RecordParsed> record1;
+  scoped_ptr<const RecordParsed> record2;
+  std::vector<const RecordParsed*> results;
+
+  record1 = RecordParsed::CreateFrom(&parser, default_time_);
+  record2 = RecordParsed::CreateFrom(&parser, default_time_);
+  EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record1.Pass()));
+  EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record2.Pass()));
+
+  cache_.FindDnsRecords(0, "ghs.l.google.com", &results, default_time_);
+
+  EXPECT_EQ(2u, results.size());
+  EXPECT_EQ(default_time_, results.front()->time_created());
+
+  EXPECT_EQ("ghs.l.google.com", results[0]->name());
+  EXPECT_EQ("ghs.l.google.com", results[1]->name());
+  EXPECT_EQ(dns_protocol::kTypeA,
+            std::min(results[0]->type(), results[1]->type()));
+  EXPECT_EQ(dns_protocol::kTypeAAAA,
+            std::max(results[0]->type(), results[1]->type()));
+}
+
+TEST_F(MDnsCacheTest, RemoveRecord) {
+  DnsRecordParser parser(kT1ResponseDatagram, sizeof(kT1ResponseDatagram),
+                         sizeof(dns_protocol::Header));
+  parser.SkipQuestion();
+
+  scoped_ptr<const RecordParsed> record1;
+  std::vector<const RecordParsed*> results;
+
+  record1 = RecordParsed::CreateFrom(&parser, default_time_);
+  EXPECT_EQ(MDnsCache::RecordAdded, cache_.UpdateDnsRecord(record1.Pass()));
+
+  cache_.FindDnsRecords(dns_protocol::kTypeCNAME, "codereview.chromium.org",
+                        &results, default_time_);
+
+  EXPECT_EQ(1u, results.size());
+
+  scoped_ptr<const RecordParsed> record_out =
+      cache_.RemoveRecord(results.front());
+
+  EXPECT_EQ(record_out.get(), results.front());
+
+  cache_.FindDnsRecords(dns_protocol::kTypeCNAME, "codereview.chromium.org",
+                        &results, default_time_);
+
+  EXPECT_EQ(0u, results.size());
+}
+
+}  // namespace net
diff --git a/net/dns/mdns_client.h b/net/dns/mdns_client.h
index f1f5adb..9e3bdb7 100644
--- a/net/dns/mdns_client.h
+++ b/net/dns/mdns_client.h
@@ -128,8 +128,7 @@
  public:
   virtual ~MDnsClient() {}
 
-  // Create listener object for RRType |rrtype| and name |name|.  If |name| is
-  // an empty string, listen to all notification of type |rrtype|.
+  // Create listener object for RRType |rrtype| and name |name|.
   virtual scoped_ptr<MDnsListener> CreateListener(
       uint16 rrtype,
       const std::string& name,
diff --git a/net/dns/mdns_client_impl.cc b/net/dns/mdns_client_impl.cc
index 2c379e7..7388e75 100644
--- a/net/dns/mdns_client_impl.cc
+++ b/net/dns/mdns_client_impl.cc
@@ -13,8 +13,14 @@
 #include "net/base/net_log.h"
 #include "net/base/rand_callback.h"
 #include "net/dns/dns_protocol.h"
+#include "net/dns/record_rdata.h"
 #include "net/udp/datagram_socket.h"
 
+// TODO(gene): Remove this temporary method of disabling NSEC support once it
+// becomes clear whether this feature should be
+// supported. http://crbug.com/255232
+#define ENABLE_NSEC
+
 namespace net {
 
 namespace {
@@ -284,12 +290,48 @@
   for (std::map<MDnsCache::Key, MDnsListener::UpdateType>::iterator i =
            update_keys.begin(); i != update_keys.end(); i++) {
     const RecordParsed* record = cache_.LookupKey(i->first);
-    if (record) {
-      AlertListeners(i->second, ListenerKey(record->type(), record->name()),
+    if (!record)
+      continue;
+
+    if (record->type() == dns_protocol::kTypeNSEC) {
+#if defined(ENABLE_NSEC)
+      NotifyNsecRecord(record);
+#endif
+    } else {
+      AlertListeners(i->second, ListenerKey(record->name(), record->type()),
                      record);
-      // Alert listeners listening only for rrtype and not for name.
-      AlertListeners(i->second, ListenerKey(record->type(), ""),
-                     record);
+    }
+  }
+}
+
+void MDnsClientImpl::Core::NotifyNsecRecord(const RecordParsed* record) {
+  DCHECK_EQ(dns_protocol::kTypeNSEC, record->type());
+  const NsecRecordRdata* rdata = record->rdata<NsecRecordRdata>();
+  DCHECK(rdata);
+
+  // Remove all cached records matching the nonexistent RR types.
+  std::vector<const RecordParsed*> records_to_remove;
+
+  cache_.FindDnsRecords(0, record->name(), &records_to_remove,
+                        base::Time::Now());
+
+  for (std::vector<const RecordParsed*>::iterator i = records_to_remove.begin();
+       i != records_to_remove.end(); i++) {
+    if ((*i)->type() == dns_protocol::kTypeNSEC)
+      continue;
+    if (!rdata->GetBit((*i)->type())) {
+      scoped_ptr<const RecordParsed> record_removed = cache_.RemoveRecord((*i));
+      DCHECK(record_removed);
+      OnRecordRemoved(record_removed.get());
+    }
+  }
+
+  // Alert all listeners waiting for the nonexistent RR types.
+  ListenerMap::iterator i =
+      listeners_.upper_bound(ListenerKey(record->name(), 0));
+  for (; i != listeners_.end() && i->first.first == record->name(); i++) {
+    if (!rdata->GetBit(i->first.second)) {
+      FOR_EACH_OBSERVER(MDnsListenerImpl, *i->second, AlertNsecRecord());
     }
   }
 }
@@ -311,7 +353,7 @@
 
 void MDnsClientImpl::Core::AddListener(
     MDnsListenerImpl* listener) {
-  ListenerKey key(listener->GetType(), listener->GetName());
+  ListenerKey key(listener->GetName(), listener->GetType());
   std::pair<ListenerMap::iterator, bool> observer_insert_result =
       listeners_.insert(
           make_pair(key, static_cast<ObserverList<MDnsListenerImpl>*>(NULL)));
@@ -327,7 +369,7 @@
 }
 
 void MDnsClientImpl::Core::RemoveListener(MDnsListenerImpl* listener) {
-  ListenerKey key(listener->GetType(), listener->GetName());
+  ListenerKey key(listener->GetName(), listener->GetType());
   ListenerMap::iterator observer_list_iterator = listeners_.find(key);
 
   DCHECK(observer_list_iterator != listeners_.end());
@@ -337,8 +379,19 @@
 
   // Remove the observer list from the map if it is empty
   if (observer_list_iterator->second->size() == 0) {
-    delete observer_list_iterator->second;
-    listeners_.erase(observer_list_iterator);
+    // Schedule the actual removal for later in case the listener removal
+    // happens while iterating over the observer list.
+    base::MessageLoop::current()->PostTask(
+        FROM_HERE, base::Bind(
+            &MDnsClientImpl::Core::CleanupObserverList, AsWeakPtr(), key));
+  }
+}
+
+void MDnsClientImpl::Core::CleanupObserverList(const ListenerKey& key) {
+  ListenerMap::iterator found = listeners_.find(key);
+  if (found != listeners_.end() && found->second->size() == 0) {
+    delete found->second;
+    listeners_.erase(found);
   }
 }
 
@@ -370,10 +423,7 @@
 void MDnsClientImpl::Core::OnRecordRemoved(
     const RecordParsed* record) {
   AlertListeners(MDnsListener::RECORD_REMOVED,
-                 ListenerKey(record->type(), record->name()), record);
-  // Alert listeners listening only for rrtype and not for name.
-  AlertListeners(MDnsListener::RECORD_REMOVED, ListenerKey(record->type(), ""),
-                 record);
+                 ListenerKey(record->name(), record->type()), record);
 }
 
 void MDnsClientImpl::Core::QueryCache(
@@ -481,6 +531,11 @@
   delegate_->OnRecordUpdate(update_type, record);
 }
 
+void MDnsListenerImpl::AlertNsecRecord() {
+  DCHECK(started_);
+  delegate_->OnNsecRecord(name_, rrtype_);
+}
+
 MDnsTransactionImpl::MDnsTransactionImpl(
     uint16 rrtype,
     const std::string& name,
@@ -541,8 +596,12 @@
   // the callback can delete the transaction.
   MDnsTransaction::ResultCallback callback = callback_;
 
-  if (flags_ & MDnsTransaction::SINGLE_RESULT)
+  // Reset the transaction if it expects a single result, or if the result
+  // is a final one (everything except for a record).
+  if (flags_ & MDnsTransaction::SINGLE_RESULT ||
+      result != MDnsTransaction::RESULT_RECORD) {
     Reset();
+  }
 
   callback.Run(result, record);
 }
@@ -563,17 +622,11 @@
 
 void MDnsTransactionImpl::SignalTransactionOver() {
   DCHECK(started_);
-  base::WeakPtr<MDnsTransactionImpl> weak_this = AsWeakPtr();
-
   if (flags_ & MDnsTransaction::SINGLE_RESULT) {
     TriggerCallback(MDnsTransaction::RESULT_NO_RESULTS, NULL);
   } else {
     TriggerCallback(MDnsTransaction::RESULT_DONE, NULL);
   }
-
-  if (weak_this) {
-    weak_this->Reset();
-  }
 }
 
 void MDnsTransactionImpl::ServeRecordsFromCache() {
@@ -587,6 +640,20 @@
       weak_this->TriggerCallback(MDnsTransaction::RESULT_RECORD,
                                  records.front());
     }
+
+#if defined(ENABLE_NSEC)
+    if (records.empty()) {
+      DCHECK(weak_this);
+      client_->core()->QueryCache(dns_protocol::kTypeNSEC, name_, &records);
+      if (!records.empty()) {
+        const NsecRecordRdata* rdata =
+            records.front()->rdata<NsecRecordRdata>();
+        DCHECK(rdata);
+        if (!rdata->GetBit(rrtype_))
+          weak_this->TriggerCallback(MDnsTransaction::RESULT_NSEC, NULL);
+      }
+    }
+#endif
   }
 }
 
@@ -610,7 +677,7 @@
 }
 
 void MDnsTransactionImpl::OnNsecRecord(const std::string& name, unsigned type) {
-  // TODO(noamsml): NSEC records not yet implemented
+  TriggerCallback(RESULT_NSEC, NULL);
 }
 
 void MDnsTransactionImpl::OnCachePurged() {
diff --git a/net/dns/mdns_client_impl.h b/net/dns/mdns_client_impl.h
index cb6a0ae8f..667632b 100644
--- a/net/dns/mdns_client_impl.h
+++ b/net/dns/mdns_client_impl.h
@@ -99,8 +99,10 @@
 
 class MDnsClientImpl : public MDnsClient {
  public:
-  // The core object exists while the MDnsClient is listening, and is
-  // deleted whenever the number of listeners reaches zero.
+  // The core object exists while the MDnsClient is listening, and is deleted
+  // whenever the number of listeners reaches zero. The deletion happens
+  // asychronously, so destroying the last listener does not immediately
+  // invalidate the core.
   class Core : public base::SupportsWeakPtr<Core>, MDnsConnection::Delegate {
    public:
     Core(MDnsClientImpl* client,
@@ -113,8 +115,7 @@
     // Send a query with a specific rrtype and name. Returns true on success.
     bool SendQuery(uint16 rrtype, std::string name);
 
-    // Add/remove a listener to the list of listener. May cause network traffic
-    // if listener is active.
+    // Add/remove a listener to the list of listeners.
     void AddListener(MDnsListenerImpl* listener);
     void RemoveListener(MDnsListenerImpl* listener);
 
@@ -128,7 +129,7 @@
     virtual void OnConnectionError(int error) OVERRIDE;
 
    private:
-    typedef std::pair<uint16, std::string> ListenerKey;
+    typedef std::pair<std::string, uint16> ListenerKey;
     typedef std::map<ListenerKey, ObserverList<MDnsListenerImpl>* >
     ListenerMap;
 
@@ -145,6 +146,12 @@
     // Callback for when a record is removed from the cache.
     void OnRecordRemoved(const RecordParsed* record);
 
+    void NotifyNsecRecord(const RecordParsed* record);
+
+    // Delete and erase the observer list for |key|. Only deletes the observer
+    // list if is empty.
+    void CleanupObserverList(const ListenerKey& key);
+
     ListenerMap listeners_;
 
     MDnsClientImpl* client_;
@@ -220,6 +227,10 @@
   // Alert the delegate of a record update.
   void AlertDelegate(MDnsListener::UpdateType update_type,
                      const RecordParsed* record_parsed);
+
+  // Alert the delegate of the existence of an Nsec record.
+  void AlertNsecRecord();
+
  private:
   uint16 rrtype_;
   std::string name_;
diff --git a/net/dns/mdns_client_unittest.cc b/net/dns/mdns_client_unittest.cc
index 83290894..ee211ef8 100644
--- a/net/dns/mdns_client_unittest.cc
+++ b/net/dns/mdns_client_unittest.cc
@@ -264,6 +264,52 @@
   0xc0, 0x0c,
 };
 
+const char kSamplePacketNsec[] = {
+  // Header
+  0x00, 0x00,               // ID is zeroed out
+  0x81, 0x80,               // Standard query response, RA, no error
+  0x00, 0x00,               // No questions (for simplicity)
+  0x00, 0x01,               // 1 RR (answers)
+  0x00, 0x00,               // 0 authority RRs
+  0x00, 0x00,               // 0 additional RRs
+
+  // Answer 1
+  0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+  0x04, '_', 't', 'c', 'p',
+  0x05, 'l', 'o', 'c', 'a', 'l',
+  0x00,
+  0x00, 0x2f,        // TYPE is NSEC.
+  0x00, 0x01,        // CLASS is IN.
+  0x00, 0x01,        // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+  0x24, 0x74,
+  0x00, 0x06,        // RDLENGTH is 6 bytes.
+  0xc0, 0x0c,
+  0x00, 0x02, 0x00, 0x08  // Only A record present
+};
+
+const char kSamplePacketAPrivet[] = {
+  // Header
+  0x00, 0x00,               // ID is zeroed out
+  0x81, 0x80,               // Standard query response, RA, no error
+  0x00, 0x00,               // No questions (for simplicity)
+  0x00, 0x01,               // 1 RR (answers)
+  0x00, 0x00,               // 0 authority RRs
+  0x00, 0x00,               // 0 additional RRs
+
+  // Answer 1
+  0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
+  0x04, '_', 't', 'c', 'p',
+  0x05, 'l', 'o', 'c', 'a', 'l',
+  0x00,
+  0x00, 0x01,        // TYPE is NSEC.
+  0x00, 0x01,        // CLASS is IN.
+  0x00, 0x01,        // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds.
+  0x24, 0x74,
+  0x00, 0x04,        // RDLENGTH is 4 bytes.
+  0xc0, 0x0c,
+  0x00, 0x02,
+};
+
 class PtrRecordCopyContainer {
  public:
   PtrRecordCopyContainer() {}
@@ -392,7 +438,6 @@
 TEST_F(MDnsTest, PassiveListeners) {
   StrictMock<MockListenerDelegate> delegate_privet;
   StrictMock<MockListenerDelegate> delegate_printer;
-  StrictMock<MockListenerDelegate> delegate_ptr;
 
   PtrRecordCopyContainer record_privet;
   PtrRecordCopyContainer record_printer;
@@ -401,12 +446,9 @@
       dns_protocol::kTypePTR, "_privet._tcp.local", &delegate_privet);
   scoped_ptr<MDnsListener> listener_printer = test_client_->CreateListener(
       dns_protocol::kTypePTR, "_printer._tcp.local", &delegate_printer);
-  scoped_ptr<MDnsListener> listener_ptr = test_client_->CreateListener(
-      dns_protocol::kTypePTR, "", &delegate_ptr);
 
   ASSERT_TRUE(listener_privet->Start());
   ASSERT_TRUE(listener_printer->Start());
-  ASSERT_TRUE(listener_ptr->Start());
 
   ASSERT_TRUE(test_client_->IsListeningForTests());
 
@@ -424,8 +466,6 @@
           &record_printer,
           &PtrRecordCopyContainer::SaveWithDummyArg));
 
-  EXPECT_CALL(delegate_ptr, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
-      .Times(Exactly(2));
 
   SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
   SimulatePacketReceive(kSamplePacket1, sizeof(kSamplePacket1));
@@ -440,11 +480,6 @@
   listener_printer.reset();
 
   ASSERT_TRUE(test_client_->IsListeningForTests());
-
-  EXPECT_CALL(delegate_ptr, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
-      .Times(Exactly(2));
-
-  SimulatePacketReceive(kSamplePacket2, sizeof(kSamplePacket2));
 }
 
 TEST_F(MDnsTest, PassiveListenersCacheCleanup) {
@@ -834,6 +869,119 @@
   EXPECT_EQ("2.3.4.5", IPAddressToString(address));
 }
 
+TEST_F(MDnsTest, NsecWithListener) {
+  StrictMock<MockListenerDelegate> delegate_privet;
+  scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+      dns_protocol::kTypeA, "_privet._tcp.local", &delegate_privet);
+
+  // Test to make sure nsec callback is NOT called for PTR
+  // (which is marked as existing).
+  StrictMock<MockListenerDelegate> delegate_privet2;
+  scoped_ptr<MDnsListener> listener_privet2 = test_client_->CreateListener(
+      dns_protocol::kTypePTR, "_privet._tcp.local", &delegate_privet2);
+
+  ASSERT_TRUE(listener_privet->Start());
+
+  EXPECT_CALL(delegate_privet,
+              OnNsecRecord("_privet._tcp.local", dns_protocol::kTypeA));
+
+  SimulatePacketReceive(kSamplePacketNsec,
+                        sizeof(kSamplePacketNsec));
+}
+
+TEST_F(MDnsTest, NsecWithTransactionFromNetwork) {
+  scoped_ptr<MDnsTransaction> transaction_privet =
+      test_client_->CreateTransaction(
+          dns_protocol::kTypeA, "_privet._tcp.local",
+          MDnsTransaction::QUERY_NETWORK |
+          MDnsTransaction::QUERY_CACHE |
+          MDnsTransaction::SINGLE_RESULT,
+          base::Bind(&MDnsTest::MockableRecordCallback,
+                     base::Unretained(this)));
+
+  EXPECT_CALL(*socket_factory_, OnSendTo(_))
+      .Times(2);
+
+  ASSERT_TRUE(transaction_privet->Start());
+
+  EXPECT_CALL(*this,
+              MockableRecordCallback(MDnsTransaction::RESULT_NSEC, NULL));
+
+  SimulatePacketReceive(kSamplePacketNsec,
+                        sizeof(kSamplePacketNsec));
+}
+
+TEST_F(MDnsTest, NsecWithTransactionFromCache) {
+  // Force mDNS to listen.
+  StrictMock<MockListenerDelegate> delegate_irrelevant;
+  scoped_ptr<MDnsListener> listener_irrelevant =
+      test_client_->CreateListener(dns_protocol::kTypePTR, "_privet._tcp.local",
+                                   &delegate_irrelevant);
+  listener_irrelevant->Start();
+
+  SimulatePacketReceive(kSamplePacketNsec,
+                        sizeof(kSamplePacketNsec));
+
+  EXPECT_CALL(*this,
+              MockableRecordCallback(MDnsTransaction::RESULT_NSEC, NULL));
+
+  scoped_ptr<MDnsTransaction> transaction_privet_a =
+      test_client_->CreateTransaction(
+          dns_protocol::kTypeA, "_privet._tcp.local",
+          MDnsTransaction::QUERY_NETWORK |
+          MDnsTransaction::QUERY_CACHE |
+          MDnsTransaction::SINGLE_RESULT,
+          base::Bind(&MDnsTest::MockableRecordCallback,
+                     base::Unretained(this)));
+
+  ASSERT_TRUE(transaction_privet_a->Start());
+
+  // Test that a PTR transaction does NOT consider the same NSEC record to be a
+  // valid answer to the query
+
+  scoped_ptr<MDnsTransaction> transaction_privet_ptr =
+      test_client_->CreateTransaction(
+          dns_protocol::kTypePTR, "_privet._tcp.local",
+          MDnsTransaction::QUERY_NETWORK |
+          MDnsTransaction::QUERY_CACHE |
+          MDnsTransaction::SINGLE_RESULT,
+          base::Bind(&MDnsTest::MockableRecordCallback,
+                     base::Unretained(this)));
+
+  EXPECT_CALL(*socket_factory_, OnSendTo(_))
+      .Times(2);
+
+  ASSERT_TRUE(transaction_privet_ptr->Start());
+}
+
+TEST_F(MDnsTest, NsecConflictRemoval) {
+  StrictMock<MockListenerDelegate> delegate_privet;
+  scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
+      dns_protocol::kTypeA, "_privet._tcp.local", &delegate_privet);
+
+  ASSERT_TRUE(listener_privet->Start());
+
+  const RecordParsed* record1;
+  const RecordParsed* record2;
+
+  EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_ADDED, _))
+      .WillOnce(SaveArg<1>(&record1));
+
+  SimulatePacketReceive(kSamplePacketAPrivet,
+                        sizeof(kSamplePacketAPrivet));
+
+  EXPECT_CALL(delegate_privet, OnRecordUpdate(MDnsListener::RECORD_REMOVED, _))
+      .WillOnce(SaveArg<1>(&record2));
+
+  EXPECT_CALL(delegate_privet,
+              OnNsecRecord("_privet._tcp.local", dns_protocol::kTypeA));
+
+  SimulatePacketReceive(kSamplePacketNsec,
+                        sizeof(kSamplePacketNsec));
+
+  EXPECT_EQ(record1, record2);
+}
+
 
 // Note: These tests assume that the ipv4 socket will always be created first.
 // This is a simplifying assumption based on the way the code works now.