Commit 53dfd8ca authored by Jordan Bayles's avatar Jordan Bayles Committed by Commit Bot

Add plumbing for mDNS Open Screen support

This patch adds classes to implement the mDNS interfaces
that Open Screen expects, using the discovery system inside of
Chromium.

Change-Id: I450b757241ff99a59ec59f5352a0b604f729b9db
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1590536
Commit-Queue: Jordan Bayles <jophba@chromium.org>
Reviewed-by: default avatarmark a. foltz <mfoltz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#664404}
parent b08e7aba
......@@ -142,6 +142,8 @@ static_library("router") {
if (enable_openscreen) {
sources += [
"providers/openscreen/discovery/open_screen_listener.cc",
"providers/openscreen/discovery/open_screen_listener.h",
"providers/openscreen/network_service_async_packet_sender.cc",
"providers/openscreen/network_service_async_packet_sender.h",
"providers/openscreen/network_service_quic_packet_writer.cc",
......@@ -152,7 +154,11 @@ static_library("router") {
configs +=
[ "//third_party/openscreen/src/build:allow_build_from_embedder" ]
deps += [ "//third_party/openscreen/src/platform" ]
deps += [
"//third_party/openscreen/src/osp/public",
"//third_party/openscreen/src/osp_base",
"//third_party/openscreen/src/platform",
]
}
}
}
......
include_rules = [
"+services/network",
"+third_party/openscreen/src"
"+third_party/openscreen/src/osp/public",
"+third_party/openscreen/src/osp_base",
"+third_party/openscreen/src/platform/api",
]
// 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 "chrome/browser/media/router/providers/openscreen/discovery/open_screen_listener.h"
#include <utility>
namespace media_router {
namespace {
const char kOpenScreenServiceType[] = "openscreen_.udp_";
openscreen::ServiceInfo ServiceInfoFromServiceDescription(
const local_discovery::ServiceDescription& desc) {
openscreen::ErrorOr<openscreen::IPAddress> address =
openscreen::IPAddress::Parse(desc.address.host());
DCHECK(address);
openscreen::ServiceInfo service_info;
service_info.service_id = desc.service_name;
service_info.friendly_name = desc.instance_name();
if (address.value().IsV4()) {
service_info.v4_endpoint =
openscreen::IPEndpoint{address.value(), desc.address.port()};
service_info.v6_endpoint = {};
} else {
service_info.v4_endpoint = {};
service_info.v6_endpoint =
openscreen::IPEndpoint{address.value(), desc.address.port()};
}
return service_info;
}
} // namespace
OpenScreenListener::OpenScreenListener(std::string service_type)
: service_type_(kOpenScreenServiceType) {}
OpenScreenListener::~OpenScreenListener() {}
bool OpenScreenListener::Start() {
is_running_ = true;
// TODO(jophba): instantiate local_discovery::ServiceDiscoveryClient
for (auto* observer : observers_) {
observer->OnStarted();
}
return true;
}
bool OpenScreenListener::StartAndSuspend() {
for (auto* observer : observers_) {
observer->OnStarted();
observer->OnSuspended();
}
return true;
}
bool OpenScreenListener::Stop() {
DCHECK(is_running_);
is_running_ = false;
for (auto* observer : observers_) {
observer->OnStopped();
}
return true;
}
bool OpenScreenListener::Suspend() {
DCHECK(is_running_);
is_running_ = false;
for (auto* observer : observers_) {
observer->OnSuspended();
}
return true;
}
bool OpenScreenListener::Resume() {
DCHECK(!is_running_);
is_running_ = true;
for (auto* observer : observers_) {
observer->OnStarted();
}
return true;
}
bool OpenScreenListener::SearchNow() {
is_running_ = true;
for (auto* observer : observers_) {
observer->OnSearching();
}
return true;
}
const std::vector<openscreen::ServiceInfo>& OpenScreenListener::GetReceivers()
const {
return receivers_;
}
void OpenScreenListener::AddObserver(ServiceListener::Observer* observer) {
CHECK(observer);
observers_.push_back(observer);
}
void OpenScreenListener::RemoveObserver(ServiceListener::Observer* observer) {
CHECK(observer);
observers_.erase(std::remove(observers_.begin(), observers_.end(), observer),
observers_.end());
}
void OpenScreenListener::RunTasks() {}
void OpenScreenListener::OnDeviceChanged(
const std::string& service_type,
bool added,
const local_discovery::ServiceDescription& service_description) {
CHECK_EQ(service_type, service_type_);
if (!is_running_) {
return;
}
openscreen::ServiceInfo service_info =
ServiceInfoFromServiceDescription(service_description);
if (added) {
receivers_.push_back(std::move(service_info));
const openscreen::ServiceInfo& ref = receivers_.back();
for (auto* observer : observers_) {
observer->OnReceiverAdded(ref);
}
} else {
auto it =
std::find_if(receivers_.begin(), receivers_.end(),
[&service_info](const openscreen::ServiceInfo& info) {
return info.service_id == service_info.service_id;
});
*it = std::move(service_info);
for (auto* observer : observers_) {
observer->OnReceiverChanged(*it);
}
}
}
void OpenScreenListener::OnDeviceRemoved(const std::string& service_type,
const std::string& service_name) {
CHECK(service_type == service_type_);
if (!is_running_) {
return;
}
const auto& removed_it =
std::find_if(receivers_.begin(), receivers_.end(),
[&service_name](openscreen::ServiceInfo& info) {
return info.service_id == service_name;
});
// Move the receiver we want to remove to the end, so we don't have to shift.
DCHECK(removed_it != receivers_.end());
const openscreen::ServiceInfo removed_info = std::move(*removed_it);
if (removed_it != receivers_.end() - 1) {
*removed_it = std::move(receivers_.back());
}
receivers_.pop_back();
for (auto* observer : observers_) {
observer->OnReceiverRemoved(removed_info);
}
}
void OpenScreenListener::OnDeviceCacheFlushed(const std::string& service_type) {
CHECK(service_type == service_type_);
receivers_.clear();
// We still flush even if not running, since it's not going to be accurate.
if (!is_running_) {
return;
}
for (auto* observer : observers_) {
observer->OnAllReceiversRemoved();
}
}
} // namespace media_router
// 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.
#ifndef CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_OPENSCREEN_DISCOVERY_OPEN_SCREEN_LISTENER_H_
#define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_OPENSCREEN_DISCOVERY_OPEN_SCREEN_LISTENER_H_
#include <string>
#include <vector>
#include "chrome/browser/local_discovery/service_discovery_device_lister.h"
#include "third_party/openscreen/src/osp/public/service_info.h"
#include "third_party/openscreen/src/osp/public/service_listener.h"
#include "third_party/openscreen/src/osp_base/ip_address.h"
namespace media_router {
class OpenScreenListener
: public openscreen::ServiceListener,
local_discovery::ServiceDiscoveryDeviceLister::Delegate {
public:
explicit OpenScreenListener(std::string service_type);
// ServiceListener overrides
~OpenScreenListener() override;
bool Start() override;
bool StartAndSuspend() override;
bool Stop() override;
bool Suspend() override;
bool Resume() override;
bool SearchNow() override;
const std::vector<openscreen::ServiceInfo>& GetReceivers() const override;
void AddObserver(ServiceListener::Observer* observer) override;
void RemoveObserver(ServiceListener::Observer* observer) override;
void RunTasks() override;
// ServiceDiscoveryDeviceLister::Delegate
void OnDeviceChanged(
const std::string& service_type,
bool added,
const local_discovery::ServiceDescription& service_description) override;
void OnDeviceRemoved(const std::string& service_type,
const std::string& service_name) override;
void OnDeviceCacheFlushed(const std::string& service_type) override;
private:
bool is_running_ = false;
const std::string service_type_;
std::vector<openscreen::ServiceInfo> receivers_;
DISALLOW_COPY_AND_ASSIGN(OpenScreenListener);
};
} // namespace media_router
#endif // CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_OPENSCREEN_DISCOVERY_OPEN_SCREEN_LISTENER_H_
// 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 "chrome/browser/media/router/providers/openscreen/discovery/open_screen_listener.h"
#include "base/time/time.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_address.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::StrictMock;
using ::testing::WithArg;
namespace media_router {
const char kServiceType[] = "openscreen_.udp_";
class MockServiceListenerObserver
: public openscreen::ServiceListener::Observer {
public:
MOCK_METHOD0(OnStarted, void());
MOCK_METHOD0(OnStopped, void());
MOCK_METHOD0(OnSuspended, void());
MOCK_METHOD0(OnSearching, void());
MOCK_METHOD1(OnReceiverAdded, void(const openscreen::ServiceInfo&));
MOCK_METHOD1(OnReceiverChanged, void(const openscreen::ServiceInfo&));
MOCK_METHOD1(OnReceiverRemoved, void(const openscreen::ServiceInfo&));
MOCK_METHOD0(OnAllReceiversRemoved, void());
MOCK_METHOD1(OnError, void(openscreen::ServiceListenerError));
MOCK_METHOD1(OnMetrics, void(openscreen::ServiceListener::Metrics));
};
// Although the testing framework can do a byte comparison, when it fails
// it's difficult to figure out *exactly* what is wrong with the actual
// Service Info class.
MATCHER_P(ServiceInfoEquals, expected, "") {
return (expected.service_id == arg.service_id) &&
(expected.friendly_name == arg.friendly_name) &&
(expected.network_interface_index == arg.network_interface_index) &&
(expected.v4_endpoint == arg.v4_endpoint) &&
(expected.v6_endpoint == arg.v6_endpoint);
}
class OpenScreenListenerTest : public ::testing::Test {
protected:
void SetUp() override {
valid_description_.service_name = "mock_service.test_service_type";
valid_description_.address = net::HostPortPair("192.168.0.10", 8888);
valid_description_.metadata = {"foo", "bar", "baz"};
valid_description_.ip_address = net::IPAddress(192, 168, 0, 10);
valid_description_.last_seen = base::Time();
service_info_.service_id = "mock_service.test_service_type";
service_info_.friendly_name = "mock_service";
service_info_.v4_endpoint =
openscreen::IPEndpoint{openscreen::IPAddress(192, 168, 0, 10), 8888};
service_info_.v6_endpoint = {};
}
OpenScreenListenerTest() : listener(kServiceType), observer() {
listener.AddObserver(&observer);
}
void ExpectReceiverAdded(const openscreen::ServiceInfo& info) {
EXPECT_CALL(observer, OnReceiverAdded(ServiceInfoEquals(info)));
}
void ExpectReceiverChanged(const openscreen::ServiceInfo& info) {
EXPECT_CALL(observer, OnReceiverChanged(ServiceInfoEquals(info)));
}
void ExpectReceiverRemoved(const openscreen::ServiceInfo& info) {
EXPECT_CALL(observer, OnReceiverRemoved(ServiceInfoEquals(info)));
}
OpenScreenListener listener;
StrictMock<MockServiceListenerObserver> observer;
local_discovery::ServiceDescription valid_description_;
openscreen::ServiceInfo service_info_;
};
TEST_F(OpenScreenListenerTest, DeviceAddedNotifiesObserversIfStarted) {
listener.OnDeviceChanged(kServiceType, true, valid_description_);
EXPECT_CALL(observer, OnStarted()).Times(1);
listener.Start();
ExpectReceiverAdded(service_info_);
listener.OnDeviceChanged(kServiceType, true, valid_description_);
}
TEST_F(OpenScreenListenerTest, DeviceChangedNotifiesObserversIfStarted) {
listener.OnDeviceChanged(kServiceType, false, valid_description_);
EXPECT_CALL(observer, OnStarted()).Times(1);
listener.Start();
ExpectReceiverAdded(service_info_);
listener.OnDeviceChanged(kServiceType, true, valid_description_);
ExpectReceiverChanged(service_info_);
listener.OnDeviceChanged(kServiceType, false, valid_description_);
}
TEST_F(OpenScreenListenerTest, DeviceRemovedNotifiesObserversIfStarted) {
listener.OnDeviceRemoved(kServiceType, valid_description_.service_name);
EXPECT_CALL(observer, OnStarted()).Times(1);
listener.Start();
ExpectReceiverAdded(service_info_);
listener.OnDeviceChanged(kServiceType, true, valid_description_);
ExpectReceiverRemoved(service_info_);
listener.OnDeviceRemoved(kServiceType, valid_description_.service_name);
}
TEST_F(OpenScreenListenerTest, CachedFlushNotifiesObserversIfStarted) {
listener.OnDeviceCacheFlushed(kServiceType);
EXPECT_CALL(observer, OnStarted()).Times(1);
listener.Start();
EXPECT_CALL(observer, OnAllReceiversRemoved()).Times(1);
listener.OnDeviceCacheFlushed(kServiceType);
}
TEST_F(OpenScreenListenerTest, CachedFlushEmptiesReceiverList) {
EXPECT_CALL(observer, OnStarted()).Times(1);
listener.Start();
ExpectReceiverAdded(service_info_);
listener.OnDeviceChanged(kServiceType, true, valid_description_);
ExpectReceiverAdded(service_info_);
listener.OnDeviceChanged(kServiceType, true, valid_description_);
EXPECT_EQ(2ul, listener.GetReceivers().size());
EXPECT_CALL(observer, OnAllReceiversRemoved()).Times(1);
listener.OnDeviceCacheFlushed(kServiceType);
EXPECT_EQ(0ul, listener.GetReceivers().size());
}
TEST_F(OpenScreenListenerTest, StartNotifiesObservers) {
EXPECT_CALL(observer, OnStarted()).Times(1);
listener.Start();
}
TEST_F(OpenScreenListenerTest, StopNotifiesObservers) {
EXPECT_CALL(observer, OnStarted()).Times(1);
EXPECT_CALL(observer, OnStopped()).Times(1);
listener.Start();
listener.Stop();
}
TEST_F(OpenScreenListenerTest, SuspendNotifiesObservers) {
EXPECT_CALL(observer, OnStarted()).Times(2);
EXPECT_CALL(observer, OnSuspended()).Times(2);
listener.Start();
listener.Suspend();
listener.StartAndSuspend();
}
TEST_F(OpenScreenListenerTest, ResumeNotifiesObservers) {
EXPECT_CALL(observer, OnStarted()).Times(2);
EXPECT_CALL(observer, OnSuspended()).Times(1);
listener.Start();
listener.Suspend();
listener.Resume();
}
TEST_F(OpenScreenListenerTest, SearchingNotifiesObservers) {
EXPECT_CALL(observer, OnStarted()).Times(1);
listener.Start();
EXPECT_CALL(observer, OnSearching()).Times(1);
listener.SearchNow();
}
TEST_F(OpenScreenListenerTest, RemovedObserversDoNotGetNotified) {
listener.RemoveObserver(&observer);
listener.Start();
listener.Stop();
listener.StartAndSuspend();
listener.Resume();
listener.SearchNow();
listener.Suspend();
listener.Resume();
listener.OnDeviceCacheFlushed(kServiceType);
listener.OnDeviceChanged(kServiceType, true, valid_description_);
listener.OnDeviceChanged(kServiceType, true, valid_description_);
listener.OnDeviceRemoved(kServiceType, valid_description_.service_name);
}
TEST_F(OpenScreenListenerTest, DoesNotRecordReceiversIfNotStarted) {
EXPECT_EQ(0ul, listener.GetReceivers().size());
listener.OnDeviceChanged(kServiceType, true, valid_description_);
listener.OnDeviceChanged(kServiceType, false, valid_description_);
listener.OnDeviceChanged(kServiceType, true, valid_description_);
EXPECT_EQ(0ul, listener.GetReceivers().size());
}
} // namespace media_router
......@@ -11,7 +11,7 @@
#include "mojo/public/cpp/bindings/interface_request.h"
namespace openscreen {
namespace media_router {
NetworkServiceAsyncPacketSender::NetworkServiceAsyncPacketSender(
network::mojom::NetworkContext* network_context) {
network::mojom::UDPSocketRequest socket_request(mojo::MakeRequest(&socket_));
......@@ -39,4 +39,4 @@ net::Error NetworkServiceAsyncPacketSender::SendTo(
return net::Error::OK;
}
} // namespace openscreen
} // namespace media_router
......@@ -13,7 +13,7 @@
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/udp_socket.mojom.h"
namespace openscreen {
namespace media_router {
class AsyncPacketSender {
public:
virtual ~AsyncPacketSender() {}
......@@ -41,6 +41,6 @@ class NetworkServiceAsyncPacketSender : public AsyncPacketSender {
DISALLOW_COPY_AND_ASSIGN(NetworkServiceAsyncPacketSender);
};
} // namespace openscreen
} // namespace media_router
#endif // CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_OPENSCREEN_NETWORK_SERVICE_ASYNC_PACKET_SENDER_H_
......@@ -16,7 +16,7 @@
#include "net/base/ip_endpoint.h"
#include "net/third_party/quiche/src/quic/core/quic_constants.h"
namespace openscreen {
namespace media_router {
namespace {
// Set a reasonable maximum number of packets in flight, for a total of
......@@ -165,4 +165,4 @@ void NetworkServiceQuicPacketWriter::WritePacketHelper(
UpdateIsWriteBlocked();
}
} // namespace openscreen
} // namespace media_router
......@@ -19,7 +19,7 @@
#include "chrome/browser/media/router/providers/openscreen/network_service_async_packet_sender.h"
namespace openscreen {
namespace media_router {
// Chrome-specific packet writer. Intended for use outside of the Network
// service, this class uses the network service's UdpSocket for sending and
......@@ -117,6 +117,6 @@ class NetworkServiceQuicPacketWriter : quic::QuicPacketWriter {
DISALLOW_COPY_AND_ASSIGN(NetworkServiceQuicPacketWriter);
};
} // namespace openscreen
} // namespace media_router
#endif // CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_OPENSCREEN_NETWORK_SERVICE_QUIC_PACKET_WRITER_H_
......@@ -13,7 +13,7 @@
#include "media/base/fake_single_thread_task_runner.h"
#include "net/base/net_errors.h"
namespace openscreen {
namespace media_router {
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
......@@ -267,4 +267,4 @@ TEST(NetworkServiceQuicPacketWriterTest, TooManyPacketsCausesWriteBlockage) {
ASSERT_FALSE(test_writer.writer->IsWriteBlocked());
}
} // namespace openscreen
} // namespace media_router
......@@ -3738,10 +3738,6 @@ test("unit_tests") {
"../renderer/media/chrome_webrtc_log_message_delegate_unittest.cc",
]
if (enable_openscreen) {
sources += [ "../browser/media/router/providers/openscreen/network_service_quic_packet_writer_unittest.cc" ]
}
deps += [
"//components/bubble:test_support",
"//services/network:test_support",
......@@ -4848,6 +4844,15 @@ test("unit_tests") {
if (is_win || is_mac) {
deps += [ "//chrome/updater:updater_tests" ]
}
if (enable_openscreen) {
include_dirs = [ "//third_party/openscreen/src" ]
sources += [
"../browser/media/router/providers/openscreen/discovery/open_screen_listener_unittest.cc",
"../browser/media/router/providers/openscreen/network_service_quic_packet_writer_unittest.cc",
]
}
}
static_library("test_support_unit") {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment