2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
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"
31 #include <arpa/inet.h>
32 #include <netinet/in.h>
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)
41 CZeroconfBrowser::ZeroconfService::tTxtRecordMap recordMap;
42 CFDataRef data = NULL;
44 data=CFNetServiceGetTXTData(serviceRef);
47 CFDictionaryRef dict = CFNetServiceCreateDictionaryWithTXTData(kCFAllocatorDefault, data);
50 CFIndex numValues = 0;
51 numValues = CFDictionaryGetCount(dict);
54 CFStringRef keys[numValues];
55 CFDataRef values[numValues];
57 CFDictionaryGetKeysAndValues(dict, (const void **)&keys, (const void **)&values);
59 for(idx = 0; idx < numValues; idx++)
62 if (DarwinCFStringRefToString(keys[idx], key))
67 CStdString((const char *)CFDataGetBytePtr(values[idx]))
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)
84 struct sockaddr_in address;
86 CFArrayRef addressResults = CFNetServiceGetAddressing( (CFNetServiceRef)serviceRef );
88 if ( addressResults != NULL )
90 CFIndex numAddressResults = CFArrayGetCount( addressResults );
91 CFDataRef sockAddrRef = NULL;
92 struct sockaddr sockHdr;
94 for ( idx = 0; idx < numAddressResults; idx++ )
96 sockAddrRef = (CFDataRef)CFArrayGetValueAtIndex( addressResults, idx );
97 if ( sockAddrRef != NULL )
99 CFDataGetBytes( sockAddrRef, CFRangeMake(0, sizeof(sockHdr)), (UInt8*)&sockHdr );
100 switch ( sockHdr.sa_family )
103 CFDataGetBytes( sockAddrRef, CFRangeMake(0, sizeof(address)), (UInt8*)&address );
104 if ( inet_ntop(sockHdr.sa_family, &address.sin_addr, buffer, sizeof(buffer)) != NULL )
107 fr_port = ntohs(address.sin_port);
122 CZeroconfBrowserOSX::CZeroconfBrowserOSX():m_runloop(0)
124 //aquire the main threads event loop
125 m_runloop = CFRunLoopGetMain();
128 CZeroconfBrowserOSX::~CZeroconfBrowserOSX()
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);
136 void CZeroconfBrowserOSX::BrowserCallback(CFNetServiceBrowserRef browser, CFOptionFlags flags, CFTypeRef domainOrService, CFStreamError *error, void *info)
140 if (error->error == noErr)
142 //make sure we receive a service
143 assert(!(flags&kCFNetServiceFlagIsDomain));
144 CFNetServiceRef service = (CFNetServiceRef)domainOrService;
147 CZeroconfBrowserOSX* p_this = reinterpret_cast<CZeroconfBrowserOSX*>(info);
150 std::string name, type, domain;
151 if (!DarwinCFStringRefToString(CFNetServiceGetName(service), name) ||
152 !DarwinCFStringRefToString(CFNetServiceGetType(service), type) ||
153 !DarwinCFStringRefToString(CFNetServiceGetDomain(service), domain))
155 CLog::Log(LOGWARNING, "CZeroconfBrowserOSX::BrowserCallback failed to convert service strings.");
159 ZeroconfService s(name, type, domain);
161 if (flags & kCFNetServiceFlagRemove)
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);
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);
173 if (! (flags & kCFNetServiceFlagMoreComing) )
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://");
182 CLog::Log(LOGERROR, "CZeroconfBrowserOSX::BrowserCallback returned"
183 "(domain = %d, error = %"PRId64")", (int)error->domain, (int64_t)error->error);
187 /// adds the service to list of found services
188 void CZeroconfBrowserOSX::
189 addDiscoveredService(CFNetServiceBrowserRef browser, CFOptionFlags flags, CZeroconfBrowser::ZeroconfService const &fcr_service)
191 CSingleLock lock(m_data_guard);
192 tDiscoveredServicesMap::iterator browserIt = m_discovered_services.find(browser);
193 if (browserIt == m_discovered_services.end())
195 //first service by this browser
196 browserIt = m_discovered_services.insert(make_pair(browser, std::vector<std::pair<ZeroconfService, unsigned int> >())).first;
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)
203 if (serviceIt->first == fcr_service)
206 if (serviceIt == services.end())
207 services.push_back(std::make_pair(fcr_service, 1));
212 void CZeroconfBrowserOSX::
213 removeDiscoveredService(CFNetServiceBrowserRef browser, CFOptionFlags flags, CZeroconfBrowser::ZeroconfService const &fcr_service)
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)
224 if (serviceIt != services.end())
228 if (!serviceIt->second)
230 //eventually remove the service
231 services.erase(serviceIt);
236 //looks like we missed the announce, no problem though..
241 bool CZeroconfBrowserOSX::doAddServiceType(const CStdString& fcr_service_type)
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);
249 //schedule the browser
250 CFNetServiceBrowserScheduleWithRunLoop(p_browser, m_runloop, kCFRunLoopCommonModes);
252 CFStringRef type = CFStringCreateWithCString(NULL, fcr_service_type.c_str(), kCFStringEncodingUTF8);
254 assert(type != NULL);
255 Boolean result = CFNetServiceBrowserSearchForServices(p_browser, domain, type, &error);
259 // Something went wrong so lets clean up.
260 CFNetServiceBrowserUnscheduleFromRunLoop(p_browser, m_runloop, kCFRunLoopCommonModes);
261 CFRelease(p_browser);
263 CLog::Log(LOGERROR, "CFNetServiceBrowserSearchForServices returned"
264 "(domain = %d, error = %"PRId64")", (int)error.domain, (int64_t)error.error);
269 CSingleLock lock(m_data_guard);
270 m_service_browsers.insert(std::make_pair(fcr_service_type, p_browser));
276 bool CZeroconfBrowserOSX::doRemoveServiceType(const CStdString &fcr_service_type)
278 //search for this browser and remove it from the map
279 CFNetServiceBrowserRef browser = 0;
281 CSingleLock lock(m_data_guard);
282 tBrowserMap::iterator it = m_service_browsers.find(fcr_service_type);
283 if (it == m_service_browsers.end())
286 browser = it->second;
287 m_service_browsers.erase(it);
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
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);
308 std::vector<CZeroconfBrowser::ZeroconfService> CZeroconfBrowserOSX::doGetFoundServices()
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)
315 const std::vector<std::pair<CZeroconfBrowser::ZeroconfService, unsigned int> >& services = it->second;
316 for(unsigned int i = 0; i < services.size(); ++i)
318 ret.push_back(services[i].first);
325 bool CZeroconfBrowserOSX::doResolveService(CZeroconfBrowser::ZeroconfService &fr_service, double f_timeout)
328 CFStringRef type = CFStringCreateWithCString(NULL, fr_service.GetType().c_str(), kCFStringEncodingUTF8);
330 CFStringRef name = CFStringCreateWithCString(NULL, fr_service.GetName().c_str(), kCFStringEncodingUTF8);
332 CFStringRef domain = CFStringCreateWithCString(NULL, fr_service.GetDomain().c_str(), kCFStringEncodingUTF8);
334 CFNetServiceRef service = CFNetServiceCreate (NULL, domain, type, name, 0);
335 if (CFNetServiceResolveWithTimeout(service, f_timeout, NULL) )
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));