aboutsummaryrefslogtreecommitdiff
path: root/cast/standalone_receiver/main.cc
blob: 1c497f51667b4eb594a1bf10dde7ba0557840c06 (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
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
// 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 <array>
#include <chrono>
#include <iostream>

#include "absl/strings/str_cat.h"
#include "cast/common/public/service_info.h"
#include "cast/receiver/channel/static_credentials.h"
#include "cast/standalone_receiver/cast_agent.h"
#include "cast/streaming/ssrc.h"
#include "discovery/common/config.h"
#include "discovery/common/reporting_client.h"
#include "discovery/public/dns_sd_service_factory.h"
#include "discovery/public/dns_sd_service_publisher.h"
#include "platform/api/time.h"
#include "platform/api/udp_socket.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 {

class DiscoveryReportingClient : public discovery::ReportingClient {
  void OnFatalError(Error error) override {
    OSP_LOG_FATAL << "Encountered fatal discovery error: " << error;
  }

  void OnRecoverableError(Error error) override {
    OSP_LOG_ERROR << "Encountered recoverable discovery error: " << error;
  }
};

struct DiscoveryState {
  SerialDeletePtr<discovery::DnsSdService> service;
  std::unique_ptr<DiscoveryReportingClient> reporting_client;
  std::unique_ptr<discovery::DnsSdServicePublisher<ServiceInfo>> publisher;
};

ErrorOr<std::unique_ptr<DiscoveryState>> StartDiscovery(
    TaskRunner* task_runner,
    const InterfaceInfo& interface) {
  discovery::Config config;

  discovery::Config::NetworkInfo::AddressFamilies supported_address_families =
      discovery::Config::NetworkInfo::kNoAddressFamily;
  if (interface.GetIpAddressV4()) {
    supported_address_families |= discovery::Config::NetworkInfo::kUseIpV4;
  }
  if (interface.GetIpAddressV6()) {
    supported_address_families |= discovery::Config::NetworkInfo::kUseIpV6;
  }
  OSP_CHECK(supported_address_families !=
            discovery::Config::NetworkInfo::kNoAddressFamily)
      << "No address families supported by the selected interface";
  config.network_info.push_back({interface, supported_address_families});

  auto state = std::make_unique<DiscoveryState>();
  state->reporting_client = std::make_unique<DiscoveryReportingClient>();
  state->service = discovery::CreateDnsSdService(
      task_runner, state->reporting_client.get(), config);

  ServiceInfo info;
  info.port = kDefaultCastPort;

  OSP_CHECK(std::any_of(interface.hardware_address.begin(),
                        interface.hardware_address.end(),
                        [](int e) { return e > 0; }));
  info.unique_id = HexEncode(interface.hardware_address);

  // TODO(jophba): add command line arguments to set these fields.
  info.model_name = "cast_standalone_receiver";
  info.friendly_name = "Cast Standalone Receiver";

  state->publisher =
      std::make_unique<discovery::DnsSdServicePublisher<ServiceInfo>>(
          state->service.get(), kCastV2ServiceId, ServiceInfoToDnsSdInstance);

  auto error = state->publisher->Register(info);
  if (!error.ok()) {
    return error;
  }
  return state;
}

std::unique_ptr<CastAgent> StartCastAgent(TaskRunnerImpl* task_runner,
                                          const InterfaceInfo& interface,
                                          GeneratedCredentials* creds) {
  auto agent = std::make_unique<CastAgent>(
      task_runner, interface, creds->provider.get(), creds->tls_credentials);
  const auto error = agent->Start();
  if (!error.ok()) {
    OSP_LOG_ERROR << "Error occurred while starting agent: " << error;
    agent.reset();
  }
  return agent;
}

void LogUsage(const char* argv0) {
  std::cerr << R"(
usage: )" << argv0
            << R"( <options> <interface>

options:
    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.

    -p, --private-key=path-to-key: Path to OpenSSL-generated private key to be
                    used for TLS authentication.

    -s, --server-certificate=path-to-cert: Path to PEM file containing a
                           server certificate to be used for TLS authentication.

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

    -v, --verbose: Enable verbose logging.

    -h, --help: Show this help message.
  )";
}

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;
}

int RunStandaloneReceiver(int argc, char* argv[]) {
  // 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'},
      {"server-certificate", required_argument, nullptr, 's'},
      {"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 server_certificate_path;
  std::unique_ptr<openscreen::TextTraceLoggingPlatform> trace_logger;
  int ch = -1;
  while ((ch = getopt_long(argc, argv, "p:s:tvhx", kArgumentOptions,
                           nullptr)) != -1) {
    switch (ch) {
      case 'p':
        private_key_path = optarg;
        break;
      case 's':
        server_certificate_path = optarg;
        break;
      case 't':
        trace_logger = std::make_unique<openscreen::TextTraceLoggingPlatform>();
        break;
      case 'v':
        is_verbose = true;
        break;
      case 'x':
        discovery_enabled = false;
        break;
      case 'h':
        LogUsage(argv[0]);
        return 1;
    }
  }
  if (private_key_path.empty() != server_certificate_path.empty()) {
    OSP_LOG_ERROR << "If a private key or server certificate path is provided, "
                     "both are required.";
    return 1;
  }
  SetLogLevel(is_verbose ? openscreen::LogLevel::kVerbose
                         : openscreen::LogLevel::kInfo);

  auto* const task_runner = new TaskRunnerImpl(&Clock::now);
  PlatformClientPosix::Create(milliseconds(50), milliseconds(50),
                              std::unique_ptr<TaskRunnerImpl>(task_runner));

  // Post tasks to kick-off the CastAgent and, if successful, start discovery to
  // make this standalone receiver visible to senders on the network.
  std::unique_ptr<DiscoveryState> discovery_state;
  std::unique_ptr<CastAgent> cast_agent;
  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<GeneratedCredentials> creds = Error::Code::kEVPInitializationError;
  if (private_key_path.empty()) {
    creds = GenerateCredentials(device_id);
  } else {
    creds = GenerateCredentials(device_id, private_key_path,
                                server_certificate_path);
  }
  OSP_CHECK(creds.is_value()) << creds.error();
  task_runner->PostTask(
      [&, interface = GetInterfaceInfoFromName(interface_name)] {
        cast_agent = StartCastAgent(task_runner, interface, &(creds.value()));
        OSP_CHECK(cast_agent) << "Failed to start CastAgent.";

        if (discovery_enabled) {
          auto result = StartDiscovery(task_runner, interface);
          OSP_CHECK(result.is_value()) << "Failed to start discovery.";
          discovery_state = std::move(result.value());
        }
      });

  // Run the event loop until an exit is requested (e.g., the video player GUI
  // window is closed, a SIGINT or SIGTERM is received, or whatever other
  // appropriate user indication that shutdown is requested).
  task_runner->RunUntilSignaled();

  // Shutdown the Cast Agent and discovery-related entities. This may cause one
  // or more tasks to be posted, and so the TaskRunner is spun to give them a
  // chance to execute.
  discovery_state.reset();
  cast_agent.reset();
  task_runner->PostTask([task_runner] { task_runner->RequestStopSoon(); });
  task_runner->RunUntilStopped();

  PlatformClientPosix::ShutDown();
  return 0;
}

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

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