Standalone Receiver: Integration with ApplicationAgent.

Makes the standalone cast receiver a full Cast V2 Receiver with a
launchable "Mirroring App." Replaces "CastAgent" with three modules:

MirroringApplication (new): A front-end launcher and message port for a
ReceiverSession (and a StreamingPlaybackController, which manages the
playback UI).

ApplicatonAgent (prior patch): An implementation of the Cast V2
Application Control spec, able to launch applications and route messages
to/from them.

CastService (new): Glues it all together, from a network server socket,
through the Cast Channel infrastructure, through the ApplicationAgent
and MirroringApplication, to a ReceiverSession and playback GUI.

Bug: b/170134354
Change-Id: I9640a3d0c40f174d9f03bc26ee3c2f160736e290
Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/2481853
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Reviewed-by: Jordan Bayles <jophba@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 16a82c8..8fad80f 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -118,7 +118,6 @@
     testonly = true
     public_deps = [
       "cast/common:discovery_e2e_test",
-      "cast/standalone_receiver:e2e_tests",
       "cast/test:e2e_tests",
       "cast/test:make_crl_tests($host_toolchain)",
     ]
diff --git a/cast/receiver/application_agent.cc b/cast/receiver/application_agent.cc
index 4dc435d..97f2da2 100644
--- a/cast/receiver/application_agent.cc
+++ b/cast/receiver/application_agent.cc
@@ -358,6 +358,11 @@
 
   if (launched_app_) {
     Json::Value& details = status[kMessageKeyApplications][0];
+    // If the Application can send/receive messages, the destination for such
+    // messages is provided here, in |transportId|. However, the other end must
+    // first set up the virtual connection by issuing a CONNECT request.
+    // Otherwise, messages will not get routed to the Application by the
+    // VirtualConnectionRouter.
     if (!message_port_.client_sender_id().empty()) {
       details[kMessageKeyTransportId] = message_port_.client_sender_id();
     }
diff --git a/cast/receiver/application_agent.h b/cast/receiver/application_agent.h
index 4aa551d..a6a792a 100644
--- a/cast/receiver/application_agent.h
+++ b/cast/receiver/application_agent.h
@@ -14,6 +14,7 @@
 #include "cast/common/channel/connection_namespace_handler.h"
 #include "cast/common/channel/virtual_connection_manager.h"
 #include "cast/common/channel/virtual_connection_router.h"
+#include "cast/common/public/cast_socket.h"
 #include "cast/receiver/channel/device_auth_namespace_handler.h"
 #include "cast/receiver/public/receiver_socket_factory.h"
 #include "platform/api/serial_delete_ptr.h"
@@ -87,6 +88,10 @@
 
   ~ApplicationAgent() final;
 
+  // Return the interface by which the CastSocket inbound traffic is delivered
+  // into this agent and any running Applications.
+  CastSocket::Client* cast_socket_client() { return &router_; }
+
   // Registers an Application for launching by this agent. |app| must outlive
   // this ApplicationAgent, or until UnregisterApplication() is called.
   void RegisterApplication(Application* app,
@@ -151,8 +156,8 @@
 
   TaskRunner* const task_runner_;
   DeviceAuthNamespaceHandler auth_handler_;
-  ConnectionNamespaceHandler connection_handler_;
   VirtualConnectionManager connection_manager_;
+  ConnectionNamespaceHandler connection_handler_;
   VirtualConnectionRouter router_;
 
   std::map<std::string, Application*> registered_applications_;
diff --git a/cast/receiver/application_agent_unittest.cc b/cast/receiver/application_agent_unittest.cc
index c89a451..6240ac0 100644
--- a/cast/receiver/application_agent_unittest.cc
+++ b/cast/receiver/application_agent_unittest.cc
@@ -78,7 +78,7 @@
 class FakeApplication : public ApplicationAgent::Application,
                         public MessagePort::Client {
  public:
-  explicit FakeApplication(const char* app_id, const char* display_name)
+  FakeApplication(const char* app_id, const char* display_name)
       : app_ids_({app_id}), display_name_(display_name) {
     OSP_CHECK(app_ids_.front().size() == 8);
   }
diff --git a/cast/standalone_receiver/BUILD.gn b/cast/standalone_receiver/BUILD.gn
index c36c604..74d53f6 100644
--- a/cast/standalone_receiver/BUILD.gn
+++ b/cast/standalone_receiver/BUILD.gn
@@ -10,8 +10,10 @@
 # application.
 if (!build_with_chromium) {
   shared_sources = [
-    "cast_agent.cc",
-    "cast_agent.h",
+    "cast_service.cc",
+    "cast_service.h",
+    "mirroring_application.cc",
+    "mirroring_application.h",
     "streaming_playback_controller.cc",
     "streaming_playback_controller.h",
   ]
@@ -58,23 +60,13 @@
     ]
   }
 
-  source_set("e2e_tests") {
-    testonly = true
-
-    sources = [ "cast_agent_integration_tests.cc" ]
-
-    deps = [
-      ":standalone_receiver_dummy",
-      "../../third_party/boringssl",
-      "../../third_party/googletest:gtest",
-      "../receiver:channel",
-    ]
-  }
-
   executable("cast_receiver") {
     sources = [ "main.cc" ]
 
-    deps = [ "../receiver:channel" ]
+    deps = [
+      "../receiver:agent",
+      "../receiver:channel",
+    ]
 
     configs += [ "../common:certificate_config" ]
 
diff --git a/cast/standalone_receiver/cast_agent.cc b/cast/standalone_receiver/cast_agent.cc
deleted file mode 100644
index 791029a..0000000
--- a/cast/standalone_receiver/cast_agent.cc
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright 2020 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 "cast/standalone_receiver/cast_agent.h"
-
-#include <fstream>
-#include <sstream>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include "absl/strings/str_cat.h"
-#include "cast/common/channel/cast_socket_message_port.h"
-#include "cast/common/channel/message_util.h"
-#include "cast/streaming/constants.h"
-#include "cast/streaming/offer_messages.h"
-#include "platform/base/tls_credentials.h"
-#include "platform/base/tls_listen_options.h"
-#include "util/json/json_serialization.h"
-#include "util/osp_logging.h"
-#include "util/trace_logging.h"
-
-namespace openscreen {
-namespace cast {
-namespace {
-
-constexpr int kDefaultMaxBacklogSize = 64;
-const TlsListenOptions kDefaultListenOptions{kDefaultMaxBacklogSize};
-
-}  // namespace
-
-CastAgent::CastAgent(
-    TaskRunner* task_runner,
-    const InterfaceInfo& interface,
-    DeviceAuthNamespaceHandler::CredentialsProvider* credentials_provider,
-    TlsCredentials tls_credentials)
-    : task_runner_(task_runner),
-      credentials_provider_(credentials_provider),
-      tls_credentials_(std::move(tls_credentials)) {
-  const IPAddress address = interface.GetIpAddressV4()
-                                ? interface.GetIpAddressV4()
-                                : interface.GetIpAddressV6();
-  OSP_CHECK(address);
-  environment_ = std::make_unique<Environment>(
-      &Clock::now, task_runner_,
-      IPEndpoint{address, kDefaultCastStreamingPort});
-  receive_endpoint_ = IPEndpoint{address, kDefaultCastPort};
-}
-
-CastAgent::~CastAgent() = default;
-
-Error CastAgent::Start() {
-  TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
-  OSP_CHECK(!current_session_);
-
-  task_runner_->PostTask([this] {
-    wake_lock_ = ScopedWakeLock::Create(task_runner_);
-
-    auth_handler_ = MakeSerialDelete<DeviceAuthNamespaceHandler>(
-        task_runner_, credentials_provider_);
-    router_ = MakeSerialDelete<VirtualConnectionRouter>(task_runner_,
-                                                        &connection_manager_);
-    message_port_ =
-        MakeSerialDelete<CastSocketMessagePort>(task_runner_, router_.get());
-    router_->AddHandlerForLocalId(kPlatformReceiverId, auth_handler_.get());
-    socket_factory_ = MakeSerialDelete<ReceiverSocketFactory>(
-        task_runner_, this, router_.get());
-    connection_factory_ = SerialDeletePtr<TlsConnectionFactory>(
-        task_runner_,
-        TlsConnectionFactory::CreateFactory(socket_factory_.get(), task_runner_)
-            .release());
-    connection_factory_->SetListenCredentials(tls_credentials_);
-    connection_factory_->Listen(receive_endpoint_, kDefaultListenOptions);
-    OSP_LOG_INFO << "Listening for connections at: " << receive_endpoint_;
-  });
-
-  return Error::None();
-}
-
-Error CastAgent::Stop() {
-  task_runner_->PostTask([this] {
-    router_.reset();
-    connection_factory_.reset();
-    controller_.reset();
-    current_session_.reset();
-    socket_factory_.reset();
-    wake_lock_.reset();
-  });
-  return Error::None();
-}
-
-void CastAgent::OnConnected(ReceiverSocketFactory* factory,
-                            const IPEndpoint& endpoint,
-                            std::unique_ptr<CastSocket> socket) {
-  TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
-  if (current_session_) {
-    OSP_LOG_WARN << "Already connected, dropping peer at: " << endpoint;
-    return;
-  }
-
-  OSP_LOG_INFO << "Received connection from peer at: " << endpoint;
-  message_port_->SetSocket(socket->GetWeakPtr());
-  router_->TakeSocket(this, std::move(socket));
-  controller_ =
-      std::make_unique<StreamingPlaybackController>(task_runner_, this);
-  current_session_ = std::make_unique<ReceiverSession>(
-      controller_.get(), environment_.get(), message_port_.get(),
-      ReceiverSession::Preferences{});
-}
-
-void CastAgent::OnError(ReceiverSocketFactory* factory, Error error) {
-  OSP_LOG_ERROR << "Cast agent received socket factory error: " << error;
-  StopCurrentSession();
-}
-
-void CastAgent::OnClose(CastSocket* cast_socket) {
-  OSP_VLOG << "Cast agent socket closed.";
-  StopCurrentSession();
-}
-
-void CastAgent::OnError(CastSocket* socket, Error error) {
-  OSP_LOG_ERROR << "Cast agent received socket error: " << error;
-  StopCurrentSession();
-}
-
-// Currently we don't do anything with the receiver output--the session
-// is automatically linked to the playback controller when it is constructed, so
-// we don't actually have to interface with the receivers. If we end up caring
-// about the receiver configurations we will have to handle OnNegotiated here.
-void CastAgent::OnNegotiated(const ReceiverSession* session,
-                             ReceiverSession::ConfiguredReceivers receivers) {
-  OSP_VLOG << "Successfully negotiated with sender.";
-}
-
-void CastAgent::OnReceiversDestroying(const ReceiverSession* session,
-                                      ReceiversDestroyingReason reason) {
-  const auto GetReasoning = [&] {
-    switch (reason) {
-      case kEndOfSession:
-        return " at end of session.";
-      case kRenegotiated:
-        return ", to be replaced with new ones.";
-    }
-    return "";
-  };
-  OSP_VLOG << "Receiver instances destroying" << GetReasoning();
-}
-
-// Currently, we just kill the session if an error is encountered.
-void CastAgent::OnError(const ReceiverSession* session, Error error) {
-  OSP_LOG_ERROR << "Cast agent received receiver session error: " << error;
-  StopCurrentSession();
-}
-
-void CastAgent::OnPlaybackError(StreamingPlaybackController* controller,
-                                Error error) {
-  OSP_LOG_ERROR << "Cast agent received playback error: " << error;
-  StopCurrentSession();
-}
-
-void CastAgent::StopCurrentSession() {
-  current_session_.reset();
-  controller_.reset();
-  router_->CloseSocket(message_port_->GetSocketId());
-  message_port_->SetSocket(nullptr);
-}
-
-}  // namespace cast
-}  // namespace openscreen
diff --git a/cast/standalone_receiver/cast_agent.h b/cast/standalone_receiver/cast_agent.h
deleted file mode 100644
index db8cf66..0000000
--- a/cast/standalone_receiver/cast_agent.h
+++ /dev/null
@@ -1,111 +0,0 @@
-// Copyright 2020 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.
-
-#ifndef CAST_STANDALONE_RECEIVER_CAST_AGENT_H_
-#define CAST_STANDALONE_RECEIVER_CAST_AGENT_H_
-
-#include <openssl/x509.h>
-
-#include <memory>
-#include <vector>
-
-#include "cast/common/channel/cast_socket_message_port.h"
-#include "cast/common/channel/virtual_connection_manager.h"
-#include "cast/common/channel/virtual_connection_router.h"
-#include "cast/common/public/cast_socket.h"
-#include "cast/receiver/channel/device_auth_namespace_handler.h"
-#include "cast/receiver/channel/static_credentials.h"
-#include "cast/receiver/public/receiver_socket_factory.h"
-#include "cast/standalone_receiver/streaming_playback_controller.h"
-#include "cast/streaming/environment.h"
-#include "cast/streaming/receiver_session.h"
-#include "platform/api/scoped_wake_lock.h"
-#include "platform/api/serial_delete_ptr.h"
-#include "platform/base/error.h"
-#include "platform/base/interface_info.h"
-#include "platform/base/tls_credentials.h"
-#include "platform/impl/task_runner.h"
-
-namespace openscreen {
-namespace cast {
-
-// This class manages sender connections, starting with listening over TLS for
-// connection attempts, constructing ReceiverSessions when OFFER messages are
-// received, and linking Receivers to the output decoder and SDL visualizer.
-//
-// Consumers of this class are expected to provide a single threaded task runner
-// implementation, a network interface information struct that will be used
-// both for TLS listening and UDP messaging, and a credentials provider used
-// for TLS listening.
-class CastAgent final : public ReceiverSocketFactory::Client,
-                        public VirtualConnectionRouter::SocketErrorHandler,
-                        public ReceiverSession::Client,
-                        public StreamingPlaybackController::Client {
- public:
-  CastAgent(
-      TaskRunner* task_runner,
-      const InterfaceInfo& interface,
-      DeviceAuthNamespaceHandler::CredentialsProvider* credentials_provider,
-      TlsCredentials tls_credentials);
-  ~CastAgent();
-
-  // Initialization occurs as part of construction, however to actually bind
-  // for discovery and listening over TLS, the CastAgent must be started.
-  Error Start();
-  Error Stop();
-
-  // ReceiverSocketFactory::Client overrides.
-  void OnConnected(ReceiverSocketFactory* factory,
-                   const IPEndpoint& endpoint,
-                   std::unique_ptr<CastSocket> socket) override;
-  void OnError(ReceiverSocketFactory* factory, Error error) override;
-
-  // VirtualConnectionRouter::SocketErrorHandler overrides.
-  void OnClose(CastSocket* cast_socket) override;
-  void OnError(CastSocket* socket, Error error) override;
-
-  // ReceiverSession::Client overrides.
-  void OnNegotiated(const ReceiverSession* session,
-                    ReceiverSession::ConfiguredReceivers receivers) override;
-  void OnReceiversDestroying(const ReceiverSession* session,
-                             ReceiversDestroyingReason reason) override;
-  void OnError(const ReceiverSession* session, Error error) override;
-
-  // StreamingPlaybackController::Client overrides
-  void OnPlaybackError(StreamingPlaybackController* controller,
-                       Error error) override;
-
- private:
-  // Helper for stopping the current session. This is useful for when we don't
-  // want to completely stop (e.g. an issue with a specific Sender) but need
-  // to terminate the current connection.
-  void StopCurrentSession();
-
-  // Member variables set as part of construction.
-  std::unique_ptr<Environment> environment_;
-  TaskRunner* const task_runner_;
-  IPEndpoint receive_endpoint_;
-  DeviceAuthNamespaceHandler::CredentialsProvider* credentials_provider_;
-  TlsCredentials tls_credentials_;
-
-  // Member variables set as part of starting up.
-  SerialDeletePtr<DeviceAuthNamespaceHandler> auth_handler_;
-  SerialDeletePtr<TlsConnectionFactory> connection_factory_;
-  VirtualConnectionManager connection_manager_;
-  SerialDeletePtr<VirtualConnectionRouter> router_;
-  SerialDeletePtr<CastSocketMessagePort> message_port_;
-  SerialDeletePtr<ReceiverSocketFactory> socket_factory_;
-  SerialDeletePtr<ScopedWakeLock> wake_lock_;
-
-  // Member variables set as part of a sender connection.
-  // NOTE: currently we only support a single sender connection and a
-  // single streaming session.
-  std::unique_ptr<ReceiverSession> current_session_;
-  std::unique_ptr<StreamingPlaybackController> controller_;
-};
-
-}  // namespace cast
-}  // namespace openscreen
-
-#endif  // CAST_STANDALONE_RECEIVER_CAST_AGENT_H_
diff --git a/cast/standalone_receiver/cast_agent_integration_tests.cc b/cast/standalone_receiver/cast_agent_integration_tests.cc
deleted file mode 100644
index 2cd6b77..0000000
--- a/cast/standalone_receiver/cast_agent_integration_tests.cc
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright 2020 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 "cast/common/certificate/cast_trust_store.h"
-#include "cast/common/certificate/testing/test_helpers.h"
-#include "cast/common/channel/virtual_connection_manager.h"
-#include "cast/common/channel/virtual_connection_router.h"
-#include "cast/receiver/channel/static_credentials.h"
-#include "cast/sender/public/sender_socket_factory.h"
-#include "cast/standalone_receiver/cast_agent.h"
-#include "gtest/gtest.h"
-#include "platform/api/serial_delete_ptr.h"
-#include "platform/api/time.h"
-#include "platform/impl/network_interface.h"
-#include "platform/impl/platform_client_posix.h"
-#include "platform/impl/task_runner.h"
-
-namespace openscreen {
-namespace cast {
-namespace {
-
-// Based heavily on SenderSocketsClient from cast_socket_e2e_test.cc.
-class MockSender final : public SenderSocketFactory::Client,
-                         public VirtualConnectionRouter::SocketErrorHandler {
- public:
-  explicit MockSender(VirtualConnectionRouter* router) : router_(router) {}
-  ~MockSender() = default;
-
-  CastSocket* socket() const { return socket_; }
-
-  // SenderSocketFactory::Client overrides.
-  void OnConnected(SenderSocketFactory* factory,
-                   const IPEndpoint& endpoint,
-                   std::unique_ptr<CastSocket> socket) override {
-    ASSERT_FALSE(socket_);
-    OSP_LOG_INFO << "Sender connected to endpoint: " << endpoint;
-    socket_ = socket.get();
-    router_->TakeSocket(this, std::move(socket));
-  }
-
-  void OnError(SenderSocketFactory* factory,
-               const IPEndpoint& endpoint,
-               Error error) override {
-    FAIL() << error;
-  }
-
-  // VirtualConnectionRouter::SocketErrorHandler overrides.
-  void OnClose(CastSocket* socket) override {}
-  void OnError(CastSocket* socket, Error error) override { FAIL() << error; }
-
- private:
-  VirtualConnectionRouter* const router_;
-  std::atomic<CastSocket*> socket_{nullptr};
-};
-
-class CastAgentIntegrationTest : public ::testing::Test {
- public:
-  void SetUp() override {
-    PlatformClientPosix::Create(std::chrono::milliseconds(50),
-                                std::chrono::milliseconds(50));
-    task_runner_ = reinterpret_cast<TaskRunnerImpl*>(
-        PlatformClientPosix::GetInstance()->GetTaskRunner());
-
-    sender_router_ = MakeSerialDelete<VirtualConnectionRouter>(
-        task_runner_, &sender_vc_manager_);
-    sender_client_ = std::make_unique<MockSender>(sender_router_.get());
-    sender_factory_ = MakeSerialDelete<SenderSocketFactory>(
-        task_runner_, sender_client_.get(), task_runner_);
-    sender_tls_factory_ = SerialDeletePtr<TlsConnectionFactory>(
-        task_runner_,
-        TlsConnectionFactory::CreateFactory(sender_factory_.get(), task_runner_)
-            .release());
-    sender_factory_->set_factory(sender_tls_factory_.get());
-  }
-
-  void TearDown() override {
-    sender_router_.reset();
-    sender_tls_factory_.reset();
-    sender_factory_.reset();
-    PlatformClientPosix::ShutDown();
-    // Must be shut down after platform client, so joined tasks
-    // depending on certs are called correctly.
-    CastTrustStore::ResetInstance();
-  }
-
-  void WaitAndAssertSenderSocketConnected() {
-    constexpr int kMaxAttempts = 10;
-    constexpr std::chrono::milliseconds kSocketWaitDelay(250);
-    for (int i = 0; i < kMaxAttempts; ++i) {
-      OSP_LOG_INFO << "\tChecking for CastSocket, attempt " << i + 1 << "/"
-                   << kMaxAttempts;
-      if (sender_client_->socket()) {
-        break;
-      }
-      std::this_thread::sleep_for(kSocketWaitDelay);
-    }
-    ASSERT_TRUE(sender_client_->socket());
-  }
-
-  void AssertConnect(const IPAddress& address) {
-    OSP_LOG_INFO << "Sending connect task";
-    task_runner_->PostTask(
-        [this, &address, port = (static_cast<uint16_t>(kDefaultCastPort))]() {
-          OSP_LOG_INFO << "Calling SenderSocketFactory::Connect";
-          sender_factory_->Connect(
-              IPEndpoint{address, port},
-              SenderSocketFactory::DeviceMediaPolicy::kNone,
-              sender_router_.get());
-        });
-    WaitAndAssertSenderSocketConnected();
-  }
-
-  TaskRunnerImpl* task_runner_;
-  // Cast socket sender components, used in conjuction to mock a Libcast sender.
-  VirtualConnectionManager sender_vc_manager_;
-  SerialDeletePtr<VirtualConnectionRouter> sender_router_;
-  std::unique_ptr<MockSender> sender_client_;
-  SerialDeletePtr<SenderSocketFactory> sender_factory_;
-  SerialDeletePtr<TlsConnectionFactory> sender_tls_factory_;
-};
-
-TEST_F(CastAgentIntegrationTest, CanConnect) {
-  absl::optional<InterfaceInfo> loopback = GetLoopbackInterfaceForTesting();
-  ASSERT_TRUE(loopback.has_value());
-
-  ErrorOr<GeneratedCredentials> creds =
-      GenerateCredentialsForTesting("Test Device Certificate");
-  ASSERT_TRUE(creds.is_value());
-  CastTrustStore::CreateInstanceForTest(creds.value().root_cert_der);
-
-  auto agent = MakeSerialDelete<CastAgent>(
-      task_runner_, task_runner_, loopback.value(),
-      creds.value().provider.get(), creds.value().tls_credentials);
-  EXPECT_TRUE(agent->Start().ok());
-  AssertConnect(loopback.value().GetIpAddressV4());
-  EXPECT_TRUE(agent->Stop().ok());
-}
-
-}  // namespace
-}  // namespace cast
-}  // namespace openscreen
diff --git a/cast/standalone_receiver/cast_service.cc b/cast/standalone_receiver/cast_service.cc
new file mode 100644
index 0000000..e4a5b53
--- /dev/null
+++ b/cast/standalone_receiver/cast_service.cc
@@ -0,0 +1,109 @@
+// Copyright 2020 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 "cast/standalone_receiver/cast_service.h"
+
+#include <utility>
+
+#include "discovery/common/config.h"
+#include "platform/api/tls_connection_factory.h"
+#include "platform/base/interface_info.h"
+#include "platform/base/tls_listen_options.h"
+#include "util/osp_logging.h"
+#include "util/stringprintf.h"
+
+namespace openscreen {
+namespace cast {
+
+namespace {
+
+constexpr uint16_t kDefaultCastServicePort = 8010;
+
+constexpr int kDefaultMaxBacklogSize = 64;
+const TlsListenOptions kDefaultListenOptions{kDefaultMaxBacklogSize};
+
+IPEndpoint DetermineEndpoint(const InterfaceInfo& interface) {
+  const IPAddress address = interface.GetIpAddressV4()
+                                ? interface.GetIpAddressV4()
+                                : interface.GetIpAddressV6();
+  OSP_CHECK(address);
+  return IPEndpoint{address, kDefaultCastServicePort};
+}
+
+discovery::Config MakeDiscoveryConfig(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;
+  }
+  config.network_info.push_back({interface, supported_address_families});
+
+  return config;
+}
+
+}  // namespace
+
+CastService::CastService(TaskRunner* task_runner,
+                         const InterfaceInfo& interface,
+                         GeneratedCredentials credentials,
+                         const std::string& friendly_name,
+                         const std::string& model_name,
+                         bool enable_discovery)
+    : local_endpoint_(DetermineEndpoint(interface)),
+      credentials_(std::move(credentials)),
+      agent_(task_runner, credentials_.provider.get()),
+      mirroring_application_(task_runner, local_endpoint_.address, &agent_),
+      socket_factory_(&agent_, agent_.cast_socket_client()),
+      connection_factory_(
+          TlsConnectionFactory::CreateFactory(&socket_factory_, task_runner)),
+      discovery_service_(enable_discovery ? discovery::CreateDnsSdService(
+                                                task_runner,
+                                                this,
+                                                MakeDiscoveryConfig(interface))
+                                          : LazyDeletedDiscoveryService()),
+      discovery_publisher_(
+          discovery_service_
+              ? MakeSerialDelete<discovery::DnsSdServicePublisher<ServiceInfo>>(
+                    task_runner,
+                    discovery_service_.get(),
+                    kCastV2ServiceId,
+                    ServiceInfoToDnsSdInstance)
+              : LazyDeletedDiscoveryPublisher()) {
+  connection_factory_->SetListenCredentials(credentials_.tls_credentials);
+  connection_factory_->Listen(local_endpoint_, kDefaultListenOptions);
+
+  if (discovery_publisher_) {
+    ServiceInfo info;
+    info.port = local_endpoint_.port;
+    info.unique_id = HexEncode(interface.hardware_address);
+    info.friendly_name = friendly_name;
+    info.model_name = model_name;
+    Error error = discovery_publisher_->Register(info);
+    if (!error.ok()) {
+      OnFatalError(std::move(error));
+    }
+  }
+}
+
+CastService::~CastService() {
+  if (discovery_publisher_) {
+    discovery_publisher_->DeregisterAll();
+  }
+}
+
+void CastService::OnFatalError(Error error) {
+  OSP_LOG_FATAL << "Encountered fatal discovery error: " << error;
+}
+
+void CastService::OnRecoverableError(Error error) {
+  OSP_LOG_ERROR << "Encountered recoverable discovery error: " << error;
+}
+
+}  // namespace cast
+}  // namespace openscreen
diff --git a/cast/standalone_receiver/cast_service.h b/cast/standalone_receiver/cast_service.h
new file mode 100644
index 0000000..99137de
--- /dev/null
+++ b/cast/standalone_receiver/cast_service.h
@@ -0,0 +1,77 @@
+// Copyright 2020 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.
+
+#ifndef CAST_STANDALONE_RECEIVER_CAST_SERVICE_H_
+#define CAST_STANDALONE_RECEIVER_CAST_SERVICE_H_
+
+#include <memory>
+#include <string>
+
+#include "cast/common/public/service_info.h"
+#include "cast/receiver/application_agent.h"
+#include "cast/receiver/channel/static_credentials.h"
+#include "cast/receiver/public/receiver_socket_factory.h"
+#include "cast/standalone_receiver/mirroring_application.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/serial_delete_ptr.h"
+#include "platform/base/error.h"
+#include "platform/base/ip_address.h"
+
+namespace openscreen {
+
+struct InterfaceInfo;
+class TaskRunner;
+class TlsConnectionFactory;
+
+namespace cast {
+
+// Assembles all the necessary components and manages their lifetimes, to create
+// a full Cast Receiver on the network, with the following overall
+// functionality:
+//
+//   * Listens for TCP connections on port 8010.
+//   * Establishes TLS tunneling over those connections.
+//   * Wraps a CastSocket API around the TLS connections.
+//   * Manages available receiver-side applications.
+//   * Provides a Cast V2 Mirroring application (media streaming playback in an
+//     on-screen window).
+//   * Publishes over mDNS to be discoverable to all senders on the same LAN.
+class CastService final : public discovery::ReportingClient {
+ public:
+  CastService(TaskRunner* task_runner,
+              const InterfaceInfo& interface,
+              GeneratedCredentials credentials,
+              const std::string& friendly_name,
+              const std::string& model_name,
+              bool enable_discovery = true);
+
+  ~CastService() final;
+
+ private:
+  using LazyDeletedDiscoveryService = SerialDeletePtr<discovery::DnsSdService>;
+  using LazyDeletedDiscoveryPublisher =
+      SerialDeletePtr<discovery::DnsSdServicePublisher<ServiceInfo>>;
+
+  // discovery::ReportingClient overrides.
+  void OnFatalError(Error error) final;
+  void OnRecoverableError(Error error) final;
+
+  const IPEndpoint local_endpoint_;
+  const GeneratedCredentials credentials_;
+
+  ApplicationAgent agent_;
+  MirroringApplication mirroring_application_;
+  ReceiverSocketFactory socket_factory_;
+  std::unique_ptr<TlsConnectionFactory> connection_factory_;
+
+  LazyDeletedDiscoveryService discovery_service_;
+  LazyDeletedDiscoveryPublisher discovery_publisher_;
+};
+
+}  // namespace cast
+}  // namespace openscreen
+
+#endif  // CAST_STANDALONE_RECEIVER_CAST_SERVICE_H_
diff --git a/cast/standalone_receiver/main.cc b/cast/standalone_receiver/main.cc
index 4e8c321..6f6b051 100644
--- a/cast/standalone_receiver/main.cc
+++ b/cast/standalone_receiver/main.cc
@@ -4,21 +4,17 @@
 
 #include <getopt.h>
 
-#include <array>
-#include <chrono>
+#include <algorithm>
 #include <iostream>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
 
 #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 "cast/standalone_receiver/cast_service.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"
@@ -34,87 +30,6 @@
 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,
-    const std::string& friendly_name,
-    const std::string& model_name) {
-  TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
-  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;
-
-  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.";
-  }
-  info.unique_id = HexEncode(interface.hardware_address);
-  info.friendly_name = friendly_name;
-  info.model_name = model_name;
-
-  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) {
-  TRACE_DEFAULT_SCOPED(TraceCategory::kStandaloneReceiver);
-  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) {
   constexpr char kTemplate[] = R"(
 usage: %s <options> <interface>
@@ -130,7 +45,7 @@
                     provided, a randomly generated one will be used for this
                     session.
 
-    -s, --developer-certificate=path-to-cert: Path to PEM file containing a
+    -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
@@ -180,6 +95,33 @@
   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<CastService> service;
+  task_runner->PostTask([&] {
+    service = std::make_unique<CastService>(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
@@ -216,7 +158,7 @@
   std::string friendly_name = "Cast Standalone Receiver";
   std::string model_name = "cast_standalone_receiver";
   bool should_generate_credentials = false;
-  std::unique_ptr<openscreen::TextTraceLoggingPlatform> trace_logger;
+  std::unique_ptr<TextTraceLoggingPlatform> trace_logger;
   int ch = -1;
   while ((ch = getopt_long(argc, argv, "p:d:f:m:gtvhx", kArgumentOptions,
                            nullptr)) != -1) {
@@ -237,7 +179,7 @@
         should_generate_credentials = true;
         break;
       case 't':
-        trace_logger = std::make_unique<openscreen::TextTraceLoggingPlatform>();
+        trace_logger = std::make_unique<TextTraceLoggingPlatform>();
         break;
       case 'v':
         is_verbose = true;
@@ -251,8 +193,7 @@
     }
   }
 
-  SetLogLevel(is_verbose ? openscreen::LogLevel::kVerbose
-                         : openscreen::LogLevel::kInfo);
+  SetLogLevel(is_verbose ? LogLevel::kVerbose : LogLevel::kInfo);
 
   // Either -g is required, or both -p and -d.
   if (should_generate_credentials) {
@@ -266,14 +207,6 @@
     return 1;
   }
 
-  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.";
@@ -283,33 +216,22 @@
   ErrorOr<GeneratedCredentials> creds = GenerateCredentials(
       device_id, private_key_path, developer_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, friendly_name, model_name);
-          OSP_CHECK(result.is_value()) << "Failed to start discovery.";
-          discovery_state = std::move(result.value());
-        }
-      });
+  const InterfaceInfo interface = GetInterfaceInfoFromName(interface_name);
+  OSP_CHECK(interface.GetIpAddressV4() || interface.GetIpAddressV6());
+  OSP_CHECK(std::any_of(interface.hardware_address.begin(),
+                        interface.hardware_address.end(),
+                        [](int e) { return e > 0; }))
+      << "Hardware address is empty. Either you are on a loopback device "
+         "or getting the network interface information failed somehow.";
 
-  // 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();
-
+  auto* const task_runner = new TaskRunnerImpl(&Clock::now);
+  PlatformClientPosix::Create(milliseconds(50), milliseconds(50),
+                              std::unique_ptr<TaskRunnerImpl>(task_runner));
+  RunCastService(task_runner, interface, std::move(creds.value()),
+                 friendly_name, model_name, discovery_enabled);
   PlatformClientPosix::ShutDown();
+
   return 0;
 }
 
diff --git a/cast/standalone_receiver/mirroring_application.cc b/cast/standalone_receiver/mirroring_application.cc
new file mode 100644
index 0000000..a04c401
--- /dev/null
+++ b/cast/standalone_receiver/mirroring_application.cc
@@ -0,0 +1,90 @@
+// Copyright 2020 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 "cast/standalone_receiver/mirroring_application.h"
+
+#include "cast/common/public/message_port.h"
+#include "cast/streaming/environment.h"
+#include "cast/streaming/message_fields.h"
+#include "cast/streaming/receiver_session.h"
+#include "platform/api/task_runner.h"
+#include "util/osp_logging.h"
+
+namespace openscreen {
+namespace cast {
+
+const char kMirroringAppId[] = "0F5096E8";
+const char kMirroringAudioOnlyAppId[] = "85CDB22F";
+
+const char kMirroringDisplayName[] = "Chrome Mirroring";
+const char kRemotingRpcNamespace[] = "urn:x-cast:com.google.cast.remoting";
+
+MirroringApplication::MirroringApplication(TaskRunner* task_runner,
+                                           const IPAddress& interface_address,
+                                           ApplicationAgent* agent)
+    : task_runner_(task_runner),
+      interface_address_(interface_address),
+      app_ids_({kMirroringAppId, kMirroringAudioOnlyAppId}),
+      agent_(agent) {
+  OSP_DCHECK(task_runner_);
+  OSP_DCHECK(agent_);
+  agent_->RegisterApplication(this);
+}
+
+MirroringApplication::~MirroringApplication() {
+  agent_->UnregisterApplication(this);  // ApplicationAgent may call Stop().
+  OSP_DCHECK(!current_session_);
+}
+
+const std::vector<std::string>& MirroringApplication::GetAppIds() const {
+  return app_ids_;
+}
+
+bool MirroringApplication::Launch(const std::string& app_id,
+                                  const Json::Value& app_params,
+                                  MessagePort* message_port) {
+  if ((app_id != kMirroringAppId && app_id != kMirroringAudioOnlyAppId) ||
+      !message_port || current_session_) {
+    return false;
+  }
+
+  wake_lock_ = ScopedWakeLock::Create(task_runner_);
+  environment_ = std::make_unique<Environment>(
+      &Clock::now, task_runner_,
+      IPEndpoint{interface_address_, kDefaultCastStreamingPort});
+  controller_ =
+      std::make_unique<StreamingPlaybackController>(task_runner_, this);
+  current_session_ = std::make_unique<ReceiverSession>(
+      controller_.get(), environment_.get(), message_port,
+      ReceiverSession::Preferences{});
+  return true;
+}
+
+std::string MirroringApplication::GetSessionId() {
+  return current_session_ ? current_session_->session_id() : std::string();
+}
+
+std::string MirroringApplication::GetDisplayName() {
+  return current_session_ ? kMirroringDisplayName : std::string();
+}
+
+std::vector<std::string> MirroringApplication::GetSupportedNamespaces() {
+  return {kCastWebrtcNamespace, kRemotingRpcNamespace};
+}
+
+void MirroringApplication::Stop() {
+  current_session_.reset();
+  controller_.reset();
+  environment_.reset();
+  wake_lock_.reset();
+}
+
+void MirroringApplication::OnPlaybackError(StreamingPlaybackController*,
+                                           Error error) {
+  OSP_LOG_ERROR << "[MirroringApplication] " << error;
+  agent_->StopApplicationIfRunning(this);  // ApplicationAgent calls Stop().
+}
+
+}  // namespace cast
+}  // namespace openscreen
diff --git a/cast/standalone_receiver/mirroring_application.h b/cast/standalone_receiver/mirroring_application.h
new file mode 100644
index 0000000..c2e8ccc
--- /dev/null
+++ b/cast/standalone_receiver/mirroring_application.h
@@ -0,0 +1,69 @@
+// Copyright 2020 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.
+
+#ifndef CAST_STANDALONE_RECEIVER_MIRRORING_APPLICATION_H_
+#define CAST_STANDALONE_RECEIVER_MIRRORING_APPLICATION_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "cast/receiver/application_agent.h"
+#include "cast/standalone_receiver/streaming_playback_controller.h"
+#include "platform/api/scoped_wake_lock.h"
+#include "platform/api/serial_delete_ptr.h"
+#include "platform/base/error.h"
+#include "platform/base/ip_address.h"
+
+namespace openscreen {
+
+class TaskRunner;
+
+namespace cast {
+
+class MessagePort;
+class ReceiverSession;
+
+// Implements a basic Cast V2 Mirroring Application which, at launch time,
+// bootstraps a ReceiverSession and StreamingPlaybackController, which set-up
+// and manage the media data streaming and play it out in an on-screen window.
+class MirroringApplication final : public ApplicationAgent::Application,
+                                   public StreamingPlaybackController::Client {
+ public:
+  MirroringApplication(TaskRunner* task_runner,
+                       const IPAddress& interface_address,
+                       ApplicationAgent* agent);
+
+  ~MirroringApplication() final;
+
+  // ApplicationAgent::Application overrides.
+  const std::vector<std::string>& GetAppIds() const final;
+  bool Launch(const std::string& app_id,
+              const Json::Value& app_params,
+              MessagePort* message_port) final;
+  std::string GetSessionId() final;
+  std::string GetDisplayName() final;
+  std::vector<std::string> GetSupportedNamespaces() final;
+  void Stop() final;
+
+  // StreamingPlaybackController::Client overrides
+  void OnPlaybackError(StreamingPlaybackController* controller,
+                       Error error) final;
+
+ private:
+  TaskRunner* const task_runner_;
+  const IPAddress interface_address_;
+  const std::vector<std::string> app_ids_;
+  ApplicationAgent* const agent_;
+
+  SerialDeletePtr<ScopedWakeLock> wake_lock_;
+  std::unique_ptr<Environment> environment_;
+  std::unique_ptr<StreamingPlaybackController> controller_;
+  std::unique_ptr<ReceiverSession> current_session_;
+};
+
+}  // namespace cast
+}  // namespace openscreen
+
+#endif  // CAST_STANDALONE_RECEIVER_MIRRORING_APPLICATION_H_
diff --git a/cast/streaming/constants.h b/cast/streaming/constants.h
index 65cb2a1..1075a81 100644
--- a/cast/streaming/constants.h
+++ b/cast/streaming/constants.h
@@ -65,11 +65,6 @@
 // The default audio number of channels is set to stereo.
 constexpr int kDefaultAudioChannels = 2;
 
-// TODO(jophba): migrate to discovering a randomly generated streaming
-// sender id. This will require communicating the ID to the sender so that
-// it can send messages appropriately.
-constexpr char kDefaultStreamingReceiverSenderId[] = "receiver-12345";
-
 // Codecs known and understood by cast senders and receivers. Note: receivers
 // are required to implement the following codecs to be Cast V2 compliant: H264,
 // VP8, AAC, Opus. Senders have to implement at least one codec for audio and
diff --git a/cast/streaming/receiver_session.cc b/cast/streaming/receiver_session.cc
index 66cd01d..a78b7bd 100644
--- a/cast/streaming/receiver_session.cc
+++ b/cast/streaming/receiver_session.cc
@@ -11,6 +11,7 @@
 
 #include "absl/strings/match.h"
 #include "absl/strings/numbers.h"
+#include "cast/common/channel/message_util.h"
 #include "cast/common/public/message_port.h"
 #include "cast/streaming/environment.h"
 #include "cast/streaming/message_fields.h"
@@ -96,8 +97,9 @@
     : client_(client),
       environment_(environment),
       preferences_(std::move(preferences)),
+      session_id_(MakeUniqueSessionId("streaming_receiver")),
       messager_(message_port,
-                kDefaultStreamingReceiverSenderId,
+                session_id_,
                 [this](Error error) {
                   OSP_DLOG_WARN << "Got a session messager error: " << error;
                   client_->OnError(this, error);
diff --git a/cast/streaming/receiver_session.h b/cast/streaming/receiver_session.h
index f7bc7f2..fc18196 100644
--- a/cast/streaming/receiver_session.h
+++ b/cast/streaming/receiver_session.h
@@ -112,6 +112,8 @@
   ReceiverSession& operator=(ReceiverSession&&) = delete;
   ~ReceiverSession();
 
+  const std::string& session_id() const { return session_id_; }
+
  private:
   // Specific message type handler methods.
   void OnOffer(SessionMessager::Message message);
@@ -135,6 +137,7 @@
   Client* const client_;
   Environment* const environment_;
   const Preferences preferences_;
+  const std::string session_id_;
   SessionMessager messager_;
 
   bool supports_wifi_status_reporting_ = false;
diff --git a/cast/streaming/sender_session.cc b/cast/streaming/sender_session.cc
index cd8f62c..63f291b 100644
--- a/cast/streaming/sender_session.cc
+++ b/cast/streaming/sender_session.cc
@@ -198,8 +198,9 @@
   // Currently we don't have a way to discover the ID of the receiver we
   // are connected to, since we have to send the first message.
   // TODO(jophba): migrate to discovered receiver ID when available.
+  static constexpr char kPlaceholderReceiverSenderId[] = "receiver-12345";
   messager_.SendMessage(SessionMessager::Message{
-      kDefaultStreamingReceiverSenderId, kCastWebrtcNamespace,
+      kPlaceholderReceiverSenderId, kCastWebrtcNamespace,
       ++current_sequence_number_, std::move(message_body)});
   return Error::None();
 }
diff --git a/discovery/dnssd/public/dns_sd_publisher.h b/discovery/dnssd/public/dns_sd_publisher.h
index abbd8b1..3c139b4 100644
--- a/discovery/dnssd/public/dns_sd_publisher.h
+++ b/discovery/dnssd/public/dns_sd_publisher.h
@@ -5,12 +5,16 @@
 #ifndef DISCOVERY_DNSSD_PUBLIC_DNS_SD_PUBLISHER_H_
 #define DISCOVERY_DNSSD_PUBLIC_DNS_SD_PUBLISHER_H_
 
+#include <string>
+
 #include "discovery/dnssd/public/dns_sd_instance.h"
 #include "platform/base/error.h"
 
 namespace openscreen {
 namespace discovery {
 
+class DnsSdInstanceEndpoint;
+
 class DnsSdPublisher {
  public:
   class Client {
diff --git a/discovery/public/dns_sd_service_publisher.h b/discovery/public/dns_sd_service_publisher.h
index 29659a5..e99d674 100644
--- a/discovery/public/dns_sd_service_publisher.h
+++ b/discovery/public/dns_sd_service_publisher.h
@@ -6,8 +6,10 @@
 #define DISCOVERY_PUBLIC_DNS_SD_SERVICE_PUBLISHER_H_
 
 #include <string>
+#include <utility>
 
 #include "discovery/dnssd/public/dns_sd_instance.h"
+#include "discovery/dnssd/public/dns_sd_instance_endpoint.h"
 #include "discovery/dnssd/public/dns_sd_publisher.h"
 #include "discovery/dnssd/public/dns_sd_service.h"
 #include "platform/base/error.h"