2 * SAP-Announcement Support for XBMC
3 * Copyright (c) 2008 elupus (Joakim Plate)
4 * Copyright (C) 2008-2013 Team XBMC
7 * This Program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2, or (at your option)
12 * This Program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with XBMC; see the file COPYING. If not, see
19 * <http://www.gnu.org/licenses/>.
23 #include "threads/SystemClock.h"
24 #include "system.h" // WIN32INCLUDES - not sure why this is needed
25 #include "GUIUserMessages.h"
26 #include "guilib/GUIWindowManager.h"
28 #include "SAPDirectory.h"
30 #include "threads/SingleLock.h"
31 #include "utils/log.h"
32 #include "utils/TimeUtils.h"
33 #include "utils/StringUtils.h"
35 #if defined(TARGET_DARWIN)
36 #include "osx/OSXGNUReplacements.h" // strnlen
39 #include "freebsd/FreeBSDGNUReplacements.h"
42 #include <sys/socket.h>
43 #include <netinet/in.h>
44 #include <arpa/inet.h>
47 //using namespace std; On VS2010, bind conflicts with std::bind
49 CSAPSessions g_sapsessions;
53 /* SAP is always on that port */
55 /* Global-scope SAP address */
56 #define SAP_V4_GLOBAL_ADDRESS "224.2.127.254"
57 /* Organization-local SAP address */
58 #define SAP_V4_ORG_ADDRESS "239.195.255.255"
59 /* Local (smallest non-link-local scope) SAP address */
60 #define SAP_V4_LOCAL_ADDRESS "239.255.255.255"
61 /* Link-local SAP address */
62 #define SAP_V4_LINK_ADDRESS "224.0.0.255"
65 #define SAP_V6_1 "FF0"
66 /* Scope is inserted between them */
67 #define SAP_V6_2 "::2:7FFE"
69 int parse_sap(const char* data, int len, struct sap_desc *h)
71 const char* data_orig = data;
77 h->version = (data[0] >> 5) & 0x7;
78 h->addrtype = (data[0] >> 4) & 0x1;
79 h->msgtype = (data[0] >> 2) & 0x1;
80 h->encrypted = (data[0] >> 1) & 0x1;
81 h->compressed = (data[0] >> 0) & 0x1;
84 h->msgid = ((data[2] << 8) | data[3]);
92 CLog::Log(LOGERROR, "%s - too little data for origin address", __FUNCTION__);
96 CLog::Log(LOGERROR, "%s - ipv6 addresses currently unsupported", __FUNCTION__);
101 CLog::Log(LOGERROR, "%s - too little data for origin address", __FUNCTION__);
105 h->origin = inet_ntoa(*(struct in_addr*)data);
110 /* skip authentication data */
114 /* payload type may be missing, then it's assumed to be sdp */
118 h->payload_type = "application/sdp";
119 return data - data_orig;
122 /* read payload type */
123 //no strnlen on non linux/win32 platforms, use handcrafted version
124 int payload_size = strnlen(data, len);
126 if (payload_size == len)
128 h->payload_type.assign(data, payload_size);
130 data += payload_size+1;
131 len -= payload_size+1;
133 return data - data_orig;
136 static int parse_sdp_line(const char* data, std::string& type, std::string& value)
138 const char* data2 = data;
141 l = strcspn(data, "=\n\r");
142 type.assign(data, l);
147 l = strcspn(data, "\n\r");
148 value.assign(data, l);
150 data += strspn(data, "\n\r");
155 static int parse_sdp_type(const char** data, std::string type, std::string& value)
158 const char* data2 = *data;
161 data2 += parse_sdp_line(data2, type2, value);
170 int parse_sdp_token(const char* data, std::string &value)
173 l = strcspn(data, " \n\r");
174 value.assign(data, l);
181 int parse_sdp_token(const char* data, int &value)
185 l = parse_sdp_token(data, str);
186 value = atoi(str.c_str());
190 int parse_sdp_origin(const char* data, struct sdp_desc_origin *o)
192 const char *data2 = data;
193 data += parse_sdp_token(data, o->username);
194 data += parse_sdp_token(data, o->sessionid);
195 data += parse_sdp_token(data, o->sessionver);
196 data += parse_sdp_token(data, o->nettype);
197 data += parse_sdp_token(data, o->addrtype);
198 data += parse_sdp_token(data, o->address);
202 int parse_sdp(const char* data, struct sdp_desc* sdp)
204 const char *data2 = data;
207 // SESSION DESCRIPTION
208 if(parse_sdp_type(&data, "v", value)) {
209 sdp->version = value;
213 if(parse_sdp_type(&data, "o", value)) {
218 if(parse_sdp_type(&data, "s", value)) {
223 if(parse_sdp_type(&data, "i", value))
226 if(parse_sdp_type(&data, "b", value))
227 sdp->bandwidth = value;
230 if(parse_sdp_type(&data, "a", value))
231 sdp->attributes.push_back(value);
239 if(parse_sdp_type(&data, "t", value))
244 if(parse_sdp_type(&data, "r", value))
247 sdp->times.push_back(time);
250 // MEDIA DESCRIPTIONS
252 sdp_desc_media media;
253 if(parse_sdp_type(&data, "m", value))
258 if(parse_sdp_type(&data, "i", value))
261 if(parse_sdp_type(&data, "c", value))
262 media.connection = value;
265 if(parse_sdp_type(&data, "a", value))
266 media.attributes.push_back(value);
271 sdp->media.push_back(media);
282 CSAPSessions::CSAPSessions() : CThread("SAPSessions")
284 m_socket = INVALID_SOCKET;
287 CSAPSessions::~CSAPSessions()
292 void CSAPSessions::StopThread(bool bWait /*= true*/)
294 if(m_socket != INVALID_SOCKET)
296 if(shutdown(m_socket, SHUT_RDWR) == SOCKET_ERROR)
297 CLog::Log(LOGERROR, "s - failed to shutdown socket");
298 #ifdef WINSOCK_VERSION
299 closesocket(m_socket);
302 CThread::StopThread(bWait);
305 bool CSAPSessions::ParseAnnounce(char* data, int len)
307 CSingleLock lock(m_section);
309 struct sap_desc header;
310 int size = parse_sap(data, len, &header);
313 CLog::Log(LOGERROR, "%s - failed to parse sap announcment", __FUNCTION__);
317 // we only want sdp payloads
318 if(header.payload_type != "application/sdp")
320 CLog::Log(LOGERROR, "%s - unknown payload type '%s'", __FUNCTION__, header.payload_type.c_str());
328 if(parse_sdp(data, &desc) < 0)
330 CLog::Log(LOGERROR, "%s - failed to parse sdp [ --->\n%s\n<--- ]", __FUNCTION__, data);
334 // check if we can find this session in our cache
335 for(std::vector<CSession>::iterator it = m_sessions.begin(); it != m_sessions.end(); it++)
337 if(it->origin == header.origin
338 && it->msgid == header.msgid
339 && it->payload_origin == desc.origin)
341 if(header.msgtype == 1)
343 CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
344 message.SetStringParam("sap://");
345 g_windowManager.SendThreadMessage(message);
346 m_sessions.erase(it);
350 // should be improved in the case of sdp
351 it->timeout = XbmcThreads::SystemClockMillis() + 60*60*1000;
356 if(header.msgtype == 1)
359 sdp_desc_origin origin;
360 if(parse_sdp_origin(desc.origin.c_str(), &origin) < 0)
362 CLog::Log(LOGERROR, "%s - failed to parse origin '%s'", __FUNCTION__, desc.origin.c_str());
366 // add a new session to our buffer
367 CStdString user = origin.username;
369 CStdString path = StringUtils::Format("sap://%s/%s/0x%x.sdp", header.origin.c_str(), desc.origin.c_str(), header.msgid);
372 session.origin = header.origin;
373 session.msgid = header.msgid;
374 session.payload_type = header.payload_type;
375 session.payload_origin = desc.origin;
376 session.payload.assign(data, len);
377 session.timeout = XbmcThreads::SystemClockMillis() + 60*60*1000;
378 m_sessions.push_back(session);
380 CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
381 message.SetStringParam("sap://");
382 g_windowManager.SendThreadMessage(message);
387 void CSAPSessions::Process()
391 m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
392 if(m_socket == INVALID_SOCKET)
395 #ifdef TARGET_WINDOWS
396 unsigned long nonblocking = 1;
397 ioctlsocket(m_socket, FIONBIO, &nonblocking);
399 fcntl(m_socket, F_SETFL, fcntl(m_socket, F_GETFL) | O_NONBLOCK);
403 setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
405 /* bind to SAP port */
406 struct sockaddr_in addr;
407 addr.sin_family = AF_INET;
408 addr.sin_addr.s_addr = INADDR_ANY;
409 addr.sin_port = htons(SAP_PORT);
410 if(bind(m_socket, (const sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
411 CLog::Log(LOGERROR, "CSAPSessions::Process - failed to bind to SAP port");
412 closesocket(m_socket);
413 m_socket = INVALID_SOCKET;
417 /* subscribe to all SAP multicast addresses */
418 mreq.imr_multiaddr.s_addr = inet_addr(SAP_V4_GLOBAL_ADDRESS);
419 mreq.imr_interface.s_addr = INADDR_ANY;
420 setsockopt(m_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
422 mreq.imr_multiaddr.s_addr = inet_addr(SAP_V4_ORG_ADDRESS);
423 mreq.imr_interface.s_addr = INADDR_ANY;
424 setsockopt(m_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
426 mreq.imr_multiaddr.s_addr = inet_addr(SAP_V4_LOCAL_ADDRESS);
427 mreq.imr_interface.s_addr = INADDR_ANY;
428 setsockopt(m_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
430 mreq.imr_multiaddr.s_addr = inet_addr(SAP_V4_LINK_ADDRESS);
431 mreq.imr_interface.s_addr = INADDR_ANY;
432 setsockopt(m_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mreq, sizeof(mreq));
434 /* start listening for announces */
435 fd_set readfds, expfds;
436 struct timeval timeout;
448 FD_SET(m_socket, &readfds);
449 FD_SET(m_socket, &expfds);
451 count = select((int)m_socket+1, &readfds, NULL, &expfds, &timeout);
452 if(count == SOCKET_ERROR) {
453 CLog::Log(LOGERROR, "%s - select returned error", __FUNCTION__);
457 if(FD_ISSET(m_socket, &readfds)) {
458 count = recv(m_socket, data, sizeof(data), 0);
460 if(count == SOCKET_ERROR) {
461 CLog::Log(LOGERROR, "%s - recv returned error", __FUNCTION__);
464 /* data must be string terminated for parsers */
466 ParseAnnounce(data, count);
471 closesocket(m_socket);
472 m_socket = INVALID_SOCKET;
478 CSAPDirectory::CSAPDirectory(void)
484 CSAPDirectory::~CSAPDirectory(void)
488 bool CSAPDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
490 if(strPath != "sap://")
493 CSingleLock lock(g_sapsessions.m_section);
495 if(!g_sapsessions.IsRunning())
496 g_sapsessions.Create();
498 // check if we can find this session in our cache
499 for(std::vector<CSAPSessions::CSession>::iterator it = g_sapsessions.m_sessions.begin(); it != g_sapsessions.m_sessions.end(); it++)
502 if(it->payload_type != "application/sdp")
504 CLog::Log(LOGDEBUG, "%s - unknown sdp payload type [%s]", __FUNCTION__, it->payload_type.c_str());
507 struct sdp_desc desc;
508 if(parse_sdp(it->payload.c_str(), &desc) <= 0)
510 CLog::Log(LOGDEBUG, "%s - invalid sdp payload [ --->\n%s\n<--- ]", __FUNCTION__, it->payload.c_str());
514 struct sdp_desc_origin origin;
515 if(parse_sdp_origin(desc.origin.c_str(), &origin) <= 0)
517 CLog::Log(LOGDEBUG, "%s - invalid sdp origin [ --->\n%s\n<--- ]", __FUNCTION__, desc.origin.c_str());
521 CFileItemPtr item(new CFileItem());
523 item->m_strTitle = desc.name;
524 item->SetLabel(item->m_strTitle);
525 if(desc.info.length() > 0 && desc.info != "N/A")
526 item->SetLabel2(desc.info);
527 item->SetLabelPreformated(true);
529 item->SetPath(it->path);