blob: a739a36dde1fad1ce1d3679bc9f8db3a1870dc20 [file] [log] [blame]
// Copyright 2019 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 "discovery/mdns/mdns_publisher.h"
#include <chrono>
#include <vector>
#include "discovery/common/config.h"
#include "discovery/mdns/mdns_probe_manager.h"
#include "discovery/mdns/mdns_sender.h"
#include "discovery/mdns/testing/mdns_test_util.h"
#include "platform/test/fake_task_runner.h"
#include "platform/test/fake_udp_socket.h"
using testing::_;
using testing::Invoke;
using testing::Return;
using testing::StrictMock;
namespace openscreen {
namespace discovery {
namespace {
constexpr Clock::duration kAnnounceGoodbyeDelay = std::chrono::milliseconds(25);
bool ContainsRecord(const std::vector<MdnsRecord::ConstRef>& records,
MdnsRecord record) {
return std::find_if(records.begin(), records.end(),
[&record](const MdnsRecord& ref) {
return ref == record;
}) != records.end();
}
} // namespace
class MockMdnsSender : public MdnsSender {
public:
explicit MockMdnsSender(UdpSocket* socket) : MdnsSender(socket) {}
MOCK_METHOD1(SendMulticast, Error(const MdnsMessage& message));
MOCK_METHOD2(SendMessage,
Error(const MdnsMessage& message, const IPEndpoint& endpoint));
};
class MockProbeManager : public MdnsProbeManager {
public:
MOCK_CONST_METHOD1(IsDomainClaimed, bool(const DomainName&));
MOCK_METHOD2(RespondToProbeQuery,
void(const MdnsMessage&, const IPEndpoint&));
};
class MdnsPublisherTesting : public MdnsPublisher {
public:
using MdnsPublisher::GetPtrRecords;
using MdnsPublisher::GetRecords;
using MdnsPublisher::MdnsPublisher;
bool IsNonPtrRecordPresent(const DomainName& name) {
auto it = records_.find(name);
if (it == records_.end()) {
return false;
}
return std::find_if(it->second.begin(), it->second.end(),
[](const RecordAnnouncerPtr& announcer) {
return announcer->record().dns_type() !=
DnsType::kPTR;
}) != it->second.end();
}
};
class MdnsPublisherTest : public testing::Test {
public:
MdnsPublisherTest()
: clock_(Clock::now()),
task_runner_(&clock_),
socket_(&task_runner_),
sender_(&socket_),
publisher_(&sender_,
&probe_manager_,
&task_runner_,
FakeClock::now,
config_) {}
~MdnsPublisherTest() {
// Clear out any remaining calls in the task runner queue.
clock_.Advance(Clock::to_duration(std::chrono::seconds(1)));
}
protected:
Error IsAnnounced(const MdnsRecord& original, const MdnsMessage& message) {
EXPECT_EQ(message.type(), MessageType::Response);
EXPECT_EQ(message.questions().size(), size_t{0});
EXPECT_EQ(message.authority_records().size(), size_t{0});
EXPECT_EQ(message.additional_records().size(), size_t{0});
EXPECT_EQ(message.answers().size(), size_t{1});
const MdnsRecord& sent = message.answers()[0];
EXPECT_EQ(original.name(), sent.name());
EXPECT_EQ(original.dns_type(), sent.dns_type());
EXPECT_EQ(original.dns_class(), sent.dns_class());
EXPECT_EQ(original.record_type(), sent.record_type());
EXPECT_EQ(original.rdata(), sent.rdata());
EXPECT_EQ(original.ttl(), sent.ttl());
return Error::None();
}
Error IsGoodbyeRecord(const MdnsRecord& original,
const MdnsMessage& message) {
EXPECT_EQ(message.type(), MessageType::Response);
EXPECT_EQ(message.questions().size(), size_t{0});
EXPECT_EQ(message.authority_records().size(), size_t{0});
EXPECT_EQ(message.additional_records().size(), size_t{0});
EXPECT_EQ(message.answers().size(), size_t{1});
const MdnsRecord& sent = message.answers()[0];
EXPECT_EQ(original.name(), sent.name());
EXPECT_EQ(original.dns_type(), sent.dns_type());
EXPECT_EQ(original.dns_class(), sent.dns_class());
EXPECT_EQ(original.record_type(), sent.record_type());
EXPECT_EQ(original.rdata(), sent.rdata());
EXPECT_EQ(std::chrono::seconds(0), sent.ttl());
return Error::None();
}
void CheckPublishedRecords(const DomainName& domain,
DnsType type,
std::vector<MdnsRecord> expected_records) {
EXPECT_EQ(publisher_.GetRecordCount(), expected_records.size());
auto records = publisher_.GetRecords(domain, type, DnsClass::kIN);
for (const auto& record : expected_records) {
EXPECT_TRUE(ContainsRecord(records, record));
}
}
void TestUniqueRecordRegistrationWorkflow(MdnsRecord record,
MdnsRecord record2) {
EXPECT_CALL(probe_manager_, IsDomainClaimed(domain_))
.WillRepeatedly(Return(true));
DnsType type = record.dns_type();
// Check preconditions.
ASSERT_EQ(record.dns_type(), record2.dns_type());
auto records = publisher_.GetRecords(domain_, type, DnsClass::kIN);
ASSERT_EQ(publisher_.GetRecordCount(), size_t{0});
ASSERT_EQ(records.size(), size_t{0});
ASSERT_NE(record, record2);
ASSERT_TRUE(records.empty());
// Register a new record.
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
EXPECT_TRUE(publisher_.RegisterRecord(record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(domain_, type, {record});
EXPECT_TRUE(publisher_.IsNonPtrRecordPresent(domain_));
// Re-register the same record.
EXPECT_FALSE(publisher_.RegisterRecord(record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(domain_, type, {record});
EXPECT_TRUE(publisher_.IsNonPtrRecordPresent(domain_));
// Update a record that doesn't exist
EXPECT_FALSE(publisher_.UpdateRegisteredRecord(record2, record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(domain_, type, {record});
EXPECT_TRUE(publisher_.IsNonPtrRecordPresent(domain_));
// Update an existing record.
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record2](const MdnsMessage& message) -> Error {
return IsAnnounced(record2, message);
});
EXPECT_TRUE(publisher_.UpdateRegisteredRecord(record, record2).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(domain_, type, {record2});
EXPECT_TRUE(publisher_.IsNonPtrRecordPresent(domain_));
// Add back the original record
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
EXPECT_TRUE(publisher_.RegisterRecord(record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(domain_, type, {record, record2});
EXPECT_TRUE(publisher_.IsNonPtrRecordPresent(domain_));
// Delete an existing record.
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record2](const MdnsMessage& message) -> Error {
return IsGoodbyeRecord(record2, message);
});
EXPECT_TRUE(publisher_.UnregisterRecord(record2).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(domain_, type, {record});
EXPECT_TRUE(publisher_.IsNonPtrRecordPresent(domain_));
// Delete a non-existing record.
EXPECT_FALSE(publisher_.UnregisterRecord(record2).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(domain_, type, {record});
EXPECT_TRUE(publisher_.IsNonPtrRecordPresent(domain_));
// Delete the last record
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsGoodbyeRecord(record, message);
});
EXPECT_TRUE(publisher_.UnregisterRecord(record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(domain_, type, {});
EXPECT_FALSE(publisher_.IsNonPtrRecordPresent(domain_));
}
FakeClock clock_;
FakeTaskRunner task_runner_;
FakeUdpSocket socket_;
StrictMock<MockMdnsSender> sender_;
StrictMock<MockProbeManager> probe_manager_;
Config config_;
MdnsPublisherTesting publisher_;
DomainName domain_{"instance", "_googlecast", "_tcp", "local"};
DomainName ptr_domain_{"_googlecast", "_tcp", "local"};
};
TEST_F(MdnsPublisherTest, ARecordRegistrationWorkflow) {
const MdnsRecord record1 = GetFakeARecord(domain_);
const MdnsRecord record2 =
GetFakeARecord(domain_, std::chrono::seconds(1000));
TestUniqueRecordRegistrationWorkflow(record1, record2);
}
TEST_F(MdnsPublisherTest, AAAARecordRegistrationWorkflow) {
const MdnsRecord record1 = GetFakeAAAARecord(domain_);
const MdnsRecord record2 =
GetFakeAAAARecord(domain_, std::chrono::seconds(1000));
TestUniqueRecordRegistrationWorkflow(record1, record2);
}
TEST_F(MdnsPublisherTest, TXTRecordRegistrationWorkflow) {
const MdnsRecord record1 = GetFakeTxtRecord(domain_);
const MdnsRecord record2 =
GetFakeTxtRecord(domain_, std::chrono::seconds(1000));
TestUniqueRecordRegistrationWorkflow(record1, record2);
}
TEST_F(MdnsPublisherTest, SRVRecordRegistrationWorkflow) {
const MdnsRecord record1 = GetFakeSrvRecord(domain_);
const MdnsRecord record2 =
GetFakeSrvRecord(domain_, std::chrono::seconds(1000));
TestUniqueRecordRegistrationWorkflow(record1, record2);
}
TEST_F(MdnsPublisherTest, PTRRecordRegistrationWorkflow) {
const MdnsRecord record = GetFakePtrRecord(domain_);
const MdnsRecord record2 =
GetFakePtrRecord(domain_, std::chrono::seconds(1000));
EXPECT_CALL(probe_manager_, IsDomainClaimed(domain_))
.WillRepeatedly(Return(true));
DnsType type = DnsType::kPTR;
// Check preconditions.
ASSERT_EQ(record.dns_type(), record2.dns_type());
ASSERT_EQ(publisher_.GetRecordCount(), size_t{0});
auto records = publisher_.GetRecords(domain_, type, DnsClass::kIN);
ASSERT_EQ(records.size(), size_t{0});
records = publisher_.GetRecords(ptr_domain_, type, DnsClass::kIN);
ASSERT_EQ(records.size(), size_t{0});
ASSERT_NE(record, record2);
ASSERT_TRUE(records.empty());
ASSERT_EQ(publisher_.GetPtrRecords(DnsClass::kANY).size(), size_t{0});
// Register a new record.
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
EXPECT_TRUE(publisher_.RegisterRecord(record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(ptr_domain_, type, {record});
ASSERT_EQ(publisher_.GetPtrRecords(DnsClass::kANY).size(), size_t{1});
// Re-register the same record.
EXPECT_FALSE(publisher_.RegisterRecord(record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(ptr_domain_, type, {record});
ASSERT_EQ(publisher_.GetPtrRecords(DnsClass::kANY).size(), size_t{1});
// Register a second record.
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record2](const MdnsMessage& message) -> Error {
return IsAnnounced(record2, message);
});
EXPECT_TRUE(publisher_.RegisterRecord(record2).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(ptr_domain_, type, {record, record2});
ASSERT_EQ(publisher_.GetPtrRecords(DnsClass::kANY).size(), size_t{2});
// Delete an existing record.
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record2](const MdnsMessage& message) -> Error {
return IsGoodbyeRecord(record2, message);
});
EXPECT_TRUE(publisher_.UnregisterRecord(record2).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(ptr_domain_, type, {record});
ASSERT_EQ(publisher_.GetPtrRecords(DnsClass::kANY).size(), size_t{1});
// Delete a non-existing record.
EXPECT_FALSE(publisher_.UnregisterRecord(record2).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(ptr_domain_, type, {record});
ASSERT_EQ(publisher_.GetPtrRecords(DnsClass::kANY).size(), size_t{1});
// Delete the last record
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsGoodbyeRecord(record, message);
});
EXPECT_TRUE(publisher_.UnregisterRecord(record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
CheckPublishedRecords(ptr_domain_, type, {});
ASSERT_EQ(publisher_.GetPtrRecords(DnsClass::kANY).size(), size_t{0});
}
TEST_F(MdnsPublisherTest, RegisteringUnownedRecordsFail) {
EXPECT_CALL(probe_manager_, IsDomainClaimed(domain_))
.WillRepeatedly(Return(false));
EXPECT_FALSE(publisher_.RegisterRecord(GetFakePtrRecord(domain_)).ok());
EXPECT_FALSE(publisher_.RegisterRecord(GetFakeSrvRecord(domain_)).ok());
EXPECT_FALSE(publisher_.RegisterRecord(GetFakeTxtRecord(domain_)).ok());
EXPECT_FALSE(publisher_.RegisterRecord(GetFakeARecord(domain_)).ok());
EXPECT_FALSE(publisher_.RegisterRecord(GetFakeAAAARecord(domain_)).ok());
}
TEST_F(MdnsPublisherTest, RegistrationAnnouncesEightTimes) {
EXPECT_CALL(probe_manager_, IsDomainClaimed(domain_))
.WillRepeatedly(Return(true));
constexpr Clock::duration kOneSecond =
Clock::to_duration(std::chrono::seconds(1));
// First announce, at registration.
const MdnsRecord record = GetFakeARecord(domain_);
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
EXPECT_TRUE(publisher_.RegisterRecord(record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
// Second announce, at 2 seconds.
testing::Mock::VerifyAndClearExpectations(&sender_);
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
clock_.Advance(kOneSecond);
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
// Third announce, at 4 seconds.
clock_.Advance(kOneSecond);
clock_.Advance(kAnnounceGoodbyeDelay);
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
clock_.Advance(kOneSecond);
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
// Fourth announce, at 8 seconds.
clock_.Advance(kOneSecond * 3);
clock_.Advance(kAnnounceGoodbyeDelay);
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
clock_.Advance(kOneSecond);
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
// Fifth announce, at 16 seconds.
clock_.Advance(kOneSecond * 7);
clock_.Advance(kAnnounceGoodbyeDelay);
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
clock_.Advance(kOneSecond);
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
// Sixth announce, at 32 seconds.
clock_.Advance(kOneSecond * 15);
clock_.Advance(kAnnounceGoodbyeDelay);
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
clock_.Advance(kOneSecond);
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
// Seventh announce, at 64 seconds.
clock_.Advance(kOneSecond * 31);
clock_.Advance(kAnnounceGoodbyeDelay);
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
clock_.Advance(kOneSecond);
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
// Eighth announce, at 128 seconds.
clock_.Advance(kOneSecond * 63);
clock_.Advance(kAnnounceGoodbyeDelay);
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsAnnounced(record, message);
});
clock_.Advance(kOneSecond);
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
// No more announcements
clock_.Advance(kOneSecond * 1024);
clock_.Advance(kAnnounceGoodbyeDelay);
testing::Mock::VerifyAndClearExpectations(&sender_);
// Sends goodbye message when removed.
EXPECT_CALL(sender_, SendMulticast(_))
.WillOnce([this, &record](const MdnsMessage& message) -> Error {
return IsGoodbyeRecord(record, message);
});
EXPECT_TRUE(publisher_.UnregisterRecord(record).ok());
clock_.Advance(kAnnounceGoodbyeDelay);
}
} // namespace discovery
} // namespace openscreen