[cstdstring] demise Format, replacing with StringUtils::Format
[vuplus_xbmc] / xbmc / filesystem / SAPDirectory.cpp
1 /*
2  *  SAP-Announcement Support for XBMC
3  *      Copyright (c) 2008 elupus (Joakim Plate)
4  *      Copyright (C) 2008-2013 Team XBMC
5  *      http://xbmc.org
6  *
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)
10  *  any later version.
11  *
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.
16  *
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/>.
20  *
21  */
22
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"
27 #include <string.h>
28 #include "SAPDirectory.h"
29 #include "FileItem.h"
30 #include "threads/SingleLock.h"
31 #include "utils/log.h"
32 #include "utils/TimeUtils.h"
33 #include "utils/StringUtils.h"
34 #include "URL.h"
35 #if defined(TARGET_DARWIN)
36 #include "osx/OSXGNUReplacements.h" // strnlen
37 #endif
38 #ifdef TARGET_FREEBSD
39 #include "freebsd/FreeBSDGNUReplacements.h"
40 #endif
41
42 #include <sys/socket.h>
43 #include <netinet/in.h>
44 #include <arpa/inet.h>
45 #include <vector>
46
47 //using namespace std; On VS2010, bind conflicts with std::bind
48
49 CSAPSessions g_sapsessions;
50
51 namespace SDP
52 {
53   /* SAP is always on that port */
54   #define SAP_PORT 9875
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"
63   #define ADD_SESSION 1
64
65   #define SAP_V6_1 "FF0"
66   /* Scope is inserted between them */
67   #define SAP_V6_2 "::2:7FFE"
68
69   int parse_sap(const char* data, int len, struct sap_desc *h)
70   {
71     const char* data_orig = data;
72
73     if(len < 4)
74       return -1;
75
76     h->clear();
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;
82
83     h->authlen    =  data[1];
84     h->msgid      = ((data[2] << 8) | data[3]);
85
86     data += 4;
87     len  -= 4;
88
89     if(h->addrtype) {
90
91       if(len < 16) {
92         CLog::Log(LOGERROR, "%s - too little data for origin address", __FUNCTION__);
93         return -1;
94       }
95
96       CLog::Log(LOGERROR, "%s - ipv6 addresses currently unsupported", __FUNCTION__);
97       return -1;
98     } else {
99
100       if(len < 4) {
101         CLog::Log(LOGERROR, "%s - too little data for origin address", __FUNCTION__);
102         return -1;
103       }
104
105       h->origin = inet_ntoa(*(struct in_addr*)data);
106       data += 4;
107       len  -= 4;
108     }
109
110     /* skip authentication data */
111     data += h->authlen;
112     len  -= h->authlen;
113
114     /* payload type may be missing, then it's assumed to be sdp */
115     if(data[0] == 'v'
116     && data[1] == '='
117     && data[2] == '0') {
118       h->payload_type = "application/sdp";
119       return data - data_orig;
120     }
121
122     /* read payload type */
123     //no strnlen on non linux/win32 platforms, use handcrafted version
124     int payload_size = strnlen(data, len);
125
126     if (payload_size == len)
127       return -1;
128     h->payload_type.assign(data, payload_size);
129
130     data += payload_size+1;
131     len  -= payload_size+1;
132
133     return data - data_orig;
134   }
135
136   static int parse_sdp_line(const char* data, std::string& type, std::string& value)
137   {
138     const char* data2 = data;
139     int l;
140
141     l = strcspn(data, "=\n\r");
142     type.assign(data, l);
143     data += l;
144     if(*data == '=')
145       data++;
146
147     l = strcspn(data, "\n\r");
148     value.assign(data, l);
149     data += l;
150     data += strspn(data, "\n\r");
151
152     return data - data2;
153   }
154
155   static int parse_sdp_type(const char** data, std::string type, std::string& value)
156   {
157     std::string type2;
158     const char* data2 = *data;
159
160     while(*data2 != 0) {
161       data2 += parse_sdp_line(data2, type2, value);
162       if(type2 == type) {
163         *data = data2;
164         return 1;
165       }
166     }
167     return 0;
168   }
169
170   int parse_sdp_token(const char* data, std::string &value)
171   {
172     int l;
173     l = strcspn(data, " \n\r");
174     value.assign(data, l);
175     if(data[l] == '\0')
176       return l;
177     else
178       return l+1;
179   }
180
181   int parse_sdp_token(const char* data, int &value)
182   {
183     int l;
184     std::string str;
185     l = parse_sdp_token(data, str);
186     value = atoi(str.c_str());
187     return l;
188   }
189
190   int parse_sdp_origin(const char* data, struct sdp_desc_origin *o)
191   {
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);
199     return data - data2;
200   }
201
202   int parse_sdp(const char* data, struct sdp_desc* sdp)
203   {
204     const char *data2 = data;
205     std::string value;
206
207     // SESSION DESCRIPTION
208     if(parse_sdp_type(&data, "v", value)) {
209       sdp->version = value;
210     } else
211       return 0;
212
213     if(parse_sdp_type(&data, "o", value)) {
214       sdp->origin = value;
215     } else
216       return 0;
217
218     if(parse_sdp_type(&data, "s", value)) {
219       sdp->name = value;
220     } else
221       return 0;
222
223     if(parse_sdp_type(&data, "i", value))
224       sdp->info = value;
225
226     if(parse_sdp_type(&data, "b", value))
227       sdp->bandwidth = value;
228
229     while(true) {
230       if(parse_sdp_type(&data, "a", value))
231         sdp->attributes.push_back(value);
232       else
233         break;
234     }
235
236     // TIME DESCRIPTIONS
237     while(true) {
238       sdp_desc_time time;
239       if(parse_sdp_type(&data, "t", value))
240         time.active = value;
241       else
242         break;
243
244       if(parse_sdp_type(&data, "r", value))
245         time.repeat = value;
246
247       sdp->times.push_back(time);
248     }
249
250     // MEDIA DESCRIPTIONS
251     while(true) {
252       sdp_desc_media media;
253       if(parse_sdp_type(&data, "m", value))
254         media.name = value;
255       else
256         break;
257
258       if(parse_sdp_type(&data, "i", value))
259         media.title = value;
260
261       if(parse_sdp_type(&data, "c", value))
262         media.connection = value;
263
264       while(true) {
265         if(parse_sdp_type(&data, "a", value))
266           media.attributes.push_back(value);
267         else
268           break;
269       }
270
271       sdp->media.push_back(media);
272     }
273
274     return data - data2;
275   }
276 }
277
278
279 using namespace SDP;
280
281
282 CSAPSessions::CSAPSessions() : CThread("SAPSessions")
283 {
284   m_socket = INVALID_SOCKET;
285 }
286
287 CSAPSessions::~CSAPSessions()
288 {
289   StopThread();
290 }
291
292 void CSAPSessions::StopThread(bool bWait /*= true*/)
293 {
294   if(m_socket != INVALID_SOCKET)
295   {
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);
300 #endif
301   }
302   CThread::StopThread(bWait);
303 }
304
305 bool CSAPSessions::ParseAnnounce(char* data, int len)
306 {
307   CSingleLock lock(m_section);
308
309   struct sap_desc header;
310   int size = parse_sap(data, len, &header);
311   if(size < 0)
312   {
313     CLog::Log(LOGERROR, "%s - failed to parse sap announcment", __FUNCTION__);
314     return false;
315   }
316
317   // we only want sdp payloads
318   if(header.payload_type != "application/sdp")
319   {
320     CLog::Log(LOGERROR, "%s - unknown payload type '%s'", __FUNCTION__, header.payload_type.c_str());
321     return false;
322   }
323
324   data += size;
325   len  -= size;
326
327   sdp_desc desc;
328   if(parse_sdp(data, &desc) < 0)
329   {
330     CLog::Log(LOGERROR, "%s - failed to parse sdp [ --->\n%s\n<--- ]", __FUNCTION__, data);
331     return false;
332   }
333
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++)
336   {
337     if(it->origin         == header.origin
338     && it->msgid          == header.msgid
339     && it->payload_origin == desc.origin)
340     {
341       if(header.msgtype == 1)
342       {
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);
347         return true;
348       }
349
350       // should be improved in the case of sdp
351       it->timeout = XbmcThreads::SystemClockMillis() + 60*60*1000;
352       return true;
353     }
354   }
355
356   if(header.msgtype == 1)
357     return true;
358
359   sdp_desc_origin origin;
360   if(parse_sdp_origin(desc.origin.c_str(), &origin) < 0)
361   {
362     CLog::Log(LOGERROR, "%s - failed to parse origin '%s'", __FUNCTION__, desc.origin.c_str());
363     return false;
364   }
365
366   // add a new session to our buffer
367   CStdString user = origin.username;
368   CURL::Encode(user);
369   CStdString path = StringUtils::Format("sap://%s/%s/0x%x.sdp", header.origin.c_str(), desc.origin.c_str(), header.msgid);
370   CSession session;
371   session.path           = path;
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);
379
380   CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_PATH);
381   message.SetStringParam("sap://");
382   g_windowManager.SendThreadMessage(message);
383
384   return true;
385 }
386
387 void CSAPSessions::Process()
388 {
389   struct ip_mreq mreq;
390
391   m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
392   if(m_socket == INVALID_SOCKET)
393     return;
394
395 #ifdef TARGET_WINDOWS
396   unsigned long nonblocking = 1;
397   ioctlsocket(m_socket, FIONBIO, &nonblocking);
398 #else
399   fcntl(m_socket, F_SETFL, fcntl(m_socket, F_GETFL) | O_NONBLOCK);
400 #endif
401
402   const char one = 1;
403   setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
404
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;
414     return;
415   }
416
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));
421
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));
425
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));
429
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));
433
434   /* start listening for announces */
435   fd_set         readfds, expfds;
436   struct timeval timeout;
437   int count;
438
439   char data[65507+1];
440
441   while(!m_bStop)
442   {
443     timeout.tv_sec  = 5;
444     timeout.tv_usec = 0;
445
446     FD_ZERO(&readfds);
447     FD_ZERO(&expfds);
448     FD_SET(m_socket, &readfds);
449     FD_SET(m_socket, &expfds);
450
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__);
454       break;
455     }
456
457     if(FD_ISSET(m_socket, &readfds)) {
458       count = recv(m_socket, data, sizeof(data), 0);
459
460       if(count == SOCKET_ERROR) {
461         CLog::Log(LOGERROR, "%s - recv returned error", __FUNCTION__);
462         break;
463       }
464       /* data must be string terminated for parsers */
465       data[count] = '\0';
466       ParseAnnounce(data, count);
467     }
468
469   }
470
471   closesocket(m_socket);
472   m_socket = INVALID_SOCKET;
473 }
474
475 namespace XFILE
476 {
477
478   CSAPDirectory::CSAPDirectory(void)
479   {
480   }
481
482
483
484   CSAPDirectory::~CSAPDirectory(void)
485   {
486   }
487
488   bool CSAPDirectory::GetDirectory(const CStdString& strPath, CFileItemList &items)
489   {
490     if(strPath != "sap://")
491       return false;
492
493     CSingleLock lock(g_sapsessions.m_section);
494
495     if(!g_sapsessions.IsRunning())
496       g_sapsessions.Create();
497
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++)
500     {
501
502       if(it->payload_type != "application/sdp")
503       {
504         CLog::Log(LOGDEBUG, "%s - unknown sdp payload type [%s]", __FUNCTION__, it->payload_type.c_str());
505         continue;
506       }
507       struct sdp_desc desc;
508       if(parse_sdp(it->payload.c_str(), &desc) <= 0)
509       {
510         CLog::Log(LOGDEBUG, "%s - invalid sdp payload [ --->\n%s\n<--- ]", __FUNCTION__, it->payload.c_str());
511         continue;
512       }
513
514       struct sdp_desc_origin origin;
515       if(parse_sdp_origin(desc.origin.c_str(), &origin) <= 0)
516       {
517         CLog::Log(LOGDEBUG, "%s - invalid sdp origin [ --->\n%s\n<--- ]", __FUNCTION__, desc.origin.c_str());
518         continue;
519       }
520
521       CFileItemPtr item(new CFileItem());
522
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);
528
529       item->SetPath(it->path);
530       items.Add(item);
531     }
532
533     return true;
534   }
535
536
537 }
538