Chromium Code Reviews| Index: net/dns/mdns_listener_unittest.cc |
| diff --git a/net/dns/mdns_listener_unittest.cc b/net/dns/mdns_listener_unittest.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..a6ec70833182992df8ea10af7f8e238e3d4bb746 |
| --- /dev/null |
| +++ b/net/dns/mdns_listener_unittest.cc |
| @@ -0,0 +1,840 @@ |
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "base/memory/ref_counted.h" |
| +#include "base/message_loop.h" |
| +#include "net/base/rand_callback.h" |
| +#include "net/base/test_completion_callback.h" |
| +#include "net/dns/mdns_listener_impl.h" |
| +#include "net/dns/record_rdata.h" |
| +#include "net/test/fake_time_system.h" |
| +#include "net/udp/udp_client_socket.h" |
| +#include "testing/gmock/include/gmock/gmock.h" |
| +#include "testing/gtest/include/gtest/gtest.h" |
| + |
| +using ::testing::Invoke; |
| +using ::testing::InvokeWithoutArgs; |
| +using ::testing::StrictMock; |
| +using ::testing::Exactly; |
| +using ::testing::_; |
| + |
| +namespace net { |
| + |
| +namespace { |
| + |
| +const char kSamplePacket1[] = { |
| + // Header |
| + 0x00, 0x00, // ID is zeroed out |
| + 0x81, 0x80, // Standard query response, RA, no error |
| + 0x00, 0x00, // No questions (for simplicity) |
| + 0x00, 0x02, // 2 RRs (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, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| + 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds. |
| + 0x24, 0x74, |
| + 0x00, 0x08, // RDLENGTH is 8 bytes. |
| + 0x05, 'h', 'e', 'l', 'l', 'o', |
| + 0xc0, 0x0c, |
| + |
| + // Answer 2 |
| + 0x08, '_', 'p', 'r', 'i', 'n', 't', 'e', 'r', |
| + 0xc0, 0x14, // Pointer to "._tcp.local" |
| + 0x00, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| + 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 49 seconds. |
| + 0x24, 0x75, |
| + 0x00, 0x08, // RDLENGTH is 8 bytes. |
| + 0x05, 'h', 'e', 'l', 'l', 'o', |
| + 0xc0, 0x32 |
| + }; |
| + |
| +const char kCorruptedPacketBadQuestion[] = { |
| + // Header |
| + 0x00, 0x00, // ID is zeroed out |
| + 0x81, 0x80, // Standard query response, RA, no error |
| + 0x00, 0x01, // One question |
| + 0x00, 0x02, // 2 RRs (answers) |
| + 0x00, 0x00, // 0 authority RRs |
| + 0x00, 0x00, // 0 additional RRs |
| + |
| + // Question is corrupted and cannot be read. |
| + 0x99, 'h', 'e', 'l', 'l', 'o', |
| + 0x00, |
| + 0x00, 0x00, |
| + 0x00, 0x00, |
| + |
| + // Answer 1 |
| + 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't', |
| + 0x04, '_', 't', 'c', 'p', |
| + 0x05, 'l', 'o', 'c', 'a', 'l', |
| + 0x00, |
| + 0x00, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| + 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds. |
| + 0x24, 0x74, |
| + 0x00, 0x99, // RDLENGTH is impossible |
| + 0x05, 'h', 'e', 'l', 'l', 'o', |
| + 0xc0, 0x0c, |
| + |
| + // Answer 2 |
| + 0x08, '_', 'p', 'r', // Useless trailing data. |
| +}; |
| + |
| +const char kCorruptedPacketUnsalvagable[] = { |
| + // Header |
| + 0x00, 0x00, // ID is zeroed out |
| + 0x81, 0x80, // Standard query response, RA, no error |
| + 0x00, 0x00, // No questions (for simplicity) |
| + 0x00, 0x02, // 2 RRs (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, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| + 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds. |
| + 0x24, 0x74, |
| + 0x00, 0x99, // RDLENGTH is impossible |
| + 0x05, 'h', 'e', 'l', 'l', 'o', |
| + 0xc0, 0x0c, |
| + |
| + // Answer 2 |
| + 0x08, '_', 'p', 'r', // Useless trailing data. |
| +}; |
| + |
| +const char kCorruptedPacketSalvagable[] = { |
| + // Header |
| + 0x00, 0x00, // ID is zeroed out |
| + 0x81, 0x80, // Standard query response, RA, no error |
| + 0x00, 0x00, // No questions (for simplicity) |
| + 0x00, 0x02, // 2 RRs (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, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| + 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds. |
| + 0x24, 0x74, |
| + 0x00, 0x08, // RDLENGTH is 8 bytes. |
| + 0x99, 'h', 'e', 'l', 'l', 'o', // Bad RDATA format. |
| + 0xc0, 0x0c, |
| + |
| + // Answer 2 |
| + 0x08, '_', 'p', 'r', 'i', 'n', 't', 'e', 'r', |
| + 0xc0, 0x14, // Pointer to "._tcp.local" |
| + 0x00, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| + 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 49 seconds. |
| + 0x24, 0x75, |
| + 0x00, 0x08, // RDLENGTH is 8 bytes. |
| + 0x05, 'h', 'e', 'l', 'l', 'o', |
| + 0xc0, 0x32 |
| +}; |
| + |
| +const char kSamplePacket2[] = { |
| + // Header |
| + 0x00, 0x00, // ID is zeroed out |
| + 0x81, 0x80, // Standard query response, RA, no error |
| + 0x00, 0x00, // No questions (for simplicity) |
| + 0x00, 0x02, // 2 RRs (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, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| + 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds. |
| + 0x24, 0x74, |
| + 0x00, 0x08, // RDLENGTH is 8 bytes. |
| + 0x05, 'z', 'z', 'z', 'z', 'z', |
| + 0xc0, 0x0c, |
| + |
| + // Answer 2 |
| + 0x08, '_', 'p', 'r', 'i', 'n', 't', 'e', 'r', |
| + 0xc0, 0x14, // Pointer to "._tcp.local" |
| + 0x00, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| + 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds. |
| + 0x24, 0x74, |
| + 0x00, 0x08, // RDLENGTH is 8 bytes. |
| + 0x05, 'z', 'z', 'z', 'z', 'z', |
| + 0xc0, 0x32 |
| + }; |
| + |
| +const char kQueryPacketPrivet[] = { |
| + // Header |
| + 0x00, 0x00, // ID is zeroed out |
| + 0x00, 0x00, // Standard query. |
| + 0x00, 0x01, // One question. |
| + 0x00, 0x00, // 0 RRs (answers) |
| + 0x00, 0x00, // 0 authority RRs |
| + 0x00, 0x00, // 0 additional RRs |
| + |
| + // Question |
| + // This part is echoed back from the respective query. |
| + 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't', |
| + 0x04, '_', 't', 'c', 'p', |
| + 0x05, 'l', 'o', 'c', 'a', 'l', |
| + 0x00, |
| + 0x00, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| +}; |
| + |
| +const char kSamplePacketAdditionalOnly[] = { |
| + // Header |
| + 0x00, 0x00, // ID is zeroed out |
| + 0x81, 0x80, // Standard query response, RA, no error |
| + 0x00, 0x00, // No questions (for simplicity) |
| + 0x00, 0x00, // 2 RRs (answers) |
| + 0x00, 0x00, // 0 authority RRs |
| + 0x00, 0x01, // 0 additional RRs |
| + |
| + // Answer 1 |
| + 0x07, '_', 'p', 'r', 'i', 'v', 'e', 't', |
| + 0x04, '_', 't', 'c', 'p', |
| + 0x05, 'l', 'o', 'c', 'a', 'l', |
| + 0x00, |
| + 0x00, 0x0c, // TYPE is PTR. |
| + 0x00, 0x01, // CLASS is IN. |
| + 0x00, 0x01, // TTL (4 bytes) is 20 hours, 47 minutes, 48 seconds. |
| + 0x24, 0x74, |
| + 0x00, 0x08, // RDLENGTH is 8 bytes. |
| + 0x05, 'h', 'e', 'l', 'l', 'o', |
| + 0xc0, 0x0c, |
| + }; |
| + |
| + |
| +// Connect to any port on an address. Returns the bound address. |
| +IPEndPoint BindToAnyPort(UDPSocket* socket, |
| + const IPAddressNumber& address) { |
| + IPEndPoint endpoint(address, 0); |
| + int status = socket->Bind(endpoint); |
| + DCHECK(status == OK); |
| + status = socket->GetLocalAddress(&endpoint); |
| + DCHECK(status == OK); |
| + |
| + return endpoint; |
| +} |
| + |
| +class TestableMDnsListenerFactoryImpl : public MDnsListenerFactoryImpl { |
| + public: |
| + explicit TestableMDnsListenerFactoryImpl(FakeTimeSystem* time_system) |
| + : MDnsListenerFactoryImpl(time_system, time_system) { |
| + } |
| + |
| + void SetIPv4SendEndpoint(const IPEndPoint& ipv4_send_endpoint) { |
| + ipv4_send_endpoint_ = ipv4_send_endpoint; |
| + } |
| + |
| + void SetIPv6SendEndpoint(const IPEndPoint& ipv6_send_endpoint) { |
| + ipv6_send_endpoint_ = ipv6_send_endpoint; |
| + } |
| + |
| + IPEndPoint ipv4_bind_endpoint() { return ipv4_bind_endpoint_; } |
| + |
| + protected: |
| + virtual IPEndPoint GetIPv4SendEndpoint() OVERRIDE { |
| + return ipv4_send_endpoint_; |
| + } |
| + |
| + virtual IPEndPoint GetIPv6SendEndpoint() OVERRIDE { |
| + return ipv6_send_endpoint_; |
| + } |
| + |
| + virtual bool BindToAddressAndMulticastGroup( |
| + UDPSocket* socket, |
| + const IPEndPoint& address, |
| + const IPAddressNumber& group) OVERRIDE { |
| + // Bind only to local address. Use address length to determine protocol. |
| + IPAddressNumber local_address; |
| + if (address.address().size() == kIPv4AddressSize) { |
| + bool success = ParseIPLiteralToNumber("127.0.0.1", |
| + &local_address); |
| + DCHECK(success); |
| + } else { |
| + DCHECK(address.address().size() == kIPv6AddressSize); |
| + bool success = ParseIPLiteralToNumber("::1", |
| + &local_address); |
| + DCHECK(success); |
| + } |
| + |
| + IPEndPoint bound_endpoint = BindToAnyPort(socket, local_address); |
| + if (address.address().size() == kIPv4AddressSize) { |
| + ipv4_bind_endpoint_ = bound_endpoint; |
| + } |
| + return true; |
| + } |
| + |
| + private: |
| + IPEndPoint ipv4_send_endpoint_; |
| + IPEndPoint ipv6_send_endpoint_; |
| + IPEndPoint ipv4_bind_endpoint_; |
| +}; |
| + |
| +class RecordParsedCopyContainer { |
| + public: |
| + RecordParsedCopyContainer() {} |
| + ~RecordParsedCopyContainer() {} |
| + |
| + bool is_set() const { return value_.get() != NULL; } |
| + |
| + void SaveWithDummyArg(int unused, const RecordParsed* value) { |
| + Save(value); |
| + } |
| + |
| + void Save(const RecordParsed* value) { |
| + value_ = value->Clone(); |
| + } |
| + |
| + const RecordParsed* operator->() const { |
| + return value_.get(); |
| + } |
| + |
| + private: |
| + scoped_ptr<const RecordParsed> value_; |
| +}; |
| + |
| +class MDnsTest : public ::testing::Test { |
| + public: |
| + MDnsTest(); |
| + virtual ~MDnsTest(); |
| + virtual void SetUp() OVERRIDE; |
| + virtual void TearDown() OVERRIDE; |
| + void DeleteTransaction(); |
| + |
| + MOCK_METHOD2(MockableRecordCallback, void(MDnsTransactionResult, |
| + const RecordParsed*)); |
| + |
| + protected: |
| + void ExpectPacket(const char* packet, unsigned size); |
| + void SendPacket(const char* packet, unsigned size); |
| + void ExpectPtrRecord( |
| + const std::string& name, |
| + const std::string& ptrdomain, |
| + const RecordParsedCopyContainer& record); |
| + |
| + base::MessageLoop* message_loop_; |
| + scoped_ptr<TestableMDnsListenerFactoryImpl> test_factory_; |
| + scoped_ptr<UDPSocket> client_socket_; |
| + scoped_ptr<UDPSocket> server_socket_; |
| + scoped_ptr<UDPSocket> server_socket6_; |
| + IPEndPoint mdns_ipv4_endpoint_; |
| + scoped_refptr<FakeTimeSystem> time_system_; |
| + |
| + scoped_ptr<MDnsTransaction> transaction_; |
| +}; |
| + |
| +class MockListenerDelegate : public MDnsListenerFactory::Delegate { |
| + public: |
| + MOCK_METHOD2(OnRecordUpdate, |
| + void(MDnsUpdateType, const RecordParsed*)); |
| + MOCK_METHOD2(OnNSecRecord, void(const std::string&, unsigned)); |
| +}; |
| + |
| +MDnsTest::MDnsTest() |
| + : message_loop_(base::MessageLoop::current()), |
| + client_socket_(new UDPSocket(DatagramSocket::DEFAULT_BIND, |
| + net::RandIntCallback(), |
| + NULL, |
| + net::NetLog::Source())), |
|
szym
2013/05/24 15:32:22
To minimize flakiness, we'd test this with MockUDP
Noam Samuel
2013/05/24 21:59:18
Hm. I tend to agree that, in retrospect, using moc
Noam Samuel
2013/05/29 21:25:16
Done.
szym
2013/06/02 19:01:23
I still see UDPSocket rather than DatagramServerSo
Noam Samuel
2013/06/03 17:57:55
DatagramServerSocket doesn't support multicasting
szym
2013/06/03 18:38:30
I think so.
Noam Samuel
2013/06/04 00:08:02
Added support in interface: https://codereview.chr
|
| + server_socket_(new UDPSocket(DatagramSocket::DEFAULT_BIND, |
| + net::RandIntCallback(), |
| + NULL, |
| + net::NetLog::Source())), |
| + server_socket6_(new UDPSocket(DatagramSocket::DEFAULT_BIND, |
| + net::RandIntCallback(), |
| + NULL, |
| + net::NetLog::Source())) { |
| + time_system_ = new FakeTimeSystem(); |
| + time_system_->SetNow(base::Time::FromDoubleT(1234.0)); |
| + test_factory_.reset(new TestableMDnsListenerFactoryImpl(time_system_)); |
| +} |
| + |
| +MDnsTest::~MDnsTest() { |
| +} |
| + |
| +void MDnsTest::SetUp() { |
| + IPAddressNumber loopback4; |
| + ParseIPLiteralToNumber("127.0.0.1", &loopback4); |
| + |
| + IPAddressNumber loopback6; |
| + ParseIPLiteralToNumber("::1", &loopback6); |
| + |
| + test_factory_->SetIPv4SendEndpoint( |
| + BindToAnyPort(server_socket_.get(), loopback4)); |
| + test_factory_->SetIPv6SendEndpoint( |
| + BindToAnyPort(server_socket6_.get(), loopback6)); |
| + |
| + client_socket_->Bind(IPEndPoint(loopback4, 0)); |
| +} |
| + |
| +void MDnsTest::TearDown() { |
| + client_socket_->Close(); |
|
szym
2013/05/24 15:32:22
Not necessary. ~MDnsTest (called immediately after
Noam Samuel
2013/05/29 21:25:16
Done.
|
| +} |
| + |
| +void MDnsTest::SendPacket(const char* packet, unsigned size) { |
| + TestCompletionCallback write_callback; |
| + |
| + scoped_refptr<IOBufferWithSize> buffer(new IOBufferWithSize(size)); |
| + memcpy(buffer->data(), packet, size); |
| + |
| + int socket_result = |
| + client_socket_->SendTo(buffer, |
| + size, |
| + test_factory_->ipv4_bind_endpoint(), |
| + write_callback.callback()); |
| + |
| + if (socket_result == ERR_IO_PENDING) { |
| + socket_result = write_callback.WaitForResult(); |
| + } |
| + |
| + ASSERT_EQ(size, static_cast<unsigned>(socket_result)); |
| + |
| + // Since the network layer uses MessageLoop, we RunUntilIdle here |
| + // so the tests do not have to. |
| + MessageLoop::current()->RunUntilIdle(); |
| +} |
| + |
| +void MDnsTest::ExpectPtrRecord( |
| + const std::string& name, |
| + const std::string& ptrdomain, |
| + const RecordParsedCopyContainer& record) { |
| + EXPECT_TRUE(record.is_set()); |
| + EXPECT_EQ(name, record->name()); |
| + EXPECT_EQ(dns_protocol::kTypePTR, record->type()); |
| + EXPECT_EQ(dns_protocol::kClassIN, record->klass()); |
| + const PtrRecordRdata* rdata = record->rdata<PtrRecordRdata>(); |
| + EXPECT_TRUE(rdata != NULL); |
| + EXPECT_EQ(ptrdomain, rdata->ptrdomain()); |
| +} |
| + |
| +void MDnsTest::ExpectPacket( |
| + const char* packet, |
| + unsigned size) { |
| + TestCompletionCallback recv_callback; |
| + IPEndPoint recv_endpoint; |
| + scoped_refptr<IOBufferWithSize> buf = new IOBufferWithSize(size); |
| + int rv; |
| + |
| + do { |
| + rv = server_socket_->RecvFrom(buf.get(), buf->size(), |
| + &recv_endpoint, recv_callback.callback()); |
| + |
| + if (rv == ERR_IO_PENDING) { |
| + rv = recv_callback.WaitForResult(); |
| + } |
| + DCHECK_GT(rv, 0); |
| + // This is an easy way to compare to addresses for a test. |
| + } while (recv_endpoint.ToString() != |
| + test_factory_->ipv4_bind_endpoint().ToString()); |
| + |
| + ASSERT_EQ(static_cast<int>(size), rv); |
| + ASSERT_EQ(0, memcmp(packet, buf->data(), size)); |
| +} |
| + |
| +void MDnsTest::DeleteTransaction() { |
| + transaction_.reset(); |
| +} |
| + |
| +TEST_F(MDnsTest, PassiveListeners) { |
| + StrictMock<MockListenerDelegate> delegate_privet; |
| + StrictMock<MockListenerDelegate> delegate_printer; |
| + StrictMock<MockListenerDelegate> delegate_ptr; |
| + |
| + RecordParsedCopyContainer record_privet; |
| + RecordParsedCopyContainer record_printer; |
| + |
| + scoped_ptr<MDnsListener> listener_privet = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false, |
| + &delegate_privet); |
| + scoped_ptr<MDnsListener> listener_printer = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_printer._tcp.local", false, false, |
| + &delegate_printer); |
| + scoped_ptr<MDnsListener> listener_ptr = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "", false, false, &delegate_ptr); |
| + |
| + ASSERT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + // Send the same packet twice to ensure no records are double-counted. |
| + |
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1)); |
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1)); |
| + |
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke( |
| + &record_privet, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + EXPECT_CALL(delegate_printer, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke( |
| + &record_printer, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + EXPECT_CALL(delegate_ptr, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(2)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local", |
| + record_privet); |
| + |
| + ExpectPtrRecord("_printer._tcp.local", "hello._printer._tcp.local", |
| + record_printer); |
| + |
| + listener_privet.reset(); |
| + listener_printer.reset(); |
| + |
| + ASSERT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + EXPECT_CALL(delegate_ptr, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(2)); |
| + |
| + SendPacket(kSamplePacket2, sizeof(kSamplePacket2)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + // Test to make sure mdns listener is not active with no listeners present. |
| + listener_ptr.reset(); |
| + |
| + ASSERT_FALSE(test_factory_->IsListeningForTests()); |
| +} |
| + |
| +TEST_F(MDnsTest, PassiveListenersCleanup) { |
| + StrictMock<MockListenerDelegate> delegate_privet; |
| + |
| + RecordParsedCopyContainer record_privet; |
| + RecordParsedCopyContainer record_privet2; |
| + |
| + scoped_ptr<MDnsListener> listener_privet = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false, |
| + &delegate_privet); |
| + |
| + ASSERT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1)); |
| + |
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke( |
| + &record_privet, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local", |
| + record_privet); |
| + |
| + base::Time expiration = time_system_->Now() + base::TimeDelta::FromSeconds( |
| + record_privet->ttl()); |
| + |
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordRemoved, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke( |
| + &record_privet2, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + time_system_->SetNow(expiration); |
| + time_system_->RunPendingTasks(); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local", |
| + record_privet2); |
| +} |
| + |
| +TEST_F(MDnsTest, PassiveListenersNotifyExisting) { |
| + StrictMock<MockListenerDelegate> delegate_privet; |
| + StrictMock<MockListenerDelegate> delegate_privet2; |
| + |
| + RecordParsedCopyContainer record_privet; |
| + RecordParsedCopyContainer record_privet2; |
| + |
| + scoped_ptr<MDnsListener> listener_privet = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false, |
| + &delegate_privet); |
| + |
| + ASSERT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1)); |
| + |
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke( |
| + &record_privet, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local", |
| + record_privet); |
| + |
| + scoped_ptr<MDnsListener> listener_privet2 = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, true, |
| + &delegate_privet2); |
| + |
| + EXPECT_CALL(delegate_privet2, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke( |
| + &record_privet2, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local", |
| + record_privet2); |
| +} |
| + |
| +TEST_F(MDnsTest, MalformedPacket) { |
| + StrictMock<MockListenerDelegate> delegate_printer; |
| + |
| + RecordParsedCopyContainer record_printer; |
| + |
| + scoped_ptr<MDnsListener> listener_printer = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_printer._tcp.local", false, false, |
| + &delegate_printer); |
| + |
| + ASSERT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + // First, send unsalvagable packet to ensure we can deal with it. |
| + SendPacket(kCorruptedPacketUnsalvagable, |
| + sizeof(kCorruptedPacketUnsalvagable)); |
| + |
| + // Regression test: send a packet where the question cannot be read. |
| + SendPacket(kCorruptedPacketBadQuestion, |
| + sizeof(kCorruptedPacketBadQuestion)); |
| + |
| + // Then send salvagable packet to ensure we can extract useful records. |
| + SendPacket(kCorruptedPacketSalvagable, |
| + sizeof(kCorruptedPacketSalvagable)); |
| + |
| + EXPECT_CALL(delegate_printer, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke( |
| + &record_printer, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_printer._tcp.local", "hello._printer._tcp.local", |
| + record_printer); |
| +} |
| + |
| +TEST_F(MDnsTest, QueryCache) { |
| + StrictMock<MockListenerDelegate> delegate_privet; |
| + |
| + RecordParsedCopyContainer record_privet; |
| + std::vector<const RecordParsed*> records_from_cache; |
| + |
| + scoped_ptr<MDnsListener> listener_privet = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false, |
| + &delegate_privet); |
| + |
| + ASSERT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1)); |
| + |
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke( |
| + &record_privet, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local", |
| + record_privet); |
| + |
| + listener_privet->QueryCache(&records_from_cache); |
| + |
| + EXPECT_EQ(1u, records_from_cache.size()); |
| + EXPECT_EQ("_privet._tcp.local", records_from_cache.front()->name()); |
| + EXPECT_EQ(dns_protocol::kTypePTR, records_from_cache.front()->type()); |
| + EXPECT_EQ(dns_protocol::kClassIN, records_from_cache.front()->klass()); |
| + |
| + const PtrRecordRdata* ptr_rdata = |
| + records_from_cache.front()->rdata<PtrRecordRdata>(); |
| + |
| + EXPECT_TRUE(ptr_rdata != NULL); |
| + |
| + EXPECT_EQ("hello._privet._tcp.local", |
| + ptr_rdata->ptrdomain()); |
| +} |
| + |
| +TEST_F(MDnsTest, Query) { |
| + StrictMock<MockListenerDelegate> delegate_privet; |
| + scoped_ptr<MDnsListener> listener_privet = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false, |
| + &delegate_privet); |
| + |
| + ASSERT_TRUE(listener_privet->SendQuery(false)); |
| + |
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet)); |
| + |
| + // Active listeners should send queries when created. |
| + |
| + scoped_ptr<MDnsListener> listener_privet2 = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", true /*active*/, |
| + false /*existing*/, &delegate_privet); |
| + |
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet)); |
| +} |
| + |
| +TEST_F(MDnsTest, TransactionNoCache) { |
| + scoped_ptr<MDnsTransaction> transaction_privet = |
| + test_factory_->CreateTransaction( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", |
| + base::Bind(&MDnsTest::MockableRecordCallback, |
| + base::Unretained(this))); |
| + |
| + EXPECT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + RecordParsedCopyContainer record_privet; |
| + |
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet)); |
| + |
| + EXPECT_CALL(*this, MockableRecordCallback(kMDnsTransactionSuccess, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke(&record_privet, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local", |
| + record_privet); |
| + |
| + EXPECT_FALSE(test_factory_->IsListeningForTests()); |
| +} |
| + |
| +TEST_F(MDnsTest, TransactionWithCache) { |
| + // Listener to force the factory to listen |
| + StrictMock<MockListenerDelegate> delegate_irrelevant; |
| + scoped_ptr<MDnsListener> listener_irrelevant = test_factory_->CreateListener( |
| + dns_protocol::kTypeA, "codereview.chromium.local", false, false, |
| + &delegate_irrelevant); |
| + |
| + EXPECT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + RecordParsedCopyContainer record_privet; |
| + |
| + EXPECT_CALL(*this, MockableRecordCallback(kMDnsTransactionSuccess, _)) |
| + .WillOnce(Invoke(&record_privet, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + scoped_ptr<MDnsTransaction> transaction_privet = |
| + test_factory_->CreateTransaction( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", |
| + base::Bind(&MDnsTest::MockableRecordCallback, |
| + base::Unretained(this))); |
| + |
| + // "Expect no packet"? |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local", |
| + record_privet); |
| +} |
| + |
| +TEST_F(MDnsTest, AdditionalRecords) { |
| + StrictMock<MockListenerDelegate> delegate_privet; |
| + |
| + RecordParsedCopyContainer record_privet; |
| + |
| + scoped_ptr<MDnsListener> listener_privet = test_factory_->CreateListener( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false, |
| + &delegate_privet); |
| + |
| + ASSERT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + // Send the same packet twice to ensure no records are double-counted. |
| + |
| + SendPacket(kSamplePacketAdditionalOnly, sizeof(kSamplePacket1)); |
| + |
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _)) |
| + .Times(Exactly(1)) |
| + .WillOnce(Invoke( |
| + &record_privet, |
| + &RecordParsedCopyContainer::SaveWithDummyArg)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local", |
| + record_privet); |
| +} |
| + |
| +TEST_F(MDnsTest, TransactionTimeout) { |
| + scoped_ptr<MDnsTransaction> transaction_privet = |
| + test_factory_->CreateTransaction( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", |
| + base::Bind(&MDnsTest::MockableRecordCallback, |
| + base::Unretained(this))); |
| + |
| + EXPECT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet)); |
| + |
| + EXPECT_CALL(*this, MockableRecordCallback(kMDnsTransactionTimeout, NULL)) |
| + .Times(Exactly(1)); |
| + |
| + time_system_->SetNow(time_system_->Now() + base::TimeDelta::FromMinutes(5)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + EXPECT_FALSE(test_factory_->IsListeningForTests()); |
| +} |
| + |
| +TEST_F(MDnsTest, TransactionReentrantDelete) { |
| + transaction_ = test_factory_->CreateTransaction( |
| + dns_protocol::kTypePTR, "_privet._tcp.local", |
| + base::Bind(&MDnsTest::MockableRecordCallback, |
| + base::Unretained(this))); |
| + |
| + EXPECT_TRUE(test_factory_->IsListeningForTests()); |
| + |
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet)); |
| + |
| + EXPECT_CALL(*this, MockableRecordCallback(kMDnsTransactionTimeout, NULL)) |
| + .Times(Exactly(1)) |
| + .WillOnce(InvokeWithoutArgs(this, &MDnsTest::DeleteTransaction)); |
| + |
| + time_system_->SetNow(time_system_->Now() + base::TimeDelta::FromMinutes(5)); |
| + |
| + time_system_->RunPendingTasks(); |
| + |
| + EXPECT_EQ(NULL, transaction_.get()); |
| + |
| + EXPECT_FALSE(test_factory_->IsListeningForTests()); |
| +} |
| + |
| +} // namespace |
| + |
| +} // namespace net |