| Index: net/dns/mdns_client_unittest.cc
|
| diff --git a/net/dns/mdns_client_unittest.cc b/net/dns/mdns_client_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..41af8ac9c2cd6c412524d94fc1c40645553bd12e
|
| --- /dev/null
|
| +++ b/net/dns/mdns_client_unittest.cc
|
| @@ -0,0 +1,872 @@
|
| +// 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_client_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::Return;
|
| +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,
|
| +};
|
| +
|
| +class MockMDnsConnectionFactory;
|
| +
|
| +class MockMDnsConnection : public MDnsConnection {
|
| + public:
|
| + MockMDnsConnection(MDnsConnection::Delegate* delegate,
|
| + MockMDnsConnectionFactory* factory)
|
| + : delegate_(delegate), factory_(factory) {
|
| + }
|
| +
|
| + virtual ~MockMDnsConnection() {
|
| + }
|
| +
|
| + virtual bool Init() OVERRIDE {
|
| + return true;
|
| + }
|
| +
|
| + virtual bool Send(IOBuffer* buffer, unsigned size) OVERRIDE;
|
| +
|
| + void SimulateRecieve(const char* packet, int size);
|
| +
|
| + private:
|
| + DnsResponse response;
|
| + MDnsConnection::Delegate* delegate_;
|
| + MockMDnsConnectionFactory* factory_;
|
| +};
|
| +
|
| +class MockMDnsConnectionFactory : public MDnsConnectionFactory {
|
| + public:
|
| + MockMDnsConnectionFactory() {}
|
| + virtual ~MockMDnsConnectionFactory() {}
|
| + virtual scoped_ptr<MDnsConnection> CreateConnection(
|
| + MDnsConnection::Delegate* delegate,
|
| + base::TaskRunner* task_runner) OVERRIDE;
|
| +
|
| + MOCK_METHOD1(OnSend, void(std::string));
|
| +
|
| + MockMDnsConnection* latest_connection() { return latest_connection_; }
|
| +
|
| + private:
|
| + MockMDnsConnection* latest_connection_;
|
| +};
|
| +
|
| +class MockMDnsConnectionDelegate : public MDnsConnection::Delegate {
|
| + public:
|
| + virtual void HandlePacket(DnsResponse* response, int size) {
|
| + OnHandlePacket(std::string(response->io_buffer()->data(), size));
|
| + }
|
| +
|
| + MOCK_METHOD1(OnHandlePacket, void(std::string));
|
| +};
|
| +
|
| +bool MockMDnsConnection::Send(IOBuffer* buffer, unsigned size) {
|
| + factory_->OnSend(std::string(buffer->data(), size));
|
| + return true;
|
| +}
|
| +
|
| +void MockMDnsConnection::SimulateRecieve(const char* packet, int size) {
|
| + memcpy(response.io_buffer()->data(), packet, size);
|
| + delegate_->HandlePacket(&response, size);
|
| +}
|
| +
|
| +scoped_ptr<MDnsConnection> MockMDnsConnectionFactory::CreateConnection(
|
| + MDnsConnection::Delegate* delegate,
|
| + base::TaskRunner* task_runner) {
|
| + latest_connection_ = new MockMDnsConnection(delegate, this);
|
| +
|
| + return scoped_ptr<MDnsConnection>(latest_connection_);
|
| +}
|
| +
|
| +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();
|
| + void DeleteTransaction();
|
| + void DeleteBothListeners();
|
| +
|
| + 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<MDnsClientImpl> test_client_;
|
| + IPEndPoint mdns_ipv4_endpoint_;
|
| + scoped_refptr<FakeTimeSystem> time_system_;
|
| + StrictMock<MockMDnsConnectionFactory> connection_factory_;
|
| +
|
| + // Transactions and listeners that can be deleted by class methods for
|
| + // reentrancy tests.
|
| + scoped_ptr<MDnsTransaction> transaction_;
|
| + scoped_ptr<MDnsListener> listener1_;
|
| + scoped_ptr<MDnsListener> listener2_;
|
| +};
|
| +
|
| +class MockListenerDelegate : public MDnsListener::Delegate {
|
| + public:
|
| + MOCK_METHOD2(OnRecordUpdate,
|
| + void(MDnsUpdateType, const RecordParsed*));
|
| + MOCK_METHOD2(OnNsecRecord, void(const std::string&, unsigned));
|
| +};
|
| +
|
| +MDnsTest::MDnsTest()
|
| + : message_loop_(base::MessageLoop::current()) {
|
| + time_system_ = new FakeTimeSystem();
|
| + time_system_->SetNow(base::Time::FromDoubleT(1234.0));
|
| + test_client_.reset(new MDnsClientImpl(time_system_, time_system_,
|
| + &connection_factory_));
|
| +}
|
| +
|
| +MDnsTest::~MDnsTest() {
|
| +}
|
| +
|
| +void MDnsTest::SendPacket(const char* packet, unsigned size) {
|
| + connection_factory_.latest_connection()->SimulateRecieve(packet, size);
|
| +}
|
| +
|
| +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) {
|
| + EXPECT_CALL(connection_factory_, OnSend(std::string(packet, size)));
|
| +}
|
| +
|
| +void MDnsTest::DeleteTransaction() {
|
| + transaction_.reset();
|
| +}
|
| +
|
| +void MDnsTest::DeleteBothListeners() {
|
| + listener1_.reset();
|
| + listener2_.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_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false,
|
| + &delegate_privet);
|
| + scoped_ptr<MDnsListener> listener_printer = test_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_printer._tcp.local", false, false,
|
| + &delegate_printer);
|
| + scoped_ptr<MDnsListener> listener_ptr = test_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "", false, false, &delegate_ptr);
|
| +
|
| + ASSERT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + // Send the same packet twice to ensure no records are double-counted.
|
| +
|
| + 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));
|
| +
|
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1));
|
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1));
|
| +
|
| + 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_client_->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();
|
| +
|
| + time_system_->RunPendingTasks();
|
| +
|
| + ASSERT_FALSE(test_client_->IsListeningForTests());
|
| +}
|
| +
|
| +TEST_F(MDnsTest, PassiveListenersCleanup) {
|
| + StrictMock<MockListenerDelegate> delegate_privet;
|
| +
|
| + RecordParsedCopyContainer record_privet;
|
| + RecordParsedCopyContainer record_privet2;
|
| +
|
| + scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false,
|
| + &delegate_privet);
|
| +
|
| + ASSERT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _))
|
| + .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);
|
| +
|
| + 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_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false,
|
| + &delegate_privet);
|
| +
|
| + ASSERT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _))
|
| + .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_CALL(delegate_privet2, OnRecordUpdate(kMDnsRecordAdded, _))
|
| + .Times(Exactly(1))
|
| + .WillOnce(Invoke(
|
| + &record_privet2,
|
| + &RecordParsedCopyContainer::SaveWithDummyArg));
|
| +
|
| + scoped_ptr<MDnsListener> listener_privet2 = test_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, true,
|
| + &delegate_privet2);
|
| +
|
| + 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_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_printer._tcp.local", false, false,
|
| + &delegate_printer);
|
| +
|
| + ASSERT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + EXPECT_CALL(delegate_printer, OnRecordUpdate(kMDnsRecordAdded, _))
|
| + .Times(Exactly(1))
|
| + .WillOnce(Invoke(
|
| + &record_printer,
|
| + &RecordParsedCopyContainer::SaveWithDummyArg));
|
| +
|
| + // 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));
|
| +
|
| + 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_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false,
|
| + &delegate_privet);
|
| +
|
| + ASSERT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _))
|
| + .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);
|
| +
|
| + 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;
|
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
|
| +
|
| + scoped_ptr<MDnsListener> listener_privet = test_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false,
|
| + &delegate_privet);
|
| +
|
| + ASSERT_TRUE(listener_privet->SendQuery(false));
|
| +
|
| + // Active listeners should send queries when created.
|
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
|
| +
|
| + scoped_ptr<MDnsListener> listener_privet2 = test_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", true /*active*/,
|
| + false /*existing*/, &delegate_privet);
|
| +}
|
| +
|
| +TEST_F(MDnsTest, TransactionNoCache) {
|
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
|
| +
|
| + scoped_ptr<MDnsTransaction> transaction_privet =
|
| + test_client_->CreateTransaction(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local",
|
| + base::Bind(&MDnsTest::MockableRecordCallback,
|
| + base::Unretained(this)));
|
| +
|
| + EXPECT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + RecordParsedCopyContainer record_privet;
|
| +
|
| + 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_client_->IsListeningForTests());
|
| +}
|
| +
|
| +TEST_F(MDnsTest, TransactionWithCache) {
|
| + // Listener to force the client to listen
|
| + StrictMock<MockListenerDelegate> delegate_irrelevant;
|
| + scoped_ptr<MDnsListener> listener_irrelevant = test_client_->CreateListener(
|
| + dns_protocol::kTypeA, "codereview.chromium.local", false, false,
|
| + &delegate_irrelevant);
|
| +
|
| + EXPECT_TRUE(test_client_->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_client_->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_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false,
|
| + &delegate_privet);
|
| +
|
| + ASSERT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _))
|
| + .Times(Exactly(1))
|
| + .WillOnce(Invoke(
|
| + &record_privet,
|
| + &RecordParsedCopyContainer::SaveWithDummyArg));
|
| +
|
| + SendPacket(kSamplePacketAdditionalOnly, sizeof(kSamplePacket1));
|
| +
|
| + time_system_->RunPendingTasks();
|
| +
|
| + ExpectPtrRecord("_privet._tcp.local", "hello._privet._tcp.local",
|
| + record_privet);
|
| +}
|
| +
|
| +TEST_F(MDnsTest, TransactionTimeout) {
|
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
|
| +
|
| + scoped_ptr<MDnsTransaction> transaction_privet =
|
| + test_client_->CreateTransaction(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local",
|
| + base::Bind(&MDnsTest::MockableRecordCallback,
|
| + base::Unretained(this)));
|
| +
|
| + EXPECT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + 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_client_->IsListeningForTests());
|
| +}
|
| +
|
| +TEST_F(MDnsTest, TransactionReentrantDelete) {
|
| + ExpectPacket(kQueryPacketPrivet, sizeof(kQueryPacketPrivet));
|
| +
|
| + transaction_ = test_client_->CreateTransaction(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local",
|
| + base::Bind(&MDnsTest::MockableRecordCallback,
|
| + base::Unretained(this)));
|
| +
|
| + EXPECT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + 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_client_->IsListeningForTests());
|
| +}
|
| +
|
| +// In order to reliably test reentrant listener deletes, we create two listeners
|
| +// and have each of them delete both, so we're guaranteed to try and deliver a
|
| +// callback to at least one deleted listener.
|
| +
|
| +TEST_F(MDnsTest, ListenerReentrantDelete) {
|
| + StrictMock<MockListenerDelegate> delegate_privet;
|
| +
|
| + listener1_ = test_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false,
|
| + &delegate_privet);
|
| +
|
| + listener2_ = test_client_->CreateListener(
|
| + dns_protocol::kTypePTR, "_privet._tcp.local", false, false,
|
| + &delegate_privet);
|
| +
|
| + EXPECT_CALL(delegate_privet, OnRecordUpdate(kMDnsRecordAdded, _))
|
| + .Times(Exactly(1))
|
| + .WillOnce(InvokeWithoutArgs(this, &MDnsTest::DeleteBothListeners));
|
| +
|
| + EXPECT_TRUE(test_client_->IsListeningForTests());
|
| +
|
| + SendPacket(kSamplePacket1, sizeof(kSamplePacket1));
|
| +
|
| + time_system_->RunPendingTasks();
|
| +
|
| + EXPECT_EQ(NULL, listener1_.get());
|
| + EXPECT_EQ(NULL, listener2_.get());
|
| +
|
| + EXPECT_FALSE(test_client_->IsListeningForTests());
|
| +}
|
| +
|
| +// Connection tests. These tests are disabled because, due to their dependence
|
| +// on multicast networking, they are falky.
|
| +
|
| +class MDnsConnectionTest : public ::testing::Test {
|
| + public:
|
| + MDnsConnectionTest() : fake_time_system_(new FakeTimeSystem),
|
| + connection_(&delegate_, fake_time_system_) {
|
| + EXPECT_TRUE(ParseIPLiteralToNumber("224.0.0.251", &multicast_address4_));
|
| + // TODO(noamsml): Figure out why client socket fails to connect to ipv6
|
| + // multicast address.
|
| + EXPECT_TRUE(ParseIPLiteralToNumber("::1", &multicast_address6_));
|
| + connection_.Init();
|
| + }
|
| + protected:
|
| + IPAddressNumber multicast_address4_;
|
| + IPAddressNumber multicast_address6_;
|
| + StrictMock<MockMDnsConnectionDelegate> delegate_;
|
| + scoped_refptr<FakeTimeSystem> fake_time_system_;
|
| + MDnsConnectionImpl connection_;
|
| + TestCompletionCallback callback_;
|
| +};
|
| +
|
| +TEST_F(MDnsConnectionTest, DISABLED_Recieve4) {
|
| + scoped_refptr<IOBuffer> buf(new IOBuffer(sizeof(kSamplePacket1)));
|
| + memcpy(buf->data(), kSamplePacket1, sizeof(kSamplePacket1));
|
| + UDPSocket socket(DatagramSocket::DEFAULT_BIND,
|
| + RandIntCallback(),
|
| + NULL, NetLog::Source());
|
| + EXPECT_CALL(delegate_, OnHandlePacket(std::string(kSamplePacket1,
|
| + sizeof(kSamplePacket1))));
|
| +
|
| + EXPECT_EQ(OK, socket.Connect(IPEndPoint(multicast_address4_, 5353)));
|
| + int rv = socket.Write(buf, sizeof(kSamplePacket1), callback_.callback());
|
| + if (rv == ERR_IO_PENDING) {
|
| + rv = callback_.GetResult(rv);
|
| + }
|
| + EXPECT_GT(rv, OK);
|
| + base::MessageLoop::current()->RunUntilIdle(); // Socket uses message loop.
|
| + fake_time_system_->RunPendingTasks();
|
| +}
|
| +
|
| +TEST_F(MDnsConnectionTest, DISABLED_Recieve6) {
|
| + scoped_refptr<IOBuffer> buf(new IOBuffer(sizeof(kSamplePacket2)));
|
| + memcpy(buf->data(), kSamplePacket2, sizeof(kSamplePacket2));
|
| + UDPSocket socket(DatagramSocket::DEFAULT_BIND,
|
| + RandIntCallback(),
|
| + NULL, NetLog::Source());
|
| + EXPECT_CALL(delegate_, OnHandlePacket(std::string(kSamplePacket2,
|
| + sizeof(kSamplePacket2))));
|
| +
|
| + EXPECT_EQ(OK, socket.Connect(IPEndPoint(multicast_address6_, 5353)));
|
| + int rv = socket.Write(buf, sizeof(kSamplePacket2), callback_.callback());
|
| + if (rv == ERR_IO_PENDING) {
|
| + rv = callback_.GetResult(rv);
|
| + }
|
| + EXPECT_GT(rv, OK);
|
| + base::MessageLoop::current()->RunUntilIdle(); // Socket uses message loop.
|
| + fake_time_system_->RunPendingTasks();
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +} // namespace net
|
|
|