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