blob: ecb5625651f5b7174531e145d9a4278528997424 [file]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "osp/impl/dns_sd_watcher_client.h"
#include <string>
#include <utility>
#include "discovery/common/config.h"
#include "discovery/dnssd/public/dns_sd_instance.h"
#include "discovery/dnssd/public/dns_sd_instance_endpoint.h"
#include "discovery/dnssd/public/dns_sd_txt_record.h"
#include "discovery/public/dns_sd_service_factory.h"
#include "osp/public/osp_constants.h"
#include "platform/base/error.h"
#include "platform/base/interface_info.h"
#include "util/osp_logging.h"
namespace openscreen::osp {
using State = ServiceListener::State;
namespace {
ErrorOr<ServiceInfo> DnsSdInstanceEndpointToServiceInfo(
const discovery::DnsSdInstanceEndpoint& endpoint) {
if (endpoint.service_id() != kOpenScreenServiceName) {
return {Error::Code::kParameterInvalid, "Not a Open Screen receiver."};
}
if (endpoint.network_interface() == kInvalidNetworkInterfaceIndex) {
return {Error::Code::kParameterInvalid, "Invalid network inferface index."};
}
// TODO(Wei): Add additional validation to check and discard records with
// invalid fingerprints early. There's a specific format for the fingerprint
// defined by the spec:
// https://w3c.github.io/openscreenprotocol/#agent-fingerprint
std::string fingerprint =
endpoint.txt().GetStringValue(kFingerprint).value("");
if (fingerprint.empty()) {
return {Error::Code::kParameterInvalid,
"Missing agent fingerprint in record."};
}
std::string auth_token = endpoint.txt().GetStringValue(kAuthToken).value("");
if (auth_token.empty()) {
return {Error::Code::kParameterInvalid,
"Missing authentication token in record."};
}
ServiceInfo service_info{endpoint.instance_id(), std::move(fingerprint),
std::move(auth_token), endpoint.network_interface()};
for (const IPEndpoint& record : endpoint.endpoints()) {
if (!service_info.v4_endpoint && record.address.IsV4()) {
service_info.v4_endpoint = record;
}
if (!service_info.v6_endpoint && record.address.IsV6()) {
service_info.v6_endpoint = record;
}
}
if (!service_info.v4_endpoint && !service_info.v6_endpoint) {
return {Error::Code::kParameterInvalid,
"No IPv4 nor IPv6 address in record."};
}
return service_info;
}
} // namespace
DnsSdWatcherClient::DnsSdWatcherClient(TaskRunner& task_runner)
: task_runner_(task_runner) {}
DnsSdWatcherClient::~DnsSdWatcherClient() = default;
void DnsSdWatcherClient::StartListener(const ServiceListener::Config& config) {
OSP_LOG_INFO << "StartListener with " << config.network_interfaces.size()
<< " interfaces";
StartWatcherInternal(config);
dns_sd_watcher_->StartDiscovery();
SetState(State::kRunning);
}
void DnsSdWatcherClient::StartAndSuspendListener(
const ServiceListener::Config& config) {
StartWatcherInternal(config);
SetState(State::kSuspended);
}
void DnsSdWatcherClient::StopListener() {
dns_sd_watcher_.reset();
SetState(State::kStopped);
}
void DnsSdWatcherClient::SuspendListener() {
OSP_CHECK(dns_sd_watcher_);
dns_sd_watcher_->StopDiscovery();
SetState(State::kSuspended);
}
void DnsSdWatcherClient::ResumeListener() {
OSP_CHECK(dns_sd_watcher_);
dns_sd_watcher_->StartDiscovery();
SetState(State::kRunning);
}
void DnsSdWatcherClient::SearchNow(State from) {
OSP_CHECK(dns_sd_watcher_);
if (from == State::kSuspended) {
dns_sd_watcher_->StartDiscovery();
}
dns_sd_watcher_->DiscoverNow();
SetState(State::kSearching);
}
void DnsSdWatcherClient::OnFatalError(const Error& error) {
listener_->OnError(error);
}
void DnsSdWatcherClient::OnRecoverableError(const Error& error) {
listener_->OnError(error);
}
void DnsSdWatcherClient::StartWatcherInternal(
const ServiceListener::Config& config) {
OSP_CHECK(!dns_sd_watcher_);
if (!dns_sd_service_) {
dns_sd_service_ = CreateDnsSdServiceInternal(config);
}
dns_sd_watcher_ = std::make_unique<OspDnsSdWatcher>(
dns_sd_service_.get(), kOpenScreenServiceName,
DnsSdInstanceEndpointToServiceInfo,
[this](std::vector<OspDnsSdWatcher::ConstRefT> all) {
OnDnsWatcherUpdated(std::move(all));
});
}
discovery::DnsSdServicePtr DnsSdWatcherClient::CreateDnsSdServiceInternal(
const ServiceListener::Config& config) {
// NOTE: With the current API, the client cannot customize the behavior of
// DNS-SD beyond the interface list.
openscreen::discovery::Config dns_sd_config;
dns_sd_config.enable_publication = false;
dns_sd_config.network_info = config.network_interfaces;
// NOTE:
// It's desirable for the DNS-SD publisher and the DNS-SD listener for OSP to
// share the underlying mDNS socket and state, to avoid the agent from
// binding 2 sockets per network interface.
//
// This can be accomplished by having the agent use a shared instance of the
// discovery::DnsSdService, e.g. through a ref-counting handle, so that the
// OSP publisher and the OSP listener don't have to coordinate through an
// additional object.
return CreateDnsSdService(*task_runner_, *this, dns_sd_config);
}
void DnsSdWatcherClient::OnDnsWatcherUpdated(
std::vector<OspDnsSdWatcher::ConstRefT> all) {
std::vector<ServiceInfo> discovered_services;
for (const ServiceInfo& service : all) {
if (!service.v4_endpoint && !service.v6_endpoint) {
continue;
}
discovered_services.push_back(service);
}
listener_->OnReceiverUpdated(discovered_services);
}
} // namespace openscreen::osp