Commit 9ec27692 authored by Robert Sesek's avatar Robert Sesek Committed by Commit Bot

Reland "mac: Simplify the local_discovery ServiceWatcher and ServiceResolver."

This is a reland of f4240b29

This fixes two issues:

- A real UAF in ServiceResolverImplMac::OnResolveComplete, where the
  callback can delete |this|, so StopResolving() is now called before
  the callback.
- A test-only UAF caused by the background thread outliving the object.
  The thread is now flushed and joined. The -stop methods for both
  NetServiceBrowser and NetServiceResolver are now called in their
  respective -deallocs, in case the Stop posted task does not run.

Original change's description:
> mac: Simplify the local_discovery ServiceWatcher and ServiceResolver.
>
> This removes the inner Container classes by moving the logic into the
> ObjC classes that already exist.
>
> Bug: 1072841
> Change-Id: If22d2d90ce3235ce160a0b740337fd71353a7ef7
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2243995
> Commit-Queue: Robert Sesek <rsesek@chromium.org>
> Reviewed-by: Elly Fong-Jones <ellyjones@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#778341}

Bug: 1072841
Change-Id: Ice53eb92d6c73c75ad6f276b301eaa1841536c8d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2248076Reviewed-by: default avatarElly Fong-Jones <ellyjones@chromium.org>
Commit-Queue: Robert Sesek <rsesek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#779350}
parent 2b961a6f
...@@ -15,19 +15,15 @@ ...@@ -15,19 +15,15 @@
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h" #include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/local_discovery/service_discovery_shared_client.h" #include "chrome/browser/local_discovery/service_discovery_shared_client.h"
#include "content/public/browser/browser_thread.h"
namespace base { namespace base {
class Thread; class Thread;
} }
namespace local_discovery { @class NetServiceBrowser;
@class NetServiceResolver;
template <class T> namespace local_discovery {
class ServiceDiscoveryThreadDeleter {
public:
inline void operator()(T* t) { t->DeleteSoon(); }
};
// Implementation of ServiceDiscoveryClient that uses the Bonjour SDK. // Implementation of ServiceDiscoveryClient that uses the Bonjour SDK.
// https://developer.apple.com/library/mac/documentation/Networking/Conceptual/ // https://developer.apple.com/library/mac/documentation/Networking/Conceptual/
...@@ -37,6 +33,8 @@ class ServiceDiscoveryClientMac : public ServiceDiscoverySharedClient { ...@@ -37,6 +33,8 @@ class ServiceDiscoveryClientMac : public ServiceDiscoverySharedClient {
ServiceDiscoveryClientMac(); ServiceDiscoveryClientMac();
private: private:
friend class ServiceDiscoveryClientMacTest;
~ServiceDiscoveryClientMac() override; ~ServiceDiscoveryClientMac() override;
// ServiceDiscoveryClient implementation. // ServiceDiscoveryClient implementation.
...@@ -60,42 +58,6 @@ class ServiceDiscoveryClientMac : public ServiceDiscoverySharedClient { ...@@ -60,42 +58,6 @@ class ServiceDiscoveryClientMac : public ServiceDiscoverySharedClient {
class ServiceWatcherImplMac : public ServiceWatcher { class ServiceWatcherImplMac : public ServiceWatcher {
public: public:
class NetServiceBrowserContainer {
public:
NetServiceBrowserContainer(
const std::string& service_type,
ServiceWatcher::UpdatedCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner);
~NetServiceBrowserContainer();
void Start();
void DiscoverNewServices();
void OnServicesUpdate(ServiceWatcher::UpdateType update,
const std::string& service);
void DeleteSoon();
private:
void StartOnDiscoveryThread();
void DiscoverOnDiscoveryThread();
bool IsOnServiceDiscoveryThread() {
return base::ThreadTaskRunnerHandle::Get() ==
service_discovery_runner_.get();
}
std::string service_type_;
ServiceWatcher::UpdatedCallback callback_;
scoped_refptr<base::SingleThreadTaskRunner> callback_runner_;
scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner_;
base::scoped_nsobject<id> delegate_;
base::scoped_nsobject<NSNetServiceBrowser> browser_;
base::WeakPtrFactory<NetServiceBrowserContainer> weak_factory_;
};
ServiceWatcherImplMac( ServiceWatcherImplMac(
const std::string& service_type, const std::string& service_type,
ServiceWatcher::UpdatedCallback callback, ServiceWatcher::UpdatedCallback callback,
...@@ -112,57 +74,28 @@ class ServiceWatcherImplMac : public ServiceWatcher { ...@@ -112,57 +74,28 @@ class ServiceWatcherImplMac : public ServiceWatcher {
void SetActivelyRefreshServices(bool actively_refresh_services) override; void SetActivelyRefreshServices(bool actively_refresh_services) override;
std::string GetServiceType() const override; std::string GetServiceType() const override;
std::string service_type_; void StartOnDiscoveryThread(
ServiceWatcher::UpdatedCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> callback_runner);
void DiscoverOnDiscoveryThread();
// These members should only be accessed on the object creator's sequence.
const std::string service_type_;
ServiceWatcher::UpdatedCallback callback_; ServiceWatcher::UpdatedCallback callback_;
bool started_; bool started_ = false;
scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner_;
// |browser_| lives on the |service_discovery_runner_|. It is released
// by move()ing it to StopServiceBrowser().
base::scoped_nsobject<NetServiceBrowser> browser_;
std::unique_ptr<NetServiceBrowserContainer, base::WeakPtrFactory<ServiceWatcherImplMac> weak_factory_{this};
ServiceDiscoveryThreadDeleter<NetServiceBrowserContainer>>
container_;
base::WeakPtrFactory<ServiceWatcherImplMac> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(ServiceWatcherImplMac); DISALLOW_COPY_AND_ASSIGN(ServiceWatcherImplMac);
}; };
class ServiceResolverImplMac : public ServiceResolver { class ServiceResolverImplMac : public ServiceResolver {
public: public:
class NetServiceContainer {
public:
NetServiceContainer(
const std::string& service_name,
ServiceResolver::ResolveCompleteCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner);
virtual ~NetServiceContainer();
void StartResolving();
void OnResolveUpdate(RequestStatus);
void SetServiceForTesting(base::scoped_nsobject<NSNetService> service);
void DeleteSoon();
private:
void StartResolvingOnDiscoveryThread();
bool IsOnServiceDiscoveryThread() {
return base::ThreadTaskRunnerHandle::Get() ==
service_discovery_runner_.get();
}
const std::string service_name_;
ServiceResolver::ResolveCompleteCallback callback_;
scoped_refptr<base::SingleThreadTaskRunner> callback_runner_;
scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner_;
base::scoped_nsobject<id> delegate_;
base::scoped_nsobject<NSNetService> service_;
ServiceDescription service_description_;
base::WeakPtrFactory<NetServiceContainer> weak_factory_;
};
ServiceResolverImplMac( ServiceResolverImplMac(
const std::string& service_name, const std::string& service_name,
ServiceResolver::ResolveCompleteCallback callback, ServiceResolver::ResolveCompleteCallback callback,
...@@ -170,29 +103,37 @@ class ServiceResolverImplMac : public ServiceResolver { ...@@ -170,29 +103,37 @@ class ServiceResolverImplMac : public ServiceResolver {
~ServiceResolverImplMac() override; ~ServiceResolverImplMac() override;
// Testing methods.
NetServiceContainer* GetContainerForTesting();
private: private:
void StartResolving() override; void StartResolving() override;
std::string GetName() const override; std::string GetName() const override;
void OnResolveComplete(RequestStatus status, void OnResolveComplete(RequestStatus status,
const ServiceDescription& description); const ServiceDescription& description);
void StartResolvingOnDiscoveryThread(
ServiceResolver::ResolveCompleteCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> callback_runner);
void StopResolving();
// These members should only be accessed on the object creator's sequence.
const std::string service_name_; const std::string service_name_;
ServiceResolver::ResolveCompleteCallback callback_; ServiceResolver::ResolveCompleteCallback callback_;
bool has_resolved_; bool has_resolved_ = false;
std::unique_ptr<NetServiceContainer, scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner_;
ServiceDiscoveryThreadDeleter<NetServiceContainer>> // |resolver_| lives on the |service_discovery_runner_|. It is released
container_; // by move()ing it to StopServiceResolver().
base::WeakPtrFactory<ServiceResolverImplMac> weak_factory_; base::scoped_nsobject<NetServiceResolver> resolver_;
base::WeakPtrFactory<ServiceResolverImplMac> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(ServiceResolverImplMac); DISALLOW_COPY_AND_ASSIGN(ServiceResolverImplMac);
}; };
// Parses the data out of the |service|, updating the |description| with the
// results.
void ParseNetService(NSNetService* service, ServiceDescription& description);
} // namespace local_discovery } // namespace local_discovery
#endif // CHROME_BROWSER_LOCAL_DISCOVERY_SERVICE_DISCOVERY_CLIENT_MAC_H_ #endif // CHROME_BROWSER_LOCAL_DISCOVERY_SERVICE_DISCOVERY_CLIENT_MAC_H_
...@@ -11,10 +11,8 @@ ...@@ -11,10 +11,8 @@
#include <stdint.h> #include <stdint.h>
#include "base/bind.h" #include "base/bind.h"
#include "base/debug/dump_without_crashing.h" #include "base/mac/foundation_util.h"
#include "base/memory/singleton.h"
#include "base/message_loop/message_pump_type.h" #include "base/message_loop/message_pump_type.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "base/strings/sys_string_conversions.h" #include "base/strings/sys_string_conversions.h"
#include "base/threading/thread.h" #include "base/threading/thread.h"
...@@ -22,30 +20,39 @@ ...@@ -22,30 +20,39 @@
#include "net/base/ip_address.h" #include "net/base/ip_address.h"
#include "net/base/ip_endpoint.h" #include "net/base/ip_endpoint.h"
using local_discovery::ServiceWatcherImplMac; using local_discovery::ServiceWatcher;
using local_discovery::ServiceResolverImplMac; using local_discovery::ServiceResolver;
using local_discovery::ServiceDescription;
@interface NetServiceBrowserDelegate
: NSObject<NSNetServiceBrowserDelegate, NSNetServiceDelegate> { @interface NetServiceBrowser
@private : NSObject <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
ServiceWatcherImplMac::NetServiceBrowserContainer* _container; // weak. // Creates a new NSNetServiceBrowser and starts listening for discovery
base::scoped_nsobject<NSMutableArray> _services; // notifications. Calls the |callback| on the |callbackRunner| when
} // changes are detected.
- (instancetype)initWithServiceType:(const std::string&)serviceType
- (id)initWithContainer: callback:(ServiceWatcher::UpdatedCallback)callback
(ServiceWatcherImplMac::NetServiceBrowserContainer*)serviceWatcherImpl; callbackRunner:
- (void)clearDiscoveredServices; (scoped_refptr<base::SingleThreadTaskRunner>)
callbackRunner;
// Forces a new scan for services.
- (void)discoverServices;
// Stops listening for discovery notifications.
- (void)stop;
@end @end
@interface NetServiceDelegate : NSObject <NSNetServiceDelegate> { @interface NetServiceResolver : NSObject <NSNetServiceDelegate>
@private // Begins resolving the local service named |name|. Calls the |callback|
ServiceResolverImplMac::NetServiceContainer* _container; // on the |callbackRunner| when done or an error occurs.
} - (instancetype)
initWithServiceName:(const std::string&)name
- (id)initWithContainer: resolvedCallback:(ServiceResolver::ResolveCompleteCallback)callback
(ServiceResolverImplMac::NetServiceContainer*)serviceResolverImpl; callbackRunner:
(scoped_refptr<base::SingleThreadTaskRunner>)callbackRunner;
// Stops any in-flight resolve operation.
- (void)stop;
@end @end
namespace local_discovery { namespace local_discovery {
...@@ -56,6 +63,16 @@ const char kServiceDiscoveryThreadName[] = "Service Discovery Thread"; ...@@ -56,6 +63,16 @@ const char kServiceDiscoveryThreadName[] = "Service Discovery Thread";
const NSTimeInterval kResolveTimeout = 10.0; const NSTimeInterval kResolveTimeout = 10.0;
// Takes ownership of |browser| and tells it to stop.
void StopServiceBrowser(base::scoped_nsobject<NetServiceBrowser> browser) {
[browser stop];
}
// Takes ownership of |resolver| and tells it to stop.
void StopServiceResolver(base::scoped_nsobject<NetServiceResolver> resolver) {
[resolver stop];
}
// Extracts the instance name, name type and domain from a full service name or // Extracts the instance name, name type and domain from a full service name or
// the service type and domain from a service type. Returns true if successful. // the service type and domain from a service type. Returns true if successful.
// TODO(justinlin): This current only handles service names with format // TODO(justinlin): This current only handles service names with format
...@@ -133,8 +150,8 @@ void ParseTxtRecord(NSData* record, std::vector<std::string>* output) { ...@@ -133,8 +150,8 @@ void ParseTxtRecord(NSData* record, std::vector<std::string>* output) {
} // namespace } // namespace
ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {} ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() = default;
ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {} ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() = default;
std::unique_ptr<ServiceWatcher> ServiceDiscoveryClientMac::CreateServiceWatcher( std::unique_ptr<ServiceWatcher> ServiceDiscoveryClientMac::CreateServiceWatcher(
const std::string& service_type, const std::string& service_type,
...@@ -177,80 +194,7 @@ void ServiceDiscoveryClientMac::StartThreadIfNotStarted() { ...@@ -177,80 +194,7 @@ void ServiceDiscoveryClientMac::StartThreadIfNotStarted() {
} }
} }
ServiceWatcherImplMac::NetServiceBrowserContainer::NetServiceBrowserContainer( // Service Watcher /////////////////////////////////////////////////////////////
const std::string& service_type,
ServiceWatcher::UpdatedCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
: service_type_(service_type),
callback_(std::move(callback)),
callback_runner_(base::ThreadTaskRunnerHandle::Get()),
service_discovery_runner_(service_discovery_runner),
weak_factory_(this) {}
ServiceWatcherImplMac::NetServiceBrowserContainer::
~NetServiceBrowserContainer() {
DCHECK(IsOnServiceDiscoveryThread());
// Work around a 10.12 bug: NSNetServiceBrowser doesn't lose interest in its
// weak delegate during deallocation, so a subsequently-deallocated delegate
// attempts to clear the pointer to itself in an NSNetServiceBrowser that's
// already gone.
// https://crbug.com/657495, https://openradar.appspot.com/28943305
[browser_ setDelegate:nil];
// Ensure the delegate clears all references to itself, which it had added as
// discovered services were reported to it.
[delegate_ clearDiscoveredServices];
}
void ServiceWatcherImplMac::NetServiceBrowserContainer::Start() {
service_discovery_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NetServiceBrowserContainer::StartOnDiscoveryThread,
weak_factory_.GetWeakPtr()));
}
void ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverNewServices() {
service_discovery_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NetServiceBrowserContainer::DiscoverOnDiscoveryThread,
weak_factory_.GetWeakPtr()));
}
void
ServiceWatcherImplMac::NetServiceBrowserContainer::StartOnDiscoveryThread() {
DCHECK(IsOnServiceDiscoveryThread());
delegate_.reset([[NetServiceBrowserDelegate alloc] initWithContainer:this]);
browser_.reset([[NSNetServiceBrowser alloc] init]);
[browser_ setDelegate:delegate_];
}
void
ServiceWatcherImplMac::NetServiceBrowserContainer::DiscoverOnDiscoveryThread() {
DCHECK(IsOnServiceDiscoveryThread());
base::scoped_nsobject<NSString> instance, type, domain;
if (!ExtractServiceInfo(service_type_, false, &instance, &type, &domain))
return;
DCHECK(![instance length]);
DVLOG(1) << "Listening for service type '" << [type UTF8String]
<< "' on domain '" << [domain UTF8String] << "'";
[browser_ searchForServicesOfType:type inDomain:domain];
}
void ServiceWatcherImplMac::NetServiceBrowserContainer::OnServicesUpdate(
ServiceWatcher::UpdateType update,
const std::string& service) {
callback_runner_->PostTask(FROM_HERE,
base::BindOnce(callback_, update, service));
}
void ServiceWatcherImplMac::NetServiceBrowserContainer::DeleteSoon() {
service_discovery_runner_->DeleteSoon(FROM_HERE, this);
}
ServiceWatcherImplMac::ServiceWatcherImplMac( ServiceWatcherImplMac::ServiceWatcherImplMac(
const std::string& service_type, const std::string& service_type,
...@@ -258,28 +202,33 @@ ServiceWatcherImplMac::ServiceWatcherImplMac( ...@@ -258,28 +202,33 @@ ServiceWatcherImplMac::ServiceWatcherImplMac(
scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner) scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
: service_type_(service_type), : service_type_(service_type),
callback_(std::move(callback)), callback_(std::move(callback)),
started_(false), service_discovery_runner_(service_discovery_runner) {}
weak_factory_(this) {
container_.reset(new NetServiceBrowserContainer(
service_type,
base::BindRepeating(&ServiceWatcherImplMac::OnServicesUpdate,
weak_factory_.GetWeakPtr()),
service_discovery_runner));
}
ServiceWatcherImplMac::~ServiceWatcherImplMac() {} ServiceWatcherImplMac::~ServiceWatcherImplMac() {
service_discovery_runner_->PostTask(
FROM_HERE, base::BindOnce(&StopServiceBrowser, std::move(browser_)));
}
void ServiceWatcherImplMac::Start() { void ServiceWatcherImplMac::Start() {
DCHECK(!started_); DCHECK(!started_);
VLOG(1) << "ServiceWatcherImplMac::Start"; VLOG(1) << "ServiceWatcherImplMac::Start";
container_->Start(); service_discovery_runner_->PostTask(
FROM_HERE, base::BindOnce(&ServiceWatcherImplMac::StartOnDiscoveryThread,
base::Unretained(this),
base::BindRepeating(
&ServiceWatcherImplMac::OnServicesUpdate,
weak_factory_.GetWeakPtr()),
base::ThreadTaskRunnerHandle::Get()));
started_ = true; started_ = true;
} }
void ServiceWatcherImplMac::DiscoverNewServices() { void ServiceWatcherImplMac::DiscoverNewServices() {
DCHECK(started_); DCHECK(started_);
VLOG(1) << "ServiceWatcherImplMac::DiscoverNewServices"; VLOG(1) << "ServiceWatcherImplMac::DiscoverNewServices";
container_->DiscoverNewServices(); service_discovery_runner_->PostTask(
FROM_HERE,
base::BindOnce(&ServiceWatcherImplMac::DiscoverOnDiscoveryThread,
base::Unretained(this)));
} }
void ServiceWatcherImplMac::SetActivelyRefreshServices( void ServiceWatcherImplMac::SetActivelyRefreshServices(
...@@ -299,177 +248,166 @@ void ServiceWatcherImplMac::OnServicesUpdate(ServiceWatcher::UpdateType update, ...@@ -299,177 +248,166 @@ void ServiceWatcherImplMac::OnServicesUpdate(ServiceWatcher::UpdateType update,
callback_.Run(update, service + "." + service_type_); callback_.Run(update, service + "." + service_type_);
} }
ServiceResolverImplMac::NetServiceContainer::NetServiceContainer( void ServiceWatcherImplMac::StartOnDiscoveryThread(
ServiceWatcher::UpdatedCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> callback_runner) {
DCHECK(service_discovery_runner_->RunsTasksInCurrentSequence());
browser_.reset([[NetServiceBrowser alloc]
initWithServiceType:service_type_
callback:std::move(callback)
callbackRunner:callback_runner]);
}
void ServiceWatcherImplMac::DiscoverOnDiscoveryThread() {
DCHECK(service_discovery_runner_->RunsTasksInCurrentSequence());
[browser_ discoverServices];
}
// Service Resolver ////////////////////////////////////////////////////////////
ServiceResolverImplMac::ServiceResolverImplMac(
const std::string& service_name, const std::string& service_name,
ServiceResolver::ResolveCompleteCallback callback, ServiceResolver::ResolveCompleteCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner) scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
: service_name_(service_name), : service_name_(service_name),
callback_(std::move(callback)), callback_(std::move(callback)),
callback_runner_(base::ThreadTaskRunnerHandle::Get()), service_discovery_runner_(service_discovery_runner) {}
service_discovery_runner_(service_discovery_runner),
weak_factory_(this) {}
ServiceResolverImplMac::NetServiceContainer::~NetServiceContainer() {
DCHECK(IsOnServiceDiscoveryThread());
// Work around a 10.12 bug: NSNetService doesn't lose interest in its weak ServiceResolverImplMac::~ServiceResolverImplMac() {
// delegate during deallocation, so a subsequently-deallocated delegate StopResolving();
// attempts to clear the pointer to itself in an NSNetService that's already
// gone.
// https://crbug.com/657495, https://openradar.appspot.com/28943305
[service_ setDelegate:nil];
} }
void ServiceResolverImplMac::NetServiceContainer::StartResolving() { void ServiceResolverImplMac::StartResolving() {
VLOG(1) << "Resolving service " << service_name_;
service_discovery_runner_->PostTask( service_discovery_runner_->PostTask(
FROM_HERE, FROM_HERE,
base::BindOnce(&NetServiceContainer::StartResolvingOnDiscoveryThread, base::BindOnce(&ServiceResolverImplMac::StartResolvingOnDiscoveryThread,
weak_factory_.GetWeakPtr())); base::Unretained(this),
base::BindOnce(&ServiceResolverImplMac::OnResolveComplete,
weak_factory_.GetWeakPtr()),
base::ThreadTaskRunnerHandle::Get()));
} }
void ServiceResolverImplMac::NetServiceContainer::DeleteSoon() { std::string ServiceResolverImplMac::GetName() const {
service_discovery_runner_->DeleteSoon(FROM_HERE, this); return service_name_;
} }
void void ServiceResolverImplMac::OnResolveComplete(
ServiceResolverImplMac::NetServiceContainer::StartResolvingOnDiscoveryThread() { RequestStatus status,
DCHECK(IsOnServiceDiscoveryThread()); const ServiceDescription& description) {
base::scoped_nsobject<NSString> instance, type, domain; VLOG(1) << "ServiceResolverImplMac::OnResolveComplete: " << service_name_
<< ", " << status;
// The service object is set ahead of time by tests.
if (service_)
return;
if (!ExtractServiceInfo(service_name_, true, &instance, &type, &domain))
return OnResolveUpdate(ServiceResolver::STATUS_KNOWN_NONEXISTENT);
VLOG(1) << "ServiceResolverImplMac::ServiceResolverImplMac::" has_resolved_ = true;
<< "StartResolvingOnDiscoveryThread: Success";
delegate_.reset([[NetServiceDelegate alloc] initWithContainer:this]);
service_.reset(
[[NSNetService alloc] initWithDomain:domain type:type name:instance]);
[service_ setDelegate:delegate_];
[service_ resolveWithTimeout:kResolveTimeout]; StopResolving();
VLOG(1) << "ServiceResolverImplMac::NetServiceContainer::" // The |callback_| can delete this.
<< "StartResolvingOnDiscoveryThread: " << service_name_ if (!callback_.is_null())
<< ", instance: " << [instance UTF8String] std::move(callback_).Run(status, description);
<< ", type: " << [type UTF8String]
<< ", domain: " << [domain UTF8String];
} }
void ServiceResolverImplMac::NetServiceContainer::OnResolveUpdate( void ServiceResolverImplMac::StartResolvingOnDiscoveryThread(
RequestStatus status) { ServiceResolver::ResolveCompleteCallback callback,
if (callback_.is_null()) scoped_refptr<base::SingleThreadTaskRunner> callback_runner) {
return; DCHECK(service_discovery_runner_->RunsTasksInCurrentSequence());
if (status != STATUS_SUCCESS) { resolver_.reset([[NetServiceResolver alloc]
callback_runner_->PostTask( initWithServiceName:service_name_
FROM_HERE, resolvedCallback:std::move(callback)
base::BindOnce(std::move(callback_), status, ServiceDescription())); callbackRunner:callback_runner]);
return; }
}
service_description_.service_name = service_name_; void ServiceResolverImplMac::StopResolving() {
service_discovery_runner_->PostTask(
FROM_HERE, base::BindOnce(&StopServiceResolver, std::move(resolver_)));
}
for (NSData* address in [service_ addresses]) { void ParseNetService(NSNetService* service, ServiceDescription& description) {
for (NSData* address in [service addresses]) {
const void* bytes = [address bytes]; const void* bytes = [address bytes];
int length = [address length]; int length = [address length];
const sockaddr* socket = static_cast<const sockaddr*>(bytes); const sockaddr* socket = static_cast<const sockaddr*>(bytes);
net::IPEndPoint end_point; net::IPEndPoint end_point;
if (end_point.FromSockAddr(socket, length)) { if (end_point.FromSockAddr(socket, length)) {
service_description_.address = description.address = net::HostPortPair::FromIPEndPoint(end_point);
net::HostPortPair::FromIPEndPoint(end_point); description.ip_address = end_point.address();
service_description_.ip_address = end_point.address();
break; break;
} }
} }
if (service_description_.address.host().empty()) { ParseTxtRecord([service TXTRecordData], &description.metadata);
VLOG(1) << "Service IP is not resolved: " << service_name_;
callback_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback_), STATUS_KNOWN_NONEXISTENT,
ServiceDescription()));
return;
}
ParseTxtRecord([service_ TXTRecordData], &service_description_.metadata);
// TODO(justinlin): Implement last_seen.
service_description_.last_seen = base::Time::Now();
callback_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback_), status, service_description_));
} }
void ServiceResolverImplMac::NetServiceContainer::SetServiceForTesting( } // namespace local_discovery
base::scoped_nsobject<NSNetService> service) {
service_ = service;
}
ServiceResolverImplMac::ServiceResolverImplMac( // Service Watcher /////////////////////////////////////////////////////////////
const std::string& service_name,
ServiceResolver::ResolveCompleteCallback callback,
scoped_refptr<base::SingleThreadTaskRunner> service_discovery_runner)
: service_name_(service_name),
callback_(std::move(callback)),
has_resolved_(false),
weak_factory_(this) {
container_.reset(new NetServiceContainer(
service_name,
base::BindOnce(&ServiceResolverImplMac::OnResolveComplete,
weak_factory_.GetWeakPtr()),
service_discovery_runner));
}
ServiceResolverImplMac::~ServiceResolverImplMac() {} @implementation NetServiceBrowser {
std::string _serviceType;
void ServiceResolverImplMac::StartResolving() { ServiceWatcher::UpdatedCallback _callback;
container_->StartResolving(); scoped_refptr<base::SingleThreadTaskRunner> _callbackRunner;
VLOG(1) << "Resolving service " << service_name_; base::scoped_nsobject<NSNetServiceBrowser> _browser;
base::scoped_nsobject<NSMutableArray<NSNetService*>> _services;
} }
std::string ServiceResolverImplMac::GetName() const { return service_name_; } - (instancetype)initWithServiceType:(const std::string&)serviceType
callback:(ServiceWatcher::UpdatedCallback)callback
void ServiceResolverImplMac::OnResolveComplete( callbackRunner:
RequestStatus status, (scoped_refptr<base::SingleThreadTaskRunner>)
const ServiceDescription& description) { callbackRunner {
VLOG(1) << "ServiceResolverImplMac::OnResolveComplete: " << service_name_ if ((self = [super init])) {
<< ", " << status; _serviceType = serviceType;
has_resolved_ = true; _callback = std::move(callback);
_callbackRunner = callbackRunner;
if (!callback_.is_null()) _services.reset([[NSMutableArray alloc] initWithCapacity:1]);
std::move(callback_).Run(status, description); _browser.reset([[NSNetServiceBrowser alloc] init]);
[_browser setDelegate:self];
}
return self;
} }
ServiceResolverImplMac::NetServiceContainer* - (void)dealloc {
ServiceResolverImplMac::GetContainerForTesting() { [self stop];
return container_.get(); [super dealloc];
} }
} // namespace local_discovery - (void)discoverServices {
base::scoped_nsobject<NSString> instance, type, domain;
if (!local_discovery::ExtractServiceInfo(_serviceType, false, &instance,
&type, &domain)) {
return;
}
@implementation NetServiceBrowserDelegate DCHECK(![instance length]);
DVLOG(1) << "Listening for service type '" << type << "' on domain '"
<< domain << "'";
- (id)initWithContainer: [_browser searchForServicesOfType:type inDomain:domain];
(ServiceWatcherImplMac::NetServiceBrowserContainer*)container {
if ((self = [super init])) {
_container = container;
_services.reset([[NSMutableArray alloc] initWithCapacity:1]);
}
return self;
} }
- (void)clearDiscoveredServices { - (void)stop {
// Work around a 10.12 bug: NSNetServiceBrowser doesn't lose interest in its
// weak delegate during deallocation, so a subsequently-deallocated delegate
// attempts to clear the pointer to itself in an NSNetServiceBrowser that's
// already gone.
// https://crbug.com/657495, https://openradar.appspot.com/28943305
[_browser setDelegate:nil];
// Ensure the delegate clears all references to itself, which it had added as
// discovered services were reported to it.
for (NSNetService* netService in _services.get()) { for (NSNetService* netService in _services.get()) {
[netService stopMonitoring]; [netService stopMonitoring];
[netService setDelegate:nil]; [netService setDelegate:nil];
} }
[_services removeAllObjects]; [_services removeAllObjects];
_browser.reset();
} }
- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser - (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser
...@@ -480,8 +418,9 @@ ServiceResolverImplMac::GetContainerForTesting() { ...@@ -480,8 +418,9 @@ ServiceResolverImplMac::GetContainerForTesting() {
[netService startMonitoring]; [netService startMonitoring];
[_services addObject:netService]; [_services addObject:netService];
_container->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_ADDED, _callbackRunner->PostTask(
base::SysNSStringToUTF8([netService name])); FROM_HERE, base::BindOnce(_callback, ServiceWatcher::UPDATE_ADDED,
base::SysNSStringToUTF8([netService name])));
} }
- (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser - (void)netServiceBrowser:(NSNetServiceBrowser*)netServiceBrowser
...@@ -489,9 +428,9 @@ ServiceResolverImplMac::GetContainerForTesting() { ...@@ -489,9 +428,9 @@ ServiceResolverImplMac::GetContainerForTesting() {
moreComing:(BOOL)moreServicesComing { moreComing:(BOOL)moreServicesComing {
NSUInteger index = [_services indexOfObject:netService]; NSUInteger index = [_services indexOfObject:netService];
if (index != NSNotFound) { if (index != NSNotFound) {
_container->OnServicesUpdate( _callbackRunner->PostTask(
local_discovery::ServiceWatcher::UPDATE_REMOVED, FROM_HERE, base::BindOnce(_callback, ServiceWatcher::UPDATE_REMOVED,
base::SysNSStringToUTF8([netService name])); base::SysNSStringToUTF8([netService name])));
// Stop monitoring this service for updates. // Stop monitoring this service for updates.
DCHECK_EQ(netService, [_services objectAtIndex:index]); DCHECK_EQ(netService, [_services objectAtIndex:index]);
...@@ -503,30 +442,108 @@ ServiceResolverImplMac::GetContainerForTesting() { ...@@ -503,30 +442,108 @@ ServiceResolverImplMac::GetContainerForTesting() {
- (void)netService:(NSNetService*)sender - (void)netService:(NSNetService*)sender
didUpdateTXTRecordData:(NSData*)data { didUpdateTXTRecordData:(NSData*)data {
_container->OnServicesUpdate(local_discovery::ServiceWatcher::UPDATE_CHANGED, _callbackRunner->PostTask(
base::SysNSStringToUTF8([sender name])); FROM_HERE, base::BindOnce(_callback, ServiceWatcher::UPDATE_CHANGED,
base::SysNSStringToUTF8([sender name])));
} }
@end @end
@implementation NetServiceDelegate // Service Resolver ////////////////////////////////////////////////////////////
@implementation NetServiceResolver {
std::string _serviceName;
ServiceResolver::ResolveCompleteCallback _callback;
scoped_refptr<base::SingleThreadTaskRunner> _callbackRunner;
ServiceDescription _serviceDescription;
base::scoped_nsobject<NSNetService> _service;
}
- (id)initWithContainer: - (instancetype)
(ServiceResolverImplMac::NetServiceContainer*)container { initWithServiceName:(const std::string&)serviceName
resolvedCallback:(ServiceResolver::ResolveCompleteCallback)callback
callbackRunner:
(scoped_refptr<base::SingleThreadTaskRunner>)callbackRunner {
if ((self = [super init])) { if ((self = [super init])) {
_container = container; _serviceName = serviceName;
_callback = std::move(callback);
_callbackRunner = callbackRunner;
base::scoped_nsobject<NSString> instance, type, domain;
if (!local_discovery::ExtractServiceInfo(_serviceName, true, &instance,
&type, &domain)) {
[self updateServiceDescription:ServiceResolver::STATUS_KNOWN_NONEXISTENT];
return self;
}
VLOG(1) << "ServiceResolverImplMac::"
<< "StartResolvingOnDiscoveryThread: " << serviceName
<< ", instance: " << instance << ", type: " << type
<< ", domain: " << domain;
_service.reset([[NSNetService alloc] initWithDomain:domain
type:type
name:instance]);
[_service setDelegate:self];
[_service resolveWithTimeout:local_discovery::kResolveTimeout];
} }
return self; return self;
} }
- (void)dealloc {
[self stop];
[super dealloc];
}
- (void)stop {
// Work around a 10.12 bug: NSNetService doesn't lose interest in its weak
// delegate during deallocation, so a subsequently-deallocated delegate
// attempts to clear the pointer to itself in an NSNetService that's already
// gone.
// https://crbug.com/657495, https://openradar.appspot.com/28943305
[_service setDelegate:nil];
_service.reset();
}
- (void)netServiceDidResolveAddress:(NSNetService*)sender { - (void)netServiceDidResolveAddress:(NSNetService*)sender {
_container->OnResolveUpdate(local_discovery::ServiceResolver::STATUS_SUCCESS); [self updateServiceDescription:ServiceResolver::STATUS_SUCCESS];
} }
- (void)netService:(NSNetService*)sender - (void)netService:(NSNetService*)sender
didNotResolve:(NSDictionary*)errorDict { didNotResolve:(NSDictionary*)errorDict {
_container->OnResolveUpdate( [self updateServiceDescription:ServiceResolver::STATUS_REQUEST_TIMEOUT];
local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT); }
- (void)updateServiceDescription:(ServiceResolver::RequestStatus)status {
if (_callback.is_null())
return;
if (status != ServiceResolver::STATUS_SUCCESS) {
_callbackRunner->PostTask(
FROM_HERE,
base::BindOnce(std::move(_callback), status, ServiceDescription()));
return;
}
_serviceDescription.service_name = _serviceName;
ParseNetService(_service.get(), _serviceDescription);
if (_serviceDescription.address.host().empty()) {
VLOG(1) << "Service IP is not resolved: " << _serviceName;
_callbackRunner->PostTask(
FROM_HERE, base::BindOnce(std::move(_callback),
ServiceResolver::STATUS_KNOWN_NONEXISTENT,
ServiceDescription()));
return;
}
// TODO(justinlin): Implement last_seen.
_serviceDescription.last_seen = base::Time::Now();
_callbackRunner->PostTask(
FROM_HERE,
base::BindOnce(std::move(_callback), status, _serviceDescription));
} }
@end @end
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include "base/mac/scoped_nsobject.h" #include "base/mac/scoped_nsobject.h"
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/threading/thread.h"
#include "chrome/browser/local_discovery/service_discovery_client.h" #include "chrome/browser/local_discovery/service_discovery_client.h"
#include "chrome/browser/local_discovery/service_discovery_client_mac.h" #include "chrome/browser/local_discovery/service_discovery_client_mac.h"
#import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h" #import "chrome/browser/ui/cocoa/test/cocoa_test_helper.h"
...@@ -76,6 +77,11 @@ class ServiceDiscoveryClientMacTest : public CocoaTest { ...@@ -76,6 +77,11 @@ class ServiceDiscoveryClientMacTest : public CocoaTest {
num_resolves_++; num_resolves_++;
} }
void StopDiscoveryThread() {
client_->service_discovery_thread_->FlushForTesting();
client_->service_discovery_thread_.reset();
}
ServiceDiscoveryClient* client() { return client_.get(); } ServiceDiscoveryClient* client() { return client_.get(); }
protected: protected:
...@@ -113,15 +119,13 @@ TEST_F(ServiceDiscoveryClientMacTest, ServiceWatcher) { ...@@ -113,15 +119,13 @@ TEST_F(ServiceDiscoveryClientMacTest, ServiceWatcher) {
ServiceWatcher::UPDATE_REMOVED, test_service_name); ServiceWatcher::UPDATE_REMOVED, test_service_name);
EXPECT_EQ(last_service_name_, test_service_name + "." + test_service_type); EXPECT_EQ(last_service_name_, test_service_name + "." + test_service_type);
EXPECT_EQ(num_updates_, 3); EXPECT_EQ(num_updates_, 3);
}
TEST_F(ServiceDiscoveryClientMacTest, ServiceResolver) { // Explicitly flush and stop the thread that |watcher| is using before
const std::string test_service_name = "Test.123._testing._tcp.local"; // |watcher| goes out of scope.
std::unique_ptr<ServiceResolver> resolver = client()->CreateServiceResolver( StopDiscoveryThread();
test_service_name, }
base::BindOnce(&ServiceDiscoveryClientMacTest::OnResolveComplete,
base::Unretained(this)));
TEST_F(ServiceDiscoveryClientMacTest, ParseServiceRecord) {
const uint8_t record_bytes[] = {2, 'a', 'b', 3, 'd', '=', 'e'}; const uint8_t record_bytes[] = {2, 'a', 'b', 3, 'd', '=', 'e'};
base::scoped_nsobject<TestNSNetService> test_service([[TestNSNetService alloc] base::scoped_nsobject<TestNSNetService> test_service([[TestNSNetService alloc]
initWithData:[NSData dataWithBytes:record_bytes initWithData:[NSData dataWithBytes:record_bytes
...@@ -139,38 +143,21 @@ TEST_F(ServiceDiscoveryClientMacTest, ServiceResolver) { ...@@ -139,38 +143,21 @@ TEST_F(ServiceDiscoveryClientMacTest, ServiceResolver) {
NSArray* addresses = @[ discoveryHost ]; NSArray* addresses = @[ discoveryHost ];
[test_service setAddresses:addresses]; [test_service setAddresses:addresses];
ServiceResolverImplMac* resolver_impl = ServiceDescription description;
static_cast<ServiceResolverImplMac*>(resolver.get()); ParseNetService(test_service.get(), description);
resolver_impl->GetContainerForTesting()->SetServiceForTesting(
base::scoped_nsobject<NSNetService>(test_service));
resolver->StartResolving();
resolver_impl->GetContainerForTesting()->OnResolveUpdate(
ServiceResolver::STATUS_SUCCESS);
base::RunLoop().RunUntilIdle(); const std::vector<std::string>& metadata = description.metadata;
EXPECT_EQ(1, num_resolves_);
const std::vector<std::string>& metadata =
last_service_description_.metadata;
EXPECT_EQ(2u, metadata.size()); EXPECT_EQ(2u, metadata.size());
EXPECT_TRUE(base::Contains(metadata, "ab")); EXPECT_TRUE(base::Contains(metadata, "ab"));
EXPECT_TRUE(base::Contains(metadata, "d=e")); EXPECT_TRUE(base::Contains(metadata, "d=e"));
EXPECT_EQ(ip_address, last_service_description_.ip_address); EXPECT_EQ(ip_address, description.ip_address);
EXPECT_EQ(kPort, last_service_description_.address.port()); EXPECT_EQ(kPort, description.address.port());
EXPECT_EQ(kIp, last_service_description_.address.host()); EXPECT_EQ(kIp, description.address.host());
} }
// https://crbug.com/586628 // https://crbug.com/586628
TEST_F(ServiceDiscoveryClientMacTest, ResolveInvalidUnicodeRecord) { TEST_F(ServiceDiscoveryClientMacTest, ParseInvalidUnicodeRecord) {
const std::string test_service_name = "Test.123._testing._tcp.local";
std::unique_ptr<ServiceResolver> resolver = client()->CreateServiceResolver(
test_service_name,
base::BindOnce(&ServiceDiscoveryClientMacTest::OnResolveComplete,
base::Unretained(this)));
const uint8_t record_bytes[] = { const uint8_t record_bytes[] = {
3, 'a', '=', 'b', 3, 'a', '=', 'b',
// The bytes after name= are the UTF-8 encoded representation of // The bytes after name= are the UTF-8 encoded representation of
...@@ -194,28 +181,17 @@ TEST_F(ServiceDiscoveryClientMacTest, ResolveInvalidUnicodeRecord) { ...@@ -194,28 +181,17 @@ TEST_F(ServiceDiscoveryClientMacTest, ResolveInvalidUnicodeRecord) {
NSArray* addresses = @[ discovery_host ]; NSArray* addresses = @[ discovery_host ];
[test_service setAddresses:addresses]; [test_service setAddresses:addresses];
ServiceResolverImplMac* resolver_impl = ServiceDescription description;
static_cast<ServiceResolverImplMac*>(resolver.get()); ParseNetService(test_service.get(), description);
resolver_impl->GetContainerForTesting()->SetServiceForTesting(
base::scoped_nsobject<NSNetService>(test_service));
resolver->StartResolving();
resolver_impl->GetContainerForTesting()->OnResolveUpdate(
ServiceResolver::STATUS_SUCCESS);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, num_resolves_);
const std::vector<std::string>& metadata = const std::vector<std::string>& metadata = description.metadata;
last_service_description_.metadata;
EXPECT_EQ(2u, metadata.size()); EXPECT_EQ(2u, metadata.size());
EXPECT_TRUE(base::Contains(metadata, "a=b")); EXPECT_TRUE(base::Contains(metadata, "a=b"));
EXPECT_TRUE(base::Contains(metadata, "cd=e9")); EXPECT_TRUE(base::Contains(metadata, "cd=e9"));
EXPECT_EQ(ip_address, last_service_description_.ip_address); EXPECT_EQ(ip_address, description.ip_address);
EXPECT_EQ(kPort, last_service_description_.address.port()); EXPECT_EQ(kPort, description.address.port());
EXPECT_EQ(kIp, last_service_description_.address.host()); EXPECT_EQ(kIp, description.address.host());
} }
TEST_F(ServiceDiscoveryClientMacTest, ResolveInvalidServiceName) { TEST_F(ServiceDiscoveryClientMacTest, ResolveInvalidServiceName) {
......
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