1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cast/standalone_sender/receiver_chooser.h"
6
7 #include <cstdint>
8 #include <iostream>
9 #include <string>
10 #include <utility>
11
12 #include "discovery/common/config.h"
13 #include "platform/api/time.h"
14 #include "util/osp_logging.h"
15
16 namespace openscreen {
17 namespace cast {
18
19 // NOTE: the compile requires a definition as well as the declaration
20 // in the header.
21 // TODO(issuetracker.google.com/174081818): move to inline C++17 feature.
22 constexpr decltype(ReceiverChooser::kWaitForStragglersDelay)
23 ReceiverChooser::kWaitForStragglersDelay;
24
ReceiverChooser(const InterfaceInfo & interface,TaskRunner * task_runner,ResultCallback result_callback)25 ReceiverChooser::ReceiverChooser(const InterfaceInfo& interface,
26 TaskRunner* task_runner,
27 ResultCallback result_callback)
28 : result_callback_(std::move(result_callback)),
29 menu_alarm_(&Clock::now, task_runner) {
30 discovery::Config config{.network_info = {interface},
31 .enable_publication = false,
32 .enable_querying = true};
33 discovery::CreateDnsSdService(task_runner, this, std::move(config));
34
35 watcher_ = std::make_unique<discovery::DnsSdServiceWatcher<ReceiverInfo>>(
36 service_.get(), kCastV2ServiceId, DnsSdInstanceEndpointToReceiverInfo,
37 [this](std::vector<std::reference_wrapper<const ReceiverInfo>> all) {
38 OnDnsWatcherUpdate(std::move(all));
39 });
40
41 OSP_LOG_INFO << "Starting discovery. Note that it can take dozens of seconds "
42 "to detect anything on some networks!";
43 task_runner->PostTask([this] { watcher_->StartDiscovery(); });
44 }
45
46 ReceiverChooser::~ReceiverChooser() = default;
47
OnFatalError(Error error)48 void ReceiverChooser::OnFatalError(Error error) {
49 OSP_LOG_FATAL << "Fatal error: " << error;
50 }
51
OnRecoverableError(Error error)52 void ReceiverChooser::OnRecoverableError(Error error) {
53 OSP_VLOG << "Recoverable error: " << error;
54 }
55
OnDnsWatcherUpdate(std::vector<std::reference_wrapper<const ReceiverInfo>> all)56 void ReceiverChooser::OnDnsWatcherUpdate(
57 std::vector<std::reference_wrapper<const ReceiverInfo>> all) {
58 bool added_some = false;
59 for (const ReceiverInfo& info : all) {
60 if (!info.IsValid() || (!info.v4_address && !info.v6_address)) {
61 continue;
62 }
63 const std::string& instance_id = info.GetInstanceId();
64 if (std::any_of(discovered_receivers_.begin(), discovered_receivers_.end(),
65 [&](const ReceiverInfo& known) {
66 return known.GetInstanceId() == instance_id;
67 })) {
68 continue;
69 }
70
71 OSP_LOG_INFO << "Discovered: " << info.friendly_name
72 << " (id: " << instance_id << ')';
73 discovered_receivers_.push_back(info);
74 added_some = true;
75 }
76
77 if (added_some) {
78 menu_alarm_.ScheduleFromNow([this] { PrintMenuAndHandleChoice(); },
79 kWaitForStragglersDelay);
80 }
81 }
82
PrintMenuAndHandleChoice()83 void ReceiverChooser::PrintMenuAndHandleChoice() {
84 if (!result_callback_) {
85 return; // A choice has already been made.
86 }
87
88 std::cout << '\n';
89 for (size_t i = 0; i < discovered_receivers_.size(); ++i) {
90 const ReceiverInfo& info = discovered_receivers_[i];
91 std::cout << '[' << i << "]: " << info.friendly_name << " @ ";
92 if (info.v6_address) {
93 std::cout << info.v6_address;
94 } else {
95 OSP_DCHECK(info.v4_address);
96 std::cout << info.v4_address;
97 }
98 std::cout << ':' << info.port << '\n';
99 }
100 std::cout << "\nEnter choice, or 'n' to wait longer: " << std::flush;
101
102 int menu_choice = -1;
103 if (std::cin >> menu_choice || std::cin.eof()) {
104 const auto callback_on_stack = std::move(result_callback_);
105 if (menu_choice >= 0 &&
106 menu_choice < static_cast<int>(discovered_receivers_.size())) {
107 const ReceiverInfo& choice = discovered_receivers_[menu_choice];
108 if (choice.v6_address) {
109 callback_on_stack(IPEndpoint{choice.v6_address, choice.port});
110 } else {
111 callback_on_stack(IPEndpoint{choice.v4_address, choice.port});
112 }
113 } else {
114 callback_on_stack(IPEndpoint{}); // Signal "bad choice" or EOF.
115 }
116 return;
117 }
118
119 // Clear bad input flag, and skip past what the user entered.
120 std::cin.clear();
121 std::string garbage;
122 std::getline(std::cin, garbage);
123 }
124
125 } // namespace cast
126 } // namespace openscreen
127