- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / local_discovery / service_discovery_client_mac.mm
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
13 using local_discovery::ServiceWatcherImplMac;
14 using 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
36 namespace local_discovery {
37
38 namespace {
39
40 const 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>.
48 bool 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
82 void 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
106 ServiceDiscoveryClientMac::ServiceDiscoveryClientMac() {}
107 ServiceDiscoveryClientMac::~ServiceDiscoveryClientMac() {}
108
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,
113                                                               callback));
114 }
115
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,
120                                                                 callback));
121 }
122
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>();
130 }
131
132 ServiceWatcherImplMac::ServiceWatcherImplMac(
133     const std::string& service_type,
134     const ServiceWatcher::UpdatedCallback& callback)
135     : service_type_(service_type), callback_(callback), started_(false) {}
136
137 ServiceWatcherImplMac::~ServiceWatcherImplMac() {}
138
139 void 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.
149 void 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
167 std::string ServiceWatcherImplMac::GetServiceType() const {
168   return service_type_;
169 }
170
171 void ServiceWatcherImplMac::OnServicesUpdate(ServiceWatcher::UpdateType update,
172                                              const std::string& service) {
173   callback_.Run(update, service + "." + service_type_);
174 }
175
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;
181   std::string type;
182   std::string domain;
183
184   if (ExtractServiceInfo(service_name, true, &instance, &type, &domain)) {
185     delegate_.reset([[NetServiceDelegate alloc] initWithServiceResolver:this]);
186     service_.reset(
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_];
192   }
193 }
194
195 ServiceResolverImplMac::~ServiceResolverImplMac() {}
196
197 void ServiceResolverImplMac::StartResolving() {
198   if (!service_.get())
199     return;
200
201   DVLOG(1) << "Resolving service " << service_name_;
202   [service_ resolveWithTimeout:kResolveTimeout];
203 }
204
205 std::string ServiceResolverImplMac::GetName() const {
206   return service_name_;
207 }
208
209 void ServiceResolverImplMac::OnResolveUpdate(RequestStatus status) {
210   if (status == STATUS_SUCCESS) {
211     service_description_.service_name = service_name_;
212
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);
223         break;
224       }
225     }
226
227     ParseTxtRecord([service_ TXTRecordData], &service_description_.metadata);
228
229     // TODO(justinlin): Implement last_seen.
230     service_description_.last_seen = base::Time::Now();
231     callback_.Run(status, service_description_);
232   } else {
233     callback_.Run(status, ServiceDescription());
234   }
235   has_resolved_ = true;
236 }
237
238 void ServiceResolverImplMac::SetServiceForTesting(
239     base::scoped_nsobject<NSNetService> service) {
240   service_ = service;
241 }
242
243 }  // namespace local_discovery
244
245 @implementation NetServiceBrowserDelegate
246
247 - (id)initWithServiceWatcher:(ServiceWatcherImplMac*)serviceWatcherImpl {
248   if ((self = [super init])) {
249     serviceWatcherImpl_ = serviceWatcherImpl;
250     services_.reset([[NSMutableArray alloc] initWithCapacity:1]);
251   }
252   return self;
253 }
254
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];
262
263   serviceWatcherImpl_->OnServicesUpdate(
264       local_discovery::ServiceWatcher::UPDATE_ADDED,
265       [[netService name] UTF8String]);
266 }
267
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]);
274
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];
280   }
281 }
282
283 - (void)netService:(NSNetService *)sender
284         didUpdateTXTRecordData:(NSData *)data {
285   serviceWatcherImpl_->OnServicesUpdate(
286       local_discovery::ServiceWatcher::UPDATE_CHANGED,
287       [[sender name] UTF8String]);
288 }
289
290 @end
291
292 @implementation NetServiceDelegate
293
294 -(id) initWithServiceResolver:(ServiceResolverImplMac*)serviceResolverImpl {
295   if ((self = [super init])) {
296     serviceResolverImpl_ = serviceResolverImpl;
297   }
298   return self;
299 }
300
301 - (void)netServiceDidResolveAddress:(NSNetService *)sender {
302   serviceResolverImpl_->OnResolveUpdate(
303       local_discovery::ServiceResolver::STATUS_SUCCESS);
304 }
305
306 - (void)netService:(NSNetService *)sender
307         didNotResolve:(NSDictionary *)errorDict {
308   serviceResolverImpl_->OnResolveUpdate(
309       local_discovery::ServiceResolver::STATUS_REQUEST_TIMEOUT);
310 }
311
312 @end