1 /*****************************************************************
2 |
3 |   Platinum - Control Point
4 |
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
6 | All rights reserved.
7 | http://www.plutinosoft.com
8 |
9 | This program is free software; you can redistribute it and/or
10 | modify it under the terms of the GNU General Public License
11 | as published by the Free Software Foundation; either version 2
12 | of the License, or (at your option) any later version.
13 |
14 | OEMs, ISVs, VARs and other distributors that combine and
15 | distribute commercially licensed software with Platinum software
16 | and do not wish to distribute the source code for the commercially
17 | licensed software under version 2, or (at your option) any later
18 | version, of the GNU General Public License (the "GPL") must enter
19 | into a commercial license agreement with Plutinosoft, LLC.
20 | licensing@plutinosoft.com
21 |
22 | This program is distributed in the hope that it will be useful,
23 | but WITHOUT ANY WARRANTY; without even the implied warranty of
24 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 | GNU General Public License for more details.
26 |
27 | You should have received a copy of the GNU General Public License
28 | along with this program; see the file LICENSE.txt. If not, write to
29 | the Free Software Foundation, Inc.,
30 | 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
31 | http://www.gnu.org/licenses/gpl-2.0.html
32 |
33 ****************************************************************/
34 
35 /*----------------------------------------------------------------------
36 |   includes
37 +---------------------------------------------------------------------*/
38 #include "PltCtrlPoint.h"
39 #include "PltUPnP.h"
40 #include "PltDeviceData.h"
41 #include "PltUtilities.h"
42 #include "PltCtrlPointTask.h"
43 #include "PltSsdp.h"
44 #include "PltHttpServer.h"
45 #include "PltConstants.h"
46 
47 NPT_SET_LOCAL_LOGGER("platinum.core.ctrlpoint")
48 
49 /*----------------------------------------------------------------------
50 |   PLT_CtrlPointListenerOnDeviceAddedIterator class
51 +---------------------------------------------------------------------*/
52 class PLT_CtrlPointListenerOnDeviceAddedIterator
53 {
54 public:
PLT_CtrlPointListenerOnDeviceAddedIterator(PLT_DeviceDataReference & device)55     PLT_CtrlPointListenerOnDeviceAddedIterator(PLT_DeviceDataReference& device) :
56         m_Device(device) {}
57 
operator ()(PLT_CtrlPointListener * & listener) const58     NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
59         return listener->OnDeviceAdded(m_Device);
60     }
61 
62 private:
63     PLT_DeviceDataReference& m_Device;
64 };
65 
66 /*----------------------------------------------------------------------
67 |   PLT_CtrlPointListenerOnDeviceRemovedIterator class
68 +---------------------------------------------------------------------*/
69 class PLT_CtrlPointListenerOnDeviceRemovedIterator
70 {
71 public:
PLT_CtrlPointListenerOnDeviceRemovedIterator(PLT_DeviceDataReference & device)72     PLT_CtrlPointListenerOnDeviceRemovedIterator(PLT_DeviceDataReference& device) :
73         m_Device(device) {}
74 
operator ()(PLT_CtrlPointListener * & listener) const75     NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
76         return listener->OnDeviceRemoved(m_Device);
77     }
78 
79 private:
80     PLT_DeviceDataReference& m_Device;
81 };
82 
83 /*----------------------------------------------------------------------
84 |   PLT_CtrlPointListenerOnActionResponseIterator class
85 +---------------------------------------------------------------------*/
86 class PLT_CtrlPointListenerOnActionResponseIterator
87 {
88 public:
PLT_CtrlPointListenerOnActionResponseIterator(NPT_Result res,PLT_ActionReference & action,void * userdata)89     PLT_CtrlPointListenerOnActionResponseIterator(NPT_Result           res,
90                                                   PLT_ActionReference& action,
91                                                   void*                userdata) :
92         m_Res(res), m_Action(action), m_Userdata(userdata) {}
93 
operator ()(PLT_CtrlPointListener * & listener) const94     NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
95         return listener->OnActionResponse(m_Res, m_Action, m_Userdata);
96     }
97 
98 private:
99     NPT_Result           m_Res;
100     PLT_ActionReference& m_Action;
101     void*                m_Userdata;
102 };
103 
104 /*----------------------------------------------------------------------
105 |   PLT_CtrlPointListenerOnEventNotifyIterator class
106 +---------------------------------------------------------------------*/
107 class PLT_CtrlPointListenerOnEventNotifyIterator
108 {
109 public:
PLT_CtrlPointListenerOnEventNotifyIterator(PLT_Service * service,NPT_List<PLT_StateVariable * > * vars)110     PLT_CtrlPointListenerOnEventNotifyIterator(PLT_Service*                  service,
111                                                NPT_List<PLT_StateVariable*>* vars) :
112         m_Service(service), m_Vars(vars) {}
113 
operator ()(PLT_CtrlPointListener * & listener) const114     NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
115         return listener->OnEventNotify(m_Service, m_Vars);
116     }
117 
118 private:
119     PLT_Service*                  m_Service;
120     NPT_List<PLT_StateVariable*>* m_Vars;
121 };
122 
123 /*----------------------------------------------------------------------
124 |   PLT_AddGetSCPDRequestIterator class
125 +---------------------------------------------------------------------*/
126 class PLT_AddGetSCPDRequestIterator
127 {
128 public:
PLT_AddGetSCPDRequestIterator(PLT_CtrlPointGetSCPDsTask & task,PLT_DeviceDataReference & device)129     PLT_AddGetSCPDRequestIterator(PLT_CtrlPointGetSCPDsTask& task,
130                                   PLT_DeviceDataReference&   device) :
131         m_Task(task), m_Device(device) {}
132 
operator ()(PLT_Service * & service) const133     NPT_Result operator()(PLT_Service*& service) const {
134         // look for the host and port of the device
135         NPT_String scpd_url = service->GetSCPDURL(true);
136 
137         NPT_LOG_FINER_3("Queueing SCPD request for service \"%s\" of device \"%s\" @ %s",
138             (const char*)service->GetServiceID(),
139             (const char*)service->GetDevice()->GetFriendlyName(),
140             (const char*)scpd_url);
141 
142         // verify url before queuing just in case
143         NPT_HttpUrl url(scpd_url);
144         if (!url.IsValid()) {
145             NPT_LOG_SEVERE_3("Invalid SCPD url \"%s\" for service \"%s\" of device \"%s\"!",
146                 (const char*)scpd_url,
147                 (const char*)service->GetServiceID(),
148                 (const char*)service->GetDevice()->GetFriendlyName());
149             return NPT_ERROR_INVALID_SYNTAX;
150         }
151 
152         // Create request and attach service to it
153         PLT_CtrlPointGetSCPDRequest* request =
154             new PLT_CtrlPointGetSCPDRequest((PLT_DeviceDataReference&)m_Device, scpd_url, "GET", NPT_HTTP_PROTOCOL_1_1);
155         return m_Task.AddSCPDRequest(request);
156     }
157 
158 private:
159     PLT_CtrlPointGetSCPDsTask& m_Task;
160     PLT_DeviceDataReference    m_Device;
161 };
162 
163 /*----------------------------------------------------------------------
164 |   PLT_EventSubscriberRemoverIterator class
165 +---------------------------------------------------------------------*/
166 // Note: The PLT_CtrlPoint::m_Lock must be acquired prior to using any
167 // function such as Apply on this iterator
168 class PLT_EventSubscriberRemoverIterator
169 {
170 public:
PLT_EventSubscriberRemoverIterator(PLT_CtrlPoint * ctrl_point)171     PLT_EventSubscriberRemoverIterator(PLT_CtrlPoint* ctrl_point) :
172         m_CtrlPoint(ctrl_point) {}
~PLT_EventSubscriberRemoverIterator()173     ~PLT_EventSubscriberRemoverIterator() {}
174 
operator ()(PLT_Service * & service) const175     NPT_Result operator()(PLT_Service*& service) const {
176         PLT_EventSubscriberReference sub;
177         if (NPT_SUCCEEDED(NPT_ContainerFind(m_CtrlPoint->m_Subscribers,
178                                             PLT_EventSubscriberFinderByService(service), sub))) {
179             NPT_LOG_INFO_1("Removed subscriber \"%s\"", (const char*)sub->GetSID());
180             m_CtrlPoint->m_Subscribers.Remove(sub);
181         }
182 
183         return NPT_SUCCESS;
184     }
185 
186 private:
187     PLT_CtrlPoint* m_CtrlPoint;
188 };
189 
190 /*----------------------------------------------------------------------
191 |   PLT_ServiceReadyIterator class
192 +---------------------------------------------------------------------*/
193 class PLT_ServiceReadyIterator
194 {
195 public:
PLT_ServiceReadyIterator()196     PLT_ServiceReadyIterator() {}
197 
operator ()(PLT_Service * & service) const198     NPT_Result operator()(PLT_Service*& service) const {
199         return service->IsValid()?NPT_SUCCESS:NPT_FAILURE;
200     }
201 };
202 
203 /*----------------------------------------------------------------------
204 |   PLT_DeviceReadyIterator class
205 +---------------------------------------------------------------------*/
206 class PLT_DeviceReadyIterator
207 {
208 public:
PLT_DeviceReadyIterator()209     PLT_DeviceReadyIterator() {}
operator ()(PLT_DeviceDataReference & device) const210     NPT_Result operator()(PLT_DeviceDataReference& device) const {
211         NPT_Result res = device->m_Services.ApplyUntil(
212             PLT_ServiceReadyIterator(),
213             NPT_UntilResultNotEquals(NPT_SUCCESS));
214         if (NPT_FAILED(res)) return res;
215 
216         res = device->m_EmbeddedDevices.ApplyUntil(
217             PLT_DeviceReadyIterator(),
218             NPT_UntilResultNotEquals(NPT_SUCCESS));
219         if (NPT_FAILED(res)) return res;
220 
221         // a device must have at least one service or embedded device
222         // otherwise it's not ready
223         if (device->m_Services.GetItemCount() == 0 &&
224             device->m_EmbeddedDevices.GetItemCount() == 0) {
225             return NPT_FAILURE;
226         }
227 
228         return NPT_SUCCESS;
229     }
230 };
231 
232 /*----------------------------------------------------------------------
233 |   PLT_CtrlPoint::PLT_CtrlPoint
234 +---------------------------------------------------------------------*/
PLT_CtrlPoint(const char * search_criteria)235 PLT_CtrlPoint::PLT_CtrlPoint(const char* search_criteria /* = "upnp:rootdevice" */) :
236     m_EventHttpServer(NULL),
237     m_TaskManager(NULL),
238 	m_Lock(true),
239     m_SearchCriteria(search_criteria),
240     m_Started(false)
241 {
242 }
243 
244 /*----------------------------------------------------------------------
245 |   PLT_CtrlPoint::~PLT_CtrlPoint
246 +---------------------------------------------------------------------*/
~PLT_CtrlPoint()247 PLT_CtrlPoint::~PLT_CtrlPoint()
248 {
249 }
250 
251 /*----------------------------------------------------------------------
252 |   PLT_CtrlPoint::IgnoreUUID
253 +---------------------------------------------------------------------*/
254 void
IgnoreUUID(const char * uuid)255 PLT_CtrlPoint::IgnoreUUID(const char* uuid)
256 {
257     if (!m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
258         m_UUIDsToIgnore.Add(uuid);
259     }
260 }
261 
262 /*----------------------------------------------------------------------
263 |   PLT_CtrlPoint::Start
264 +---------------------------------------------------------------------*/
265 NPT_Result
Start(PLT_SsdpListenTask * task)266 PLT_CtrlPoint::Start(PLT_SsdpListenTask* task)
267 {
268     if (m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
269 
270     m_TaskManager = new PLT_TaskManager();
271 
272     m_EventHttpServer = new PLT_HttpServer();
273     m_EventHttpServer->AddRequestHandler(new PLT_HttpRequestHandler(this), "/", true, true);
274     m_EventHttpServer->Start();
275 
276     // house keeping task
277     m_TaskManager->StartTask(new PLT_CtrlPointHouseKeepingTask(this));
278 
279     // add ourselves as an listener to SSDP multicast advertisements
280     task->AddListener(this);
281 
282     //
283     // use next line instead for DLNA testing, faster frequency for M-SEARCH
284     //return m_SearchCriteria.GetLength()?Search(NPT_HttpUrl("239.255.255.250", 1900, "*"), m_SearchCriteria, 1, 5000):NPT_SUCCESS;
285     //
286 
287     m_Started = true;
288 
289     return m_SearchCriteria.GetLength()?Search(NPT_HttpUrl("239.255.255.250", 1900, "*"), m_SearchCriteria):NPT_SUCCESS;
290 }
291 
292 /*----------------------------------------------------------------------
293 |   PLT_CtrlPoint::GetPort
294 +---------------------------------------------------------------------*/
295 NPT_Result
GetPort(NPT_UInt16 & port)296 PLT_CtrlPoint::GetPort(NPT_UInt16& port)
297 {
298     if (!m_Started) return NPT_ERROR_INVALID_STATE;
299 
300     port = m_EventHttpServer->GetPort();
301     return NPT_SUCCESS;
302 }
303 
304 /*----------------------------------------------------------------------
305 |   PLT_CtrlPoint::Stop
306 +---------------------------------------------------------------------*/
307 NPT_Result
Stop(PLT_SsdpListenTask * task)308 PLT_CtrlPoint::Stop(PLT_SsdpListenTask* task)
309 {
310     if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
311 
312     m_Started = false;
313 
314     task->RemoveListener(this);
315 
316     m_EventHttpServer->Stop();
317     m_TaskManager->Abort();
318 
319     // force remove all devices
320     NPT_List<PLT_DeviceDataReference>::Iterator iter = m_RootDevices.GetFirstItem();
321     while (iter) {
322         NotifyDeviceRemoved(*iter);
323         ++iter;
324     }
325 
326     // we can safely clear everything without a lock
327     // as there are no more tasks pending
328     m_RootDevices.Clear();
329     m_Subscribers.Clear();
330 
331     m_EventHttpServer = NULL;
332     m_TaskManager = NULL;
333 
334     return NPT_SUCCESS;
335 }
336 
337 /*----------------------------------------------------------------------
338 |   PLT_CtrlPoint::AddListener
339 +---------------------------------------------------------------------*/
340 NPT_Result
AddListener(PLT_CtrlPointListener * listener)341 PLT_CtrlPoint::AddListener(PLT_CtrlPointListener* listener)
342 {
343     NPT_AutoLock lock(m_Lock);
344     if (!m_ListenerList.Contains(listener)) {
345         m_ListenerList.Add(listener);
346     }
347     return NPT_SUCCESS;
348 }
349 
350 /*----------------------------------------------------------------------
351 |   PLT_CtrlPoint::RemoveListener
352 +---------------------------------------------------------------------*/
353 NPT_Result
RemoveListener(PLT_CtrlPointListener * listener)354 PLT_CtrlPoint::RemoveListener(PLT_CtrlPointListener* listener)
355 {
356     NPT_AutoLock lock(m_Lock);
357     m_ListenerList.Remove(listener);
358     return NPT_SUCCESS;
359 }
360 
361 /*----------------------------------------------------------------------
362 |   PLT_CtrlPoint::CreateSearchTask
363 +---------------------------------------------------------------------*/
364 PLT_SsdpSearchTask*
CreateSearchTask(const NPT_HttpUrl & url,const char * target,NPT_Cardinal mx,NPT_TimeInterval frequency,const NPT_IpAddress & address)365 PLT_CtrlPoint::CreateSearchTask(const NPT_HttpUrl&   url,
366                                 const char*          target,
367                                 NPT_Cardinal         mx,
368                                 NPT_TimeInterval     frequency,
369                                 const NPT_IpAddress& address)
370 {
371     // make sure mx is at least 1
372     if (mx<1) mx=1;
373 
374     // create socket
375     NPT_Reference<NPT_UdpMulticastSocket> socket(new NPT_UdpMulticastSocket(NPT_SOCKET_FLAG_CANCELLABLE));
376     socket->SetInterface(address);
377     socket->SetTimeToLive(PLT_Constants::GetInstance().GetSearchMulticastTimeToLive());
378 
379     // bind to something > 1024 and different than 1900
380     int retries = 20;
381     do {
382         int random = NPT_System::GetRandomInteger();
383         int port = (unsigned short)(1024 + (random % 15000));
384         if (port == 1900) continue;
385 
386         if (NPT_SUCCEEDED(socket->Bind(
387             NPT_SocketAddress(NPT_IpAddress::Any, port),
388             false)))
389             break;
390 
391     } while (--retries);
392 
393     if (retries == 0) {
394         NPT_LOG_SEVERE("Couldn't bind socket for Search Task");
395         return NULL;
396     }
397 
398     // create request
399     NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1);
400     PLT_UPnPMessageHelper::SetMX(*request, mx);
401     PLT_UPnPMessageHelper::SetST(*request, target);
402     PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\"");
403     request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, *PLT_Constants::GetInstance().GetDefaultUserAgent());
404 
405     // create task
406     PLT_SsdpSearchTask* task = new PLT_SsdpSearchTask(
407         socket.AsPointer(),
408         this,
409         request,
410         (frequency.ToMillis()>0 && frequency.ToMillis()<5000)?NPT_TimeInterval(5.):frequency);  /* repeat no less than every 5 secs */
411     socket.Detach();
412 
413     return task;
414 }
415 
416 /*----------------------------------------------------------------------
417 |   PLT_CtrlPoint::Search
418 +---------------------------------------------------------------------*/
419 NPT_Result
Search(const NPT_HttpUrl & url,const char * target,NPT_Cardinal mx,NPT_TimeInterval frequency,NPT_TimeInterval initial_delay)420 PLT_CtrlPoint::Search(const NPT_HttpUrl& url,
421                       const char*        target,
422                       NPT_Cardinal       mx /* = 5 */,
423                       NPT_TimeInterval   frequency /* = NPT_TimeInterval(50.) */,
424                       NPT_TimeInterval   initial_delay /* = NPT_TimeInterval(0.) */)
425 {
426     if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
427 
428     NPT_List<NPT_NetworkInterface*> if_list;
429     NPT_List<NPT_NetworkInterface*>::Iterator net_if;
430     NPT_List<NPT_NetworkInterfaceAddress>::Iterator net_if_addr;
431 
432     NPT_CHECK_SEVERE(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list, true));
433 
434     for (net_if = if_list.GetFirstItem();
435          net_if;
436          net_if++) {
437         // make sure the interface is at least broadcast or multicast
438         if (!((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_MULTICAST) &&
439             !((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_BROADCAST)) {
440             continue;
441         }
442 
443         for (net_if_addr = (*net_if)->GetAddresses().GetFirstItem();
444              net_if_addr;
445              net_if_addr++) {
446             // create task
447             PLT_SsdpSearchTask* task = CreateSearchTask(url,
448                 target,
449                 mx,
450                 frequency,
451                 (*net_if_addr).GetPrimaryAddress());
452             m_TaskManager->StartTask(task, &initial_delay);
453         }
454     }
455 
456     if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
457     return NPT_SUCCESS;
458 }
459 
460 /*----------------------------------------------------------------------
461 |   PLT_CtrlPoint::Discover
462 +---------------------------------------------------------------------*/
463 NPT_Result
Discover(const NPT_HttpUrl & url,const char * target,NPT_Cardinal mx,NPT_TimeInterval frequency,NPT_TimeInterval initial_delay)464 PLT_CtrlPoint::Discover(const NPT_HttpUrl& url,
465                         const char*        target,
466                         NPT_Cardinal       mx, /* = 5 */
467                         NPT_TimeInterval   frequency /* = NPT_TimeInterval(50.) */,
468                         NPT_TimeInterval   initial_delay /* = NPT_TimeInterval(0.) */)
469 {
470     if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
471 
472     // make sure mx is at least 1
473     if (mx<1) mx = 1;
474 
475     // create socket
476     NPT_UdpSocket* socket = new NPT_UdpSocket(NPT_SOCKET_FLAG_CANCELLABLE);
477 
478     // create request
479     NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1);
480     PLT_UPnPMessageHelper::SetMX(*request, mx);
481     PLT_UPnPMessageHelper::SetST(*request, target);
482     PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\"");
483     request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, *PLT_Constants::GetInstance().GetDefaultUserAgent());
484 
485     // force HOST to be the regular multicast address:port
486     // Some servers do care (like WMC) otherwise they won't respond to us
487     request->GetHeaders().SetHeader(NPT_HTTP_HEADER_HOST, "239.255.255.250:1900");
488 
489     // create task
490     PLT_ThreadTask* task = new PLT_SsdpSearchTask(
491         socket,
492         this,
493         request,
494         (frequency.ToMillis()>0 && frequency.ToMillis()<5000)?NPT_TimeInterval(5.):frequency);  /* repeat no less than every 5 secs */
495     return m_TaskManager->StartTask(task, &initial_delay);
496 }
497 
498 /*----------------------------------------------------------------------
499 |   PLT_CtrlPoint::DoHouseKeeping
500 +---------------------------------------------------------------------*/
501 NPT_Result
DoHouseKeeping()502 PLT_CtrlPoint::DoHouseKeeping()
503 {
504     NPT_List<PLT_DeviceDataReference> devices_to_remove;
505 
506     // remove expired devices
507     {
508         NPT_AutoLock lock(m_Lock);
509 
510         PLT_DeviceDataReference head, device;
511         while (NPT_SUCCEEDED(m_RootDevices.PopHead(device))) {
512             NPT_TimeStamp    last_update = device->GetLeaseTimeLastUpdate();
513             NPT_TimeInterval lease_time  = device->GetLeaseTime();
514 
515             // check if device lease time has expired or if failed to renew subscribers
516             // TODO: UDA 1.1 says that root device and all embedded devices must have expired
517             // before we can assume they're all no longer unavailable (we may have missed the root device renew)
518             NPT_TimeStamp now;
519             NPT_System::GetCurrentTimeStamp(now);
520             if (now > last_update + NPT_TimeInterval((double)lease_time*2)) {
521                 devices_to_remove.Add(device);
522             } else {
523                 // add the device back to our list since it is still alive
524                 m_RootDevices.Add(device);
525 
526                 // keep track of first device added back to list
527                 // to know we checked all devices in initial list
528                 if (head.IsNull()) head = device;
529             }
530 
531             // have we exhausted initial list?
532             if (!head.IsNull() && head == *m_RootDevices.GetFirstItem())
533                 break;
534         };
535     }
536 
537     // remove old devices
538     {
539         NPT_AutoLock lock(m_Lock);
540 
541         for (NPT_List<PLT_DeviceDataReference>::Iterator device =
542              devices_to_remove.GetFirstItem();
543              device;
544              device++) {
545              RemoveDevice(*device);
546         }
547     }
548 
549     // renew subscribers of subscribed device services
550     NPT_List<PLT_ThreadTask*> tasks;
551     {
552         NPT_AutoLock lock(m_Lock);
553 
554         NPT_List<PLT_EventSubscriberReference>::Iterator sub = m_Subscribers.GetFirstItem();
555         while (sub) {
556             NPT_TimeStamp now;
557             NPT_System::GetCurrentTimeStamp(now);
558 
559             // time to renew if within 90 secs of expiration
560             if (now > (*sub)->GetExpirationTime() - NPT_TimeStamp(90.)) {
561                 PLT_ThreadTask* task = RenewSubscriber(*sub);
562                 if (task) tasks.Add(task);
563             }
564             sub++;
565         }
566     }
567 
568     // Queue up all tasks now outside of lock, in case they
569     // block because the task manager has maxed out number of running tasks
570     // and to avoid a deadlock with tasks trying to acquire the lock in the response
571     NPT_List<PLT_ThreadTask*>::Iterator task = tasks.GetFirstItem();
572     while (task) {
573         PLT_ThreadTask* _task = *task++;
574         m_TaskManager->StartTask(_task);
575     }
576 
577     return NPT_SUCCESS;
578 }
579 
580 /*----------------------------------------------------------------------
581 |   PLT_CtrlPoint::FindDevice
582 +---------------------------------------------------------------------*/
583 NPT_Result
FindDevice(const char * uuid,PLT_DeviceDataReference & device,bool return_root)584 PLT_CtrlPoint::FindDevice(const char*              uuid,
585                           PLT_DeviceDataReference& device,
586                           bool                     return_root /* = false */)
587 {
588     NPT_List<PLT_DeviceDataReference>::Iterator iter = m_RootDevices.GetFirstItem();
589     while (iter) {
590         // device uuid found immediately as root device
591         if ((*iter)->GetUUID().Compare(uuid) == 0) {
592             device = *iter;
593             return NPT_SUCCESS;
594         } else if (NPT_SUCCEEDED((*iter)->FindEmbeddedDevice(uuid, device))) {
595             // we found the uuid as an embedded device of this root
596             // return root if told, otherwise return found embedded device
597             if (return_root) device = (*iter);
598             return NPT_SUCCESS;
599         }
600         ++iter;
601     }
602 
603     return NPT_ERROR_NO_SUCH_ITEM;
604 }
605 
606 /*----------------------------------------------------------------------
607 |   PLT_CtrlPoint::FindActionDesc
608 +---------------------------------------------------------------------*/
609 NPT_Result
FindActionDesc(PLT_DeviceDataReference & device,const char * service_type,const char * action_name,PLT_ActionDesc * & action_desc)610 PLT_CtrlPoint::FindActionDesc(PLT_DeviceDataReference& device,
611                               const char*              service_type,
612                               const char*              action_name,
613                               PLT_ActionDesc*&         action_desc)
614 {
615     if (device.IsNull()) return NPT_ERROR_INVALID_PARAMETERS;
616 
617     // look for the service
618     PLT_Service* service;
619     if (NPT_FAILED(device->FindServiceByType(service_type, service))) {
620         NPT_LOG_FINE_1("Service %s not found", (const char*)service_type);
621         return NPT_FAILURE;
622     }
623 
624     action_desc = service->FindActionDesc(action_name);
625     if (action_desc == NULL) {
626         NPT_LOG_FINE_1("Action %s not found in service", action_name);
627         return NPT_FAILURE;
628     }
629 
630     return NPT_SUCCESS;
631 }
632 
633 /*----------------------------------------------------------------------
634 |   PLT_CtrlPoint::CreateAction
635 +---------------------------------------------------------------------*/
636 NPT_Result
CreateAction(PLT_DeviceDataReference & device,const char * service_type,const char * action_name,PLT_ActionReference & action)637 PLT_CtrlPoint::CreateAction(PLT_DeviceDataReference& device,
638                             const char*              service_type,
639                             const char*              action_name,
640                             PLT_ActionReference&     action)
641 {
642     if (device.IsNull()) return NPT_ERROR_INVALID_PARAMETERS;
643 
644     NPT_AutoLock lock(m_Lock);
645 
646     PLT_ActionDesc* action_desc;
647     NPT_CHECK_SEVERE(FindActionDesc(device,
648         service_type,
649         action_name,
650         action_desc));
651 
652     PLT_DeviceDataReference root_device;
653     NPT_CHECK_SEVERE(FindDevice(device->GetUUID(), root_device, true));
654 
655     action = new PLT_Action(*action_desc, root_device);
656     return NPT_SUCCESS;
657 }
658 
659 /*----------------------------------------------------------------------
660 |   PLT_CtrlPoint::SetupResponse
661 +---------------------------------------------------------------------*/
662 NPT_Result
SetupResponse(NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse & response)663 PLT_CtrlPoint::SetupResponse(NPT_HttpRequest&              request,
664                              const NPT_HttpRequestContext& context,
665                              NPT_HttpResponse&             response)
666 {
667     NPT_COMPILER_UNUSED(context);
668 
669     if (request.GetMethod().Compare("NOTIFY") == 0) {
670         return ProcessHttpNotify(request, context, response);
671     }
672 
673     NPT_LOG_SEVERE("CtrlPoint received bad http request\r\n");
674     response.SetStatus(412, "Precondition Failed");
675     return NPT_SUCCESS;
676 }
677 
678 /*----------------------------------------------------------------------
679 |   PLT_CtrlPoint::DecomposeLastChangeVar
680 +---------------------------------------------------------------------*/
681 NPT_Result
DecomposeLastChangeVar(NPT_List<PLT_StateVariable * > & vars)682 PLT_CtrlPoint::DecomposeLastChangeVar(NPT_List<PLT_StateVariable*>& vars)
683 {
684     // parse LastChange var into smaller vars
685     PLT_StateVariable* lastChangeVar = NULL;
686     if (NPT_SUCCEEDED(NPT_ContainerFind(vars,
687                                         PLT_StateVariableNameFinder("LastChange"),
688                                         lastChangeVar))) {
689         vars.Remove(lastChangeVar);
690         PLT_Service* var_service = lastChangeVar->GetService();
691         NPT_String text = lastChangeVar->GetValue();
692 
693         NPT_XmlNode* xml = NULL;
694         NPT_XmlParser parser;
695         if (NPT_FAILED(parser.Parse(text, xml)) || !xml || !xml->AsElementNode()) {
696             delete xml;
697             return NPT_ERROR_INVALID_FORMAT;
698         }
699 
700         NPT_XmlElementNode* node = xml->AsElementNode();
701         if (!node->GetTag().Compare("Event", true)) {
702             // look for the instance with attribute id = 0
703             NPT_XmlElementNode* instance = NULL;
704             for (NPT_Cardinal i=0; i<node->GetChildren().GetItemCount(); i++) {
705                 NPT_XmlElementNode* child;
706                 if (NPT_FAILED(PLT_XmlHelper::GetChild(node, child, i)))
707                     continue;
708 
709                 if (!child->GetTag().Compare("InstanceID", true)) {
710                     // extract the "val" attribute value
711                     NPT_String value;
712                     if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(child, "val", value)) &&
713                         !value.Compare("0")) {
714                         instance = child;
715                         break;
716                     }
717                 }
718             }
719 
720             // did we find an instance with id = 0 ?
721             if (instance != NULL) {
722                 // all the children of the Instance node are state variables
723                 for (NPT_Cardinal j=0; j<instance->GetChildren().GetItemCount(); j++) {
724                     NPT_XmlElementNode* var_node;
725                     if (NPT_FAILED(PLT_XmlHelper::GetChild(instance, var_node, j)))
726                         continue;
727 
728                     // look for the state variable in this service
729                     const NPT_String* value = var_node->GetAttribute("val");
730                     PLT_StateVariable* var = var_service->FindStateVariable(var_node->GetTag());
731                     if (value != NULL && var != NULL) {
732                         // get the value and set the state variable
733                         // if it succeeded, add it to the list of vars we'll event
734                         if (NPT_SUCCEEDED(var->SetValue(*value))) {
735                             vars.Add(var);
736                             NPT_LOG_FINE_2("LastChange var change for (%s): %s",
737                                            (const char*)var->GetName(),
738                                            (const char*)var->GetValue());
739                         }
740                     }
741                 }
742             }
743         }
744         delete xml;
745     }
746 
747     return NPT_SUCCESS;
748 }
749 
750 /*----------------------------------------------------------------------
751 |   PLT_CtrlPoint::ProcessEventNotification
752 +---------------------------------------------------------------------*/
753 NPT_Result
ProcessEventNotification(PLT_EventSubscriberReference subscriber,PLT_EventNotification * notification,NPT_List<PLT_StateVariable * > & vars)754 PLT_CtrlPoint::ProcessEventNotification(PLT_EventSubscriberReference subscriber,
755                                         PLT_EventNotification*       notification,
756                                         NPT_List<PLT_StateVariable*> &vars)
757 {
758     NPT_XmlElementNode* xml = NULL;
759     PLT_Service* service = subscriber->GetService();
760     PLT_DeviceData* device  = service->GetDevice();
761 
762     NPT_String uuid = device->GetUUID();
763     NPT_String service_id = service->GetServiceID();
764 
765     // callback uri for this sub
766     NPT_String callback_uri = "/" + uuid + "/" + service_id;
767 
768     if (notification->m_RequestUrl.GetPath().Compare(callback_uri, true)) {
769         NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
770     }
771 
772     // if the sequence number is less than our current one, we got it out of order
773     // so we disregard it
774     if (subscriber->GetEventKey() && notification->m_EventKey < subscriber->GetEventKey()) {
775         NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
776     }
777 
778     // parse body
779     if (NPT_FAILED(PLT_XmlHelper::Parse(notification->m_XmlBody, xml))) {
780         NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
781     }
782 
783     // check envelope
784     if (xml->GetTag().Compare("propertyset", true)) {
785         NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
786     }
787 
788     // check property set
789     // keep a vector of the state variables that changed
790     NPT_XmlElementNode* property;
791     PLT_StateVariable*  var;
792     for (NPT_List<NPT_XmlNode*>::Iterator children = xml->GetChildren().GetFirstItem();
793          children;
794          children++) {
795         NPT_XmlElementNode* child = (*children)->AsElementNode();
796         if (!child) continue;
797 
798         // check property
799         if (child->GetTag().Compare("property", true)) continue;
800 
801         if (NPT_FAILED(PLT_XmlHelper::GetChild(child, property))) {
802             NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
803         }
804 
805         var = service->FindStateVariable(property->GetTag());
806         if (var == NULL) continue;
807 
808         if (NPT_FAILED(var->SetValue(property->GetText()?*property->GetText():""))) {
809             NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
810         }
811 
812         vars.Add(var);
813     }
814 
815     // update sequence
816     subscriber->SetEventKey(notification->m_EventKey);
817 
818     // Look if a state variable LastChange was received and decompose it into
819     // independent state variable updates
820     DecomposeLastChangeVar(vars);
821 
822     delete xml;
823     return NPT_SUCCESS;
824 
825 failure:
826     NPT_LOG_SEVERE("CtrlPoint failed to process event notification");
827     delete xml;
828     return NPT_SUCCESS;
829 }
830 
831 /*----------------------------------------------------------------------
832 |   PLT_CtrlPoint::AddPendingEventNotification
833 +---------------------------------------------------------------------*/
834 NPT_Result
AddPendingEventNotification(PLT_EventNotification * notification)835 PLT_CtrlPoint::AddPendingEventNotification(PLT_EventNotification *notification)
836 {
837     // Only keep a maximum of 20 pending notifications
838     while (m_PendingNotifications.GetItemCount() > 20) {
839         PLT_EventNotification *garbage = NULL;
840         m_PendingNotifications.PopHead(garbage);
841         delete garbage;
842     }
843 
844     m_PendingNotifications.Add(notification);
845     return NPT_SUCCESS;
846 }
847 
848 /*----------------------------------------------------------------------
849 |   PLT_CtrlPoint::ProcessPendingEventNotifications
850 +---------------------------------------------------------------------*/
851 NPT_Result
ProcessPendingEventNotifications()852 PLT_CtrlPoint::ProcessPendingEventNotifications()
853 {
854     NPT_Cardinal count = m_PendingNotifications.GetItemCount();
855     while (count--) {
856         NPT_List<PLT_StateVariable*> vars;
857         PLT_Service *service = NULL;
858         PLT_EventNotification *notification;
859 
860         if (NPT_SUCCEEDED(m_PendingNotifications.PopHead(notification))) {
861             PLT_EventSubscriberReference sub;
862 
863             // look for the subscriber with that sid
864             if (NPT_FAILED(NPT_ContainerFind(m_Subscribers,
865                                              PLT_EventSubscriberFinderBySID(notification->m_SID),
866                                              sub))) {
867                 m_PendingNotifications.Add(notification);
868                 continue;
869             }
870 
871             // keep track of service for listeners later
872             service = sub->GetService();
873 
874             // Reprocess notification
875             NPT_LOG_WARNING_1("Reprocessing delayed notification for subscriber %s", (const char*)notification->m_SID);
876             NPT_Result result = ProcessEventNotification(sub, notification, vars);
877             delete notification;
878 
879             if (NPT_FAILED(result)) continue;
880         }
881 
882         // notify listeners
883         if (service && vars.GetItemCount()) {
884             m_ListenerList.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service, &vars));
885         }
886     }
887 
888     return NPT_SUCCESS;
889 }
890 
891 /*----------------------------------------------------------------------
892 |   PLT_CtrlPoint::ProcessHttpNotify
893 +---------------------------------------------------------------------*/
894 NPT_Result
ProcessHttpNotify(const NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse & response)895 PLT_CtrlPoint::ProcessHttpNotify(const NPT_HttpRequest&        request,
896                                  const NPT_HttpRequestContext& context,
897                                  NPT_HttpResponse&             response)
898 {
899     NPT_COMPILER_UNUSED(context);
900 
901     NPT_AutoLock lock(m_Lock);
902 
903     NPT_List<PLT_StateVariable*> vars;
904     PLT_Service* service = NULL;
905     PLT_EventSubscriberReference sub;
906     NPT_Result result;
907 
908     PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINER, "PLT_CtrlPoint::ProcessHttpNotify:", &request);
909 
910     // Create notification from request
911     PLT_EventNotification* notification = PLT_EventNotification::Parse(request, context, response);
912     NPT_CHECK_POINTER_LABEL_WARNING(notification, bad_request);
913 
914     // Give a last change to process pending notifications before throwing them out
915     // by AddPendingNotification
916     ProcessPendingEventNotifications();
917 
918     // look for the subscriber with that sid
919     if (NPT_FAILED(NPT_ContainerFind(m_Subscribers,
920                                      PLT_EventSubscriberFinderBySID(notification->m_SID),
921                                      sub))) {
922         NPT_LOG_WARNING_1("Subscriber %s not found, delaying notification process.\n", (const char*)notification->m_SID);
923         AddPendingEventNotification(notification);
924         return NPT_SUCCESS;
925     }
926 
927     // Process notification for subscriber
928     service = sub->GetService();
929     result  = ProcessEventNotification(sub, notification, vars);
930     delete notification;
931 
932     NPT_CHECK_LABEL_WARNING(result, bad_request);
933 
934     // Notify listeners
935     if (vars.GetItemCount()) {
936         m_ListenerList.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service, &vars));
937     }
938 
939     return NPT_SUCCESS;
940 
941 bad_request:
942     NPT_LOG_SEVERE("CtrlPoint received bad event notify request\r\n");
943     if (response.GetStatusCode() == 200) {
944         response.SetStatus(412, "Precondition Failed");
945     }
946     return NPT_SUCCESS;
947 }
948 
949 /*----------------------------------------------------------------------
950 |   PLT_CtrlPoint::ProcessSsdpSearchResponse
951 +---------------------------------------------------------------------*/
952 NPT_Result
ProcessSsdpSearchResponse(NPT_Result res,const NPT_HttpRequestContext & context,NPT_HttpResponse * response)953 PLT_CtrlPoint::ProcessSsdpSearchResponse(NPT_Result                    res,
954                                          const NPT_HttpRequestContext& context,
955                                          NPT_HttpResponse*             response)
956 {
957     NPT_CHECK_SEVERE(res);
958     NPT_CHECK_POINTER_SEVERE(response);
959 
960     NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
961     NPT_String protocol   = response->GetProtocol();
962 
963     NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessSsdpSearchResponse from %s:%d",
964         (const char*)context.GetRemoteAddress().GetIpAddress().ToString() ,
965         context.GetRemoteAddress().GetPort());
966     PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, prefix, response);
967 
968     // any 2xx responses are ok
969     if (response->GetStatusCode()/100 == 2) {
970         const NPT_String* st  = response->GetHeaders().GetHeaderValue("st");
971         const NPT_String* usn = response->GetHeaders().GetHeaderValue("usn");
972         const NPT_String* ext = response->GetHeaders().GetHeaderValue("ext");
973         NPT_CHECK_POINTER_SEVERE(st);
974         NPT_CHECK_POINTER_SEVERE(usn);
975         NPT_CHECK_POINTER_SEVERE(ext);
976 
977         NPT_String uuid;
978 
979         // if we get an advertisement other than uuid
980         // verify it's formatted properly
981         if (usn != st) {
982             NPT_List<NPT_String> components = usn->Split("::");
983             if (components.GetItemCount() != 2)
984                 return NPT_FAILURE;
985 
986             if (st->Compare(*components.GetItem(1), true))
987                 return NPT_FAILURE;
988 
989             uuid = components.GetItem(0)->SubString(5);
990         } else {
991             uuid = usn->SubString(5);
992         }
993 
994         if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
995             NPT_LOG_FINE_1("CtrlPoint received a search response from ourselves (%s)\n", (const char*)uuid);
996             return NPT_SUCCESS;
997         }
998 
999         return ProcessSsdpMessage(*response, context, uuid);
1000     }
1001 
1002     return NPT_FAILURE;
1003 }
1004 
1005 /*----------------------------------------------------------------------
1006 |   PLT_CtrlPoint::OnSsdpPacket
1007 +---------------------------------------------------------------------*/
1008 NPT_Result
OnSsdpPacket(const NPT_HttpRequest & request,const NPT_HttpRequestContext & context)1009 PLT_CtrlPoint::OnSsdpPacket(const NPT_HttpRequest&        request,
1010                             const NPT_HttpRequestContext& context)
1011 {
1012     return ProcessSsdpNotify(request, context);
1013 }
1014 
1015 /*----------------------------------------------------------------------
1016 |   PLT_CtrlPoint::ProcessSsdpNotify
1017 +---------------------------------------------------------------------*/
1018 NPT_Result
ProcessSsdpNotify(const NPT_HttpRequest & request,const NPT_HttpRequestContext & context)1019 PLT_CtrlPoint::ProcessSsdpNotify(const NPT_HttpRequest&        request,
1020                                  const NPT_HttpRequestContext& context)
1021 {
1022     // get the address of who sent us some data back
1023     NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
1024     NPT_String method     = request.GetMethod();
1025     NPT_String uri        = request.GetUrl().GetPath(true);
1026     NPT_String protocol   = request.GetProtocol();
1027 
1028     if (method.Compare("NOTIFY") == 0) {
1029         const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request);
1030         const NPT_String* nt  = PLT_UPnPMessageHelper::GetNT(request);
1031         const NPT_String* usn = PLT_UPnPMessageHelper::GetUSN(request);
1032 
1033         NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessSsdpNotify from %s:%d (%s)",
1034             context.GetRemoteAddress().GetIpAddress().ToString().GetChars(),
1035             context.GetRemoteAddress().GetPort(),
1036             usn?usn->GetChars():"unknown");
1037         PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINER, prefix, &request);
1038 
1039         if ((uri.Compare("*") != 0) || (protocol.Compare("HTTP/1.1") != 0))
1040             return NPT_FAILURE;
1041 
1042         NPT_CHECK_POINTER_SEVERE(nts);
1043         NPT_CHECK_POINTER_SEVERE(nt);
1044         NPT_CHECK_POINTER_SEVERE(usn);
1045 
1046         NPT_String uuid;
1047 
1048         // if we get an advertisement other than uuid
1049         // verify it's formatted properly
1050         if (*usn != *nt) {
1051             NPT_List<NPT_String> components = usn->Split("::");
1052             if (components.GetItemCount() != 2)
1053                 return NPT_FAILURE;
1054 
1055             if (nt->Compare(*components.GetItem(1), true))
1056                 return NPT_FAILURE;
1057 
1058             uuid = components.GetItem(0)->SubString(5);
1059         } else {
1060             uuid = usn->SubString(5);
1061         }
1062 
1063         if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
1064             NPT_LOG_FINE_1("Received a NOTIFY request from ourselves (%s)\n", (const char*)uuid);
1065             return NPT_SUCCESS;
1066         }
1067 
1068         // if it's a byebye, remove the device and return right away
1069         if (nts->Compare("ssdp:byebye", true) == 0) {
1070             NPT_LOG_INFO_1("Received a byebye NOTIFY request from %s\n", (const char*)uuid);
1071 
1072             NPT_AutoLock lock(m_Lock);
1073 
1074             // look for root device
1075             PLT_DeviceDataReference root_device;
1076             FindDevice(uuid, root_device, true);
1077 
1078             if (!root_device.IsNull()) RemoveDevice(root_device);
1079             return NPT_SUCCESS;
1080         }
1081 
1082         return ProcessSsdpMessage(request, context, uuid);
1083     }
1084 
1085     return NPT_FAILURE;
1086 }
1087 
1088 /*----------------------------------------------------------------------
1089 |   PLT_CtrlPoint::AddDevice
1090 +---------------------------------------------------------------------*/
1091 NPT_Result
AddDevice(PLT_DeviceDataReference & data)1092 PLT_CtrlPoint::AddDevice(PLT_DeviceDataReference& data)
1093 {
1094     NPT_AutoLock lock(m_Lock);
1095 
1096     return NotifyDeviceReady(data);
1097 }
1098 
1099 /*----------------------------------------------------------------------
1100 |   PLT_CtrlPoint::NotifyDeviceReady
1101 +---------------------------------------------------------------------*/
1102 NPT_Result
NotifyDeviceReady(PLT_DeviceDataReference & data)1103 PLT_CtrlPoint::NotifyDeviceReady(PLT_DeviceDataReference& data)
1104 {
1105     m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceAddedIterator(data));
1106 
1107     /* recursively add embedded devices */
1108     NPT_Array<PLT_DeviceDataReference> embedded_devices =
1109         data->GetEmbeddedDevices();
1110     for (NPT_Cardinal i=0;i<embedded_devices.GetItemCount();i++) {
1111         NotifyDeviceReady(embedded_devices[i]);
1112     }
1113 
1114     return NPT_SUCCESS;
1115 }
1116 
1117 /*----------------------------------------------------------------------
1118 |   PLT_CtrlPoint::RemoveDevice
1119 +---------------------------------------------------------------------*/
1120 NPT_Result
RemoveDevice(PLT_DeviceDataReference & data)1121 PLT_CtrlPoint::RemoveDevice(PLT_DeviceDataReference& data)
1122 {
1123     NPT_AutoLock lock(m_Lock);
1124 
1125     NotifyDeviceRemoved(data);
1126     CleanupDevice(data);
1127 
1128     return NPT_SUCCESS;
1129 }
1130 
1131 /*----------------------------------------------------------------------
1132 |   PLT_CtrlPoint::NotifyDeviceRemoved
1133 +---------------------------------------------------------------------*/
1134 NPT_Result
NotifyDeviceRemoved(PLT_DeviceDataReference & data)1135 PLT_CtrlPoint::NotifyDeviceRemoved(PLT_DeviceDataReference& data)
1136 {
1137     m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceRemovedIterator(data));
1138 
1139     /* recursively add embedded devices */
1140     NPT_Array<PLT_DeviceDataReference> embedded_devices =
1141         data->GetEmbeddedDevices();
1142     for (NPT_Cardinal i=0;i<embedded_devices.GetItemCount();i++) {
1143         NotifyDeviceRemoved(embedded_devices[i]);
1144     }
1145 
1146     return NPT_SUCCESS;
1147 }
1148 
1149 /*----------------------------------------------------------------------
1150 |   PLT_CtrlPoint::CleanupDevice
1151 +---------------------------------------------------------------------*/
1152 NPT_Result
CleanupDevice(PLT_DeviceDataReference & data)1153 PLT_CtrlPoint::CleanupDevice(PLT_DeviceDataReference& data)
1154 {
1155     if (data.IsNull()) return NPT_ERROR_INVALID_PARAMETERS;
1156 
1157     NPT_LOG_INFO_1("Removing %s from device list\n", (const char*)data->GetUUID());
1158 
1159     // Note: This must take the lock prior to being called
1160     // we can't take the lock here because this function
1161     // will be recursively called if device contains embedded devices
1162 
1163     /* recursively remove embedded devices */
1164     NPT_Array<PLT_DeviceDataReference> embedded_devices = data->GetEmbeddedDevices();
1165     for (NPT_Cardinal i=0;i<embedded_devices.GetItemCount();i++) {
1166         CleanupDevice(embedded_devices[i]);
1167     }
1168 
1169     /* remove from list */
1170     m_RootDevices.Remove(data);
1171 
1172     /* unsubscribe from services */
1173     data->m_Services.Apply(PLT_EventSubscriberRemoverIterator(this));
1174 
1175     return NPT_SUCCESS;
1176 }
1177 
1178 /*----------------------------------------------------------------------
1179 |   PLT_CtrlPoint::ProcessSsdpMessage
1180 +---------------------------------------------------------------------*/
1181 NPT_Result
ProcessSsdpMessage(const NPT_HttpMessage & message,const NPT_HttpRequestContext & context,NPT_String & uuid)1182 PLT_CtrlPoint::ProcessSsdpMessage(const NPT_HttpMessage&        message,
1183                                   const NPT_HttpRequestContext& context,
1184                                   NPT_String&                   uuid)
1185 {
1186     NPT_COMPILER_UNUSED(context);
1187 
1188     NPT_AutoLock lock(m_Lock);
1189 
1190     // check if we should ignore our own UUID
1191     if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) return NPT_SUCCESS;
1192 
1193     const NPT_String* url = PLT_UPnPMessageHelper::GetLocation(message);
1194     NPT_CHECK_POINTER_SEVERE(url);
1195 
1196     // Fix for Connect360 which uses localhost in device description url
1197     NPT_HttpUrl location(*url);
1198     if (location.GetHost().ToLowercase() == "localhost" ||
1199         location.GetHost().ToLowercase() == "127.0.0.1") {
1200         location.SetHost(context.GetRemoteAddress().GetIpAddress().ToString());
1201     }
1202 
1203     // be nice and assume a default lease time if not found even though it's required
1204     NPT_TimeInterval leasetime;
1205     if (NPT_FAILED(PLT_UPnPMessageHelper::GetLeaseTime(message, leasetime))) {
1206         leasetime = *PLT_Constants::GetInstance().GetDefaultSubscribeLease();
1207     }
1208 
1209     // check if device (or embedded device) is already known
1210     PLT_DeviceDataReference data;
1211     if (NPT_SUCCEEDED(FindDevice(uuid, data))) {
1212 
1213 //        // in case we missed the byebye and the device description has changed (ip or port)
1214 //        // reset base and assumes device is the same (same number of services and embedded devices)
1215 //        // FIXME: The right way is to remove the device and rescan it though but how do we know it changed?
1216 //        PLT_DeviceReadyIterator device_tester;
1217 //        if (NPT_SUCCEEDED(device_tester(data)) && data->GetDescriptionUrl().Compare(location.ToString(), true)) {
1218 //            NPT_LOG_INFO_2("Old device \"%s\" detected @ new location %s",
1219 //                (const char*)data->GetFriendlyName(),
1220 //                (const char*)location.ToString());
1221 //            data->SetURLBase(location);
1222 //        }
1223 
1224         // renew expiration time
1225         data->SetLeaseTime(leasetime);
1226         NPT_LOG_FINE_1("Device \"%s\" expiration time renewed..",
1227             (const char*)data->GetFriendlyName());
1228 
1229         return NPT_SUCCESS;
1230     }
1231 
1232     // start inspection
1233     return InspectDevice(location, uuid, leasetime);
1234 }
1235 
1236 /*----------------------------------------------------------------------
1237 |   PLT_CtrlPoint::InspectDevice
1238 +---------------------------------------------------------------------*/
1239 NPT_Result
InspectDevice(const NPT_HttpUrl & location,const char * uuid,NPT_TimeInterval leasetime)1240 PLT_CtrlPoint::InspectDevice(const NPT_HttpUrl& location,
1241                              const char*        uuid,
1242                              NPT_TimeInterval   leasetime)
1243 {
1244     NPT_AutoLock lock(m_Lock);
1245 
1246     // check if already inspecting device
1247     NPT_String pending_uuid;
1248     if (NPT_SUCCEEDED(NPT_ContainerFind(m_PendingInspections,
1249                                         NPT_StringFinder(uuid),
1250                                         pending_uuid))) {
1251         return NPT_SUCCESS;
1252     }
1253 
1254     NPT_LOG_INFO_2("Inspecting device \"%s\" detected @ %s",
1255         uuid,
1256         (const char*)location.ToString());
1257 
1258     if (!location.IsValid()) {
1259         NPT_LOG_INFO_1("Invalid device description url: %s",
1260             (const char*) location.ToString());
1261         return NPT_FAILURE;
1262     }
1263 
1264     // remember that we're now inspecting the device
1265     m_PendingInspections.Add(uuid);
1266 
1267     // Start a task to retrieve the description
1268     PLT_CtrlPointGetDescriptionTask* task = new PLT_CtrlPointGetDescriptionTask(
1269         location,
1270         this,
1271         leasetime,
1272         uuid);
1273 
1274     // Add a delay to make sure that we received late NOTIFY bye-bye
1275     NPT_TimeInterval delay(.5f);
1276     m_TaskManager->StartTask(task, &delay);
1277 
1278     return NPT_SUCCESS;
1279 }
1280 
1281 /*----------------------------------------------------------------------
1282 |   PLT_CtrlPoint::FetchDeviceSCPDs
1283 +---------------------------------------------------------------------*/
1284 NPT_Result
FetchDeviceSCPDs(PLT_CtrlPointGetSCPDsTask * task,PLT_DeviceDataReference & device,NPT_Cardinal level)1285 PLT_CtrlPoint::FetchDeviceSCPDs(PLT_CtrlPointGetSCPDsTask* task,
1286                                 PLT_DeviceDataReference&   device,
1287                                 NPT_Cardinal               level)
1288 {
1289     if (level == 5 && device->m_EmbeddedDevices.GetItemCount()) {
1290         NPT_LOG_FATAL("Too many embedded devices depth! ");
1291         return NPT_FAILURE;
1292     }
1293 
1294     ++level;
1295 
1296     // fetch embedded devices services scpds first
1297     for (NPT_Cardinal i = 0;
1298          i<device->m_EmbeddedDevices.GetItemCount();
1299          i++) {
1300          NPT_CHECK_SEVERE(FetchDeviceSCPDs(task, device->m_EmbeddedDevices[i], level));
1301     }
1302 
1303     // Get SCPD of device services now and bail right away if one fails
1304     return device->m_Services.ApplyUntil(
1305         PLT_AddGetSCPDRequestIterator(*task, device),
1306         NPT_UntilResultNotEquals(NPT_SUCCESS));
1307 }
1308 
1309 /*----------------------------------------------------------------------
1310 |   PLT_CtrlPoint::ProcessGetDescriptionResponse
1311 +---------------------------------------------------------------------*/
1312 NPT_Result
ProcessGetDescriptionResponse(NPT_Result res,const NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse * response,NPT_TimeInterval leasetime,NPT_String uuid)1313 PLT_CtrlPoint::ProcessGetDescriptionResponse(NPT_Result                    res,
1314                                              const NPT_HttpRequest&        request,
1315                                              const NPT_HttpRequestContext& context,
1316                                              NPT_HttpResponse*             response,
1317                                              NPT_TimeInterval              leasetime,
1318                                              NPT_String                    uuid)
1319 {
1320     NPT_COMPILER_UNUSED(request);
1321 
1322     NPT_AutoLock lock(m_Lock);
1323 
1324     PLT_CtrlPointGetSCPDsTask* task = NULL;
1325     NPT_String desc;
1326     PLT_DeviceDataReference root_device;
1327     PLT_DeviceDataReference device;
1328 
1329     // Add a delay, some devices need it (aka Rhapsody)
1330     NPT_TimeInterval delay(0.1f);
1331 
1332     NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessGetDescriptionResponse @ %s (result = %d, status = %d)",
1333         (const char*)request.GetUrl().ToString(),
1334         res,
1335         response?response->GetStatusCode():0);
1336 
1337     // Remove pending inspection
1338     m_PendingInspections.Remove(uuid);
1339 
1340     // verify response was ok
1341     NPT_CHECK_LABEL_FATAL(res, bad_response);
1342     NPT_CHECK_POINTER_LABEL_FATAL(response, bad_response);
1343 
1344     // log response
1345     PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, prefix, response);
1346 
1347     // get response body
1348     res = PLT_HttpHelper::GetBody(*response, desc);
1349     NPT_CHECK_SEVERE(res);
1350 
1351     // create new root device
1352     NPT_CHECK_SEVERE(PLT_DeviceData::SetDescription(root_device, leasetime, request.GetUrl(), desc, context));
1353 
1354     // make sure root device was not previously queried
1355     if (NPT_FAILED(FindDevice(root_device->GetUUID(), device))) {
1356         m_RootDevices.Add(root_device);
1357 
1358         NPT_LOG_INFO_3("Device \"%s\" is now known as \"%s\" (%s)",
1359             (const char*)root_device->GetUUID(),
1360             (const char*)root_device->GetFriendlyName(),
1361             (const char*)root_device->GetDescriptionUrl(NULL));
1362 
1363         // create one single task to fetch all scpds one after the other
1364         task = new PLT_CtrlPointGetSCPDsTask(this, root_device);
1365         NPT_CHECK_LABEL_SEVERE(res = FetchDeviceSCPDs(task, root_device, 0),
1366                                cleanup);
1367 
1368         // if device has embedded devices, we want to delay fetching scpds
1369         // just in case there's a chance all the initial NOTIFY bye-bye have
1370         // not all been received yet which would cause to remove the devices
1371         // as we're adding them
1372         if (root_device->m_EmbeddedDevices.GetItemCount() > 0) {
1373             delay = 1.f;
1374         }
1375         NPT_CHECK_LABEL_SEVERE(res = m_TaskManager->StartTask(task, &delay),
1376                                failure);
1377     }
1378 
1379     return NPT_SUCCESS;
1380 
1381 bad_response:
1382     NPT_LOG_SEVERE_2("Bad Description response @ %s: %s",
1383         (const char*)request.GetUrl().ToString(),
1384         (const char*)desc);
1385 
1386 cleanup:
1387     if (task) delete task;
1388 
1389 failure:
1390     return res;
1391 }
1392 
1393 /*----------------------------------------------------------------------
1394 |   PLT_CtrlPoint::ProcessGetSCPDResponse
1395 +---------------------------------------------------------------------*/
1396 NPT_Result
ProcessGetSCPDResponse(NPT_Result res,const NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse * response,PLT_DeviceDataReference & device)1397 PLT_CtrlPoint::ProcessGetSCPDResponse(NPT_Result                    res,
1398                                       const NPT_HttpRequest&        request,
1399                                       const NPT_HttpRequestContext& context,
1400                                       NPT_HttpResponse*             response,
1401                                       PLT_DeviceDataReference&      device)
1402 {
1403     NPT_COMPILER_UNUSED(context);
1404 
1405     NPT_AutoLock lock(m_Lock);
1406 
1407     PLT_DeviceReadyIterator device_tester;
1408     NPT_String              scpd;
1409     PLT_DeviceDataReference root_device;
1410     PLT_Service*            service;
1411 
1412     NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessGetSCPDResponse for a service of device \"%s\" @ %s (result = %d, status = %d)",
1413         (const char*)device->GetFriendlyName(),
1414         (const char*)request.GetUrl().ToString(),
1415         res,
1416         response?response->GetStatusCode():0);
1417 
1418     // verify response was ok
1419     NPT_CHECK_LABEL_FATAL(res, bad_response);
1420     NPT_CHECK_POINTER_LABEL_FATAL(response, bad_response);
1421 
1422     PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, prefix, response);
1423 
1424     // make sure root device hasn't disappeared
1425     NPT_CHECK_LABEL_WARNING(FindDevice(device->GetUUID(), root_device, true),
1426                             bad_response);
1427 
1428     res = device->FindServiceBySCPDURL(request.GetUrl().ToRequestString(), service);
1429     NPT_CHECK_LABEL_SEVERE(res, bad_response);
1430 
1431     // get response body
1432     res = PLT_HttpHelper::GetBody(*response, scpd);
1433     NPT_CHECK_LABEL_FATAL(res, bad_response);
1434 
1435     // DIAL support
1436     if (root_device->GetType().Compare("urn:dial-multiscreen-org:device:dial:1") == 0) {
1437         AddDevice(root_device);
1438         return NPT_SUCCESS;
1439     }
1440 
1441     // set the service scpd
1442     res = service->SetSCPDXML(scpd);
1443     NPT_CHECK_LABEL_SEVERE(res, bad_response);
1444 
1445     // if root device is ready, notify listeners about it and embedded devices
1446     if (NPT_SUCCEEDED(device_tester(root_device))) {
1447         AddDevice(root_device);
1448     }
1449 
1450     return NPT_SUCCESS;
1451 
1452 bad_response:
1453     NPT_LOG_SEVERE_2("Bad SCPD response for device \"%s\":%s",
1454         (const char*)device->GetFriendlyName(),
1455         (const char*)scpd);
1456 
1457     if (!root_device.IsNull()) RemoveDevice(root_device);
1458     return res;
1459 }
1460 
1461 /*----------------------------------------------------------------------
1462 |   PLT_CtrlPoint::RenewSubscriber
1463 +---------------------------------------------------------------------*/
1464 PLT_ThreadTask*
RenewSubscriber(PLT_EventSubscriberReference subscriber)1465 PLT_CtrlPoint::RenewSubscriber(PLT_EventSubscriberReference subscriber)
1466 {
1467     NPT_AutoLock lock(m_Lock);
1468 
1469     PLT_DeviceDataReference root_device;
1470     if (NPT_FAILED(FindDevice(subscriber->GetService()->GetDevice()->GetUUID(),
1471                               root_device,
1472                               true))) {
1473         return NULL;
1474     }
1475 
1476     NPT_LOG_FINE_3("Renewing subscriber \"%s\" for service \"%s\" of device \"%s\"",
1477         (const char*)subscriber->GetSID(),
1478         (const char*)subscriber->GetService()->GetServiceID(),
1479         (const char*)subscriber->GetService()->GetDevice()->GetFriendlyName());
1480 
1481     // create the request
1482     NPT_HttpRequest* request = new NPT_HttpRequest(
1483         subscriber->GetService()->GetEventSubURL(true),
1484         "SUBSCRIBE",
1485         NPT_HTTP_PROTOCOL_1_1);
1486 
1487     PLT_UPnPMessageHelper::SetSID(*request, subscriber->GetSID());
1488     PLT_UPnPMessageHelper::SetTimeOut(*request,
1489         (NPT_Int32)PLT_Constants::GetInstance().GetDefaultSubscribeLease()->ToSeconds());
1490 
1491     // Prepare the request
1492     // create a task to post the request
1493     return new PLT_CtrlPointSubscribeEventTask(
1494         request,
1495         this,
1496         root_device,
1497         subscriber->GetService());
1498 }
1499 
1500 /*----------------------------------------------------------------------
1501 |   PLT_CtrlPoint::Subscribe
1502 +---------------------------------------------------------------------*/
1503 NPT_Result
Subscribe(PLT_Service * service,bool cancel,void * userdata)1504 PLT_CtrlPoint::Subscribe(PLT_Service* service,
1505                          bool         cancel,
1506                          void*        userdata)
1507 {
1508     NPT_AutoLock lock(m_Lock);
1509 
1510     if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
1511 
1512     NPT_HttpRequest* request = NULL;
1513 
1514     // make sure service is subscribable
1515     if (!service->IsSubscribable()) return NPT_FAILURE;
1516 
1517     // event url
1518     NPT_HttpUrl url(service->GetEventSubURL(true));
1519 
1520     // look for the corresponding root device & sub
1521     PLT_DeviceDataReference root_device;
1522     PLT_EventSubscriberReference sub;
1523     NPT_CHECK_WARNING(FindDevice(service->GetDevice()->GetUUID(),
1524                                  root_device,
1525                                  true));
1526 
1527     // look for the subscriber with that service to decide if it's a renewal or not
1528     NPT_ContainerFind(m_Subscribers,
1529                       PLT_EventSubscriberFinderByService(service),
1530                       sub);
1531 
1532     if (cancel == false) {
1533         // renewal?
1534         if (!sub.IsNull()) {
1535             PLT_ThreadTask* task = RenewSubscriber(sub);
1536             return m_TaskManager->StartTask(task);
1537         }
1538 
1539         NPT_LOG_INFO_2("Subscribing to service \"%s\" of device \"%s\"",
1540             (const char*)service->GetServiceID(),
1541             (const char*)service->GetDevice()->GetFriendlyName());
1542 
1543         // prepare the callback url
1544         NPT_String uuid         = service->GetDevice()->GetUUID();
1545         NPT_String service_id   = service->GetServiceID();
1546         NPT_String callback_uri = "/" + uuid + "/" + service_id;
1547 
1548         // create the request
1549         request = new NPT_HttpRequest(url, "SUBSCRIBE", NPT_HTTP_PROTOCOL_1_1);
1550         // specify callback url using ip of interface used when
1551         // retrieving device description
1552         NPT_HttpUrl callbackUrl(
1553             service->GetDevice()->m_LocalIfaceIp.ToString(),
1554             m_EventHttpServer->GetPort(),
1555             callback_uri);
1556 
1557         // set the required headers for a new subscription
1558         PLT_UPnPMessageHelper::SetNT(*request, "upnp:event");
1559         PLT_UPnPMessageHelper::SetCallbacks(*request,
1560             "<" + callbackUrl.ToString() + ">");
1561         PLT_UPnPMessageHelper::SetTimeOut(*request,
1562             (NPT_Int32)PLT_Constants::GetInstance().GetDefaultSubscribeLease()->ToSeconds());
1563     } else {
1564         NPT_LOG_INFO_3("Unsubscribing subscriber \"%s\" for service \"%s\" of device \"%s\"",
1565             (const char*)(!sub.IsNull()?sub->GetSID().GetChars():"unknown"),
1566             (const char*)service->GetServiceID(),
1567             (const char*)service->GetDevice()->GetFriendlyName());
1568 
1569         // cancellation
1570         if (sub.IsNull()) return NPT_FAILURE;
1571 
1572         // create the request
1573         request = new NPT_HttpRequest(url, "UNSUBSCRIBE", NPT_HTTP_PROTOCOL_1_1);
1574         PLT_UPnPMessageHelper::SetSID(*request, sub->GetSID());
1575 
1576         // remove from list now
1577         m_Subscribers.Remove(sub, true);
1578     }
1579 
1580     // verify we have request to send just in case
1581     NPT_CHECK_POINTER_FATAL(request);
1582 
1583     // Prepare the request
1584     // create a task to post the request
1585     PLT_ThreadTask* task = new PLT_CtrlPointSubscribeEventTask(
1586         request,
1587         this,
1588 		root_device,
1589         service,
1590         userdata);
1591     m_TaskManager->StartTask(task);
1592 
1593     return NPT_SUCCESS;
1594 }
1595 
1596 /*----------------------------------------------------------------------
1597 |   PLT_CtrlPoint::ProcessSubscribeResponse
1598 +---------------------------------------------------------------------*/
1599 NPT_Result
ProcessSubscribeResponse(NPT_Result res,const NPT_HttpRequest & request,const NPT_HttpRequestContext & context,NPT_HttpResponse * response,PLT_Service * service,void *)1600 PLT_CtrlPoint::ProcessSubscribeResponse(NPT_Result                    res,
1601                                         const NPT_HttpRequest&        request,
1602                                         const NPT_HttpRequestContext& context,
1603                                         NPT_HttpResponse*             response,
1604                                         PLT_Service*                  service,
1605                                         void*                  /* userdata */)
1606 {
1607     NPT_COMPILER_UNUSED(context);
1608 
1609     NPT_AutoLock lock(m_Lock);
1610 
1611     const NPT_String*    sid = NULL;
1612     NPT_Int32            seconds = -1;
1613     PLT_EventSubscriberReference sub;
1614     bool                 subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE");
1615 
1616     NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for service \"%s\" (result = %d, status code = %d)",
1617         (const char*)subscription?"S":"Uns",
1618         (const char*)service->GetServiceID(),
1619         res,
1620         response?response->GetStatusCode():0);
1621     PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, prefix, response);
1622 
1623     // if there's a failure or it's a response to a cancellation
1624     // we get out (any 2xx status code ok)
1625     if (NPT_FAILED(res) || response == NULL || response->GetStatusCode()/100 != 2) {
1626         goto failure;
1627     }
1628 
1629     if (subscription) {
1630         if (!(sid = PLT_UPnPMessageHelper::GetSID(*response)) ||
1631             NPT_FAILED(PLT_UPnPMessageHelper::GetTimeOut(*response, seconds))) {
1632             NPT_CHECK_LABEL_SEVERE(res = NPT_ERROR_INVALID_SYNTAX, failure);
1633         }
1634 
1635         // Look for subscriber
1636         NPT_ContainerFind(m_Subscribers,
1637             PLT_EventSubscriberFinderBySID(*sid),
1638             sub);
1639 
1640         NPT_LOG_INFO_5("%s subscriber \"%s\" for service \"%s\" of device \"%s\" (timeout = %d)",
1641                        !sub.IsNull()?"Updating timeout for":"Creating new",
1642                        (const char*)*sid,
1643                        (const char*)service->GetServiceID(),
1644                        (const char*)service->GetDevice()->GetFriendlyName(),
1645                        seconds);
1646 
1647         // create new subscriber if sid never seen before
1648         // or update subscriber expiration otherwise
1649         if (sub.IsNull()) {
1650             sub = new PLT_EventSubscriber(m_TaskManager, service, *sid, seconds);
1651             m_Subscribers.Add(sub);
1652         } else {
1653             sub->SetTimeout(seconds);
1654         }
1655 
1656         // Process any pending notifcations for that subscriber we got a bit too early
1657         ProcessPendingEventNotifications();
1658 
1659         return NPT_SUCCESS;
1660     }
1661 
1662     goto remove_sub;
1663 
1664 failure:
1665     NPT_LOG_SEVERE_4("%subscription failed of sub \"%s\" for service \"%s\" of device \"%s\"",
1666         (const char*)subscription?"S":"Uns",
1667         (const char*)(sid?*sid:"Unknown"),
1668         (const char*)service->GetServiceID(),
1669         (const char*)service->GetDevice()->GetFriendlyName());
1670     res = NPT_FAILED(res)?res:NPT_FAILURE;
1671 
1672 remove_sub:
1673     // in case it was a renewal look for the subscriber with that service and remove it from the list
1674     if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers,
1675                                         PLT_EventSubscriberFinderByService(service),
1676                                         sub))) {
1677         m_Subscribers.Remove(sub);
1678     }
1679 
1680     return res;
1681 }
1682 
1683 /*----------------------------------------------------------------------
1684 |   PLT_CtrlPoint::InvokeAction
1685 +---------------------------------------------------------------------*/
1686 NPT_Result
InvokeAction(PLT_ActionReference & action,void * userdata)1687 PLT_CtrlPoint::InvokeAction(PLT_ActionReference& action,
1688                             void*                userdata)
1689 {
1690     if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
1691 
1692     PLT_Service* service = action->GetActionDesc().GetService();
1693 
1694     // create the request
1695     NPT_HttpUrl url(service->GetControlURL(true));
1696     NPT_HttpRequest* request = new NPT_HttpRequest(url, "POST", NPT_HTTP_PROTOCOL_1_1);
1697 
1698     // create a memory stream for our request body
1699     NPT_MemoryStreamReference stream(new NPT_MemoryStream);
1700     action->FormatSoapRequest(*stream);
1701 
1702     // set the request body
1703     NPT_HttpEntity* entity = NULL;
1704     PLT_HttpHelper::SetBody(*request, (NPT_InputStreamReference)stream, &entity);
1705 
1706     entity->SetContentType("text/xml; charset=\"utf-8\"");
1707     NPT_String service_type = service->GetServiceType();
1708     NPT_String action_name  = action->GetActionDesc().GetName();
1709     request->GetHeaders().SetHeader("SOAPAction", "\"" + service_type + "#" + action_name + "\"");
1710 
1711     // create a task to post the request
1712     PLT_CtrlPointInvokeActionTask* task = new PLT_CtrlPointInvokeActionTask(
1713         request,
1714         this,
1715         action,
1716         userdata);
1717 
1718     // queue the request
1719     m_TaskManager->StartTask(task);
1720 
1721     return NPT_SUCCESS;
1722 }
1723 
1724 /*----------------------------------------------------------------------
1725 |   PLT_CtrlPoint::ProcessActionResponse
1726 +---------------------------------------------------------------------*/
1727 NPT_Result
ProcessActionResponse(NPT_Result res,const NPT_HttpRequest & request,const NPT_HttpRequestContext &,NPT_HttpResponse * response,PLT_ActionReference & action,void * userdata)1728 PLT_CtrlPoint::ProcessActionResponse(NPT_Result                    res,
1729                                      const NPT_HttpRequest&        request,
1730                                      const NPT_HttpRequestContext& /*context*/,
1731                                      NPT_HttpResponse*             response,
1732                                      PLT_ActionReference&          action,
1733                                      void*                         userdata)
1734 {
1735 	NPT_COMPILER_UNUSED(request);
1736 
1737     NPT_String          service_type;
1738     NPT_String          str;
1739     NPT_XmlElementNode* xml = NULL;
1740     NPT_String          name;
1741     NPT_String          soap_action_name;
1742     NPT_XmlElementNode* soap_action_response;
1743     NPT_XmlElementNode* soap_body;
1744     NPT_XmlElementNode* fault;
1745     const NPT_String*   attr = NULL;
1746     PLT_ActionDesc&     action_desc = action->GetActionDesc();
1747 
1748     // reset the error code and desc
1749     action->SetError(0, "");
1750 
1751     // check context validity
1752     if (NPT_FAILED(res) || response == NULL) {
1753         PLT_Service* service = action_desc.GetService();
1754 		NPT_COMPILER_UNUSED(service);
1755         NPT_LOG_WARNING_4("Failed to reach %s for %s.%s (%d)",
1756                           request.GetUrl().ToString().GetChars(),
1757                           service->GetDevice()->GetUUID().GetChars(),
1758                           service->GetServiceName().GetChars(),
1759                           res);
1760         goto failure;
1761     }
1762 
1763     PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, "PLT_CtrlPoint::ProcessActionResponse:", response);
1764 
1765     NPT_LOG_FINER("Reading/Parsing Action Response Body...");
1766     if (NPT_FAILED(PLT_HttpHelper::ParseBody(*response, xml))) {
1767         goto failure;
1768     }
1769 
1770     NPT_LOG_FINER("Analyzing Action Response Body...");
1771 
1772     // read envelope
1773     if (xml->GetTag().Compare("Envelope", true))
1774         goto failure;
1775 
1776     // check namespace
1777     if (!xml->GetNamespace() || xml->GetNamespace()->Compare("http://schemas.xmlsoap.org/soap/envelope/"))
1778         goto failure;
1779 
1780     // check encoding
1781     attr = xml->GetAttribute("encodingStyle", "http://schemas.xmlsoap.org/soap/envelope/");
1782     if (!attr || attr->Compare("http://schemas.xmlsoap.org/soap/encoding/"))
1783         goto failure;
1784 
1785     // read action
1786     soap_body = PLT_XmlHelper::GetChild(xml, "Body");
1787     if (soap_body == NULL)
1788         goto failure;
1789 
1790     // check if an error occurred
1791     fault = PLT_XmlHelper::GetChild(soap_body, "Fault");
1792     if (fault != NULL) {
1793         // we have an error
1794         ParseFault(action, fault);
1795         goto failure;
1796     }
1797 
1798     if (NPT_FAILED(PLT_XmlHelper::GetChild(soap_body, soap_action_response)))
1799         goto failure;
1800 
1801     // verify action name is identical to SOAPACTION header
1802     if (soap_action_response->GetTag().Compare(action_desc.GetName() + "Response", true))
1803         goto failure;
1804 
1805     // verify namespace
1806     if (!soap_action_response->GetNamespace() ||
1807          soap_action_response->GetNamespace()->Compare(action_desc.GetService()->GetServiceType()))
1808          goto failure;
1809 
1810     // read all the arguments if any
1811     for (NPT_List<NPT_XmlNode*>::Iterator args = soap_action_response->GetChildren().GetFirstItem();
1812          args;
1813          args++) {
1814         NPT_XmlElementNode* child = (*args)->AsElementNode();
1815         if (!child) continue;
1816 
1817         action->SetArgumentValue(child->GetTag(), child->GetText()?*child->GetText():"");
1818         if (NPT_FAILED(res)) goto failure;
1819     }
1820 
1821     // create a buffer for our response body and call the service
1822     res = action->VerifyArguments(false);
1823     if (NPT_FAILED(res)) goto failure;
1824 
1825     goto cleanup;
1826 
1827 failure:
1828     // override res with failure if necessary
1829     if (NPT_SUCCEEDED(res)) res = NPT_FAILURE;
1830     // fallthrough
1831 
1832 cleanup:
1833     {
1834         NPT_AutoLock lock(m_Lock);
1835         m_ListenerList.Apply(PLT_CtrlPointListenerOnActionResponseIterator(res, action, userdata));
1836     }
1837 
1838     delete xml;
1839     return res;
1840 }
1841 
1842 /*----------------------------------------------------------------------
1843 |   PLT_CtrlPoint::ParseFault
1844 +---------------------------------------------------------------------*/
1845 NPT_Result
ParseFault(PLT_ActionReference & action,NPT_XmlElementNode * fault)1846 PLT_CtrlPoint::ParseFault(PLT_ActionReference& action,
1847                           NPT_XmlElementNode*  fault)
1848 {
1849     NPT_XmlElementNode* detail = fault->GetChild("detail");
1850     if (detail == NULL) return NPT_FAILURE;
1851 
1852     NPT_XmlElementNode *upnp_error, *error_code, *error_desc;
1853     upnp_error = detail->GetChild("upnp_error");
1854 
1855 	// WMP12 Hack
1856 	if (upnp_error == NULL) {
1857         upnp_error = detail->GetChild("UPnPError", NPT_XML_ANY_NAMESPACE);
1858         if (upnp_error == NULL) return NPT_FAILURE;
1859 	}
1860 
1861     error_code = upnp_error->GetChild("errorCode", NPT_XML_ANY_NAMESPACE);
1862     error_desc = upnp_error->GetChild("errorDescription", NPT_XML_ANY_NAMESPACE);
1863     NPT_Int32  code = 501;
1864     NPT_String desc;
1865     if (error_code && error_code->GetText()) {
1866         NPT_String value = *error_code->GetText();
1867         value.ToInteger(code);
1868     }
1869     if (error_desc && error_desc->GetText()) {
1870         desc = *error_desc->GetText();
1871     }
1872     action->SetError(code, desc);
1873     return NPT_SUCCESS;
1874 }
1875