diff --git a/chrome/VERSION b/chrome/VERSION index 39608e4..f320c01 100644 --- a/chrome/VERSION +++ b/chrome/VERSION
@@ -1,4 +1,4 @@ MAJOR=60 MINOR=0 -BUILD=3085 +BUILD=3086 PATCH=0
diff --git a/chrome/browser/media/router/BUILD.gn b/chrome/browser/media/router/BUILD.gn index 449a9d0..983897e 100644 --- a/chrome/browser/media/router/BUILD.gn +++ b/chrome/browser/media/router/BUILD.gn
@@ -40,8 +40,6 @@ "media_router_metrics.h", "media_routes_observer.cc", "media_routes_observer.h", - "media_sink_service.cc", - "media_sink_service.h", "media_sinks_observer.cc", "media_sinks_observer.h", "mojo/media_route_controller.cc",
diff --git a/chrome/browser/media/router/discovery/BUILD.gn b/chrome/browser/media/router/discovery/BUILD.gn index 3115fe5..21199e12 100644 --- a/chrome/browser/media/router/discovery/BUILD.gn +++ b/chrome/browser/media/router/discovery/BUILD.gn
@@ -18,12 +18,18 @@ sources = [ "dial/device_description_fetcher.cc", "dial/device_description_fetcher.h", + "dial/device_description_service.cc", + "dial/device_description_service.h", "dial/dial_device_data.cc", "dial/dial_device_data.h", + "dial/dial_media_sink_service.cc", + "dial/dial_media_sink_service.h", "dial/dial_registry.cc", "dial/dial_registry.h", "dial/dial_service.cc", "dial/dial_service.h", + "dial/parsed_dial_device_description.cc", + "dial/parsed_dial_device_description.h", "dial/safe_dial_device_description_parser.cc", "dial/safe_dial_device_description_parser.h", ]
diff --git a/chrome/browser/media/router/discovery/dial/device_description_fetcher.h b/chrome/browser/media/router/discovery/dial/device_description_fetcher.h index 3287f72..4152d152 100644 --- a/chrome/browser/media/router/discovery/dial/device_description_fetcher.h +++ b/chrome/browser/media/router/discovery/dial/device_description_fetcher.h
@@ -43,6 +43,8 @@ ~DeviceDescriptionFetcher() override; + const GURL& device_description_url() { return device_description_url_; } + void Start(); private:
diff --git a/chrome/browser/media/router/discovery/dial/device_description_service.cc b/chrome/browser/media/router/discovery/dial/device_description_service.cc new file mode 100644 index 0000000..ce271a4 --- /dev/null +++ b/chrome/browser/media/router/discovery/dial/device_description_service.cc
@@ -0,0 +1,324 @@ +// Copyright 2017 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/discovery/dial/device_description_service.h" + +#if DCHECK_IS_ON() +#include <sstream> +#endif + +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "chrome/browser/media/router/discovery/dial/device_description_fetcher.h" +#include "chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.h" +#include "net/base/ip_address.h" +#include "url/gurl.h" + +namespace { + +enum ErrorType { + NONE, + MISSING_UNIQUE_ID, + MISSING_FRIENDLY_NAME, + MISSING_APP_URL, + INVALID_APP_URL +}; + +// How long to cache a device description. +constexpr int kDeviceDescriptionCacheTimeHours = 12; + +// Time interval to clean up cache entries. +constexpr int kCacheCleanUpTimeoutMins = 30; + +// Maximum size on the number of cached entries. +constexpr int kCacheMaxEntries = 256; + +#if DCHECK_IS_ON() +// Replaces "<element_name>content</element_name>" with +// "<element_name>***</element_name>" +void Scrub(const std::string& element_name, std::string* xml_text) { + size_t pos = xml_text->find("<" + element_name + ">"); + size_t end_pos = xml_text->find("</" + element_name + ">"); + + if (pos == std::string::npos || end_pos == std::string::npos) + return; + + size_t start_pos = pos + element_name.length() + 2; + if (end_pos > start_pos) + xml_text->replace(start_pos, end_pos - start_pos, "***"); +} + +// Removes unique identifiers from the device description. +// |xml_text|: The device description XML. +// Returns original XML text if <UDN> or <serialNumber> field does not exist. +std::string ScrubDeviceDescriptionXml(const std::string& xml_text) { + std::string scrubbed_xml(xml_text); + Scrub("UDN", &scrubbed_xml); + Scrub("serialNumber", &scrubbed_xml); + return scrubbed_xml; +} + +std::string CachedDeviceDescriptionToString( + const media_router::DeviceDescriptionService::CacheEntry& cached_data) { + std::stringstream ss; + ss << "CachedDialDeviceDescription [unique_id]: " + << cached_data.description_data.unique_id + << " [friendly_name]: " << cached_data.description_data.friendly_name + << " [model_name]: " << cached_data.description_data.model_name + << " [app_url]: " << cached_data.description_data.app_url + << " [expire_time]: " << cached_data.expire_time + << " [config_id]: " << cached_data.config_id; + + return ss.str(); +} +#endif + +bool IsValidAppUrl(const GURL& app_url, const std::string& ip_address) { + return app_url.SchemeIs(url::kHttpScheme) && app_url.host() == ip_address; +} + +// Checks mandatory fields. Returns ErrorType::NONE if device description is +// valid; Otherwise returns specific error type. +ErrorType ValidateParsedDeviceDescription( + const std::string& expected_ip_address, + const media_router::ParsedDialDeviceDescription& description_data) { + if (description_data.unique_id.empty()) { + return ErrorType::MISSING_UNIQUE_ID; + } + if (description_data.friendly_name.empty()) { + return ErrorType::MISSING_FRIENDLY_NAME; + } + if (!description_data.app_url.is_valid()) { + return ErrorType::MISSING_APP_URL; + } + if (!IsValidAppUrl(description_data.app_url, expected_ip_address)) { + return ErrorType::INVALID_APP_URL; + } + return ErrorType::NONE; +} + +} // namespace + +namespace media_router { + +DeviceDescriptionService::DeviceDescriptionService( + const DeviceDescriptionParseSuccessCallback& success_cb, + const DeviceDescriptionParseErrorCallback& error_cb) + : success_cb_(success_cb), error_cb_(error_cb) {} + +DeviceDescriptionService::~DeviceDescriptionService() { + if (!pending_device_labels_.empty()) { + DLOG(WARNING) << "Fail to finish parsing " << pending_device_labels_.size() + << " devices."; + } +} + +void DeviceDescriptionService::GetDeviceDescriptions( + const std::vector<DialDeviceData>& devices, + net::URLRequestContextGetter* request_context) { + DCHECK(thread_checker_.CalledOnValidThread()); + + std::map<std::string, std::unique_ptr<DeviceDescriptionFetcher>> + existing_fetcher_map; + for (auto& fetcher_it : device_description_fetcher_map_) { + std::string device_label = fetcher_it.first; + const auto& device_it = + std::find_if(devices.begin(), devices.end(), + [&device_label](const DialDeviceData& device_data) { + return device_data.label() == device_label; + }); + if (device_it == devices.end() || + device_it->device_description_url() == + fetcher_it.second->device_description_url()) { + existing_fetcher_map.insert( + std::make_pair(device_label, std::move(fetcher_it.second))); + } + } + + // Remove all out dated fetchers. + device_description_fetcher_map_ = std::move(existing_fetcher_map); + + for (const auto& device_data : devices) { + auto* cache_entry = CheckAndUpdateCache(device_data); + if (cache_entry) { + // Get device description from cache. + success_cb_.Run(device_data, cache_entry->description_data); + continue; + } + + FetchDeviceDescription(device_data, request_context); + } + + // Start a clean up timer. + if (!clean_up_timer_) { + clean_up_timer_.reset(new base::RepeatingTimer()); + clean_up_timer_->Start( + FROM_HERE, base::TimeDelta::FromMinutes(kCacheCleanUpTimeoutMins), this, + &DeviceDescriptionService::CleanUpCacheEntries); + } +} + +void DeviceDescriptionService::CleanUpCacheEntries() { + DCHECK(thread_checker_.CalledOnValidThread()); + base::Time now = GetNow(); + + DVLOG(2) << "Before clean up, cache size: " << description_cache_.size(); + base::EraseIf(description_cache_, + [&now](const std::pair<std::string, CacheEntry>& cache_pair) { + return cache_pair.second.expire_time < now; + }); + DVLOG(2) << "After clean up, cache size: " << description_cache_.size(); + + if (description_cache_.empty() && device_description_fetcher_map_.empty()) { + DVLOG(2) << "Cache is empty, stop clean up timer..."; + clean_up_timer_.reset(); + } +} + +void DeviceDescriptionService::FetchDeviceDescription( + const DialDeviceData& device_data, + net::URLRequestContextGetter* request_context) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Existing Fetcher. + const auto& it = device_description_fetcher_map_.find(device_data.label()); + if (it != device_description_fetcher_map_.end()) + return; + + auto device_description_fetcher = + base::WrapUnique(new DeviceDescriptionFetcher( + device_data.device_description_url(), request_context, + base::BindOnce( + &DeviceDescriptionService::OnDeviceDescriptionFetchComplete, + base::Unretained(this), device_data), + base::BindOnce( + &DeviceDescriptionService::OnDeviceDescriptionFetchError, + base::Unretained(this), device_data))); + + device_description_fetcher->Start(); + device_description_fetcher_map_.insert(std::make_pair( + device_data.label(), std::move(device_description_fetcher))); + + pending_device_labels_.insert(device_data.label()); +} + +const DeviceDescriptionService::CacheEntry* +DeviceDescriptionService::CheckAndUpdateCache( + const DialDeviceData& device_data) { + const auto& it = description_cache_.find(device_data.label()); + if (it == description_cache_.end()) + return nullptr; + + // If the entry's config_id does not match, or it has expired, remove it. + if (it->second.config_id != device_data.config_id() || + GetNow() >= it->second.expire_time) { + DVLOG(2) << "Removing invalid entry " << it->first; + description_cache_.erase(it); + return nullptr; + } + + // Entry is valid. + return &it->second; +} + +void DeviceDescriptionService::OnParsedDeviceDescription( + const DialDeviceData& device_data, + const GURL& app_url, + chrome::mojom::DialDeviceDescriptionPtr parsed_device_description) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Last callback for current utility process. Release |parser_| and + // SafeDialDeviceDescriptionParser object will be destroyed after this + // callback. + pending_device_labels_.erase(device_data.label()); + if (pending_device_labels_.empty()) { + parser_.reset(); + } + + if (!parsed_device_description) { + error_cb_.Run(device_data, "Failed to parse device description XML"); + return; + } + + ParsedDialDeviceDescription description_data; + description_data.unique_id = parsed_device_description->unique_id; + description_data.friendly_name = parsed_device_description->friendly_name; + description_data.model_name = parsed_device_description->model_name; + description_data.app_url = app_url; + + auto error = ValidateParsedDeviceDescription( + device_data.device_description_url().host(), description_data); + + if (error != ErrorType::NONE) { + DLOG(WARNING) << "Device description failed to validate: " << error; + error_cb_.Run(device_data, "Failed to process fetch result"); + return; + } + + if (description_cache_.size() >= kCacheMaxEntries) { + success_cb_.Run(device_data, description_data); + return; + } + + CacheEntry cached_description_data; + cached_description_data.expire_time = + GetNow() + base::TimeDelta::FromHours(kDeviceDescriptionCacheTimeHours); + cached_description_data.config_id = device_data.config_id(); + cached_description_data.description_data = description_data; + +#if DCHECK_IS_ON() + DVLOG(2) << "Got device description for " << device_data.label() + << "... device description was: " + << CachedDeviceDescriptionToString(cached_description_data); +#endif + + DVLOG(2) << "Caching device description for " << device_data.label(); + description_cache_.insert( + std::make_pair(device_data.label(), cached_description_data)); + + success_cb_.Run(device_data, description_data); +} + +void DeviceDescriptionService::OnDeviceDescriptionFetchComplete( + const DialDeviceData& device_data, + const DialDeviceDescriptionData& description_data) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (!parser_) + parser_ = base::MakeUnique<SafeDialDeviceDescriptionParser>(); + + parser_->Start( + description_data.device_description, + base::Bind(&DeviceDescriptionService::OnParsedDeviceDescription, + base::Unretained(this), device_data, + description_data.app_url)); + +#if DCHECK_IS_ON() + DVLOG(2) << "Device description: " + << ScrubDeviceDescriptionXml(description_data.device_description); +#endif + + device_description_fetcher_map_.erase(device_data.label()); +} + +void DeviceDescriptionService::OnDeviceDescriptionFetchError( + const DialDeviceData& device_data, + const std::string& error_message) { + DCHECK(thread_checker_.CalledOnValidThread()); + DVLOG(2) << "OnDeviceDescriptionFetchError [label]: " << device_data.label(); + + error_cb_.Run(device_data, error_message); + device_description_fetcher_map_.erase(device_data.label()); +} + +base::Time DeviceDescriptionService::GetNow() { + return base::Time::Now(); +} + +DeviceDescriptionService::CacheEntry::CacheEntry() : config_id(-1) {} +DeviceDescriptionService::CacheEntry::CacheEntry(const CacheEntry& other) = + default; +DeviceDescriptionService::CacheEntry::~CacheEntry() = default; + +} // namespace media_router
diff --git a/chrome/browser/media/router/discovery/dial/device_description_service.h b/chrome/browser/media/router/discovery/dial/device_description_service.h new file mode 100644 index 0000000..6398d9b --- /dev/null +++ b/chrome/browser/media/router/discovery/dial/device_description_service.h
@@ -0,0 +1,168 @@ +// Copyright 2017 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_DISCOVERY_DIAL_DEVICE_DESCRIPTION_SERVICE_H_ +#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_DEVICE_DESCRIPTION_SERVICE_H_ + +#include <memory> +#include <set> +#include <string> + +#include "base/callback.h" +#include "base/gtest_prod_util.h" +#include "base/threading/thread_checker.h" +#include "base/timer/timer.h" +#include "chrome/browser/media/router/discovery/dial/dial_device_data.h" +#include "chrome/browser/media/router/discovery/dial/parsed_dial_device_description.h" +#include "chrome/common/media_router/mojo/dial_device_description_parser.mojom.h" + +namespace net { +class URLRequestContextGetter; +} + +namespace media_router { + +class DeviceDescriptionFetcher; +class SafeDialDeviceDescriptionParser; + +// This class fetches and parses device description XML for DIAL devices. Actual +// parsing happens in a separate utility process via SafeDeviceDescriptionParser +// (instead of in this class). This class lives on IO thread. +class DeviceDescriptionService { + public: + // Represents cached device description data parsed from device description + // XML. + struct CacheEntry { + CacheEntry(); + CacheEntry(const CacheEntry& other); + ~CacheEntry(); + + // The expiration time from the cache. + base::Time expire_time; + + // The device description version number (non-negative). + int32_t config_id; + + // Parsed device description data from XML. + ParsedDialDeviceDescription description_data; + }; + + // Called if parsing device description XML in utility process succeeds, and + // all fields are valid. + // |device_data|: The device to look up. + // |description_data|: Device description data from device description XML. + using DeviceDescriptionParseSuccessCallback = + base::Callback<void(const DialDeviceData& device_data, + const ParsedDialDeviceDescription& description_data)>; + + // Called if parsing device description XML in utility process fails, or some + // parsed fields are missing or invalid. + using DeviceDescriptionParseErrorCallback = + base::Callback<void(const DialDeviceData& device_data, + const std::string& error_message)>; + + DeviceDescriptionService( + const DeviceDescriptionParseSuccessCallback& success_cb, + const DeviceDescriptionParseErrorCallback& error_cb); + virtual ~DeviceDescriptionService(); + + // For each device in |devices|, if there is a valid cache entry for it, call + // |success_cb_| with cached device description; otherwise start fetching + // device description XML and parsing XML in utility process. Call + // |success_cb_| if both fetching and parsing succeeds; otherwise call + // |error_cb_|. + // |request_context|: Used by the background URLFetchers. + virtual void GetDeviceDescriptions( + const std::vector<DialDeviceData>& devices, + net::URLRequestContextGetter* request_context); + + private: + friend class DeviceDescriptionServiceTest; + friend class TestDeviceDescriptionService; + FRIEND_TEST_ALL_PREFIXES(DeviceDescriptionServiceTest, + TestGetDeviceDescriptionRemoveOutDatedFetchers); + FRIEND_TEST_ALL_PREFIXES(DeviceDescriptionServiceTest, + TestGetDeviceDescriptionFetchURLError); + FRIEND_TEST_ALL_PREFIXES(DeviceDescriptionServiceTest, + TestCleanUpCacheEntries); + FRIEND_TEST_ALL_PREFIXES(DeviceDescriptionServiceTest, + TestSafeParserProperlyCreated); + + // Checks the cache for a valid device description. If the entry is found but + // is expired, it is removed from the cache. Returns cached entry of + // parsed device description. Returns nullptr if cache entry does not exist or + // is not valid. + // |device_data|: The device to look up. + const CacheEntry* CheckAndUpdateCache(const DialDeviceData& device_data); + + // Issues a HTTP GET request for the device description. No-op if there is + // already a pending request. + // |device_data|: The device to look up. + // |request_context|: Used by the background URLFetchers. + void FetchDeviceDescription(const DialDeviceData& device_data, + net::URLRequestContextGetter* request_context); + + // Invoked when HTTP GET request finishes. + // |device_data|: Device data initiating the HTTP request. + // |description_data|: Response from HTTP request. + void OnDeviceDescriptionFetchComplete( + const DialDeviceData& device_data, + const DialDeviceDescriptionData& description_data); + + // Invoked when HTTP GET request fails. + // |device_data|: Device data initiating the HTTP request. + // |error_message|: Error message from HTTP request. + void OnDeviceDescriptionFetchError(const DialDeviceData& device_data, + const std::string& error_message); + + // Invoked when SafeDialDeviceDescriptionParser finishes parsing device + // description XML. + // |device_data|: Device data initiating XML parsing in utility process. + // |app_url|: The app Url. + // |device_description_ptr|: Parsed device description from utility process, + // or nullptr if parsing failed. + void OnParsedDeviceDescription( + const DialDeviceData& device_data, + const GURL& app_url, + chrome::mojom::DialDeviceDescriptionPtr device_description_ptr); + + // Remove expired cache entries from |description_map_|. + void CleanUpCacheEntries(); + + // Used by unit tests. + virtual base::Time GetNow(); + + // Map of current device description fetches in progress, keyed by device + // label. + std::map<std::string, std::unique_ptr<DeviceDescriptionFetcher>> + device_description_fetcher_map_; + + // Set of device labels to be parsed in current utility process. + std::set<std::string> pending_device_labels_; + + // Map of current cached device descriptions, keyed by device label. + std::map<std::string, CacheEntry> description_cache_; + + // See comments for DeviceDescriptionParseSuccessCallback. + DeviceDescriptionParseSuccessCallback success_cb_; + + // See comments for DeviceDescriptionParseErrorCallback. + DeviceDescriptionParseErrorCallback error_cb_; + + // Timer for clean up expired cache entries. + std::unique_ptr<base::RepeatingTimer> clean_up_timer_; + + // Safe DIAL parser associated with utility process. When this object is + // destroyed, associating utility process will shutdown. Keep |parser_| alive + // until finish parsing all device descriptions of devices passed in + // |GetDeviceDescriptions()|. If second |GetDeviceDescriptions()| arrives and + // |parser_| is still alive, reuse |parser_| instead of creating a new object. + std::unique_ptr<SafeDialDeviceDescriptionParser> parser_; + + base::ThreadChecker thread_checker_; +}; + +} // namespace media_router + +#endif // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_DEVICE_DESCRIPTION_SERVICE_H_
diff --git a/chrome/browser/media/router/discovery/dial/device_description_service_unittest.cc b/chrome/browser/media/router/discovery/dial/device_description_service_unittest.cc new file mode 100644 index 0000000..b58a67d --- /dev/null +++ b/chrome/browser/media/router/discovery/dial/device_description_service_unittest.cc
@@ -0,0 +1,328 @@ +// Copyright 2017 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/discovery/dial/device_description_service.h" + +#include "base/strings/stringprintf.h" +#include "base/test/mock_callback.h" +#include "chrome/browser/media/router/discovery/dial/device_description_fetcher.h" +#include "chrome/browser/media/router/discovery/dial/dial_device_data.h" +#include "chrome/browser/media/router/discovery/dial/parsed_dial_device_description.h" +#include "chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Return; +using ::testing::SaveArg; + +// Create Test Data +namespace { + +media_router::DialDeviceData CreateDialDeviceData(int num) { + media_router::DialDeviceData device_data( + base::StringPrintf("Device id %d", num), + GURL(base::StringPrintf("http://192.168.1.%d/dd.xml", num)), + base::Time::Now()); + device_data.set_label(base::StringPrintf("Device label %d", num)); + return device_data; +} + +media_router::DialDeviceDescriptionData CreateDialDeviceDescriptionData( + int num) { + return media_router::DialDeviceDescriptionData( + "", GURL(base::StringPrintf("http://192.168.1.%d/apps", num))); +} + +chrome::mojom::DialDeviceDescriptionPtr CreateDialDeviceDescriptionPtr( + int num) { + chrome::mojom::DialDeviceDescriptionPtr description_ptr = + chrome::mojom::DialDeviceDescription::New(); + description_ptr->friendly_name = base::StringPrintf("Friendly name %d", num); + description_ptr->model_name = base::StringPrintf("Model name %d", num); + description_ptr->unique_id = base::StringPrintf("Unique ID %d", num); + return description_ptr; +} + +media_router::ParsedDialDeviceDescription CreateParsedDialDeviceDescription( + int num) { + media_router::ParsedDialDeviceDescription description_data; + description_data.app_url = + GURL(base::StringPrintf("http://192.168.1.%d/apps", num)); + description_data.friendly_name = base::StringPrintf("Friendly name %d", num); + description_data.model_name = base::StringPrintf("Model name %d", num); + description_data.unique_id = base::StringPrintf("Unique ID %d", num); + return description_data; +} + +} // namespace + +namespace media_router { + +class TestSafeDialDeviceDescriptionParser + : public SafeDialDeviceDescriptionParser { + public: + ~TestSafeDialDeviceDescriptionParser() override {} + + MOCK_METHOD2(Start, + void(const std::string& xml_text, + const DeviceDescriptionCallback& callback)); +}; + +class TestDeviceDescriptionService : public DeviceDescriptionService { + public: + TestDeviceDescriptionService( + const DeviceDescriptionParseSuccessCallback& success_cb, + const DeviceDescriptionParseErrorCallback& error_cb) + : DeviceDescriptionService(success_cb, error_cb) {} + + MOCK_METHOD0(CreateSafeParser, SafeDialDeviceDescriptionParser*()); +}; + +class DeviceDescriptionServiceTest : public ::testing::Test { + public: + DeviceDescriptionServiceTest() + : device_description_service_(mock_success_cb_.Get(), + mock_error_cb_.Get()), + fetcher_map_( + device_description_service_.device_description_fetcher_map_), + description_cache_(device_description_service_.description_cache_), + thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {} + + TestDeviceDescriptionService* device_description_service() { + return &device_description_service_; + } + + void AddToCache(const std::string& device_label, + const ParsedDialDeviceDescription& description_data, + bool expired) { + DeviceDescriptionService::CacheEntry cache_entry; + cache_entry.expire_time = base::Time::Now(); + if (!expired) + cache_entry.expire_time += base::TimeDelta::FromHours(12); + cache_entry.description_data = description_data; + description_cache_[device_label] = cache_entry; + } + + void SetTestParser( + std::unique_ptr<TestSafeDialDeviceDescriptionParser> test_parser) { + if (device_description_service_.parser_) + return; + device_description_service_.parser_ = std::move(test_parser); + } + + void OnDeviceDescriptionFetchComplete(int num) { + auto device_data = CreateDialDeviceData(num); + auto description_response_data = CreateDialDeviceDescriptionData(num); + auto parsed_description_data = CreateParsedDialDeviceDescription(num); + + EXPECT_CALL(mock_success_cb_, Run(device_data, parsed_description_data)); + + device_description_service_.OnDeviceDescriptionFetchComplete( + device_data, description_response_data); + device_description_service_.OnParsedDeviceDescription( + device_data, description_response_data.app_url, + CreateDialDeviceDescriptionPtr(num)); + } + + void TestOnParsedDeviceDescription( + chrome::mojom::DialDeviceDescriptionPtr description_ptr, + const std::string& error_message) { + GURL app_url("http://192.168.1.1/apps"); + auto device_data = CreateDialDeviceData(1); + auto description_data = CreateParsedDialDeviceDescription(1); + + if (!error_message.empty()) { + EXPECT_CALL(mock_error_cb_, Run(device_data, error_message)); + } else { + EXPECT_CALL(mock_success_cb_, Run(device_data, description_data)); + } + device_description_service()->OnParsedDeviceDescription( + device_data, app_url, std::move(description_ptr)); + } + + protected: + base::MockCallback< + DeviceDescriptionService::DeviceDescriptionParseSuccessCallback> + mock_success_cb_; + base::MockCallback< + DeviceDescriptionService::DeviceDescriptionParseErrorCallback> + mock_error_cb_; + + TestDeviceDescriptionService device_description_service_; + std::map<std::string, std::unique_ptr<DeviceDescriptionFetcher>>& + fetcher_map_; + std::map<std::string, DeviceDescriptionService::CacheEntry>& + description_cache_; + + const content::TestBrowserThreadBundle thread_bundle_; + TestingProfile profile_; +}; + +TEST_F(DeviceDescriptionServiceTest, TestGetDeviceDescriptionFromCache) { + auto device_data = CreateDialDeviceData(1); + auto description_data = CreateParsedDialDeviceDescription(1); + EXPECT_CALL(mock_success_cb_, Run(device_data, description_data)); + + AddToCache(device_data.label(), description_data, false /* expired */); + + std::vector<DialDeviceData> devices = {device_data}; + device_description_service()->GetDeviceDescriptions(devices, nullptr); +} + +TEST_F(DeviceDescriptionServiceTest, TestGetDeviceDescriptionFetchURL) { + DialDeviceData device_data = CreateDialDeviceData(1); + std::vector<DialDeviceData> devices = {device_data}; + + // Create Fetcher + EXPECT_TRUE(fetcher_map_.empty()); + device_description_service()->GetDeviceDescriptions( + devices, profile_.GetRequestContext()); + EXPECT_EQ(size_t(1), fetcher_map_.size()); + + // Remove fetcher and create safe parser + auto test_parser = base::MakeUnique<TestSafeDialDeviceDescriptionParser>(); + EXPECT_CALL(*test_parser, Start(_, _)); + + SetTestParser(std::move(test_parser)); + OnDeviceDescriptionFetchComplete(1); +} + +TEST_F(DeviceDescriptionServiceTest, TestGetDeviceDescriptionFetchURLError) { + DialDeviceData device_data = CreateDialDeviceData(1); + std::vector<DialDeviceData> devices; + devices.push_back(device_data); + + // Create Fetcher + EXPECT_TRUE(fetcher_map_.empty()); + device_description_service()->GetDeviceDescriptions( + devices, profile_.GetRequestContext()); + EXPECT_EQ(size_t(1), fetcher_map_.size()); + + EXPECT_CALL(mock_error_cb_, Run(device_data, "")); + + device_description_service()->OnDeviceDescriptionFetchError(device_data, ""); + EXPECT_TRUE(fetcher_map_.empty()); +} + +TEST_F(DeviceDescriptionServiceTest, + TestGetDeviceDescriptionRemoveOutDatedFetchers) { + DialDeviceData device_data_1 = CreateDialDeviceData(1); + DialDeviceData device_data_2 = CreateDialDeviceData(2); + DialDeviceData device_data_3 = CreateDialDeviceData(3); + + std::vector<DialDeviceData> devices; + devices.push_back(device_data_1); + devices.push_back(device_data_2); + + // insert fetchers + device_description_service()->GetDeviceDescriptions( + devices, profile_.GetRequestContext()); + + // Keep fetchers non exist in current device list and remove fetchers with + // different description url. + GURL new_url_2 = GURL("http://example.com"); + device_data_2.set_device_description_url(new_url_2); + + devices.clear(); + devices.push_back(device_data_2); + devices.push_back(device_data_3); + device_description_service()->GetDeviceDescriptions( + devices, profile_.GetRequestContext()); + + EXPECT_EQ(size_t(3), fetcher_map_.size()); + + auto* description_fetcher = fetcher_map_[device_data_2.label()].get(); + EXPECT_EQ(new_url_2, description_fetcher->device_description_url()); + + EXPECT_CALL(mock_error_cb_, Run(_, _)).Times(3); + device_description_service()->OnDeviceDescriptionFetchError(device_data_1, + ""); + device_description_service()->OnDeviceDescriptionFetchError(device_data_2, + ""); + device_description_service()->OnDeviceDescriptionFetchError(device_data_3, + ""); +} + +TEST_F(DeviceDescriptionServiceTest, TestCleanUpCacheEntries) { + DialDeviceData device_data_1 = CreateDialDeviceData(1); + DialDeviceData device_data_2 = CreateDialDeviceData(2); + DialDeviceData device_data_3 = CreateDialDeviceData(3); + + AddToCache(device_data_1.label(), ParsedDialDeviceDescription(), + true /* expired */); + AddToCache(device_data_2.label(), ParsedDialDeviceDescription(), + true /* expired */); + AddToCache(device_data_3.label(), ParsedDialDeviceDescription(), + false /* expired */); + + device_description_service_.CleanUpCacheEntries(); + EXPECT_EQ(size_t(1), description_cache_.size()); + EXPECT_TRUE(base::ContainsKey(description_cache_, device_data_3.label())); + + AddToCache(device_data_3.label(), ParsedDialDeviceDescription(), + true /* expired*/); + device_description_service_.CleanUpCacheEntries(); + EXPECT_TRUE(description_cache_.empty()); +} + +TEST_F(DeviceDescriptionServiceTest, TestOnParsedDeviceDescription) { + GURL app_url("http://192.168.1.1/apps"); + DialDeviceData device_data = CreateDialDeviceData(1); + + // null_ptr + std::string error_message = "Failed to parse device description XML"; + TestOnParsedDeviceDescription(nullptr, error_message); + + // Empty field + error_message = "Failed to process fetch result"; + TestOnParsedDeviceDescription(chrome::mojom::DialDeviceDescription::New(), + error_message); + + // Valid device description ptr and put in cache + auto description_ptr = CreateDialDeviceDescriptionPtr(1); + TestOnParsedDeviceDescription(std::move(description_ptr), ""); + EXPECT_EQ(size_t(1), description_cache_.size()); + + // Valid device description ptr and skip cache. + size_t cache_num = 256; + for (size_t i = 0; i < cache_num; i++) { + AddToCache(std::to_string(i), ParsedDialDeviceDescription(), + false /* expired */); + } + + EXPECT_EQ(size_t(cache_num + 1), description_cache_.size()); + description_ptr = CreateDialDeviceDescriptionPtr(1); + TestOnParsedDeviceDescription(std::move(description_ptr), ""); + EXPECT_EQ(size_t(cache_num + 1), description_cache_.size()); +} + +TEST_F(DeviceDescriptionServiceTest, TestSafeParserProperlyCreated) { + DialDeviceData device_data_1 = CreateDialDeviceData(1); + DialDeviceData device_data_2 = CreateDialDeviceData(2); + DialDeviceData device_data_3 = CreateDialDeviceData(3); + + std::vector<DialDeviceData> devices = {device_data_1, device_data_2, + device_data_3}; + + // insert fetchers + device_description_service()->GetDeviceDescriptions( + devices, profile_.GetRequestContext()); + auto test_parser = base::MakeUnique<TestSafeDialDeviceDescriptionParser>(); + EXPECT_CALL(*test_parser, Start(_, _)).Times(3); + + EXPECT_FALSE(device_description_service()->parser_); + SetTestParser(std::move(test_parser)); + OnDeviceDescriptionFetchComplete(1); + + EXPECT_TRUE(device_description_service()->parser_); + OnDeviceDescriptionFetchComplete(2); + OnDeviceDescriptionFetchComplete(3); + + EXPECT_FALSE(device_description_service()->parser_); +} + +} // namespace media_router
diff --git a/chrome/browser/media/router/discovery/dial/dial_device_data.cc b/chrome/browser/media/router/discovery/dial/dial_device_data.cc index c130b834..56ca213 100644 --- a/chrome/browser/media/router/discovery/dial/dial_device_data.cc +++ b/chrome/browser/media/router/discovery/dial/dial_device_data.cc
@@ -3,6 +3,7 @@ // found in the LICENSE file. #include "chrome/browser/media/router/discovery/dial/dial_device_data.h" +#include "net/base/ip_address.h" namespace media_router { @@ -31,7 +32,16 @@ // static bool DialDeviceData::IsDeviceDescriptionUrl(const GURL& url) { - return url.is_valid() && !url.is_empty() && url.SchemeIsHTTPOrHTTPS(); + if (!url.is_valid() || url.is_empty() || !url.SchemeIsHTTPOrHTTPS()) + return false; + + net::IPAddress address; + if (!net::ParseURLHostnameToAddress(url.host(), &address)) + return false; + + // TODO(crbug.com/679432): check that this IP address matches the address that + // we received the SSDP advertisement from. + return address.IsReserved(); } bool DialDeviceData::UpdateFrom(const DialDeviceData& new_data) {
diff --git a/chrome/browser/media/router/discovery/dial/dial_device_data_unittest.cc b/chrome/browser/media/router/discovery/dial/dial_device_data_unittest.cc index ac78065..edc95dfc 100644 --- a/chrome/browser/media/router/discovery/dial/dial_device_data_unittest.cc +++ b/chrome/browser/media/router/discovery/dial/dial_device_data_unittest.cc
@@ -69,9 +69,9 @@ } TEST(DialDeviceDataTest, TestIsDeviceDescriptionUrl) { - EXPECT_TRUE(DialDeviceData::IsDeviceDescriptionUrl( + EXPECT_FALSE(DialDeviceData::IsDeviceDescriptionUrl( GURL("http://some.device.com/dd.xml"))); - EXPECT_TRUE(DialDeviceData::IsDeviceDescriptionUrl( + EXPECT_FALSE(DialDeviceData::IsDeviceDescriptionUrl( GURL("https://some.device.com/dd.xml"))); EXPECT_TRUE(DialDeviceData::IsDeviceDescriptionUrl( GURL("http://192.168.1.1:1234/dd.xml")));
diff --git a/chrome/browser/media/router/discovery/dial/dial_media_sink_service.cc b/chrome/browser/media/router/discovery/dial/dial_media_sink_service.cc new file mode 100644 index 0000000..562dcd5 --- /dev/null +++ b/chrome/browser/media/router/discovery/dial/dial_media_sink_service.cc
@@ -0,0 +1,147 @@ +// Copyright 2017 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/discovery/dial/dial_media_sink_service.h" + +#include "chrome/browser/media/router/discovery/dial/dial_device_data.h" +#include "chrome/browser/profiles/profile.h" +#include "content/public/browser/browser_thread.h" +#include "net/url_request/url_request_context_getter.h" + +using content::BrowserThread; + +namespace { +// Time interval when media sink service sends sinks to MRP. +const int kFetchCompleteTimeoutSecs = 3; +} + +namespace media_router { + +DialMediaSinkService::DialMediaSinkService( + const OnSinksDiscoveredCallback& callback, + net::URLRequestContextGetter* request_context) + : MediaSinkService(callback), request_context_(request_context) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK(request_context_); +} + +DialMediaSinkService::~DialMediaSinkService() {} + +void DialMediaSinkService::Start() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + dial_registry()->RegisterObserver(this); + dial_registry()->StartPeriodicDiscovery(); +} + +void DialMediaSinkService::Stop() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + dial_registry()->UnregisterObserver(this); +} + +DialRegistry* DialMediaSinkService::dial_registry() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + return DialRegistry::GetInstance(); +} + +DeviceDescriptionService* DialMediaSinkService::GetDescriptionService() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + if (!description_service_.get()) { + description_service_.reset(new DeviceDescriptionService( + base::Bind(&DialMediaSinkService::OnDeviceDescriptionAvailable, + base::Unretained(this)), + base::Bind(&DialMediaSinkService::OnDeviceDescriptionError, + base::Unretained(this)))); + } + return description_service_.get(); +} + +void DialMediaSinkService::OnDialDeviceEvent( + const DialRegistry::DeviceList& devices) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DVLOG(2) << "DialMediaSinkService::OnDialDeviceEvent found " << devices.size() + << " devices"; + + // Add a finish timer. + finish_timer_.reset(new base::OneShotTimer()); + base::TimeDelta finish_delay = + base::TimeDelta::FromSeconds(kFetchCompleteTimeoutSecs); + finish_timer_->Start(FROM_HERE, finish_delay, this, + &DialMediaSinkService::OnFetchCompleted); + + current_sinks_.clear(); + current_devices_ = devices; + + GetDescriptionService()->GetDeviceDescriptions(devices, + request_context_.get()); +} + +void DialMediaSinkService::OnDialError(DialRegistry::DialErrorCode type) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DVLOG(2) << "OnDialError [DialErrorCode]: " << static_cast<int>(type); +} + +void DialMediaSinkService::OnDeviceDescriptionAvailable( + const DialDeviceData& device_data, + const ParsedDialDeviceDescription& description_data) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + + if (!base::ContainsValue(current_devices_, device_data)) { + DVLOG(2) << "Device data not found in current device data list..."; + return; + } + + // When use this "sink" within browser, please note it will have a different + // ID when it is sent to the extension, because it derives a different sink ID + // using the given sink ID. + MediaSink sink(description_data.unique_id, description_data.friendly_name, + MediaSink::IconType::GENERIC); + DialSinkExtraData extra_data; + extra_data.app_url = description_data.app_url; + extra_data.model_name = description_data.model_name; + std::string ip_address = device_data.device_description_url().host(); + if (!extra_data.ip_address.AssignFromIPLiteral(ip_address)) { + DVLOG(1) << "Invalid ip_address: " << ip_address; + return; + } + + current_sinks_.insert(MediaSinkInternal(sink, extra_data)); + + if (finish_timer_) + return; + + // Start fetch timer again if device description comes back after + // |finish_timer_| fires. + base::TimeDelta finish_delay = + base::TimeDelta::FromSeconds(kFetchCompleteTimeoutSecs); + finish_timer_.reset(new base::OneShotTimer()); + finish_timer_->Start(FROM_HERE, finish_delay, this, + &DialMediaSinkService::OnFetchCompleted); +} + +void DialMediaSinkService::OnDeviceDescriptionError( + const DialDeviceData& device, + const std::string& error_message) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DVLOG(2) << "OnDescriptionFetchesError [message]: " << error_message; +} + +void DialMediaSinkService::OnFetchCompleted() { + DCHECK_CURRENTLY_ON(BrowserThread::IO); + DCHECK(!sink_discovery_callback_.is_null()); + + finish_timer_.reset(); + + auto sinks = current_sinks_; + if (sinks == mrp_sinks_) { + DVLOG(2) << "No update to sink list."; + return; + } + + DVLOG(2) << "Send sinks to media router, [size]: " << sinks.size(); + sink_discovery_callback_.Run( + std::vector<MediaSinkInternal>(sinks.begin(), sinks.end())); + mrp_sinks_ = std::move(sinks); +} + +} // namespace media_router
diff --git a/chrome/browser/media/router/discovery/dial/dial_media_sink_service.h b/chrome/browser/media/router/discovery/dial/dial_media_sink_service.h new file mode 100644 index 0000000..86f72c5 --- /dev/null +++ b/chrome/browser/media/router/discovery/dial/dial_media_sink_service.h
@@ -0,0 +1,89 @@ +// Copyright 2017 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_DISCOVERY_DIAL_DIAL_MEDIA_SINK_SERVICE_H_ +#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_DIAL_MEDIA_SINK_SERVICE_H_ + +#include <memory> +#include <set> + +#include "chrome/browser/media/router/discovery/dial/device_description_service.h" +#include "chrome/browser/media/router/discovery/dial/dial_registry.h" +#include "chrome/common/media_router/discovery/media_sink_internal.h" +#include "chrome/common/media_router/discovery/media_sink_service.h" + +namespace media_router { + +class DeviceDescriptionService; +class DialRegistry; + +// A service which can be used to start background discovery and resolution of +// DIAL devices (Smart TVs, Game Consoles, etc.). +// This class is not thread safe. All methods must be called from the IO thread. +class DialMediaSinkService : public MediaSinkService, + public DialRegistry::Observer { + public: + DialMediaSinkService(const OnSinksDiscoveredCallback& callback, + net::URLRequestContextGetter* request_context); + ~DialMediaSinkService() override; + + // MediaSinkService implementation + void Start() override; + + void Stop(); + + protected: + virtual DialRegistry* dial_registry(); + + // Returns instance of device description service. Create a new one if none + // exists. + virtual DeviceDescriptionService* GetDescriptionService(); + + private: + friend class DialMediaSinkServiceTest; + FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceTest, TestStart); + FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceTest, TestFetchCompleted); + FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceTest, TestIsDifferent); + FRIEND_TEST_ALL_PREFIXES(DialMediaSinkServiceTest, + TestOnDeviceDescriptionAvailable); + + // api::dial::DialRegistry::Observer implementation + void OnDialDeviceEvent(const DialRegistry::DeviceList& devices) override; + void OnDialError(DialRegistry::DialErrorCode type) override; + + // Called when description service successfully fetches and parses device + // description XML. Restart |finish_timer_| if it is not running. + void OnDeviceDescriptionAvailable( + const DialDeviceData& device_data, + const ParsedDialDeviceDescription& description_data); + + // Called when fails to fetch or parse device description XML. + void OnDeviceDescriptionError(const DialDeviceData& device, + const std::string& error_message); + + // Called when |finish_timer_| expires. + void OnFetchCompleted(); + + // Timer for finishing fetching. Starts in |OnDialDeviceEvent()|, and expires + // 3 seconds later. If |OnDeviceDescriptionAvailable()| is called after + // |finish_timer_| expires, |finish_timer_| is restarted. + std::unique_ptr<base::OneShotTimer> finish_timer_; + + std::unique_ptr<DeviceDescriptionService> description_service_; + + // Sorted sinks from current round of discovery. + std::set<MediaSinkInternal> current_sinks_; + + // Sorted sinks sent to Media Router Provider in last FetchCompleted(). + std::set<MediaSinkInternal> mrp_sinks_; + + // Device data list from current round of discovery. + DialRegistry::DeviceList current_devices_; + + scoped_refptr<net::URLRequestContextGetter> request_context_; +}; + +} // namespace media_router + +#endif // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_DIAL_MEDIA_SINK_SERVICE_H_
diff --git a/chrome/browser/media/router/discovery/dial/dial_media_sink_service_unittest.cc b/chrome/browser/media/router/discovery/dial/dial_media_sink_service_unittest.cc new file mode 100644 index 0000000..cf542a8 --- /dev/null +++ b/chrome/browser/media/router/discovery/dial/dial_media_sink_service_unittest.cc
@@ -0,0 +1,232 @@ +// Copyright 2017 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/discovery/dial/dial_media_sink_service.h" +#include "base/test/mock_callback.h" +#include "chrome/browser/media/router/discovery/dial/dial_device_data.h" +#include "chrome/browser/media/router/discovery/dial/dial_registry.h" +#include "chrome/browser/media/router/test_helper.h" +#include "chrome/test/base/testing_profile.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Return; + +namespace { + +media_router::DialSinkExtraData CreateDialSinkExtraData( + const std::string& model_name, + const std::string& ip_address, + const std::string& app_url) { + media_router::DialSinkExtraData dial_extra_data; + EXPECT_TRUE(dial_extra_data.ip_address.AssignFromIPLiteral(ip_address)); + dial_extra_data.model_name = model_name; + dial_extra_data.app_url = GURL(app_url); + return dial_extra_data; +} + +std::vector<media_router::MediaSinkInternal> CreateDialMediaSinks() { + media_router::MediaSink sink1("sink1", "sink_name_1", + media_router::MediaSink::IconType::CAST); + media_router::DialSinkExtraData extra_data1 = CreateDialSinkExtraData( + "model_name1", "192.168.1.1", "https://example1.com"); + + media_router::MediaSink sink2("sink2", "sink_name_2", + media_router::MediaSink::IconType::CAST); + media_router::DialSinkExtraData extra_data2 = CreateDialSinkExtraData( + "model_name2", "192.168.1.2", "https://example2.com"); + + std::vector<media_router::MediaSinkInternal> sinks; + sinks.push_back(media_router::MediaSinkInternal(sink1, extra_data1)); + sinks.push_back(media_router::MediaSinkInternal(sink2, extra_data2)); + return sinks; +} + +} // namespace + +namespace media_router { + +class TestDialRegistry : public DialRegistry { + public: + TestDialRegistry() {} + ~TestDialRegistry() {} + + MOCK_METHOD1(RegisterObserver, void(DialRegistry::Observer* observer)); + MOCK_METHOD1(UnregisterObserver, void(DialRegistry::Observer* observer)); +}; + +class MockDeviceDescriptionService : public DeviceDescriptionService { + public: + MockDeviceDescriptionService(DeviceDescriptionParseSuccessCallback success_cb, + DeviceDescriptionParseErrorCallback error_cb) + : DeviceDescriptionService(success_cb, error_cb) {} + ~MockDeviceDescriptionService() override {} + + MOCK_METHOD2(GetDeviceDescriptions, + void(const std::vector<DialDeviceData>& devices, + net::URLRequestContextGetter* request_context)); +}; + +class TestDialMediaSinkService : public DialMediaSinkService { + public: + TestDialMediaSinkService( + const MediaSinkService::OnSinksDiscoveredCallback& callback, + net::URLRequestContextGetter* request_context, + TestDialRegistry* test_dial_registry, + MockDeviceDescriptionService* mock_description_service) + : DialMediaSinkService(callback, request_context), + test_dial_registry_(test_dial_registry), + mock_description_service_(mock_description_service) {} + + DialRegistry* dial_registry() override { return test_dial_registry_; } + + DeviceDescriptionService* GetDescriptionService() override { + return mock_description_service_; + } + + private: + TestDialRegistry* test_dial_registry_; + MockDeviceDescriptionService* mock_description_service_; +}; + +class DialMediaSinkServiceTest : public ::testing::Test { + public: + DialMediaSinkServiceTest() + : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), + mock_description_service_(mock_success_cb_.Get(), mock_error_cb_.Get()), + media_sink_service_(mock_sink_discovered_cb_.Get(), + profile_.GetRequestContext(), + &test_dial_registry_, + &mock_description_service_) {} + + DialMediaSinkService* media_sink_service() { return &media_sink_service_; } + + void TestFetchCompleted(const std::vector<MediaSinkInternal>& old_sinks, + const std::vector<MediaSinkInternal>& new_sinks) { + media_sink_service()->mrp_sinks_ = + std::set<MediaSinkInternal>(old_sinks.begin(), old_sinks.end()); + media_sink_service()->current_sinks_ = + std::set<MediaSinkInternal>(new_sinks.begin(), new_sinks.end()); + EXPECT_CALL(mock_sink_discovered_cb_, Run(new_sinks)); + media_sink_service()->OnFetchCompleted(); + } + + protected: + const content::TestBrowserThreadBundle thread_bundle_; + TestingProfile profile_; + + base::MockCallback<MediaSinkService::OnSinksDiscoveredCallback> + mock_sink_discovered_cb_; + base::MockCallback< + MockDeviceDescriptionService::DeviceDescriptionParseSuccessCallback> + mock_success_cb_; + base::MockCallback< + MockDeviceDescriptionService::DeviceDescriptionParseErrorCallback> + mock_error_cb_; + + TestDialRegistry test_dial_registry_; + MockDeviceDescriptionService mock_description_service_; + + TestDialMediaSinkService media_sink_service_; +}; + +TEST_F(DialMediaSinkServiceTest, TestStart) { + EXPECT_CALL(test_dial_registry_, RegisterObserver(&media_sink_service_)); + media_sink_service()->Start(); + + DialRegistry::DeviceList deviceList; + DialDeviceData first_device("first", GURL("http://127.0.0.1/dd.xml"), + base::Time::Now()); + DialDeviceData second_device("second", GURL("http://127.0.0.2/dd.xml"), + base::Time::Now()); + DialDeviceData third_device("third", GURL("http://127.0.0.3/dd.xml"), + base::Time::Now()); + deviceList.push_back(first_device); + deviceList.push_back(second_device); + deviceList.push_back(third_device); + + EXPECT_CALL(mock_description_service_, GetDeviceDescriptions(deviceList, _)); + + media_sink_service()->OnDialDeviceEvent(deviceList); + EXPECT_TRUE(media_sink_service_.finish_timer_->IsRunning()); +} + +TEST_F(DialMediaSinkServiceTest, TestOnDeviceDescriptionAvailable) { + DialDeviceData device_data("first", GURL("http://127.0.0.1/dd.xml"), + base::Time::Now()); + ParsedDialDeviceDescription device_description; + device_description.model_name = "model name"; + device_description.friendly_name = "friendly name"; + device_description.app_url = GURL("http://192.168.1.1/apps"); + device_description.unique_id = "unique id"; + + media_sink_service()->OnDeviceDescriptionAvailable(device_data, + device_description); + EXPECT_TRUE(media_sink_service()->current_sinks_.empty()); + + std::vector<DialDeviceData> deviceList{device_data}; + EXPECT_CALL(mock_description_service_, GetDeviceDescriptions(deviceList, _)); + + media_sink_service()->OnDialDeviceEvent(deviceList); + media_sink_service()->OnDeviceDescriptionAvailable(device_data, + device_description); + + EXPECT_EQ(size_t(1), media_sink_service()->current_sinks_.size()); +} + +TEST_F(DialMediaSinkServiceTest, TestFetchCompleted) { + std::vector<MediaSinkInternal> old_sinks; + std::vector<MediaSinkInternal> new_sinks = CreateDialMediaSinks(); + TestFetchCompleted(old_sinks, new_sinks); + + // Same sink + EXPECT_CALL(mock_sink_discovered_cb_, Run(new_sinks)).Times(0); + media_sink_service()->OnFetchCompleted(); +} + +TEST_F(DialMediaSinkServiceTest, TestFetchCompleted_OneNewSink) { + std::vector<MediaSinkInternal> old_sinks = CreateDialMediaSinks(); + std::vector<MediaSinkInternal> new_sinks = CreateDialMediaSinks(); + MediaSink sink3("sink3", "sink_name_3", MediaSink::IconType::CAST); + DialSinkExtraData extra_data3 = CreateDialSinkExtraData( + "model_name3", "192.168.1.3", "https://example3.com"); + new_sinks.push_back(MediaSinkInternal(sink3, extra_data3)); + TestFetchCompleted(old_sinks, new_sinks); +} + +TEST_F(DialMediaSinkServiceTest, TestFetchCompleted_RemovedOneSink) { + std::vector<MediaSinkInternal> old_sinks = CreateDialMediaSinks(); + std::vector<MediaSinkInternal> new_sinks = CreateDialMediaSinks(); + new_sinks.erase(new_sinks.begin()); + TestFetchCompleted(old_sinks, new_sinks); +} + +TEST_F(DialMediaSinkServiceTest, TestFetchCompleted_UpdatedOneSink) { + std::vector<MediaSinkInternal> old_sinks = CreateDialMediaSinks(); + std::vector<MediaSinkInternal> new_sinks = CreateDialMediaSinks(); + new_sinks[0].set_name("sink_name_4"); + TestFetchCompleted(old_sinks, new_sinks); +} + +TEST_F(DialMediaSinkServiceTest, TestFetchCompleted_Mixed) { + std::vector<MediaSinkInternal> old_sinks = CreateDialMediaSinks(); + + MediaSink sink1("sink1", "sink_name_1", MediaSink::IconType::CAST); + DialSinkExtraData extra_data2 = CreateDialSinkExtraData( + "model_name2", "192.168.1.2", "https://example2.com"); + + MediaSink sink3("sink3", "sink_name_3", MediaSink::IconType::CAST); + DialSinkExtraData extra_data3 = CreateDialSinkExtraData( + "model_name3", "192.168.1.3", "https://example3.com"); + + std::vector<MediaSinkInternal> new_sinks; + new_sinks.push_back(MediaSinkInternal(sink1, extra_data2)); + new_sinks.push_back(MediaSinkInternal(sink3, extra_data3)); + + TestFetchCompleted(old_sinks, new_sinks); +} + +} // namespace media_router
diff --git a/chrome/browser/media/router/discovery/dial/dial_registry.h b/chrome/browser/media/router/discovery/dial/dial_registry.h index 96253372..c1f885c2 100644 --- a/chrome/browser/media/router/discovery/dial/dial_registry.h +++ b/chrome/browser/media/router/discovery/dial/dial_registry.h
@@ -63,9 +63,9 @@ void OnListenerRemoved(); // pass a reference of |observer| to allow it to notify on DIAL device events. - // This class does not take ownership of |observer|. - void RegisterObserver(Observer* observer); - void UnregisterObserver(Observer* observer); + // This class does not take ownership of observer. + virtual void RegisterObserver(Observer* observer); + virtual void UnregisterObserver(Observer* observer); // Called by the DIAL API to try to kickoff a discovery if there is not one // already active. @@ -102,6 +102,7 @@ using DeviceByLabelMap = std::map<std::string, DialDeviceData*>; friend class MockDialRegistry; + friend class TestDialRegistry; friend struct base::DefaultSingletonTraits<DialRegistry>; DialRegistry();
diff --git a/chrome/browser/media/router/discovery/dial/parsed_dial_device_description.cc b/chrome/browser/media/router/discovery/dial/parsed_dial_device_description.cc new file mode 100644 index 0000000..a72e9863 --- /dev/null +++ b/chrome/browser/media/router/discovery/dial/parsed_dial_device_description.cc
@@ -0,0 +1,40 @@ +// Copyright (c) 2017 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/discovery/dial/parsed_dial_device_description.h" + +namespace media_router { + +ParsedDialDeviceDescription::ParsedDialDeviceDescription() = default; + +ParsedDialDeviceDescription::ParsedDialDeviceDescription( + const std::string& unique_id, + const std::string& friendly_name, + const GURL& app_url) + : unique_id(unique_id), friendly_name(friendly_name), app_url(app_url) {} + +ParsedDialDeviceDescription::ParsedDialDeviceDescription( + const ParsedDialDeviceDescription& other) = default; +ParsedDialDeviceDescription::~ParsedDialDeviceDescription() = default; + +ParsedDialDeviceDescription& ParsedDialDeviceDescription::operator=( + const ParsedDialDeviceDescription& other) { + if (this == &other) + return *this; + + this->unique_id = other.unique_id; + this->friendly_name = other.friendly_name; + this->model_name = other.model_name; + this->app_url = other.app_url; + + return *this; +} + +bool ParsedDialDeviceDescription::operator==( + const ParsedDialDeviceDescription& other) const { + return unique_id == other.unique_id && friendly_name == other.friendly_name && + model_name == other.model_name && app_url == other.app_url; +} + +} // namespace media_router
diff --git a/chrome/browser/media/router/discovery/dial/parsed_dial_device_description.h b/chrome/browser/media/router/discovery/dial/parsed_dial_device_description.h new file mode 100644 index 0000000..4c066a17 --- /dev/null +++ b/chrome/browser/media/router/discovery/dial/parsed_dial_device_description.h
@@ -0,0 +1,41 @@ +// Copyright 2017 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_DISCOVERY_DIAL_PARSED_DIAL_DEVICE_DESCRIPTION_H_ +#define CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_PARSED_DIAL_DEVICE_DESCRIPTION_H_ + +#include <string> +#include "url/gurl.h" + +namespace media_router { + +struct ParsedDialDeviceDescription { + ParsedDialDeviceDescription(); + ParsedDialDeviceDescription(const std::string& unique_id, + const std::string& friendly_name, + const GURL& app_url); + ParsedDialDeviceDescription(const ParsedDialDeviceDescription& other); + ~ParsedDialDeviceDescription(); + + ParsedDialDeviceDescription& operator=( + const ParsedDialDeviceDescription& other); + + bool operator==(const ParsedDialDeviceDescription& other) const; + + // UUID (UDN). + std::string unique_id; + + // Short user-friendly device name. + std::string friendly_name; + + // Device model name. + std::string model_name; + + // The DIAL application URL (used to launch DIAL applications). + GURL app_url; +}; + +} // namespace media_router + +#endif // CHROME_BROWSER_MEDIA_ROUTER_DISCOVERY_DIAL_PARSED_DIAL_DEVICE_DESCRIPTION_H_
diff --git a/chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.cc b/chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.cc index 81ea291..bb2cccc 100644 --- a/chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.cc +++ b/chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.cc
@@ -19,51 +19,30 @@ void SafeDialDeviceDescriptionParser::Start( const std::string& xml_text, const DeviceDescriptionCallback& callback) { + DVLOG(2) << "Start parsing device description..."; DCHECK(thread_checker_.CalledOnValidThread()); - DCHECK(!utility_process_mojo_client_); + DCHECK(callback); - device_description_callback_ = callback; + if (!utility_process_mojo_client_) { + DVLOG(2) << "Start utility process in background..."; + utility_process_mojo_client_ = + base::MakeUnique<content::UtilityProcessMojoClient< + chrome::mojom::DialDeviceDescriptionParser>>( + l10n_util::GetStringUTF16( + IDS_UTILITY_PROCESS_DIAL_DEVICE_DESCRIPTION_PARSER_NAME)); - utility_process_mojo_client_ = - base::MakeUnique<content::UtilityProcessMojoClient< - chrome::mojom::DialDeviceDescriptionParser>>( - l10n_util::GetStringUTF16( - IDS_UTILITY_PROCESS_DIAL_DEVICE_DESCRIPTION_PARSER_NAME)); + utility_process_mojo_client_->set_error_callback( + base::Bind(callback, nullptr)); - utility_process_mojo_client_->set_error_callback(base::Bind( - &SafeDialDeviceDescriptionParser::OnParseDeviceDescriptionFailed, - base::Unretained(this))); - - // This starts utility process in the background. - utility_process_mojo_client_->Start(); + // This starts utility process in the background. + utility_process_mojo_client_->Start(); + } // This call is queued up until the Mojo message pipe has been established to // the service running in the utility process. - utility_process_mojo_client_->service()->ParseDialDeviceDescription( - xml_text, - base::Bind( - &SafeDialDeviceDescriptionParser::OnParseDeviceDescriptionComplete, - base::Unretained(this))); -} - -void SafeDialDeviceDescriptionParser::OnParseDeviceDescriptionComplete( - chrome::mojom::DialDeviceDescriptionPtr device_description) { - DCHECK(thread_checker_.CalledOnValidThread()); - - utility_process_mojo_client_.reset(); // Terminate the utility process. - - DCHECK(device_description_callback_); - device_description_callback_.Run(std::move(device_description)); -} - -void SafeDialDeviceDescriptionParser::OnParseDeviceDescriptionFailed() { - DCHECK(thread_checker_.CalledOnValidThread()); - - utility_process_mojo_client_.reset(); // Terminate the utility process. - - DCHECK(device_description_callback_); - device_description_callback_.Run(nullptr); + utility_process_mojo_client_->service()->ParseDialDeviceDescription(xml_text, + callback); } } // namespace media_router
diff --git a/chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.h b/chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.h index 360725b6..172d89d0 100644 --- a/chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.h +++ b/chrome/browser/media/router/discovery/dial/safe_dial_device_description_parser.h
@@ -10,6 +10,7 @@ #include "base/callback.h" #include "base/macros.h" +#include "base/memory/ref_counted.h" #include "base/optional.h" #include "base/threading/thread_checker.h" #include "chrome/common/media_router/mojo/dial_device_description_parser.mojom.h" @@ -29,31 +30,21 @@ ParseDialDeviceDescriptionCallback; SafeDialDeviceDescriptionParser(); + virtual ~SafeDialDeviceDescriptionParser(); // Start parsing device description XML file in utility process. - void Start(const std::string& xml_text, - const DeviceDescriptionCallback& callback); - - private: - ~SafeDialDeviceDescriptionParser(); - - // See SafeDialDeviceDescriptionParser::DeviceDescriptionCallback - void OnParseDeviceDescriptionComplete( - chrome::mojom::DialDeviceDescriptionPtr device_description); - // TODO(crbug.com/702766): Add an enum type describing why utility process // fails to parse device description xml. - void OnParseDeviceDescriptionFailed(); + virtual void Start(const std::string& xml_text, + const DeviceDescriptionCallback& callback); + private: // Utility client used to send device description parsing task to the utility // process. std::unique_ptr<content::UtilityProcessMojoClient< chrome::mojom::DialDeviceDescriptionParser>> utility_process_mojo_client_; - // Only accessed on the IO thread. - DeviceDescriptionCallback device_description_callback_; - base::ThreadChecker thread_checker_; DISALLOW_COPY_AND_ASSIGN(SafeDialDeviceDescriptionParser);
diff --git a/chrome/browser/media/router/media_sink_service.h b/chrome/browser/media/router/media_sink_service.h deleted file mode 100644 index 9b910c6..0000000 --- a/chrome/browser/media/router/media_sink_service.h +++ /dev/null
@@ -1,58 +0,0 @@ -// Copyright 2017 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_MEDIA_SINK_SERVICE_H_ -#define CHROME_BROWSER_MEDIA_ROUTER_MEDIA_SINK_SERVICE_H_ - -#include <memory> -#include <vector> - -#include "base/callback.h" -#include "chrome/common/media_router/media_sink.h" - -namespace media_router { - -class MediaSinksObserver; - -// A service which can be used to start background discovery and resolution of -// MediaSinks. Often these are remote devices, like Chromecast. In addition, the -// service is capable of answering MediaSink queries using the sinks that it -// generated. -// This class is not thread safe. All methods must be called from the IO thread. -class MediaSinkService { - public: - // Callback to be invoked when this class finishes sink discovering. - // Arg 0: Sinks discovered and resolved by the service. - using OnSinksDiscoveredCallback = - base::Callback<void(const std::vector<MediaSink>&)>; - - explicit MediaSinkService( - const OnSinksDiscoveredCallback& sinks_discovered_callback); - - virtual ~MediaSinkService(); - - // Starts sink discovery. No-ops if already started. - // Sinks discovered and resolved are continuously passed to - // |callback|. - virtual void Start() = 0; - - // Adds a sink query to observe for MediaSink updates. - // Multiple observers can be added for a given MediaSource. - // Start() must be called first. This class does not take - // ownership of |observer|. - virtual void AddSinkQuery(MediaSinksObserver* observer) = 0; - - // Removes a sink query and stops observing MediaSink updates. No-op if - // |observer| is not registered with this class. - virtual void RemoveSinkQuery(MediaSinksObserver* observer) = 0; - - protected: - OnSinksDiscoveredCallback sinks_discovered_callback_; - - DISALLOW_COPY_AND_ASSIGN(MediaSinkService); -}; - -} // namespace media_router - -#endif // CHROME_BROWSER_MEDIA_ROUTER_MEDIA_SINK_SERVICE_H_
diff --git a/chrome/common/media_router/BUILD.gn b/chrome/common/media_router/BUILD.gn index 5616d272..ed0f520b 100644 --- a/chrome/common/media_router/BUILD.gn +++ b/chrome/common/media_router/BUILD.gn
@@ -15,6 +15,8 @@ sources = [ "discovery/media_sink_internal.cc", "discovery/media_sink_internal.h", + "discovery/media_sink_service.cc", + "discovery/media_sink_service.h", "issue.cc", "issue.h", "media_route.cc",
diff --git a/chrome/common/media_router/discovery/media_sink_internal.cc b/chrome/common/media_router/discovery/media_sink_internal.cc index 3b9a77f..0cab52e6 100644 --- a/chrome/common/media_router/discovery/media_sink_internal.cc +++ b/chrome/common/media_router/discovery/media_sink_internal.cc
@@ -64,6 +64,14 @@ return false; } +bool MediaSinkInternal::operator!=(const MediaSinkInternal& other) const { + return !operator==(other); +} + +bool MediaSinkInternal::operator<(const MediaSinkInternal& other) const { + return sink_.id() < other.sink().id(); +} + void MediaSinkInternal::set_sink(const MediaSink& sink) { sink_ = sink; }
diff --git a/chrome/common/media_router/discovery/media_sink_internal.h b/chrome/common/media_router/discovery/media_sink_internal.h index 3a9c2bb6..25d7b617 100644 --- a/chrome/common/media_router/discovery/media_sink_internal.h +++ b/chrome/common/media_router/discovery/media_sink_internal.h
@@ -73,6 +73,9 @@ MediaSinkInternal& operator=(const MediaSinkInternal& other); bool operator==(const MediaSinkInternal& other) const; + bool operator!=(const MediaSinkInternal& other) const; + // Sorted by sink id. + bool operator<(const MediaSinkInternal& other) const; // Used by mojo. void set_sink_id(const MediaSink::Id& sink_id) { sink_.set_sink_id(sink_id); }
diff --git a/chrome/common/media_router/discovery/media_sink_internal_unittest.cc b/chrome/common/media_router/discovery/media_sink_internal_unittest.cc index 220ecda..2267f6e 100644 --- a/chrome/common/media_router/discovery/media_sink_internal_unittest.cc +++ b/chrome/common/media_router/discovery/media_sink_internal_unittest.cc
@@ -3,67 +3,61 @@ // found in the LICENSE file. #include "chrome/common/media_router/discovery/media_sink_internal.h" - -#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" +namespace { +constexpr char kSinkId[] = "sinkId123"; +constexpr char kSinkName[] = "The sink"; +constexpr char kIPAddress[] = "192.168.1.2"; +constexpr char kModelName[] = "model name"; +constexpr char kAppUrl[] = "https://example.com"; + +media_router::DialSinkExtraData CreateDialSinkExtraData( + const std::string& model_name, + const std::string& ip_address, + const std::string& app_url) { + media_router::DialSinkExtraData dial_extra_data; + EXPECT_TRUE(dial_extra_data.ip_address.AssignFromIPLiteral(ip_address)); + dial_extra_data.model_name = model_name; + dial_extra_data.app_url = GURL(app_url); + + return dial_extra_data; +} + +media_router::CastSinkExtraData CreateCastSinkExtraData( + const std::string& model_name, + const std::string& ip_address, + uint8_t capabilities, + int cast_channel_id) { + media_router::CastSinkExtraData cast_extra_data; + EXPECT_TRUE(cast_extra_data.ip_address.AssignFromIPLiteral(ip_address)); + cast_extra_data.model_name = model_name; + cast_extra_data.capabilities = 2; + cast_extra_data.cast_channel_id = 3; + return cast_extra_data; +} + +// static +media_router::DialSinkExtraData CreateDialSinkExtraData() { + return CreateDialSinkExtraData(kModelName, kIPAddress, kAppUrl); +} + +// static +media_router::CastSinkExtraData CreateCastSinkExtraData() { + return CreateCastSinkExtraData(kModelName, kIPAddress, 2, 3); +} + +} // namespace + namespace media_router { -class MediaSinkInternalTest : public ::testing::Test { - public: - MediaSinkInternalTest() - : media_sink_(sink_id_, sink_name_, MediaSink::IconType::CAST) {} - - DialSinkExtraData CreateDialSinkExtraData() { - return CreateDialSinkExtraData(model_name_, ip_address_, app_url_); - } - - DialSinkExtraData CreateDialSinkExtraData(const std::string& model_name, - const std::string& ip_address, - const std::string& app_url) { - DialSinkExtraData dial_extra_data; - EXPECT_TRUE(dial_extra_data.ip_address.AssignFromIPLiteral(ip_address)); - dial_extra_data.model_name = model_name; - dial_extra_data.app_url = GURL(app_url); - - return dial_extra_data; - } - - CastSinkExtraData CreateCastSinkExtraData() { - return CreateCastSinkExtraData(model_name_, ip_address_, 2, 3); - } - - CastSinkExtraData CreateCastSinkExtraData(const std::string& model_name, - const std::string& ip_address, - uint8_t capabilities, - int cast_channel_id) { - CastSinkExtraData cast_extra_data; - EXPECT_TRUE(cast_extra_data.ip_address.AssignFromIPLiteral(ip_address)); - cast_extra_data.model_name = model_name; - cast_extra_data.capabilities = 2; - cast_extra_data.cast_channel_id = 3; - return cast_extra_data; - } - - MediaSink media_sink() { return media_sink_; } - - private: - std::string sink_id_ = "sinkId123"; - std::string sink_name_ = "The sink"; - std::string ip_address_ = "192.168.1.2"; - std::string model_name_ = "model name"; - std::string app_url_ = "https://example.com"; - - MediaSink media_sink_; -}; - -TEST_F(MediaSinkInternalTest, TestIsValidSinkId) { +TEST(MediaSinkInternalTest, TestIsValidSinkId) { EXPECT_FALSE(MediaSinkInternal::IsValidSinkId("")); EXPECT_TRUE(MediaSinkInternal::IsValidSinkId("rjuKv_yxhY4jg7QBIp0kbngLjR6A")); } -TEST_F(MediaSinkInternalTest, TestConstructorAndAssignment) { - MediaSink sink = media_sink(); +TEST(MediaSinkInternalTest, TestConstructorAndAssignment) { + MediaSink sink(kSinkId, kSinkName, MediaSink::IconType::CAST); DialSinkExtraData dial_extra_data = CreateDialSinkExtraData(); CastSinkExtraData cast_extra_data = CreateCastSinkExtraData(); @@ -99,8 +93,8 @@ } } -TEST_F(MediaSinkInternalTest, TestSetExtraData) { - MediaSink sink = media_sink(); +TEST(MediaSinkInternalTest, TestSetExtraData) { + MediaSink sink(kSinkId, kSinkName, MediaSink::IconType::CAST); DialSinkExtraData dial_extra_data = CreateDialSinkExtraData(); CastSinkExtraData cast_extra_data = CreateCastSinkExtraData();
diff --git a/chrome/browser/media/router/media_sink_service.cc b/chrome/common/media_router/discovery/media_sink_service.cc similarity index 61% rename from chrome/browser/media/router/media_sink_service.cc rename to chrome/common/media_router/discovery/media_sink_service.cc index 80f15a53..ea86eea 100644 --- a/chrome/browser/media/router/media_sink_service.cc +++ b/chrome/common/media_router/discovery/media_sink_service.cc
@@ -2,13 +2,13 @@ // 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/media_sink_service.h" +#include "chrome/common/media_router/discovery/media_sink_service.h" namespace media_router { MediaSinkService::MediaSinkService( - const OnSinksDiscoveredCallback& sinks_discovered_callback) - : sinks_discovered_callback_(sinks_discovered_callback) {} + const OnSinksDiscoveredCallback& sink_discovery_callback) + : sink_discovery_callback_(sink_discovery_callback) {} MediaSinkService::~MediaSinkService() = default;
diff --git a/chrome/common/media_router/discovery/media_sink_service.h b/chrome/common/media_router/discovery/media_sink_service.h new file mode 100644 index 0000000..5e2f0bc --- /dev/null +++ b/chrome/common/media_router/discovery/media_sink_service.h
@@ -0,0 +1,47 @@ +// Copyright 2017 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_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_SERVICE_H_ +#define CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_SERVICE_H_ + +#include <memory> +#include <vector> + +#include "base/callback.h" +#include "chrome/common/media_router/discovery/media_sink_internal.h" +#include "chrome/common/media_router/media_sink.h" + +namespace media_router { + +// A service which can be used to start background discovery and resolution of +// MediaSinks. Often these are remote devices, like Chromecast. In addition, the +// service is capable of answering MediaSink queries using the sinks that it +// generated. +// This class is not thread safe. All methods must be called from the IO thread. +class MediaSinkService { + public: + // Callback to be invoked when this class finishes sink discovering. + // Arg 0: Sinks discovered and resolved by the service. + using OnSinksDiscoveredCallback = + base::Callback<void(const std::vector<MediaSinkInternal>&)>; + + explicit MediaSinkService( + const OnSinksDiscoveredCallback& sink_discovery_callback); + + virtual ~MediaSinkService(); + + // Starts sink discovery. No-ops if already started. + // Sinks discovered and resolved are continuously passed to + // |callback|. + virtual void Start() = 0; + + protected: + OnSinksDiscoveredCallback sink_discovery_callback_; + + DISALLOW_COPY_AND_ASSIGN(MediaSinkService); +}; + +} // namespace media_router + +#endif // CHROME_COMMON_MEDIA_ROUTER_DISCOVERY_MEDIA_SINK_SERVICE_H_
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 787f95b..60e5b5a 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn
@@ -3615,7 +3615,9 @@ "../browser/download/download_dir_policy_handler_unittest.cc", "../browser/lifetime/keep_alive_registry_unittest.cc", "../browser/media/router/discovery/dial/device_description_fetcher_unittest.cc", + "../browser/media/router/discovery/dial/device_description_service_unittest.cc", "../browser/media/router/discovery/dial/dial_device_data_unittest.cc", + "../browser/media/router/discovery/dial/dial_media_sink_service_unittest.cc", "../browser/media/router/discovery/dial/dial_registry_unittest.cc", "../browser/media/router/discovery/dial/dial_service_unittest.cc",