1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/local_discovery/service_discovery_client_mac.h"
7 #import <Foundation/Foundation.h>
11 #include "base/memory/singleton.h"
13 using local_discovery::ServiceWatcherImplMac;
14 using local_discovery::ServiceResolverImplMac;
16 @interface NetServiceBrowserDelegate
17 : NSObject<NSNetServiceBrowserDelegate, NSNetServiceDelegate> {
19 ServiceWatcherImplMac* serviceWatcherImpl_; // weak.
20 base::scoped_nsobject<NSMutableArray> services_;
23 - (id)initWithServiceWatcher:(ServiceWatcherImplMac*)serviceWatcherImpl;
27 @interface NetServiceDelegate : NSObject <NSNetServiceDelegate> {
29 ServiceResolverImplMac* serviceResolverImpl_;
32 -(id) initWithServiceResolver:(ServiceResolverImplMac*)serviceResolverImpl;
36 namespace local_discovery {
40 const NSTimeInterval kResolveTimeout = 10.0;
42 // Extracts the instance name, name type and domain from a full service name or
43 // the service type and domain from a service type. Returns true if successful.
44 // TODO(justinlin): This current only handles service names with format
45 // <name>._<protocol2>._<protocol1>.<domain>. Service names with
46 // subtypes will not parse correctly:
47 // <name>._<type>._<sub>._<protocol2>._<protocol1>.<domain>.
48 bool ExtractServiceInfo(const std::string& service,
50 std::string* instance,
52 std::string* domain) {
56 const size_t last_period = service.find_last_of('.');
57 if (last_period == std::string::npos || service.length() <= last_period)
60 if (!is_service_name) {
61 *instance = std::string();
62 *type = service.substr(0, last_period) + ".";
64 // Find third last period that delimits type and instance name.
65 size_t type_period = last_period;
66 for (int i = 0; i < 2; ++i) {
67 type_period = service.find_last_of('.', type_period - 1);
68 if (type_period == std::string::npos)
72 *instance = service.substr(0, type_period);
73 *type = service.substr(type_period + 1, last_period - type_period);
75 *domain = service.substr(last_period + 1) + ".";
77 return !domain->empty() &&
79 (!is_service_name || !instance->empty());
82 void ParseTxtRecord(NSData* record, std::vector<std::string>* output) {
83 if (record == nil || [record length] <= 1)
86 const uint8* record_bytes = reinterpret_cast<const uint8*>([record bytes]);
87 const uint8* const record_end = record_bytes + [record length];
88 // TODO(justinlin): More strict bounds checking.
89 while (record_bytes < record_end) {
90 uint8 size = *record_bytes++;
94 if (record_bytes + size <= record_end) {
96 [[[NSString alloc] initWithBytes:record_bytes
98 encoding:NSUTF8StringEncoding] UTF8String]);
100 record_bytes += size;
106 ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {}
107 ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {}
109 scoped_ptr<ServiceWatcher> ServiceDiscoveryClientMac::CreateServiceWatcher(
110 const std::string& service_type,
111 const ServiceWatcher::UpdatedCallback& callback) {
112 return scoped_ptr<ServiceWatcher>(new ServiceWatcherImplMac(service_type,
116 scoped_ptr<ServiceResolver> ServiceDiscoveryClientMac::CreateServiceResolver(
117 const std::string& service_name,
118 const ServiceResolver::ResolveCompleteCallback& callback) {
119 return scoped_ptr<ServiceResolver>(new ServiceResolverImplMac(service_name,
123 scoped_ptr<LocalDomainResolver>
124 ServiceDiscoveryClientMac::CreateLocalDomainResolver(
125 const std::string& domain,
126 net::AddressFamily address_family,
127 const LocalDomainResolver::IPAddressCallback& callback) {
128 NOTIMPLEMENTED(); // TODO(justinlin): Implement.
129 return scoped_ptr<LocalDomainResolver>();
132 ServiceWatcherImplMac::ServiceWatcherImplMac(
133 const std::string& service_type,
134 const ServiceWatcher::UpdatedCallback& callback)
135 : service_type_(service_type), callback_(callback), started_(false) {}
137 ServiceWatcherImplMac::~ServiceWatcherImplMac() {}
139 void ServiceWatcherImplMac::Start() {
141 delegate_.reset([[NetServiceBrowserDelegate alloc]
142 initWithServiceWatcher:this]);
143 browser_.reset([[NSNetServiceBrowser alloc] init]);
144 [browser_ setDelegate:delegate_];
148 // TODO(justinlin): Implement flushing DNS cache to respect parameter.
149 void ServiceWatcherImplMac::DiscoverNewServices(bool force_update) {
152 std::string instance;
156 if (!ExtractServiceInfo(service_type_, false, &instance, &type, &domain))
159 DCHECK(instance.empty());
160 DVLOG(1) << "Listening for service type '" << type
161 << "' on domain '" << domain << "'";
163 [browser_ searchForServicesOfType:[NSString stringWithUTF8String:type.c_str()]
164 inDomain:[NSString stringWithUTF8String:domain.c_str()]];
167 std::string ServiceWatcherImplMac::GetServiceType() const {
168 return service_type_;
171 void ServiceWatcherImplMac::OnServicesUpdate(ServiceWatcher::UpdateType update,
172 const std::string& service) {
173 callback_.Run(update, service + "." + service_type_);
176 ServiceResolverImplMac::ServiceResolverImplMac(
177 const std::string& service_name,
178 const ServiceResolver::ResolveCompleteCallback& callback)
179 : service_name_(service_name), callback_(callback), has_resolved_(false) {
180 std::string instance;
184 if (ExtractServiceInfo(service_name, true, &instance, &type, &domain)) {
185 delegate_.reset([[NetServiceDelegate alloc] initWithServiceResolver:this]);
187 [[NSNetService alloc]
188 initWithDomain:[[NSString alloc] initWithUTF8String:domain.c_str()]
189 type:[[NSString alloc] initWithUTF8String:type.c_str()]
190 name:[[NSString alloc] initWithUTF8String:instance.c_str()]]);
191 [service_ setDelegate:delegate_];
195 ServiceResolverImplMac::~ServiceResolverImplMac() {}
197 void ServiceResolverImplMac::StartResolving() {
201 DVLOG(1) << "Resolving service " << service_name_;
202 [service_ resolveWithTimeout:kResolveTimeout];
205 std::string ServiceResolverImplMac::GetName() const {
206 return service_name_;
209 void ServiceResolverImplMac::OnResolveUpdate(RequestStatus status) {
210 if (status == STATUS_SUCCESS) {
211 service_description_.service_name = service_name_;
213 for (NSData* address in [service_ addresses]) {
214 const void* bytes = [address bytes];
215 // TODO(justinlin): Handle IPv6 addresses?
216 if (static_cast<const sockaddr*>(bytes)->sa_family == AF_INET) {
217 const sockaddr_in* sock = static_cast<const sockaddr_in*>(bytes);
218 char addr[INET_ADDRSTRLEN];
219 inet_ntop(AF_INET, &sock->sin_addr, addr, INET_ADDRSTRLEN);
220 service_description_.address =
221 net::HostPortPair(addr, ntohs(sock->sin_port));
222 net::ParseIPLiteralToNumber(addr, &service_description_.ip_address);
227 ParseTxtRecord([service_ TXTRecordData], &service_description_.metadata);
229 // TODO(justinlin): Implement last_seen.
230 service_description_.last_seen = base::Time::Now();
231 callback_.Run(status, service_description_);
233 callback_.Run(status, ServiceDescription());
235 has_resolved_ = true;
238 void ServiceResolverImplMac::SetServiceForTesting(
239 base::scoped_nsobject<NSNetService> service) {
243 } // namespace local_discovery
245 @implementation NetServiceBrowserDelegate
247 - (id)initWithServiceWatcher:(ServiceWatcherImplMac*)serviceWatcherImpl {
248 if ((self = [super init])) {
249 serviceWatcherImpl_ = serviceWatcherImpl;
250 services_.reset([[NSMutableArray alloc] initWithCapacity:1]);
255 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
256 didFindService:(NSNetService *)netService
257 moreComing:(BOOL)moreServicesComing {
258 // Start monitoring this service for updates.
259 [netService setDelegate:self];
260 [netService startMonitoring];
261 [services_ addObject:netService];
263 serviceWatcherImpl_->OnServicesUpdate(
264 local_discovery::ServiceWatcher::UPDATE_ADDED,
265 [[netService name] UTF8String]);
268 - (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser
269 didRemoveService:(NSNetService *)netService
270 moreComing:(BOOL)moreServicesComing {
271 serviceWatcherImpl_->OnServicesUpdate(
272 local_discovery::ServiceWatcher::UPDATE_REMOVED,
273 [[netService name] UTF8String]);
275 NSUInteger index = [services_ indexOfObject:netService];
276 if (index != NSNotFound) {
277 // Stop monitoring this service for updates.
278 [[services_ objectAtIndex:index] stopMonitoring];
279 [services_ removeObjectAtIndex:index];
283 - (void)netService:(NSNetService *)sender
284 didUpdateTXTRecordData:(NSData *)data {
285 serviceWatcherImpl_->OnServicesUpdate(
286 local_discovery::ServiceWatcher::UPDATE_CHANGED,
287 [[sender name] UTF8String]);
292 @implementation NetServiceDelegate
294 -(id) initWithServiceResolver:(ServiceResolverImplMac*)serviceResolverImpl {
295 if ((self = [super init])) {
296 serviceResolverImpl_ = serviceResolverImpl;
301 - (void)netServiceDidResolveAddress:(NSNetService *)sender {
302 serviceResolverImpl_->OnResolveUpdate(
303 local_discovery::ServiceResolver::STATUS_SUCCESS);
306 - (void)netService:(NSNetService *)sender
307 didNotResolve:(NSDictionary *)errorDict {
308 serviceResolverImpl_->OnResolveUpdate(
309 local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT);