blob: 156c14b5f712995e3616cfeef95c3a4eb2c84820 [file] [log] [blame]
// Copyright 2022 The Fuchsia 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 "src/connectivity/network/mdns/service/agents/instance_requestor.h"
#include <lib/zx/time.h>
#include <gtest/gtest.h>
#include "src/connectivity/network/mdns/service/common/mdns_names.h"
#include "src/connectivity/network/mdns/service/common/service_instance.h"
#include "src/connectivity/network/mdns/service/common/type_converters.h"
#include "src/connectivity/network/mdns/service/test/agent_test.h"
namespace mdns {
namespace test {
class InstanceRequestorTest : public AgentTest {
public:
InstanceRequestorTest() = default;
protected:
class Subscriber : public Mdns::Subscriber {
public:
struct InstanceId {
InstanceId(const std::string& service, const std::string& instance)
: service_(service), instance_(instance) {}
std::string service_;
std::string instance_;
};
void InstanceDiscovered(const std::string& service, const std::string& instance,
const std::vector<inet::SocketAddress>& addresses,
const std::vector<std::vector<uint8_t>>& text, uint16_t srv_priority,
uint16_t srv_weight, const std::string& target) {
instance_discovered_params_ = std::make_unique<ServiceInstance>(
service, instance, target, addresses, text, srv_priority, srv_weight);
}
void InstanceChanged(const std::string& service, const std::string& instance,
const std::vector<inet::SocketAddress>& addresses,
const std::vector<std::vector<uint8_t>>& text, uint16_t srv_priority,
uint16_t srv_weight, const std::string& target) {
instance_changed_params_ = std::make_unique<ServiceInstance>(
service, instance, target, addresses, text, srv_priority, srv_weight);
}
void InstanceLost(const std::string& service, const std::string& instance) {
instance_lost_params_ = std::make_unique<InstanceId>(service, instance);
}
void Query(DnsType type_queried) { query_param_ = type_queried; }
std::unique_ptr<ServiceInstance> ExpectInstanceDiscoveredCalled() {
EXPECT_TRUE(!!instance_discovered_params_);
return std::move(instance_discovered_params_);
}
std::unique_ptr<ServiceInstance> ExpectInstanceChangedCalled() {
EXPECT_TRUE(!!instance_changed_params_);
return std::move(instance_changed_params_);
}
std::unique_ptr<InstanceId> ExpectInstanceLostCalled() {
EXPECT_TRUE(!!instance_lost_params_);
return std::move(instance_lost_params_);
}
void ExpectQueryCalled(DnsType type) {
EXPECT_NE(DnsType::kInvalid, query_param_);
EXPECT_EQ(type, query_param_);
query_param_ = DnsType::kInvalid;
}
void ExpectNoOther() {
EXPECT_FALSE(!!instance_discovered_params_);
EXPECT_FALSE(!!instance_changed_params_);
EXPECT_FALSE(!!instance_lost_params_);
EXPECT_EQ(DnsType::kInvalid, query_param_);
}
private:
std::unique_ptr<ServiceInstance> instance_discovered_params_;
std::unique_ptr<ServiceInstance> instance_changed_params_;
std::unique_ptr<InstanceId> instance_lost_params_;
DnsType query_param_ = DnsType::kInvalid;
};
void ReceivePublication(InstanceRequestor& under_test, const std::string& host_full_name,
const std::string& service_name, const std::string& instance_name,
inet::IpPort port, const std::vector<std::vector<uint8_t>>& text,
ReplyAddress sender_address, bool include_txt = true,
bool include_address = true) {
auto service_full_name = MdnsNames::ServiceFullName(service_name);
auto instance_full_name = MdnsNames::InstanceFullName(instance_name, service_name);
DnsResource ptr_resource(service_full_name, DnsType::kPtr);
ptr_resource.ptr_.pointer_domain_name_ = DnsName(instance_full_name);
under_test.ReceiveResource(ptr_resource, MdnsResourceSection::kAnswer, sender_address);
DnsResource srv_resource(instance_full_name, DnsType::kSrv);
srv_resource.srv_.port_ = port;
srv_resource.srv_.target_ = DnsName(host_full_name);
under_test.ReceiveResource(srv_resource, MdnsResourceSection::kAdditional, sender_address);
if (include_txt) {
DnsResource txt_resource(instance_full_name, DnsType::kTxt);
txt_resource.txt_.strings_ = text;
under_test.ReceiveResource(txt_resource, MdnsResourceSection::kAdditional, sender_address);
}
if (include_address) {
DnsResource a_resource(host_full_name, sender_address.socket_address().address());
under_test.ReceiveResource(a_resource, MdnsResourceSection::kAdditional, sender_address);
}
under_test.EndOfMessage();
}
void ReceivePtr(InstanceRequestor& under_test, const std::string& service_name,
const std::string& instance_name, ReplyAddress sender_address) {
auto service_full_name = MdnsNames::ServiceFullName(service_name);
auto instance_full_name = MdnsNames::InstanceFullName(instance_name, service_name);
DnsResource ptr_resource(service_full_name, DnsType::kPtr);
ptr_resource.ptr_.pointer_domain_name_ = DnsName(instance_full_name);
under_test.ReceiveResource(ptr_resource, MdnsResourceSection::kAnswer, sender_address);
under_test.EndOfMessage();
}
void ReceiveSrv(InstanceRequestor& under_test, const std::string& host_full_name,
const std::string& service_name, const std::string& instance_name,
inet::IpPort port, ReplyAddress sender_address) {
auto instance_full_name = MdnsNames::InstanceFullName(instance_name, service_name);
DnsResource srv_resource(instance_full_name, DnsType::kSrv);
srv_resource.srv_.port_ = port;
srv_resource.srv_.target_ = DnsName(host_full_name);
under_test.ReceiveResource(srv_resource, MdnsResourceSection::kAnswer, sender_address);
under_test.EndOfMessage();
}
void ReceiveTxt(InstanceRequestor& under_test, const std::string& service_name,
const std::string& instance_name, const std::vector<std::vector<uint8_t>>& text,
ReplyAddress sender_address) {
auto instance_full_name = MdnsNames::InstanceFullName(instance_name, service_name);
DnsResource txt_resource(instance_full_name, DnsType::kTxt);
txt_resource.txt_.strings_ = text;
under_test.ReceiveResource(txt_resource, MdnsResourceSection::kAnswer, sender_address);
under_test.EndOfMessage();
}
void ReceiveAddress(InstanceRequestor& under_test, const std::string& host_full_name,
ReplyAddress sender_address) {
DnsResource a_resource(host_full_name, sender_address.socket_address().address());
under_test.ReceiveResource(a_resource, MdnsResourceSection::kAnswer, sender_address);
under_test.EndOfMessage();
}
};
const zx::duration kMinDelay = zx::sec(1);
const zx::duration kMaxDelay = zx::hour(1);
constexpr char kHostFullName[] = "test2host.local.";
constexpr char kHostName[] = "test2host";
constexpr char kServiceName[] = "_testservice._tcp.";
constexpr char kServiceFullName[] = "_testservice._tcp.local.";
constexpr char kAnyServiceFullName[] = "_services._dns-sd._udp.local.";
constexpr char kInstanceName[] = "testinstance";
constexpr char kInstanceFullName[] = "testinstance._testservice._tcp.local.";
const inet::IpPort kPort = inet::IpPort::From_uint16_t(1234);
const std::vector<std::vector<uint8_t>> kText = fidl::To<std::vector<std::vector<uint8_t>>>(
std::vector<std::string>{"color=red", "shape=round"});
const std::vector<std::vector<uint8_t>> kAltText = fidl::To<std::vector<std::vector<uint8_t>>>(
std::vector<std::string>{"color=green", "shape=square"});
constexpr bool kIncludeLocal = true;
constexpr bool kExcludeLocal = false;
constexpr bool kIncludeLocalProxies = true;
constexpr bool kExcludeLocalProxies = false;
constexpr bool kFromLocalProxyHost = true;
constexpr bool kFromLocalHost = false;
const std::vector<HostAddress> kHostAddresses{
HostAddress(inet::IpAddress(192, 168, 1, 200), 1, zx::sec(450)),
HostAddress(inet::IpAddress(0xfe80, 200), 1, zx::sec(450))};
const std::vector<inet::SocketAddress> kSocketAddresses{
inet::SocketAddress(inet::IpAddress(192, 168, 1, 200), kPort, 1),
inet::SocketAddress(inet::IpAddress(0xfe80, 200), kPort, 1)};
const std::vector<inet::SocketAddress> kSocketAddressesReversed{
inet::SocketAddress(inet::IpAddress(0xfe80, 200), kPort, 1),
inet::SocketAddress(inet::IpAddress(192, 168, 1, 200), kPort, 1)};
constexpr zx::duration kAdditionalInterval = zx::sec(1);
constexpr uint32_t kAdditionalIntervalMultiplier = 2;
constexpr uint32_t kAdditionalMaxQueries = 3;
// Tests nominal startup behavior of the requestor.
TEST_F(InstanceRequestorTest, QuerySequence) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
// Queries should be send after 1s, 2s, 4s, etc, capping out an an hour.
auto delay = kMinDelay;
while (delay < kMaxDelay) {
ExpectPostTaskForTimeAndInvoke(delay, delay);
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
delay = delay * 2;
}
ExpectPostTaskForTime(kMaxDelay, kMaxDelay);
ExpectNoOther();
}
// Tests the behavior of the requestor when a response is received.
TEST_F(InstanceRequestorTest, Response) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
ReplyAddress sender_address(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when responses indicate a change to an instance.
TEST_F(InstanceRequestorTest, Change) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
// Respond.
ReplyAddress sender_address(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
// Respond with different text.
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kAltText,
sender_address);
params = subscriber.ExpectInstanceChangedCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
kAltText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when an expiration indicates the removal of an instance.
TEST_F(InstanceRequestorTest, Removal) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
// Respond.
ReplyAddress sender_address(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
// Expire the PTR resource.
DnsResource ptr_resource(kServiceFullName, DnsType::kPtr);
ptr_resource.ptr_.pointer_domain_name_ = DnsName(kInstanceFullName);
ptr_resource.time_to_live_ = 0;
under_test.ReceiveResource(ptr_resource, MdnsResourceSection::kExpired, sender_address);
auto instance_id = subscriber.ExpectInstanceLostCalled();
EXPECT_EQ(kInstanceName, instance_id->instance_);
EXPECT_EQ(kServiceName, instance_id->service_);
subscriber.ExpectNoOther();
}
// Tests that the requstor removed itself when the last subscriber is removed.
TEST_F(InstanceRequestorTest, RemoveSelf) {
// Need to |make_shared|, because |RemoveSelf| calls |shared_from_this|.
auto under_test = std::make_shared<InstanceRequestor>(
this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal, kExcludeLocalProxies);
SetAgent(*under_test);
Subscriber subscriber;
under_test->AddSubscriber(&subscriber);
under_test->RemoveSubscriber(&subscriber);
ExpectPostTaskForTimeAndInvoke(zx::sec(0), zx::sec(0));
ExpectRemoveAgentCall();
}
// Tests the behavior of the requestor when configured for wireless-only operation.
TEST_F(InstanceRequestorTest, WirelessOnly) {
InstanceRequestor under_test(this, kServiceName, Media::kWireless, IpVersions::kBoth,
kExcludeLocal, kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message =
ExpectOutboundMessage(ReplyAddress::Multicast(Media::kWireless, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
ReplyAddress sender_address0(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWired, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address0);
subscriber.ExpectNoOther();
ReplyAddress sender_address1(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address1);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(
ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address1.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when configured for wired-only operation.
TEST_F(InstanceRequestorTest, WiredOnly) {
InstanceRequestor under_test(this, kServiceName, Media::kWired, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kWired, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
ReplyAddress sender_address0(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address0);
subscriber.ExpectNoOther();
ReplyAddress sender_address1(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWired, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address1);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(
ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address1.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when configured for IPv4-only operation.
TEST_F(InstanceRequestorTest, V4Only) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kV4, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kV4));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
ReplyAddress sender_address0(inet::SocketAddress(0xfe80, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(0xfe80, 100), 1, Media::kWired, IpVersions::kV6);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address0);
subscriber.ExpectNoOther();
ReplyAddress sender_address1(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address1);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(
ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address1.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when configured for IPv6-only operation.
TEST_F(InstanceRequestorTest, V6Only) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kV6, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kV6));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
ReplyAddress sender_address0(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWired, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address0);
subscriber.ExpectNoOther();
ReplyAddress sender_address1(inet::SocketAddress(0xfe80, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(0xfe80, 100), 1, Media::kWireless, IpVersions::kV6);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address1);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(
ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address1.socket_address().address(), kPort, 1)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when configured to discover services on the local host.
TEST_F(InstanceRequestorTest, LocalInstance) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kIncludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
SetLocalHostAddresses(kHostAddresses);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
// Expect to see an added local service instance.
under_test.OnAddLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kLocalHostName, kSocketAddresses, kText),
kFromLocalHost);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(
ServiceInstance(kServiceName, kInstanceName, kLocalHostName, kSocketAddressesReversed, kText),
*params);
// |OnChangeLocalServiceInstance| should do nothing if the instance doesn't change.
under_test.OnChangeLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kLocalHostName, kSocketAddresses, kText),
kFromLocalHost);
subscriber.ExpectNoOther();
// If the instance does change, we should see the notification.
under_test.OnChangeLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kLocalHostName, kSocketAddresses, kAltText),
false);
params = subscriber.ExpectInstanceChangedCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kLocalHostName, kSocketAddressesReversed,
kAltText),
*params);
subscriber.ExpectNoOther();
// |OnRemoveLocalServiceInstance| should notify of a removal.
under_test.OnRemoveLocalServiceInstance(kServiceName, kInstanceName, kFromLocalHost);
auto lost_params = subscriber.ExpectInstanceLostCalled();
EXPECT_EQ(kServiceName, lost_params->service_);
EXPECT_EQ(kInstanceName, lost_params->instance_);
// Expect that local proxy host instances are ignored.
under_test.OnAddLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kHostName, kSocketAddresses, kText),
kFromLocalProxyHost);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when configured to discover services on a local proxy host.
TEST_F(InstanceRequestorTest, LocalProxyInstance) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kIncludeLocalProxies);
SetAgent(under_test);
SetLocalHostAddresses(kHostAddresses);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
// Expect to see an added local service instance.
under_test.OnAddLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kHostName, kSocketAddresses, kText),
kFromLocalProxyHost);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(
ServiceInstance(kServiceName, kInstanceName, kHostName, kSocketAddressesReversed, kText),
*params);
// |OnChangeLocalServiceInstance| should do nothing if the instance doesn't change.
under_test.OnChangeLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kHostName, kSocketAddresses, kText),
kFromLocalProxyHost);
subscriber.ExpectNoOther();
// If the instance does change, we should see the notification.
under_test.OnChangeLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kHostName, kSocketAddresses, kAltText),
kFromLocalProxyHost);
params = subscriber.ExpectInstanceChangedCalled();
EXPECT_EQ(
ServiceInstance(kServiceName, kInstanceName, kHostName, kSocketAddressesReversed, kAltText),
*params);
subscriber.ExpectNoOther();
// |OnRemoveLocalServiceInstance| should notify of a removal.
under_test.OnRemoveLocalServiceInstance(kServiceName, kInstanceName, kFromLocalProxyHost);
auto lost_params = subscriber.ExpectInstanceLostCalled();
EXPECT_EQ(kServiceName, lost_params->service_);
EXPECT_EQ(kInstanceName, lost_params->instance_);
// Expect that local host instances are ignored.
under_test.OnAddLocalServiceInstance(
ServiceInstance(kServiceName, kInstanceName, kLocalHostName, kSocketAddresses, kText),
kFromLocalHost);
subscriber.ExpectNoOther();
}
// Tests the behavior of a requestor for any service when a response is received.
TEST_F(InstanceRequestorTest, AnyResponse) {
InstanceRequestor under_test(this, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kAnyServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
ReplyAddress sender_address(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address);
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when a response containing no addresses is received.
TEST_F(InstanceRequestorTest, ResponseSansAddresses) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
// Receive a response with no address records.
ReplyAddress sender_address(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address, true, false); // yes TXT record, no A record.
subscriber.ExpectNoOther();
// Expect a A/AAAA queries.
ExpectQueryCall(DnsType::kA, kHostFullName, Media::kBoth, IpVersions::kBoth, now(),
kAdditionalInterval, kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
ExpectQueryCall(DnsType::kAaaa, kHostFullName, Media::kBoth, IpVersions::kBoth, now(),
kAdditionalInterval, kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
// Receive a response with only an address record.
ReceiveAddress(under_test, kHostFullName, sender_address);
// Expect discovery of the instance to be reported to the client.
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when a response containing no TXT resource is received.
TEST_F(InstanceRequestorTest, ResponseSansTxt) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
// Receive a response with no TXT record.
ReplyAddress sender_address(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address, false, true); // no TXT record, yes A record.
// Expect the instance to be reported to the client with no text.
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
{}, 0, 0),
*params);
// Expect a TXT query call.
ExpectQueryCall(DnsType::kTxt, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
// Receive a response with only a TXT record.
ReceiveTxt(under_test, kServiceName, kInstanceName, kText, sender_address);
// Expect change of the instance to be reported to the client with text.
params = subscriber.ExpectInstanceChangedCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when a response containing only PTR is received.
TEST_F(InstanceRequestorTest, ResponsePtrOnly) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
// Receive a response only a PTR record.
ReplyAddress sender_address(
inet::SocketAddress(192, 168, 1, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(192, 168, 1, 100), 1, Media::kWireless, IpVersions::kV4);
ReceivePtr(under_test, kServiceName, kInstanceName, sender_address);
subscriber.ExpectNoOther();
// Expect an SRV query calls.
ExpectQueryCall(DnsType::kSrv, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
// Receive a response with only an SRV record.
ReceiveSrv(under_test, kHostFullName, kServiceName, kInstanceName, kPort, sender_address);
subscriber.ExpectNoOther();
// Expect a A, AAAA and TXT query calls.
ExpectQueryCall(DnsType::kA, kHostFullName, Media::kBoth, IpVersions::kBoth, now(),
kAdditionalInterval, kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
ExpectQueryCall(DnsType::kAaaa, kHostFullName, Media::kBoth, IpVersions::kBoth, now(),
kAdditionalInterval, kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
ExpectQueryCall(DnsType::kTxt, MdnsNames::InstanceFullName(kInstanceName, kServiceName),
Media::kBoth, IpVersions::kBoth, now(), kAdditionalInterval,
kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
// Receive a response with only an address record.
ReceiveAddress(under_test, kHostFullName, sender_address);
// Expect the instance to be reported to the client with no text.
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
{}, 0, 0),
*params);
subscriber.ExpectNoOther();
// Receive a response with only a TXT record.
ReceiveTxt(under_test, kServiceName, kInstanceName, kText, sender_address);
// Expect change of the instance to be reported to the client with text.
params = subscriber.ExpectInstanceChangedCalled();
EXPECT_EQ(ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
// Tests the behavior of the requestor when a response containing no addresses is received on V6.
TEST_F(InstanceRequestorTest, ResponseSansAddressesV6) {
InstanceRequestor under_test(this, kServiceName, Media::kBoth, IpVersions::kBoth, kExcludeLocal,
kExcludeLocalProxies);
SetAgent(under_test);
Subscriber subscriber;
under_test.AddSubscriber(&subscriber);
under_test.Start(kLocalHostFullName);
// Expect a PTR question on start.
auto message = ExpectOutboundMessage(ReplyAddress::Multicast(Media::kBoth, IpVersions::kBoth));
ExpectQuestion(message.get(), kServiceFullName, DnsType::kPtr);
ExpectNoOtherQuestionOrResource(message.get());
ExpectPostTaskForTime(kMinDelay, kMinDelay);
ExpectNoOther();
subscriber.ExpectQueryCalled(DnsType::kPtr);
subscriber.ExpectNoOther();
// Receive a response with no address records.
ReplyAddress sender_address(inet::SocketAddress(0xfe80, 1, inet::IpPort::From_uint16_t(5353)),
inet::IpAddress(0xfe80, 100), 1, Media::kWireless, IpVersions::kV6);
ReceivePublication(under_test, kHostFullName, kServiceName, kInstanceName, kPort, kText,
sender_address, true, false); // yes TXT record, no A record.
subscriber.ExpectNoOther();
// Expect a A/AAAA queries.
ExpectQueryCall(DnsType::kA, kHostFullName, Media::kBoth, IpVersions::kBoth, now(),
kAdditionalInterval, kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
ExpectQueryCall(DnsType::kAaaa, kHostFullName, Media::kBoth, IpVersions::kBoth, now(),
kAdditionalInterval, kAdditionalIntervalMultiplier, kAdditionalMaxQueries);
// Receive a response with only an address record.
ReceiveAddress(under_test, kHostFullName, sender_address);
// Expect discovery of the instance to be reported to the client.
auto params = subscriber.ExpectInstanceDiscoveredCalled();
EXPECT_EQ(
ServiceInstance(kServiceName, kInstanceName, kHostName,
{inet::SocketAddress(sender_address.socket_address().address(), kPort, 1)},
kText, 0, 0),
*params);
subscriber.ExpectNoOther();
}
} // namespace test
} // namespace mdns