service_discovery_client_mac.mm revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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. 4 5#include "chrome/browser/local_discovery/service_discovery_client_mac.h" 6 7#import <Foundation/Foundation.h> 8#import <arpa/inet.h> 9#import <net/if_dl.h> 10 11#include "base/memory/singleton.h" 12 13using local_discovery::ServiceWatcherImplMac; 14using local_discovery::ServiceResolverImplMac; 15 16@interface NetServiceBrowserDelegate 17 : NSObject<NSNetServiceBrowserDelegate, NSNetServiceDelegate> { 18 @private 19 ServiceWatcherImplMac* serviceWatcherImpl_; // weak. 20 base::scoped_nsobject<NSMutableArray> services_; 21} 22 23- (id)initWithServiceWatcher:(ServiceWatcherImplMac*)serviceWatcherImpl; 24 25@end 26 27@interface NetServiceDelegate : NSObject <NSNetServiceDelegate> { 28 @private 29 ServiceResolverImplMac* serviceResolverImpl_; 30} 31 32-(id) initWithServiceResolver:(ServiceResolverImplMac*)serviceResolverImpl; 33 34@end 35 36namespace local_discovery { 37 38namespace { 39 40const NSTimeInterval kResolveTimeout = 10.0; 41 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>. 48bool ExtractServiceInfo(const std::string& service, 49 bool is_service_name, 50 std::string* instance, 51 std::string* type, 52 std::string* domain) { 53 if (service.empty()) 54 return false; 55 56 const size_t last_period = service.find_last_of('.'); 57 if (last_period == std::string::npos || service.length() <= last_period) 58 return false; 59 60 if (!is_service_name) { 61 *instance = std::string(); 62 *type = service.substr(0, last_period) + "."; 63 } else { 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) 69 return false; 70 } 71 72 *instance = service.substr(0, type_period); 73 *type = service.substr(type_period + 1, last_period - type_period); 74 } 75 *domain = service.substr(last_period + 1) + "."; 76 77 return !domain->empty() && 78 !type->empty() && 79 (!is_service_name || !instance->empty()); 80} 81 82void ParseTxtRecord(NSData* record, std::vector<std::string>* output) { 83 if (record == nil || [record length] <= 1) 84 return; 85 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++; 91 if (size <= 0) 92 continue; 93 94 if (record_bytes + size <= record_end) { 95 output->push_back( 96 [[[NSString alloc] initWithBytes:record_bytes 97 length:size 98 encoding:NSUTF8StringEncoding] UTF8String]); 99 } 100 record_bytes += size; 101 } 102} 103 104} // namespace 105 106ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {} 107ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {} 108 109scoped_ptr<ServiceWatcher> ServiceDiscoveryClientMac::CreateServiceWatcher( 110 const std::string& service_type, 111 const ServiceWatcher::UpdatedCallback& callback) { 112 return scoped_ptr<ServiceWatcher>(new ServiceWatcherImplMac(service_type, 113 callback)); 114} 115 116scoped_ptr<ServiceResolver> ServiceDiscoveryClientMac::CreateServiceResolver( 117 const std::string& service_name, 118 const ServiceResolver::ResolveCompleteCallback& callback) { 119 return scoped_ptr<ServiceResolver>(new ServiceResolverImplMac(service_name, 120 callback)); 121} 122 123scoped_ptr<LocalDomainResolver> 124ServiceDiscoveryClientMac::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>(); 130} 131 132ServiceWatcherImplMac::ServiceWatcherImplMac( 133 const std::string& service_type, 134 const ServiceWatcher::UpdatedCallback& callback) 135 : service_type_(service_type), callback_(callback), started_(false) {} 136 137ServiceWatcherImplMac::~ServiceWatcherImplMac() {} 138 139void ServiceWatcherImplMac::Start() { 140 DCHECK(!started_); 141 delegate_.reset([[NetServiceBrowserDelegate alloc] 142 initWithServiceWatcher:this]); 143 browser_.reset([[NSNetServiceBrowser alloc] init]); 144 [browser_ setDelegate:delegate_]; 145 started_ = true; 146} 147 148// TODO(justinlin): Implement flushing DNS cache to respect parameter. 149void ServiceWatcherImplMac::DiscoverNewServices(bool force_update) { 150 DCHECK(started_); 151 152 std::string instance; 153 std::string type; 154 std::string domain; 155 156 if (!ExtractServiceInfo(service_type_, false, &instance, &type, &domain)) 157 return; 158 159 DCHECK(instance.empty()); 160 DVLOG(1) << "Listening for service type '" << type 161 << "' on domain '" << domain << "'"; 162 163 [browser_ searchForServicesOfType:[NSString stringWithUTF8String:type.c_str()] 164 inDomain:[NSString stringWithUTF8String:domain.c_str()]]; 165} 166 167void ServiceWatcherImplMac::SetActivelyRefreshServices( 168 bool actively_refresh_services) { 169 DCHECK(started_); 170 // TODO(noamsml): Implement this method. 171} 172 173std::string ServiceWatcherImplMac::GetServiceType() const { 174 return service_type_; 175} 176 177void ServiceWatcherImplMac::OnServicesUpdate(ServiceWatcher::UpdateType update, 178 const std::string& service) { 179 callback_.Run(update, service + "." + service_type_); 180} 181 182ServiceResolverImplMac::ServiceResolverImplMac( 183 const std::string& service_name, 184 const ServiceResolver::ResolveCompleteCallback& callback) 185 : service_name_(service_name), callback_(callback), has_resolved_(false) { 186 std::string instance; 187 std::string type; 188 std::string domain; 189 190 if (ExtractServiceInfo(service_name, true, &instance, &type, &domain)) { 191 delegate_.reset([[NetServiceDelegate alloc] initWithServiceResolver:this]); 192 service_.reset( 193 [[NSNetService alloc] 194 initWithDomain:[[NSString alloc] initWithUTF8String:domain.c_str()] 195 type:[[NSString alloc] initWithUTF8String:type.c_str()] 196 name:[[NSString alloc] initWithUTF8String:instance.c_str()]]); 197 [service_ setDelegate:delegate_]; 198 } 199} 200 201ServiceResolverImplMac::~ServiceResolverImplMac() {} 202 203void ServiceResolverImplMac::StartResolving() { 204 if (!service_.get()) 205 return; 206 207 DVLOG(1) << "Resolving service " << service_name_; 208 [service_ resolveWithTimeout:kResolveTimeout]; 209} 210 211std::string ServiceResolverImplMac::GetName() const { 212 return service_name_; 213} 214 215void ServiceResolverImplMac::OnResolveUpdate(RequestStatus status) { 216 if (status == STATUS_SUCCESS) { 217 service_description_.service_name = service_name_; 218 219 for (NSData* address in [service_ addresses]) { 220 const void* bytes = [address bytes]; 221 // TODO(justinlin): Handle IPv6 addresses? 222 if (static_cast<const sockaddr*>(bytes)->sa_family == AF_INET) { 223 const sockaddr_in* sock = static_cast<const sockaddr_in*>(bytes); 224 char addr[INET_ADDRSTRLEN]; 225 inet_ntop(AF_INET, &sock->sin_addr, addr, INET_ADDRSTRLEN); 226 service_description_.address = 227 net::HostPortPair(addr, ntohs(sock->sin_port)); 228 net::ParseIPLiteralToNumber(addr, &service_description_.ip_address); 229 break; 230 } 231 } 232 233 ParseTxtRecord([service_ TXTRecordData], &service_description_.metadata); 234 235 // TODO(justinlin): Implement last_seen. 236 service_description_.last_seen = base::Time::Now(); 237 callback_.Run(status, service_description_); 238 } else { 239 callback_.Run(status, ServiceDescription()); 240 } 241 has_resolved_ = true; 242} 243 244void ServiceResolverImplMac::SetServiceForTesting( 245 base::scoped_nsobject<NSNetService> service) { 246 service_ = service; 247} 248 249} // namespace local_discovery 250 251@implementation NetServiceBrowserDelegate 252 253- (id)initWithServiceWatcher:(ServiceWatcherImplMac*)serviceWatcherImpl { 254 if ((self = [super init])) { 255 serviceWatcherImpl_ = serviceWatcherImpl; 256 services_.reset([[NSMutableArray alloc] initWithCapacity:1]); 257 } 258 return self; 259} 260 261- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser 262 didFindService:(NSNetService *)netService 263 moreComing:(BOOL)moreServicesComing { 264 // Start monitoring this service for updates. 265 [netService setDelegate:self]; 266 [netService startMonitoring]; 267 [services_ addObject:netService]; 268 269 serviceWatcherImpl_->OnServicesUpdate( 270 local_discovery::ServiceWatcher::UPDATE_ADDED, 271 [[netService name] UTF8String]); 272} 273 274- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser 275 didRemoveService:(NSNetService *)netService 276 moreComing:(BOOL)moreServicesComing { 277 serviceWatcherImpl_->OnServicesUpdate( 278 local_discovery::ServiceWatcher::UPDATE_REMOVED, 279 [[netService name] UTF8String]); 280 281 NSUInteger index = [services_ indexOfObject:netService]; 282 if (index != NSNotFound) { 283 // Stop monitoring this service for updates. 284 [[services_ objectAtIndex:index] stopMonitoring]; 285 [services_ removeObjectAtIndex:index]; 286 } 287} 288 289- (void)netService:(NSNetService *)sender 290 didUpdateTXTRecordData:(NSData *)data { 291 serviceWatcherImpl_->OnServicesUpdate( 292 local_discovery::ServiceWatcher::UPDATE_CHANGED, 293 [[sender name] UTF8String]); 294} 295 296@end 297 298@implementation NetServiceDelegate 299 300-(id) initWithServiceResolver:(ServiceResolverImplMac*)serviceResolverImpl { 301 if ((self = [super init])) { 302 serviceResolverImpl_ = serviceResolverImpl; 303 } 304 return self; 305} 306 307- (void)netServiceDidResolveAddress:(NSNetService *)sender { 308 serviceResolverImpl_->OnResolveUpdate( 309 local_discovery::ServiceResolver::STATUS_SUCCESS); 310} 311 312- (void)netService:(NSNetService *)sender 313 didNotResolve:(NSDictionary *)errorDict { 314 serviceResolverImpl_->OnResolveUpdate( 315 local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT); 316} 317 318@end 319