[cosmetics] update date in GPL header
[vuplus_xbmc] / xbmc / network / osx / ZeroconfBrowserOSX.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://www.xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "system.h"
22
23 #include "ZeroconfBrowserOSX.h"
24 #include "GUIUserMessages.h"
25 #include "guilib/GUIWindowManager.h"
26 #include "guilib/GUIMessage.h"
27 #include "threads/SingleLock.h"
28 #include "utils/log.h"
29 #include "osx/DarwinUtils.h"
30
31 #include <arpa/inet.h>
32 #include <netinet/in.h>
33
34 namespace
35 {
36   //helper for getting a the txt-records list
37   //returns true on success, false if nothing found or error
38   CZeroconfBrowser::ZeroconfService::tTxtRecordMap GetTxtRecords(CFNetServiceRef serviceRef)  
39   {
40     CFIndex idx = 0;
41     CZeroconfBrowser::ZeroconfService::tTxtRecordMap recordMap;
42     CFDataRef data = NULL;
43
44     data=CFNetServiceGetTXTData(serviceRef);
45     if (data != NULL)
46     {
47       CFDictionaryRef dict = CFNetServiceCreateDictionaryWithTXTData(kCFAllocatorDefault, data);
48       if (dict != NULL)
49       {
50         CFIndex numValues = 0;
51         numValues = CFDictionaryGetCount(dict);
52         if (numValues > 0)
53         {
54           CFStringRef keys[numValues];
55           CFDataRef values[numValues];
56
57           CFDictionaryGetKeysAndValues(dict, (const void **)&keys,  (const void **)&values);
58
59           for(idx = 0; idx < numValues; idx++)
60           {
61             std::string key;
62             if (DarwinCFStringRefToString(keys[idx], key))
63             {
64               recordMap.insert(
65                 std::make_pair(
66                   key,
67                   CStdString((const char *)CFDataGetBytePtr(values[idx]))
68                 )
69               );
70             }
71           }
72         }
73         CFRelease(dict);
74       }
75     }
76     return recordMap;
77   }
78
79   //helper to get (first) IP and port from a resolved service
80   //returns true on success, false on if none was found
81   bool CopyFirstIPv4Address(CFNetServiceRef serviceRef, CStdString &fr_address, int &fr_port)
82   {
83     CFIndex idx;
84     struct sockaddr_in address;
85     char buffer[256];
86     CFArrayRef addressResults = CFNetServiceGetAddressing( (CFNetServiceRef)serviceRef );
87     
88     if ( addressResults != NULL )
89     {
90       CFIndex numAddressResults = CFArrayGetCount( addressResults );
91       CFDataRef sockAddrRef = NULL;
92       struct sockaddr sockHdr;
93       
94       for ( idx = 0; idx < numAddressResults; idx++ )
95       {
96         sockAddrRef = (CFDataRef)CFArrayGetValueAtIndex( addressResults, idx );
97         if ( sockAddrRef != NULL )
98         {
99           CFDataGetBytes( sockAddrRef, CFRangeMake(0, sizeof(sockHdr)), (UInt8*)&sockHdr );
100           switch ( sockHdr.sa_family )
101           {
102             case AF_INET:
103               CFDataGetBytes( sockAddrRef, CFRangeMake(0, sizeof(address)), (UInt8*)&address );
104               if ( inet_ntop(sockHdr.sa_family, &address.sin_addr, buffer, sizeof(buffer)) != NULL )
105               {
106                 fr_address = buffer;
107                 fr_port = ntohs(address.sin_port);
108                 return true;
109               }
110               break;
111             case AF_INET6:
112             default:
113               break;
114           }
115         }
116       }
117     }
118     return false;
119   }
120 }
121
122 CZeroconfBrowserOSX::CZeroconfBrowserOSX():m_runloop(0)
123 {
124   //aquire the main threads event loop
125   m_runloop = CFRunLoopGetMain();
126 }
127
128 CZeroconfBrowserOSX::~CZeroconfBrowserOSX()
129 {
130   CSingleLock lock(m_data_guard);
131   //make sure there are no browsers anymore
132   for(tBrowserMap::iterator it = m_service_browsers.begin(); it != m_service_browsers.end(); ++it )
133     doRemoveServiceType(it->first);
134 }
135
136 void CZeroconfBrowserOSX::BrowserCallback(CFNetServiceBrowserRef browser, CFOptionFlags flags, CFTypeRef domainOrService, CFStreamError *error, void *info)
137 {
138   assert(info);
139
140   if (error->error == noErr)
141   {
142     //make sure we receive a service
143     assert(!(flags&kCFNetServiceFlagIsDomain));  
144     CFNetServiceRef service = (CFNetServiceRef)domainOrService;
145     assert(service);
146     //get our instance
147     CZeroconfBrowserOSX* p_this = reinterpret_cast<CZeroconfBrowserOSX*>(info);
148
149     //store the service
150     std::string name, type, domain;
151     if (!DarwinCFStringRefToString(CFNetServiceGetName(service), name) ||
152         !DarwinCFStringRefToString(CFNetServiceGetType(service), type) ||
153         !DarwinCFStringRefToString(CFNetServiceGetDomain(service), domain))
154     {
155       CLog::Log(LOGWARNING, "CZeroconfBrowserOSX::BrowserCallback failed to convert service strings.");
156       return;
157     }
158
159     ZeroconfService s(name, type, domain);
160
161     if (flags & kCFNetServiceFlagRemove)
162     {
163       CLog::Log(LOGDEBUG, "CZeroconfBrowserOSX::BrowserCallback service named: %s, type: %s, domain: %s disappeared", 
164         s.GetName().c_str(), s.GetType().c_str(), s.GetDomain().c_str());
165       p_this->removeDiscoveredService(browser, flags, s);      
166     }
167     else
168     {
169       CLog::Log(LOGDEBUG, "CZeroconfBrowserOSX::BrowserCallback found service named: %s, type: %s, domain: %s", 
170         s.GetName().c_str(), s.GetType().c_str(), s.GetDomain().c_str());
171       p_this->addDiscoveredService(browser, flags, s);
172     }
173     if (! (flags & kCFNetServiceFlagMoreComing) )
174     {
175       CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
176       message.SetStringParam("zeroconf://");
177       g_windowManager.SendThreadMessage(message);
178       CLog::Log(LOGDEBUG, "CZeroconfBrowserOSX::BrowserCallback sent gui update for path zeroconf://");
179     }
180   } else
181   {
182     CLog::Log(LOGERROR, "CZeroconfBrowserOSX::BrowserCallback returned"
183       "(domain = %d, error = %"PRId64")", (int)error->domain, (int64_t)error->error);
184   }
185 }
186
187 /// adds the service to list of found services
188 void CZeroconfBrowserOSX::
189 addDiscoveredService(CFNetServiceBrowserRef browser, CFOptionFlags flags, CZeroconfBrowser::ZeroconfService const &fcr_service)
190 {
191   CSingleLock lock(m_data_guard);
192   tDiscoveredServicesMap::iterator browserIt = m_discovered_services.find(browser);
193   if (browserIt == m_discovered_services.end())
194   {
195      //first service by this browser
196      browserIt = m_discovered_services.insert(make_pair(browser, std::vector<std::pair<ZeroconfService, unsigned int> >())).first;
197   }
198   //search this service
199   std::vector<std::pair<ZeroconfService, unsigned int> >& services = browserIt->second;
200   std::vector<std::pair<ZeroconfService, unsigned int> >::iterator serviceIt = services.begin();
201   for( ; serviceIt != services.end(); ++serviceIt)
202   {
203     if (serviceIt->first == fcr_service)
204       break;
205   }
206   if (serviceIt == services.end())
207     services.push_back(std::make_pair(fcr_service, 1));
208   else
209     ++serviceIt->second;
210 }
211
212 void CZeroconfBrowserOSX::
213 removeDiscoveredService(CFNetServiceBrowserRef browser, CFOptionFlags flags, CZeroconfBrowser::ZeroconfService const &fcr_service)
214 {
215   CSingleLock lock(m_data_guard);
216   tDiscoveredServicesMap::iterator browserIt = m_discovered_services.find(browser);
217   assert(browserIt != m_discovered_services.end());
218   //search this service
219   std::vector<std::pair<ZeroconfService, unsigned int> >& services = browserIt->second;
220   std::vector<std::pair<ZeroconfService, unsigned int> >::iterator serviceIt = services.begin();
221   for( ; serviceIt != services.end(); ++serviceIt)
222     if (serviceIt->first == fcr_service)
223       break;
224   if (serviceIt != services.end())
225   {
226     //decrease refCount
227     --serviceIt->second;
228     if (!serviceIt->second)
229     {
230       //eventually remove the service
231       services.erase(serviceIt);
232     }
233   }
234   else
235   {
236     //looks like we missed the announce, no problem though..
237   }
238 }
239
240
241 bool CZeroconfBrowserOSX::doAddServiceType(const CStdString& fcr_service_type)
242 {
243   CFNetServiceClientContext clientContext = { 0, this, NULL, NULL, NULL };
244   CFStringRef domain = CFSTR("");
245   CFNetServiceBrowserRef p_browser = CFNetServiceBrowserCreate(kCFAllocatorDefault,
246     CZeroconfBrowserOSX::BrowserCallback, &clientContext);
247   assert(p_browser != NULL);
248
249   //schedule the browser
250   CFNetServiceBrowserScheduleWithRunLoop(p_browser, m_runloop, kCFRunLoopCommonModes);
251   CFStreamError error;
252   CFStringRef type = CFStringCreateWithCString(NULL, fcr_service_type.c_str(), kCFStringEncodingUTF8);
253
254   assert(type != NULL);
255   Boolean result = CFNetServiceBrowserSearchForServices(p_browser, domain, type, &error);
256   CFRelease(type);
257   if (result == false)
258   {
259     // Something went wrong so lets clean up.
260     CFNetServiceBrowserUnscheduleFromRunLoop(p_browser, m_runloop, kCFRunLoopCommonModes);         
261     CFRelease(p_browser);
262     p_browser = NULL;
263     CLog::Log(LOGERROR, "CFNetServiceBrowserSearchForServices returned"
264       "(domain = %d, error = %"PRId64")", (int)error.domain, (int64_t)error.error);
265   }
266   else
267   {
268     //store the browser
269     CSingleLock lock(m_data_guard);
270     m_service_browsers.insert(std::make_pair(fcr_service_type, p_browser));
271   }
272
273   return result;
274 }
275
276 bool CZeroconfBrowserOSX::doRemoveServiceType(const CStdString &fcr_service_type)
277 {
278   //search for this browser and remove it from the map
279   CFNetServiceBrowserRef browser = 0;
280   {
281     CSingleLock lock(m_data_guard);
282     tBrowserMap::iterator it = m_service_browsers.find(fcr_service_type);
283     if (it == m_service_browsers.end())
284       return false;
285
286     browser = it->second;
287     m_service_browsers.erase(it);
288   }
289   assert(browser);
290     
291   //now kill the browser
292   CFStreamError streamerror;
293   CFNetServiceBrowserStopSearch(browser, &streamerror);
294   CFNetServiceBrowserUnscheduleFromRunLoop(browser, m_runloop, kCFRunLoopCommonModes);
295   CFNetServiceBrowserInvalidate(browser);
296   //remove the services of this browser
297   {
298     CSingleLock lock(m_data_guard);
299     tDiscoveredServicesMap::iterator it = m_discovered_services.find(browser);
300     if (it != m_discovered_services.end())
301       m_discovered_services.erase(it);
302   }
303   CFRelease(browser);
304
305   return true;
306 }
307
308 std::vector<CZeroconfBrowser::ZeroconfService> CZeroconfBrowserOSX::doGetFoundServices()
309 {
310   std::vector<CZeroconfBrowser::ZeroconfService> ret;
311   CSingleLock lock(m_data_guard);
312   for(tDiscoveredServicesMap::const_iterator it = m_discovered_services.begin();
313       it != m_discovered_services.end(); ++it)
314   {
315     const std::vector<std::pair<CZeroconfBrowser::ZeroconfService, unsigned int> >& services = it->second;
316     for(unsigned int i = 0; i < services.size(); ++i)
317     {
318       ret.push_back(services[i].first);
319     }
320   }
321
322   return ret;
323 }
324
325 bool CZeroconfBrowserOSX::doResolveService(CZeroconfBrowser::ZeroconfService &fr_service, double f_timeout)
326 {
327   bool ret = false;
328   CFStringRef type = CFStringCreateWithCString(NULL, fr_service.GetType().c_str(), kCFStringEncodingUTF8);
329
330   CFStringRef name = CFStringCreateWithCString(NULL, fr_service.GetName().c_str(), kCFStringEncodingUTF8);
331
332   CFStringRef domain = CFStringCreateWithCString(NULL, fr_service.GetDomain().c_str(), kCFStringEncodingUTF8);
333
334   CFNetServiceRef service = CFNetServiceCreate (NULL, domain, type, name, 0);
335   if (CFNetServiceResolveWithTimeout(service, f_timeout, NULL) )
336   {
337     CStdString ip; 
338     int port = 0;
339     ret = CopyFirstIPv4Address(service, ip, port);
340     fr_service.SetIP(ip);
341     fr_service.SetPort(port);
342     //get txt-record list
343     fr_service.SetTxtRecords(GetTxtRecords(service));
344   }
345   CFRelease(type);
346   CFRelease(name);
347   CFRelease(domain);
348   CFRelease(service);
349
350   return ret;
351 }