• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   using discovery::Config;
31   Config config;
32   // TODO(miu): Remove AddressFamilies from the Config in a follow-up patch. No
33   // client uses this to do anything other than "enabled for all address
34   // families," and so it doesn't need to be configurable.
35   Config::NetworkInfo::AddressFamilies families =
36       Config::NetworkInfo::kNoAddressFamily;
37   if (interface.GetIpAddressV4()) {
38     families |= Config::NetworkInfo::kUseIpV4;
39   }
40   if (interface.GetIpAddressV6()) {
41     families |= Config::NetworkInfo::kUseIpV6;
42   }
43   config.network_info.push_back({interface, families});
44   config.enable_publication = false;
45   config.enable_querying = true;
46   service_ =
47       discovery::CreateDnsSdService(task_runner, this, std::move(config));
48 
49   watcher_ = std::make_unique<discovery::DnsSdServiceWatcher<ServiceInfo>>(
50       service_.get(), kCastV2ServiceId, DnsSdInstanceEndpointToServiceInfo,
51       [this](std::vector<std::reference_wrapper<const ServiceInfo>> all) {
52         OnDnsWatcherUpdate(std::move(all));
53       });
54 
55   OSP_LOG_INFO << "Starting discovery. Note that it can take dozens of seconds "
56                   "to detect anything on some networks!";
57   task_runner->PostTask([this] { watcher_->StartDiscovery(); });
58 }
59 
60 ReceiverChooser::~ReceiverChooser() = default;
61 
OnFatalError(Error error)62 void ReceiverChooser::OnFatalError(Error error) {
63   OSP_LOG_FATAL << "Fatal error: " << error;
64 }
65 
OnRecoverableError(Error error)66 void ReceiverChooser::OnRecoverableError(Error error) {
67   OSP_VLOG << "Recoverable error: " << error;
68 }
69 
OnDnsWatcherUpdate(std::vector<std::reference_wrapper<const ServiceInfo>> all)70 void ReceiverChooser::OnDnsWatcherUpdate(
71     std::vector<std::reference_wrapper<const ServiceInfo>> all) {
72   bool added_some = false;
73   for (const ServiceInfo& info : all) {
74     if (!info.IsValid() || (!info.v4_address && !info.v6_address)) {
75       continue;
76     }
77     const std::string& instance_id = info.GetInstanceId();
78     if (std::any_of(discovered_receivers_.begin(), discovered_receivers_.end(),
79                     [&](const ServiceInfo& known) {
80                       return known.GetInstanceId() == instance_id;
81                     })) {
82       continue;
83     }
84 
85     OSP_LOG_INFO << "Discovered: " << info.friendly_name
86                  << " (id: " << instance_id << ')';
87     discovered_receivers_.push_back(info);
88     added_some = true;
89   }
90 
91   if (added_some) {
92     menu_alarm_.ScheduleFromNow([this] { PrintMenuAndHandleChoice(); },
93                                 kWaitForStragglersDelay);
94   }
95 }
96 
PrintMenuAndHandleChoice()97 void ReceiverChooser::PrintMenuAndHandleChoice() {
98   if (!result_callback_) {
99     return;  // A choice has already been made.
100   }
101 
102   std::cout << '\n';
103   for (size_t i = 0; i < discovered_receivers_.size(); ++i) {
104     const ServiceInfo& info = discovered_receivers_[i];
105     std::cout << '[' << i << "]: " << info.friendly_name << " @ ";
106     if (info.v6_address) {
107       std::cout << info.v6_address;
108     } else {
109       OSP_DCHECK(info.v4_address);
110       std::cout << info.v4_address;
111     }
112     std::cout << ':' << info.port << '\n';
113   }
114   std::cout << "\nEnter choice, or 'n' to wait longer: " << std::flush;
115 
116   int menu_choice = -1;
117   if (std::cin >> menu_choice || std::cin.eof()) {
118     const auto callback_on_stack = std::move(result_callback_);
119     if (menu_choice >= 0 &&
120         menu_choice < static_cast<int>(discovered_receivers_.size())) {
121       const ServiceInfo& choice = discovered_receivers_[menu_choice];
122       if (choice.v6_address) {
123         callback_on_stack(IPEndpoint{choice.v6_address, choice.port});
124       } else {
125         callback_on_stack(IPEndpoint{choice.v4_address, choice.port});
126       }
127     } else {
128       callback_on_stack(IPEndpoint{});  // Signal "bad choice" or EOF.
129     }
130     return;
131   }
132 
133   // Clear bad input flag, and skip past what the user entered.
134   std::cin.clear();
135   std::string garbage;
136   std::getline(std::cin, garbage);
137 }
138 
139 }  // namespace cast
140 }  // namespace openscreen
141