1 // Copyright 2019 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 <getopt.h>
6 
7 #include <algorithm>
8 #include <iostream>
9 #include <memory>
10 #include <string>
11 #include <utility>
12 #include <vector>
13 
14 #include "absl/strings/str_cat.h"
15 #include "cast/receiver/channel/static_credentials.h"
16 #include "cast/standalone_receiver/cast_service.h"
17 #include "platform/api/time.h"
18 #include "platform/base/error.h"
19 #include "platform/base/ip_address.h"
20 #include "platform/impl/logging.h"
21 #include "platform/impl/network_interface.h"
22 #include "platform/impl/platform_client_posix.h"
23 #include "platform/impl/task_runner.h"
24 #include "platform/impl/text_trace_logging_platform.h"
25 #include "util/chrono_helpers.h"
26 #include "util/stringprintf.h"
27 #include "util/trace_logging.h"
28 
29 namespace openscreen {
30 namespace cast {
31 namespace {
32 
LogUsage(const char * argv0)33 void LogUsage(const char* argv0) {
34   constexpr char kTemplate[] = R"(
35 usage: %s <options> <interface>
36 
37     interface
38         Specifies the network interface to bind to. The interface is
39         looked up from the system interface registry.
40         Mandatory, as it must be known for publishing discovery.
41 
42 options:
43     -p, --private-key=path-to-key: Path to OpenSSL-generated private key to be
44                     used for TLS authentication. If a private key is not
45                     provided, a randomly generated one will be used for this
46                     session.
47 
48     -d, --developer-certificate=path-to-cert: Path to PEM file containing a
49                            developer generated server root TLS certificate.
50                            If a root server certificate is not provided, one
51                            will be generated using a randomly generated
52                            private key. Note that if a certificate path is
53                            passed, the private key path is a mandatory field.
54 
55     -g, --generate-credentials: Instructs the binary to generate a private key
56                                 and self-signed root certificate with the CA
57                                 bit set to true, and then exit. The resulting
58                                 private key and certificate can then be used as
59                                 values for the -p and -s flags.
60 
61     -f, --friendly-name: Friendly name to be used for device discovery.
62 
63     -m, --model-name: Model name to be used for device discovery.
64 
65     -t, --tracing: Enable performance tracing logging.
66 
67     -v, --verbose: Enable verbose logging.
68 
69     -h, --help: Show this help message.
70 )";
71 
72   std::cerr << StringPrintf(kTemplate, argv0);
73 }
74 
GetInterfaceInfoFromName(const char * name)75 InterfaceInfo GetInterfaceInfoFromName(const char* name) {
76   OSP_CHECK(name != nullptr) << "Missing mandatory argument: interface.";
77   InterfaceInfo interface_info;
78   std::vector<InterfaceInfo> network_interfaces = GetNetworkInterfaces();
79   for (auto& interface : network_interfaces) {
80     if (interface.name == name) {
81       interface_info = std::move(interface);
82       break;
83     }
84   }
85 
86   if (interface_info.name.empty()) {
87     auto error_or_info = GetLoopbackInterfaceForTesting();
88     if (error_or_info.has_value()) {
89       if (error_or_info.value().name == name) {
90         interface_info = std::move(error_or_info.value());
91       }
92     }
93   }
94   OSP_CHECK(!interface_info.name.empty()) << "Invalid interface specified.";
95   return interface_info;
96 }
97 
RunCastService(TaskRunnerImpl * task_runner,const InterfaceInfo & interface,GeneratedCredentials creds,const std::string & friendly_name,const std::string & model_name,bool discovery_enabled)98 void RunCastService(TaskRunnerImpl* task_runner,
99                     const InterfaceInfo& interface,
100                     GeneratedCredentials creds,
101                     const std::string& friendly_name,
102                     const std::string& model_name,
103                     bool discovery_enabled) {
104   std::unique_ptr<CastService> service;
105   task_runner->PostTask([&] {
106     service = std::make_unique<CastService>(task_runner, interface,
107                                             std::move(creds), friendly_name,
108                                             model_name, discovery_enabled);
109   });
110 
111   OSP_LOG_INFO << "CastService is running. CTRL-C (SIGINT), or send a "
112                   "SIGTERM to exit.";
113   task_runner->RunUntilSignaled();
114 
115   // Spin the TaskRunner to execute destruction/shutdown tasks.
116   OSP_LOG_INFO << "Shutting down...";
117   task_runner->PostTask([&] {
118     service.reset();
119     task_runner->RequestStopSoon();
120   });
121   task_runner->RunUntilStopped();
122   OSP_LOG_INFO << "Bye!";
123 }
124 
RunStandaloneReceiver(int argc,char * argv[])125 int RunStandaloneReceiver(int argc, char* argv[]) {
126 #if !defined(CAST_ALLOW_DEVELOPER_CERTIFICATE)
127   OSP_LOG_FATAL
128       << "It compiled! However cast_receiver currently only supports using a "
129          "passed-in certificate and private key, and must be built with "
130          "cast_allow_developer_certificate=true set in the GN args to "
131          "actually do anything interesting.";
132   return 1;
133 #endif
134 
135   // A note about modifying command line arguments: consider uniformity
136   // between all Open Screen executables. If it is a platform feature
137   // being exposed, consider if it applies to the standalone receiver,
138   // standalone sender, osp demo, and test_main argument options.
139   const struct option kArgumentOptions[] = {
140       {"private-key", required_argument, nullptr, 'p'},
141       {"developer-certificate", required_argument, nullptr, 'd'},
142       {"generate-credentials", no_argument, nullptr, 'g'},
143       {"friendly-name", required_argument, nullptr, 'f'},
144       {"model-name", required_argument, nullptr, 'm'},
145       {"tracing", no_argument, nullptr, 't'},
146       {"verbose", no_argument, nullptr, 'v'},
147       {"help", no_argument, nullptr, 'h'},
148 
149       // Discovery is enabled by default, however there are cases where it
150       // needs to be disabled, such as on Mac OS X.
151       {"disable-discovery", no_argument, nullptr, 'x'},
152       {nullptr, 0, nullptr, 0}};
153 
154   bool is_verbose = false;
155   bool discovery_enabled = true;
156   std::string private_key_path;
157   std::string developer_certificate_path;
158   std::string friendly_name = "Cast Standalone Receiver";
159   std::string model_name = "cast_standalone_receiver";
160   bool should_generate_credentials = false;
161   std::unique_ptr<TextTraceLoggingPlatform> trace_logger;
162   int ch = -1;
163   while ((ch = getopt_long(argc, argv, "p:d:f:m:gtvhx", kArgumentOptions,
164                            nullptr)) != -1) {
165     switch (ch) {
166       case 'p':
167         private_key_path = optarg;
168         break;
169       case 'd':
170         developer_certificate_path = optarg;
171         break;
172       case 'f':
173         friendly_name = optarg;
174         break;
175       case 'm':
176         model_name = optarg;
177         break;
178       case 'g':
179         should_generate_credentials = true;
180         break;
181       case 't':
182         trace_logger = std::make_unique<TextTraceLoggingPlatform>();
183         break;
184       case 'v':
185         is_verbose = true;
186         break;
187       case 'x':
188         discovery_enabled = false;
189         break;
190       case 'h':
191         LogUsage(argv[0]);
192         return 1;
193     }
194   }
195 
196   SetLogLevel(is_verbose ? LogLevel::kVerbose : LogLevel::kInfo);
197 
198   // Either -g is required, or both -p and -d.
199   if (should_generate_credentials) {
200     GenerateDeveloperCredentialsToFile();
201     return 0;
202   }
203   if (private_key_path.empty() || developer_certificate_path.empty()) {
204     OSP_LOG_FATAL << "You must either invoke with -g to generate credentials, "
205                      "or provide both a private key path and root certificate "
206                      "using -p and -d";
207     return 1;
208   }
209 
210   const char* interface_name = argv[optind];
211   OSP_CHECK(interface_name && strlen(interface_name) > 0)
212       << "No interface name provided.";
213 
214   std::string device_id =
215       absl::StrCat("Standalone Receiver on ", interface_name);
216   ErrorOr<GeneratedCredentials> creds = GenerateCredentials(
217       device_id, private_key_path, developer_certificate_path);
218   OSP_CHECK(creds.is_value()) << creds.error();
219 
220   const InterfaceInfo interface = GetInterfaceInfoFromName(interface_name);
221   OSP_CHECK(interface.GetIpAddressV4() || interface.GetIpAddressV6());
222   if (std::all_of(interface.hardware_address.begin(),
223                   interface.hardware_address.end(),
224                   [](int e) { return e == 0; })) {
225     OSP_LOG_WARN
226         << "Hardware address is empty. Either you are on a loopback device "
227            "or getting the network interface information failed somehow. "
228            "Discovery publishing will be disabled.";
229     discovery_enabled = false;
230   }
231 
232   auto* const task_runner = new TaskRunnerImpl(&Clock::now);
233   PlatformClientPosix::Create(milliseconds(50),
234                               std::unique_ptr<TaskRunnerImpl>(task_runner));
235   RunCastService(task_runner, interface, std::move(creds.value()),
236                  friendly_name, model_name, discovery_enabled);
237   PlatformClientPosix::ShutDown();
238 
239   return 0;
240 }
241 
242 }  // namespace
243 }  // namespace cast
244 }  // namespace openscreen
245 
main(int argc,char * argv[])246 int main(int argc, char* argv[]) {
247   return openscreen::cast::RunStandaloneReceiver(argc, argv);
248 }
249