// Copyright 2019 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 #include #include #include #include #include #include #include "absl/strings/str_cat.h" #include "cast/receiver/channel/static_credentials.h" #include "cast/standalone_receiver/cast_service.h" #include "platform/api/time.h" #include "platform/base/error.h" #include "platform/base/ip_address.h" #include "platform/impl/logging.h" #include "platform/impl/network_interface.h" #include "platform/impl/platform_client_posix.h" #include "platform/impl/task_runner.h" #include "platform/impl/text_trace_logging_platform.h" #include "util/chrono_helpers.h" #include "util/stringprintf.h" #include "util/trace_logging.h" namespace openscreen { namespace cast { namespace { void LogUsage(const char* argv0) { constexpr char kTemplate[] = R"( usage: %s interface Specifies the network interface to bind to. The interface is looked up from the system interface registry. Mandatory, as it must be known for publishing discovery. options: -p, --private-key=path-to-key: Path to OpenSSL-generated private key to be used for TLS authentication. If a private key is not provided, a randomly generated one will be used for this session. -d, --developer-certificate=path-to-cert: Path to PEM file containing a developer generated server root TLS certificate. If a root server certificate is not provided, one will be generated using a randomly generated private key. Note that if a certificate path is passed, the private key path is a mandatory field. -g, --generate-credentials: Instructs the binary to generate a private key and self-signed root certificate with the CA bit set to true, and then exit. The resulting private key and certificate can then be used as values for the -p and -s flags. -f, --friendly-name: Friendly name to be used for device discovery. -m, --model-name: Model name to be used for device discovery. -t, --tracing: Enable performance tracing logging. -v, --verbose: Enable verbose logging. -h, --help: Show this help message. )"; std::cerr << StringPrintf(kTemplate, argv0); } InterfaceInfo GetInterfaceInfoFromName(const char* name) { OSP_CHECK(name != nullptr) << "Missing mandatory argument: interface."; InterfaceInfo interface_info; std::vector network_interfaces = GetNetworkInterfaces(); for (auto& interface : network_interfaces) { if (interface.name == name) { interface_info = std::move(interface); break; } } if (interface_info.name.empty()) { auto error_or_info = GetLoopbackInterfaceForTesting(); if (error_or_info.has_value()) { if (error_or_info.value().name == name) { interface_info = std::move(error_or_info.value()); } } } OSP_CHECK(!interface_info.name.empty()) << "Invalid interface specified."; return interface_info; } void RunCastService(TaskRunnerImpl* task_runner, const InterfaceInfo& interface, GeneratedCredentials creds, const std::string& friendly_name, const std::string& model_name, bool discovery_enabled) { std::unique_ptr service; task_runner->PostTask([&] { service = std::make_unique(task_runner, interface, std::move(creds), friendly_name, model_name, discovery_enabled); }); OSP_LOG_INFO << "CastService is running. CTRL-C (SIGINT), or send a " "SIGTERM to exit."; task_runner->RunUntilSignaled(); // Spin the TaskRunner to execute destruction/shutdown tasks. OSP_LOG_INFO << "Shutting down..."; task_runner->PostTask([&] { service.reset(); task_runner->RequestStopSoon(); }); task_runner->RunUntilStopped(); OSP_LOG_INFO << "Bye!"; } int RunStandaloneReceiver(int argc, char* argv[]) { #if !defined(CAST_ALLOW_DEVELOPER_CERTIFICATE) OSP_LOG_FATAL << "It compiled! However cast_receiver currently only supports using a " "passed-in certificate and private key, and must be built with " "cast_allow_developer_certificate=true set in the GN args to " "actually do anything interesting."; return 1; #endif // A note about modifying command line arguments: consider uniformity // between all Open Screen executables. If it is a platform feature // being exposed, consider if it applies to the standalone receiver, // standalone sender, osp demo, and test_main argument options. const struct option kArgumentOptions[] = { {"private-key", required_argument, nullptr, 'p'}, {"developer-certificate", required_argument, nullptr, 'd'}, {"generate-credentials", no_argument, nullptr, 'g'}, {"friendly-name", required_argument, nullptr, 'f'}, {"model-name", required_argument, nullptr, 'm'}, {"tracing", no_argument, nullptr, 't'}, {"verbose", no_argument, nullptr, 'v'}, {"help", no_argument, nullptr, 'h'}, // Discovery is enabled by default, however there are cases where it // needs to be disabled, such as on Mac OS X. {"disable-discovery", no_argument, nullptr, 'x'}, {nullptr, 0, nullptr, 0}}; bool is_verbose = false; bool discovery_enabled = true; std::string private_key_path; std::string developer_certificate_path; std::string friendly_name = "Cast Standalone Receiver"; std::string model_name = "cast_standalone_receiver"; bool should_generate_credentials = false; std::unique_ptr trace_logger; int ch = -1; while ((ch = getopt_long(argc, argv, "p:d:f:m:gtvhx", kArgumentOptions, nullptr)) != -1) { switch (ch) { case 'p': private_key_path = optarg; break; case 'd': developer_certificate_path = optarg; break; case 'f': friendly_name = optarg; break; case 'm': model_name = optarg; break; case 'g': should_generate_credentials = true; break; case 't': trace_logger = std::make_unique(); break; case 'v': is_verbose = true; break; case 'x': discovery_enabled = false; break; case 'h': LogUsage(argv[0]); return 1; } } SetLogLevel(is_verbose ? LogLevel::kVerbose : LogLevel::kInfo); // Either -g is required, or both -p and -d. if (should_generate_credentials) { GenerateDeveloperCredentialsToFile(); return 0; } if (private_key_path.empty() || developer_certificate_path.empty()) { OSP_LOG_FATAL << "You must either invoke with -g to generate credentials, " "or provide both a private key path and root certificate " "using -p and -d"; return 1; } const char* interface_name = argv[optind]; OSP_CHECK(interface_name && strlen(interface_name) > 0) << "No interface name provided."; std::string device_id = absl::StrCat("Standalone Receiver on ", interface_name); ErrorOr creds = GenerateCredentials( device_id, private_key_path, developer_certificate_path); OSP_CHECK(creds.is_value()) << creds.error(); const InterfaceInfo interface = GetInterfaceInfoFromName(interface_name); OSP_CHECK(interface.GetIpAddressV4() || interface.GetIpAddressV6()); if (std::all_of(interface.hardware_address.begin(), interface.hardware_address.end(), [](int e) { return e == 0; })) { OSP_LOG_WARN << "Hardware address is empty. Either you are on a loopback device " "or getting the network interface information failed somehow. " "Discovery publishing will be disabled."; discovery_enabled = false; } auto* const task_runner = new TaskRunnerImpl(&Clock::now); PlatformClientPosix::Create(milliseconds(50), std::unique_ptr(task_runner)); RunCastService(task_runner, interface, std::move(creds.value()), friendly_name, model_name, discovery_enabled); PlatformClientPosix::ShutDown(); return 0; } } // namespace } // namespace cast } // namespace openscreen int main(int argc, char* argv[]) { return openscreen::cast::RunStandaloneReceiver(argc, argv); }