| // 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 |