blob: 7d6732e237ce71adf100bb5b66611b6be8e10ff2 [file] [log] [blame]
// Copyright 2020 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 "cast/standalone_sender/receiver_chooser.h"
#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include "discovery/common/config.h"
#include "platform/api/time.h"
#include "util/osp_logging.h"
namespace openscreen {
namespace cast {
ReceiverChooser::ReceiverChooser(const InterfaceInfo& interface,
TaskRunner* task_runner,
ResultCallback result_callback)
: result_callback_(std::move(result_callback)),
menu_alarm_(&Clock::now, task_runner) {
using discovery::Config;
Config config;
// TODO(miu): Remove AddressFamilies from the Config in a follow-up patch. No
// client uses this to do anything other than "enabled for all address
// families," and so it doesn't need to be configurable.
Config::NetworkInfo::AddressFamilies families =
Config::NetworkInfo::kNoAddressFamily;
if (interface.GetIpAddressV4()) {
families |= Config::NetworkInfo::kUseIpV4;
}
if (interface.GetIpAddressV6()) {
families |= Config::NetworkInfo::kUseIpV6;
}
config.network_info.push_back({interface, families});
config.enable_publication = false;
config.enable_querying = true;
service_ =
discovery::CreateDnsSdService(task_runner, this, std::move(config));
watcher_ = std::make_unique<discovery::DnsSdServiceWatcher<ServiceInfo>>(
service_.get(), kCastV2ServiceId, DnsSdInstanceEndpointToServiceInfo,
[this](std::vector<std::reference_wrapper<const ServiceInfo>> all) {
OnDnsWatcherUpdate(std::move(all));
});
OSP_LOG_INFO << "Starting discovery. Note that it can take dozens of seconds "
"to detect anything on some networks!";
task_runner->PostTask([this] { watcher_->StartDiscovery(); });
}
ReceiverChooser::~ReceiverChooser() = default;
void ReceiverChooser::OnFatalError(Error error) {
OSP_LOG_FATAL << "Fatal error: " << error;
}
void ReceiverChooser::OnRecoverableError(Error error) {
OSP_VLOG << "Recoverable error: " << error;
}
void ReceiverChooser::OnDnsWatcherUpdate(
std::vector<std::reference_wrapper<const ServiceInfo>> all) {
bool added_some = false;
for (const ServiceInfo& info : all) {
if (!info.IsValid() || (!info.v4_address && !info.v6_address)) {
continue;
}
const std::string& instance_id = info.GetInstanceId();
if (std::any_of(discovered_receivers_.begin(), discovered_receivers_.end(),
[&](const ServiceInfo& known) {
return known.GetInstanceId() == instance_id;
})) {
continue;
}
OSP_LOG_INFO << "Discovered: " << info.friendly_name
<< " (id: " << instance_id << ')';
discovered_receivers_.push_back(info);
added_some = true;
}
if (added_some) {
menu_alarm_.ScheduleFromNow([this] { PrintMenuAndHandleChoice(); },
kWaitForStragglersDelay);
}
}
void ReceiverChooser::PrintMenuAndHandleChoice() {
if (!result_callback_) {
return; // A choice has already been made.
}
std::cout << '\n';
for (size_t i = 0; i < discovered_receivers_.size(); ++i) {
const ServiceInfo& info = discovered_receivers_[i];
std::cout << '[' << i << "]: " << info.friendly_name << " @ ";
if (info.v6_address) {
std::cout << info.v6_address;
} else {
OSP_DCHECK(info.v4_address);
std::cout << info.v4_address;
}
std::cout << ':' << info.port << '\n';
}
std::cout << "\nEnter choice, or 'n' to wait longer: " << std::flush;
int menu_choice = -1;
if (std::cin >> menu_choice || std::cin.eof()) {
const auto callback_on_stack = std::move(result_callback_);
if (menu_choice >= 0 &&
menu_choice < static_cast<int>(discovered_receivers_.size())) {
const ServiceInfo& choice = discovered_receivers_[menu_choice];
if (choice.v6_address) {
callback_on_stack(IPEndpoint{choice.v6_address, choice.port});
} else {
callback_on_stack(IPEndpoint{choice.v4_address, choice.port});
}
} else {
callback_on_stack(IPEndpoint{}); // Signal "bad choice" or EOF.
}
return;
}
// Clear bad input flag, and skip past what the user entered.
std::cin.clear();
std::string garbage;
std::getline(std::cin, garbage);
}
} // namespace cast
} // namespace openscreen