aboutsummaryrefslogtreecommitdiff
path: root/cast/standalone_receiver/main.cc
blob: ac001f58a2ba11967ea9bb2956181b49684661cf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
// 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 <getopt.h>

#include <algorithm>
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#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 <options> <interface>

    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 receiver discovery.

    -m, --model-name: Model name to be used for receiver discovery.

    -t, --tracing: Enable performance tracing logging.

    -v, --verbose: Enable verbose logging.

    -h, --help: Show this help message.

    -x, --disable-discovery: Disable discovery, useful for platforms like Mac OS
                             where our implementation is incompatible with
                             the native Bonjour service.

)";

  std::cerr << StringPrintf(kTemplate, argv0);
}

InterfaceInfo GetInterfaceInfoFromName(const char* name) {
  OSP_CHECK(name != nullptr) << "Missing mandatory argument: interface.";
  InterfaceInfo interface_info;
  std::vector<InterfaceInfo> 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* runner, CastService::Configuration config) {
  std::unique_ptr<CastService> service;
  runner->PostTask(
      [&] { service = std::make_unique<CastService>(std::move(config)); });

  OSP_LOG_INFO << "CastService is running. CTRL-C (SIGINT), or send a "
                  "SIGTERM to exit.";
  runner->RunUntilSignaled();

  // Spin the TaskRunner to execute destruction/shutdown tasks.
  OSP_LOG_INFO << "Shutting down...";
  runner->PostTask([&] {
    service.reset();
    runner->RequestStopSoon();
  });
  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

#if !defined(CAST_STANDALONE_RECEIVER_HAVE_EXTERNAL_LIBS)
  OSP_LOG_INFO
      << "Note: compiled without external libs. The dummy player will "
         "be linked and no video decoding will occur. If this is not desired, "
         "install the required external libraries. For more information, see: "
         "[external_libraries.md](../../build/config/external_libraries.md).";
#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 enable_discovery = 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<TextTraceLoggingPlatform> trace_logger;
  int ch = -1;
  while ((ch = getopt_long(argc, argv, "p:d:f:m:grtvhx", 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<TextTraceLoggingPlatform>();
        break;
      case 'v':
        is_verbose = true;
        break;
      case 'x':
        enable_discovery = 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 receiver_id =
      absl::StrCat("Standalone Receiver on ", interface_name);
  ErrorOr<GeneratedCredentials> creds = GenerateCredentials(
      receiver_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());

  auto* const task_runner = new TaskRunnerImpl(&Clock::now);
  PlatformClientPosix::Create(milliseconds(50),
                              std::unique_ptr<TaskRunnerImpl>(task_runner));
  RunCastService(task_runner,
                 CastService::Configuration{
                     task_runner, interface, std::move(creds.value()),
                     friendly_name, model_name, enable_discovery});
  PlatformClientPosix::ShutDown();

  return 0;
}

}  // namespace
}  // namespace cast
}  // namespace openscreen

int main(int argc, char* argv[]) {
  return openscreen::cast::RunStandaloneReceiver(argc, argv);
}