Merge pull request #25959 from neo1973/TagLib_deprecation_warnings
[xbmc.git] / lib / libUPnP / Platinum / Source / Core / PltCtrlPoint.cpp
blob7d476d18066da4ab7eac17dfc5b5cf01de118305
1 /*****************************************************************
3 | Platinum - Control Point
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
6 | All rights reserved.
7 | http://www.plutinosoft.com
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.
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
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.
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
33 ****************************************************************/
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"
47 NPT_SET_LOCAL_LOGGER("platinum.core.ctrlpoint")
49 /*----------------------------------------------------------------------
50 | PLT_CtrlPointListenerOnDeviceAddedIterator class
51 +---------------------------------------------------------------------*/
52 class PLT_CtrlPointListenerOnDeviceAddedIterator
54 public:
55 PLT_CtrlPointListenerOnDeviceAddedIterator(PLT_DeviceDataReference& device) :
56 m_Device(device) {}
58 NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
59 return listener->OnDeviceAdded(m_Device);
62 private:
63 PLT_DeviceDataReference& m_Device;
66 /*----------------------------------------------------------------------
67 | PLT_CtrlPointListenerOnDeviceRemovedIterator class
68 +---------------------------------------------------------------------*/
69 class PLT_CtrlPointListenerOnDeviceRemovedIterator
71 public:
72 PLT_CtrlPointListenerOnDeviceRemovedIterator(PLT_DeviceDataReference& device) :
73 m_Device(device) {}
75 NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
76 return listener->OnDeviceRemoved(m_Device);
79 private:
80 PLT_DeviceDataReference& m_Device;
83 /*----------------------------------------------------------------------
84 | PLT_CtrlPointListenerOnActionResponseIterator class
85 +---------------------------------------------------------------------*/
86 class PLT_CtrlPointListenerOnActionResponseIterator
88 public:
89 PLT_CtrlPointListenerOnActionResponseIterator(NPT_Result res,
90 PLT_ActionReference& action,
91 void* userdata) :
92 m_Res(res), m_Action(action), m_Userdata(userdata) {}
94 NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
95 return listener->OnActionResponse(m_Res, m_Action, m_Userdata);
98 private:
99 NPT_Result m_Res;
100 PLT_ActionReference& m_Action;
101 void* m_Userdata;
104 /*----------------------------------------------------------------------
105 | PLT_CtrlPointListenerOnEventNotifyIterator class
106 +---------------------------------------------------------------------*/
107 class PLT_CtrlPointListenerOnEventNotifyIterator
109 public:
110 PLT_CtrlPointListenerOnEventNotifyIterator(PLT_Service* service,
111 NPT_List<PLT_StateVariable*>* vars) :
112 m_Service(service), m_Vars(vars) {}
114 NPT_Result operator()(PLT_CtrlPointListener*& listener) const {
115 return listener->OnEventNotify(m_Service, m_Vars);
118 private:
119 PLT_Service* m_Service;
120 NPT_List<PLT_StateVariable*>* m_Vars;
123 /*----------------------------------------------------------------------
124 | PLT_AddGetSCPDRequestIterator class
125 +---------------------------------------------------------------------*/
126 class PLT_AddGetSCPDRequestIterator
128 public:
129 PLT_AddGetSCPDRequestIterator(PLT_CtrlPointGetSCPDsTask& task,
130 PLT_DeviceDataReference& device) :
131 m_Task(task), m_Device(device) {}
133 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);
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);
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;
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);
158 private:
159 PLT_CtrlPointGetSCPDsTask& m_Task;
160 PLT_DeviceDataReference m_Device;
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
170 public:
171 PLT_EventSubscriberRemoverIterator(PLT_CtrlPoint* ctrl_point) :
172 m_CtrlPoint(ctrl_point) {}
173 ~PLT_EventSubscriberRemoverIterator() {}
175 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);
183 return NPT_SUCCESS;
186 private:
187 PLT_CtrlPoint* m_CtrlPoint;
190 /*----------------------------------------------------------------------
191 | PLT_ServiceReadyIterator class
192 +---------------------------------------------------------------------*/
193 class PLT_ServiceReadyIterator
195 public:
196 PLT_ServiceReadyIterator() {}
198 NPT_Result operator()(PLT_Service*& service) const {
199 return service->IsValid()?NPT_SUCCESS:NPT_FAILURE;
203 /*----------------------------------------------------------------------
204 | PLT_DeviceReadyIterator class
205 +---------------------------------------------------------------------*/
206 class PLT_DeviceReadyIterator
208 public:
209 PLT_DeviceReadyIterator() {}
210 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;
216 res = device->m_EmbeddedDevices.ApplyUntil(
217 PLT_DeviceReadyIterator(),
218 NPT_UntilResultNotEquals(NPT_SUCCESS));
219 if (NPT_FAILED(res)) return res;
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;
228 return NPT_SUCCESS;
232 /*----------------------------------------------------------------------
233 | PLT_CtrlPoint::PLT_CtrlPoint
234 +---------------------------------------------------------------------*/
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)
244 /*----------------------------------------------------------------------
245 | PLT_CtrlPoint::~PLT_CtrlPoint
246 +---------------------------------------------------------------------*/
247 PLT_CtrlPoint::~PLT_CtrlPoint()
251 /*----------------------------------------------------------------------
252 | PLT_CtrlPoint::IgnoreUUID
253 +---------------------------------------------------------------------*/
254 void
255 PLT_CtrlPoint::IgnoreUUID(const char* uuid)
257 if (!m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
258 m_UUIDsToIgnore.Add(uuid);
262 /*----------------------------------------------------------------------
263 | PLT_CtrlPoint::Start
264 +---------------------------------------------------------------------*/
265 NPT_Result
266 PLT_CtrlPoint::Start(PLT_SsdpListenTask* task)
268 if (m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
270 m_TaskManager = new PLT_TaskManager();
272 m_EventHttpServer = new PLT_HttpServer();
273 m_EventHttpServer->AddRequestHandler(new PLT_HttpRequestHandler(this), "/", true, true);
274 m_EventHttpServer->Start();
276 // house keeping task
277 m_TaskManager->StartTask(new PLT_CtrlPointHouseKeepingTask(this));
279 // add ourselves as an listener to SSDP multicast advertisements
280 task->AddListener(this);
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;
287 m_Started = true;
289 return m_SearchCriteria.GetLength()?Search(NPT_HttpUrl("239.255.255.250", 1900, "*"), m_SearchCriteria):NPT_SUCCESS;
292 /*----------------------------------------------------------------------
293 | PLT_CtrlPoint::GetPort
294 +---------------------------------------------------------------------*/
295 NPT_Result
296 PLT_CtrlPoint::GetPort(NPT_UInt16& port)
298 if (!m_Started) return NPT_ERROR_INVALID_STATE;
300 port = m_EventHttpServer->GetPort();
301 return NPT_SUCCESS;
304 /*----------------------------------------------------------------------
305 | PLT_CtrlPoint::Stop
306 +---------------------------------------------------------------------*/
307 NPT_Result
308 PLT_CtrlPoint::Stop(PLT_SsdpListenTask* task)
310 if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
312 m_Started = false;
314 task->RemoveListener(this);
316 m_EventHttpServer->Stop();
317 m_TaskManager->Abort();
319 // force remove all devices
320 NPT_List<PLT_DeviceDataReference>::Iterator iter = m_RootDevices.GetFirstItem();
321 while (iter) {
322 NotifyDeviceRemoved(*iter);
323 ++iter;
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();
331 m_EventHttpServer = NULL;
332 m_TaskManager = NULL;
334 return NPT_SUCCESS;
337 /*----------------------------------------------------------------------
338 | PLT_CtrlPoint::AddListener
339 +---------------------------------------------------------------------*/
340 NPT_Result
341 PLT_CtrlPoint::AddListener(PLT_CtrlPointListener* listener)
343 NPT_AutoLock lock(m_Lock);
344 if (!m_ListenerList.Contains(listener)) {
345 m_ListenerList.Add(listener);
347 return NPT_SUCCESS;
350 /*----------------------------------------------------------------------
351 | PLT_CtrlPoint::RemoveListener
352 +---------------------------------------------------------------------*/
353 NPT_Result
354 PLT_CtrlPoint::RemoveListener(PLT_CtrlPointListener* listener)
356 NPT_AutoLock lock(m_Lock);
357 m_ListenerList.Remove(listener);
358 return NPT_SUCCESS;
361 /*----------------------------------------------------------------------
362 | PLT_CtrlPoint::CreateSearchTask
363 +---------------------------------------------------------------------*/
364 PLT_SsdpSearchTask*
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)
371 // make sure mx is at least 1
372 if (mx<1) mx=1;
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());
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;
386 if (NPT_SUCCEEDED(socket->Bind(
387 NPT_SocketAddress(NPT_IpAddress::Any, port),
388 false)))
389 break;
391 } while (--retries);
393 if (retries == 0) {
394 NPT_LOG_SEVERE("Couldn't bind socket for Search Task");
395 return NULL;
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());
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();
413 return task;
416 /*----------------------------------------------------------------------
417 | PLT_CtrlPoint::Search
418 +---------------------------------------------------------------------*/
419 NPT_Result
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.) */)
426 if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
428 NPT_List<NPT_NetworkInterface*> if_list;
429 NPT_List<NPT_NetworkInterface*>::Iterator net_if;
430 NPT_List<NPT_NetworkInterfaceAddress>::Iterator net_if_addr;
432 NPT_CHECK_SEVERE(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list, true));
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;
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);
456 if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
457 return NPT_SUCCESS;
460 /*----------------------------------------------------------------------
461 | PLT_CtrlPoint::Discover
462 +---------------------------------------------------------------------*/
463 NPT_Result
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.) */)
470 if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
472 // make sure mx is at least 1
473 if (mx<1) mx = 1;
475 // create socket
476 NPT_UdpSocket* socket = new NPT_UdpSocket(NPT_SOCKET_FLAG_CANCELLABLE);
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());
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");
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);
498 /*----------------------------------------------------------------------
499 | PLT_CtrlPoint::DoHouseKeeping
500 +---------------------------------------------------------------------*/
501 NPT_Result
502 PLT_CtrlPoint::DoHouseKeeping()
504 NPT_List<PLT_DeviceDataReference> devices_to_remove;
506 // remove expired devices
508 NPT_AutoLock lock(m_Lock);
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();
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);
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;
531 // have we exhausted initial list?
532 if (!head.IsNull() && head == *m_RootDevices.GetFirstItem())
533 break;
537 // remove old devices
539 NPT_AutoLock lock(m_Lock);
541 for (NPT_List<PLT_DeviceDataReference>::Iterator device =
542 devices_to_remove.GetFirstItem();
543 device;
544 device++) {
545 RemoveDevice(*device);
549 // renew subscribers of subscribed device services
550 NPT_List<PLT_ThreadTask*> tasks;
552 NPT_AutoLock lock(m_Lock);
554 NPT_List<PLT_EventSubscriberReference>::Iterator sub = m_Subscribers.GetFirstItem();
555 while (sub) {
556 NPT_TimeStamp now;
557 NPT_System::GetCurrentTimeStamp(now);
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);
564 sub++;
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);
577 return NPT_SUCCESS;
580 /*----------------------------------------------------------------------
581 | PLT_CtrlPoint::FindDevice
582 +---------------------------------------------------------------------*/
583 NPT_Result
584 PLT_CtrlPoint::FindDevice(const char* uuid,
585 PLT_DeviceDataReference& device,
586 bool return_root /* = false */)
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;
600 ++iter;
603 return NPT_ERROR_NO_SUCH_ITEM;
606 /*----------------------------------------------------------------------
607 | PLT_CtrlPoint::FindActionDesc
608 +---------------------------------------------------------------------*/
609 NPT_Result
610 PLT_CtrlPoint::FindActionDesc(PLT_DeviceDataReference& device,
611 const char* service_type,
612 const char* action_name,
613 PLT_ActionDesc*& action_desc)
615 if (device.IsNull()) return NPT_ERROR_INVALID_PARAMETERS;
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;
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;
630 return NPT_SUCCESS;
633 /*----------------------------------------------------------------------
634 | PLT_CtrlPoint::CreateAction
635 +---------------------------------------------------------------------*/
636 NPT_Result
637 PLT_CtrlPoint::CreateAction(PLT_DeviceDataReference& device,
638 const char* service_type,
639 const char* action_name,
640 PLT_ActionReference& action)
642 if (device.IsNull()) return NPT_ERROR_INVALID_PARAMETERS;
644 NPT_AutoLock lock(m_Lock);
646 PLT_ActionDesc* action_desc;
647 NPT_CHECK_SEVERE(FindActionDesc(device,
648 service_type,
649 action_name,
650 action_desc));
652 PLT_DeviceDataReference root_device;
653 NPT_CHECK_SEVERE(FindDevice(device->GetUUID(), root_device, true));
655 action = new PLT_Action(*action_desc, root_device);
656 return NPT_SUCCESS;
659 /*----------------------------------------------------------------------
660 | PLT_CtrlPoint::SetupResponse
661 +---------------------------------------------------------------------*/
662 NPT_Result
663 PLT_CtrlPoint::SetupResponse(NPT_HttpRequest& request,
664 const NPT_HttpRequestContext& context,
665 NPT_HttpResponse& response)
667 NPT_COMPILER_UNUSED(context);
669 if (request.GetMethod().Compare("NOTIFY") == 0) {
670 return ProcessHttpNotify(request, context, response);
673 NPT_LOG_SEVERE("CtrlPoint received bad http request\r\n");
674 response.SetStatus(412, "Precondition Failed");
675 return NPT_SUCCESS;
678 /*----------------------------------------------------------------------
679 | PLT_CtrlPoint::DecomposeLastChangeVar
680 +---------------------------------------------------------------------*/
681 NPT_Result
682 PLT_CtrlPoint::DecomposeLastChangeVar(NPT_List<PLT_StateVariable*>& vars)
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();
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;
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;
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;
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;
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());
744 delete xml;
747 return NPT_SUCCESS;
750 /*----------------------------------------------------------------------
751 | PLT_CtrlPoint::ProcessEventNotification
752 +---------------------------------------------------------------------*/
753 NPT_Result
754 PLT_CtrlPoint::ProcessEventNotification(PLT_EventSubscriberReference subscriber,
755 PLT_EventNotification* notification,
756 NPT_List<PLT_StateVariable*> &vars)
758 NPT_XmlElementNode* xml = NULL;
759 PLT_Service* service = subscriber->GetService();
760 PLT_DeviceData* device = service->GetDevice();
762 NPT_String uuid = device->GetUUID();
763 NPT_String service_id = service->GetServiceID();
765 // callback uri for this sub
766 NPT_String callback_uri = "/" + uuid + "/" + service_id;
768 if (notification->m_RequestUrl.GetPath().Compare(callback_uri, true)) {
769 NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
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);
778 // parse body
779 if (NPT_FAILED(PLT_XmlHelper::Parse(notification->m_XmlBody, xml))) {
780 NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
783 // check envelope
784 if (xml->GetTag().Compare("propertyset", true)) {
785 NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
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;
798 // check property
799 if (child->GetTag().Compare("property", true)) continue;
801 if (NPT_FAILED(PLT_XmlHelper::GetChild(child, property))) {
802 NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
805 var = service->FindStateVariable(property->GetTag());
806 if (var == NULL) continue;
808 if (NPT_FAILED(var->SetValue(property->GetText() ? property->GetText()->GetChars() : "")))
810 NPT_CHECK_LABEL_WARNING(NPT_FAILURE, failure);
813 vars.Add(var);
816 // update sequence
817 subscriber->SetEventKey(notification->m_EventKey);
819 // Look if a state variable LastChange was received and decompose it into
820 // independent state variable updates
821 DecomposeLastChangeVar(vars);
823 delete xml;
824 return NPT_SUCCESS;
826 failure:
827 NPT_LOG_SEVERE("CtrlPoint failed to process event notification");
828 delete xml;
829 return NPT_SUCCESS;
832 /*----------------------------------------------------------------------
833 | PLT_CtrlPoint::AddPendingEventNotification
834 +---------------------------------------------------------------------*/
835 NPT_Result
836 PLT_CtrlPoint::AddPendingEventNotification(PLT_EventNotification *notification)
838 // Only keep a maximum of 20 pending notifications
839 while (m_PendingNotifications.GetItemCount() > 20) {
840 PLT_EventNotification *garbage = NULL;
841 m_PendingNotifications.PopHead(garbage);
842 delete garbage;
845 m_PendingNotifications.Add(notification);
846 return NPT_SUCCESS;
849 /*----------------------------------------------------------------------
850 | PLT_CtrlPoint::ProcessPendingEventNotifications
851 +---------------------------------------------------------------------*/
852 NPT_Result
853 PLT_CtrlPoint::ProcessPendingEventNotifications()
855 NPT_Cardinal count = m_PendingNotifications.GetItemCount();
856 while (count--) {
857 NPT_List<PLT_StateVariable*> vars;
858 PLT_Service *service = NULL;
859 PLT_EventNotification *notification;
861 if (NPT_SUCCEEDED(m_PendingNotifications.PopHead(notification))) {
862 PLT_EventSubscriberReference sub;
864 // look for the subscriber with that sid
865 if (NPT_FAILED(NPT_ContainerFind(m_Subscribers,
866 PLT_EventSubscriberFinderBySID(notification->m_SID),
867 sub))) {
868 m_PendingNotifications.Add(notification);
869 continue;
872 // keep track of service for listeners later
873 service = sub->GetService();
875 // Reprocess notification
876 NPT_LOG_WARNING_1("Reprocessing delayed notification for subscriber %s", (const char*)notification->m_SID);
877 NPT_Result result = ProcessEventNotification(sub, notification, vars);
878 delete notification;
880 if (NPT_FAILED(result)) continue;
883 // notify listeners
884 if (service && vars.GetItemCount()) {
885 m_ListenerList.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service, &vars));
889 return NPT_SUCCESS;
892 /*----------------------------------------------------------------------
893 | PLT_CtrlPoint::ProcessHttpNotify
894 +---------------------------------------------------------------------*/
895 NPT_Result
896 PLT_CtrlPoint::ProcessHttpNotify(const NPT_HttpRequest& request,
897 const NPT_HttpRequestContext& context,
898 NPT_HttpResponse& response)
900 NPT_COMPILER_UNUSED(context);
902 NPT_AutoLock lock(m_Lock);
904 NPT_List<PLT_StateVariable*> vars;
905 PLT_Service* service = NULL;
906 PLT_EventSubscriberReference sub;
907 NPT_Result result;
909 PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINER, "PLT_CtrlPoint::ProcessHttpNotify:", &request);
911 // Create notification from request
912 PLT_EventNotification* notification = PLT_EventNotification::Parse(request, context, response);
913 NPT_CHECK_POINTER_LABEL_WARNING(notification, bad_request);
915 // Give a last change to process pending notifications before throwing them out
916 // by AddPendingNotification
917 ProcessPendingEventNotifications();
919 // look for the subscriber with that sid
920 if (NPT_FAILED(NPT_ContainerFind(m_Subscribers,
921 PLT_EventSubscriberFinderBySID(notification->m_SID),
922 sub))) {
923 NPT_LOG_WARNING_1("Subscriber %s not found, delaying notification process.\n", (const char*)notification->m_SID);
924 AddPendingEventNotification(notification);
925 return NPT_SUCCESS;
928 // Process notification for subscriber
929 service = sub->GetService();
930 result = ProcessEventNotification(sub, notification, vars);
931 delete notification;
933 NPT_CHECK_LABEL_WARNING(result, bad_request);
935 // Notify listeners
936 if (vars.GetItemCount()) {
937 m_ListenerList.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service, &vars));
940 return NPT_SUCCESS;
942 bad_request:
943 NPT_LOG_SEVERE("CtrlPoint received bad event notify request\r\n");
944 if (response.GetStatusCode() == 200) {
945 response.SetStatus(412, "Precondition Failed");
947 return NPT_SUCCESS;
950 /*----------------------------------------------------------------------
951 | PLT_CtrlPoint::ProcessSsdpSearchResponse
952 +---------------------------------------------------------------------*/
953 NPT_Result
954 PLT_CtrlPoint::ProcessSsdpSearchResponse(NPT_Result res,
955 const NPT_HttpRequestContext& context,
956 NPT_HttpResponse* response)
958 NPT_CHECK_SEVERE(res);
959 NPT_CHECK_POINTER_SEVERE(response);
961 NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
962 NPT_String protocol = response->GetProtocol();
964 NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessSsdpSearchResponse from %s:%d",
965 (const char*)context.GetRemoteAddress().GetIpAddress().ToString() ,
966 context.GetRemoteAddress().GetPort());
967 PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, prefix, response);
969 // any 2xx responses are ok
970 if (response->GetStatusCode()/100 == 2) {
971 const NPT_String* st = response->GetHeaders().GetHeaderValue("st");
972 const NPT_String* usn = response->GetHeaders().GetHeaderValue("usn");
973 const NPT_String* ext = response->GetHeaders().GetHeaderValue("ext");
974 NPT_CHECK_POINTER_SEVERE(st);
975 NPT_CHECK_POINTER_SEVERE(usn);
976 NPT_CHECK_POINTER_SEVERE(ext);
978 NPT_String uuid;
980 // if we get an advertisement other than uuid
981 // verify it's formatted properly
982 if (usn != st) {
983 NPT_List<NPT_String> components = usn->Split("::");
984 if (components.GetItemCount() != 2)
985 return NPT_FAILURE;
987 if (st->Compare(*components.GetItem(1), true))
988 return NPT_FAILURE;
990 uuid = components.GetItem(0)->SubString(5);
991 } else {
992 uuid = usn->SubString(5);
995 if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
996 NPT_LOG_FINE_1("CtrlPoint received a search response from ourselves (%s)\n", (const char*)uuid);
997 return NPT_SUCCESS;
1000 return ProcessSsdpMessage(*response, context, uuid);
1003 return NPT_FAILURE;
1006 /*----------------------------------------------------------------------
1007 | PLT_CtrlPoint::OnSsdpPacket
1008 +---------------------------------------------------------------------*/
1009 NPT_Result
1010 PLT_CtrlPoint::OnSsdpPacket(const NPT_HttpRequest& request,
1011 const NPT_HttpRequestContext& context)
1013 return ProcessSsdpNotify(request, context);
1016 /*----------------------------------------------------------------------
1017 | PLT_CtrlPoint::ProcessSsdpNotify
1018 +---------------------------------------------------------------------*/
1019 NPT_Result
1020 PLT_CtrlPoint::ProcessSsdpNotify(const NPT_HttpRequest& request,
1021 const NPT_HttpRequestContext& context)
1023 // get the address of who sent us some data back
1024 NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString();
1025 NPT_String method = request.GetMethod();
1026 NPT_String uri = request.GetUrl().GetPath(true);
1027 NPT_String protocol = request.GetProtocol();
1029 if (method.Compare("NOTIFY") == 0) {
1030 const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request);
1031 const NPT_String* nt = PLT_UPnPMessageHelper::GetNT(request);
1032 const NPT_String* usn = PLT_UPnPMessageHelper::GetUSN(request);
1034 NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessSsdpNotify from %s:%d (%s)",
1035 context.GetRemoteAddress().GetIpAddress().ToString().GetChars(),
1036 context.GetRemoteAddress().GetPort(),
1037 usn?usn->GetChars():"unknown");
1038 PLT_LOG_HTTP_REQUEST(NPT_LOG_LEVEL_FINER, prefix, &request);
1040 if ((uri.Compare("*") != 0) || (protocol.Compare("HTTP/1.1") != 0))
1041 return NPT_FAILURE;
1043 NPT_CHECK_POINTER_SEVERE(nts);
1044 NPT_CHECK_POINTER_SEVERE(nt);
1045 NPT_CHECK_POINTER_SEVERE(usn);
1047 NPT_String uuid;
1049 // if we get an advertisement other than uuid
1050 // verify it's formatted properly
1051 if (*usn != *nt) {
1052 NPT_List<NPT_String> components = usn->Split("::");
1053 if (components.GetItemCount() != 2)
1054 return NPT_FAILURE;
1056 if (nt->Compare(*components.GetItem(1), true))
1057 return NPT_FAILURE;
1059 uuid = components.GetItem(0)->SubString(5);
1060 } else {
1061 uuid = usn->SubString(5);
1064 if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) {
1065 NPT_LOG_FINE_1("Received a NOTIFY request from ourselves (%s)\n", (const char*)uuid);
1066 return NPT_SUCCESS;
1069 // if it's a byebye, remove the device and return right away
1070 if (nts->Compare("ssdp:byebye", true) == 0) {
1071 NPT_LOG_INFO_1("Received a byebye NOTIFY request from %s\n", (const char*)uuid);
1073 NPT_AutoLock lock(m_Lock);
1075 // look for root device
1076 PLT_DeviceDataReference root_device;
1077 FindDevice(uuid, root_device, true);
1079 if (!root_device.IsNull()) RemoveDevice(root_device);
1080 return NPT_SUCCESS;
1083 return ProcessSsdpMessage(request, context, uuid);
1086 return NPT_FAILURE;
1089 /*----------------------------------------------------------------------
1090 | PLT_CtrlPoint::AddDevice
1091 +---------------------------------------------------------------------*/
1092 NPT_Result
1093 PLT_CtrlPoint::AddDevice(PLT_DeviceDataReference& data)
1095 NPT_AutoLock lock(m_Lock);
1097 return NotifyDeviceReady(data);
1100 /*----------------------------------------------------------------------
1101 | PLT_CtrlPoint::NotifyDeviceReady
1102 +---------------------------------------------------------------------*/
1103 NPT_Result
1104 PLT_CtrlPoint::NotifyDeviceReady(PLT_DeviceDataReference& data)
1106 m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceAddedIterator(data));
1108 /* recursively add embedded devices */
1109 NPT_Array<PLT_DeviceDataReference> embedded_devices =
1110 data->GetEmbeddedDevices();
1111 for (NPT_Cardinal i=0;i<embedded_devices.GetItemCount();i++) {
1112 NotifyDeviceReady(embedded_devices[i]);
1115 return NPT_SUCCESS;
1118 /*----------------------------------------------------------------------
1119 | PLT_CtrlPoint::RemoveDevice
1120 +---------------------------------------------------------------------*/
1121 NPT_Result
1122 PLT_CtrlPoint::RemoveDevice(PLT_DeviceDataReference& data)
1124 NPT_AutoLock lock(m_Lock);
1126 NotifyDeviceRemoved(data);
1127 CleanupDevice(data);
1129 return NPT_SUCCESS;
1132 /*----------------------------------------------------------------------
1133 | PLT_CtrlPoint::NotifyDeviceRemoved
1134 +---------------------------------------------------------------------*/
1135 NPT_Result
1136 PLT_CtrlPoint::NotifyDeviceRemoved(PLT_DeviceDataReference& data)
1138 m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceRemovedIterator(data));
1140 /* recursively add embedded devices */
1141 NPT_Array<PLT_DeviceDataReference> embedded_devices =
1142 data->GetEmbeddedDevices();
1143 for (NPT_Cardinal i=0;i<embedded_devices.GetItemCount();i++) {
1144 NotifyDeviceRemoved(embedded_devices[i]);
1147 return NPT_SUCCESS;
1150 /*----------------------------------------------------------------------
1151 | PLT_CtrlPoint::CleanupDevice
1152 +---------------------------------------------------------------------*/
1153 NPT_Result
1154 PLT_CtrlPoint::CleanupDevice(PLT_DeviceDataReference& data)
1156 if (data.IsNull()) return NPT_ERROR_INVALID_PARAMETERS;
1158 NPT_LOG_INFO_1("Removing %s from device list\n", (const char*)data->GetUUID());
1160 // Note: This must take the lock prior to being called
1161 // we can't take the lock here because this function
1162 // will be recursively called if device contains embedded devices
1164 /* recursively remove embedded devices */
1165 NPT_Array<PLT_DeviceDataReference> embedded_devices = data->GetEmbeddedDevices();
1166 for (NPT_Cardinal i=0;i<embedded_devices.GetItemCount();i++) {
1167 CleanupDevice(embedded_devices[i]);
1170 /* remove from list */
1171 m_RootDevices.Remove(data);
1173 /* unsubscribe from services */
1174 data->m_Services.Apply(PLT_EventSubscriberRemoverIterator(this));
1176 return NPT_SUCCESS;
1179 /*----------------------------------------------------------------------
1180 | PLT_CtrlPoint::ProcessSsdpMessage
1181 +---------------------------------------------------------------------*/
1182 NPT_Result
1183 PLT_CtrlPoint::ProcessSsdpMessage(const NPT_HttpMessage& message,
1184 const NPT_HttpRequestContext& context,
1185 NPT_String& uuid)
1187 NPT_COMPILER_UNUSED(context);
1189 NPT_AutoLock lock(m_Lock);
1191 // check if we should ignore our own UUID
1192 if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) return NPT_SUCCESS;
1194 const NPT_String* url = PLT_UPnPMessageHelper::GetLocation(message);
1195 NPT_CHECK_POINTER_SEVERE(url);
1197 // Fix for Connect360 which uses localhost in device description url
1198 NPT_HttpUrl location(*url);
1199 if (location.GetHost().ToLowercase() == "localhost" ||
1200 location.GetHost().ToLowercase() == "127.0.0.1") {
1201 location.SetHost(context.GetRemoteAddress().GetIpAddress().ToString());
1204 // be nice and assume a default lease time if not found even though it's required
1205 NPT_TimeInterval leasetime;
1206 if (NPT_FAILED(PLT_UPnPMessageHelper::GetLeaseTime(message, leasetime))) {
1207 leasetime = *PLT_Constants::GetInstance().GetDefaultSubscribeLease();
1210 // check if device (or embedded device) is already known
1211 PLT_DeviceDataReference data;
1212 if (NPT_SUCCEEDED(FindDevice(uuid, data))) {
1214 // // in case we missed the byebye and the device description has changed (ip or port)
1215 // // reset base and assumes device is the same (same number of services and embedded devices)
1216 // // FIXME: The right way is to remove the device and rescan it though but how do we know it changed?
1217 // PLT_DeviceReadyIterator device_tester;
1218 // if (NPT_SUCCEEDED(device_tester(data)) && data->GetDescriptionUrl().Compare(location.ToString(), true)) {
1219 // NPT_LOG_INFO_2("Old device \"%s\" detected @ new location %s",
1220 // (const char*)data->GetFriendlyName(),
1221 // (const char*)location.ToString());
1222 // data->SetURLBase(location);
1223 // }
1225 // renew expiration time
1226 data->SetLeaseTime(leasetime);
1227 NPT_LOG_FINE_1("Device \"%s\" expiration time renewed..",
1228 (const char*)data->GetFriendlyName());
1230 return NPT_SUCCESS;
1233 // start inspection
1234 return InspectDevice(location, uuid, leasetime);
1237 /*----------------------------------------------------------------------
1238 | PLT_CtrlPoint::InspectDevice
1239 +---------------------------------------------------------------------*/
1240 NPT_Result
1241 PLT_CtrlPoint::InspectDevice(const NPT_HttpUrl& location,
1242 const char* uuid,
1243 NPT_TimeInterval leasetime)
1245 NPT_AutoLock lock(m_Lock);
1247 // check if already inspecting device
1248 NPT_String pending_uuid;
1249 if (NPT_SUCCEEDED(NPT_ContainerFind(m_PendingInspections,
1250 NPT_StringFinder(uuid),
1251 pending_uuid))) {
1252 return NPT_SUCCESS;
1255 NPT_LOG_INFO_2("Inspecting device \"%s\" detected @ %s",
1256 uuid,
1257 (const char*)location.ToString());
1259 if (!location.IsValid()) {
1260 NPT_LOG_INFO_1("Invalid device description url: %s",
1261 (const char*) location.ToString());
1262 return NPT_FAILURE;
1265 // remember that we're now inspecting the device
1266 m_PendingInspections.Add(uuid);
1268 // Start a task to retrieve the description
1269 PLT_CtrlPointGetDescriptionTask* task = new PLT_CtrlPointGetDescriptionTask(
1270 location,
1271 this,
1272 leasetime,
1273 uuid);
1275 // Add a delay to make sure that we received late NOTIFY bye-bye
1276 NPT_TimeInterval delay(.5f);
1277 m_TaskManager->StartTask(task, &delay);
1279 return NPT_SUCCESS;
1282 /*----------------------------------------------------------------------
1283 | PLT_CtrlPoint::FetchDeviceSCPDs
1284 +---------------------------------------------------------------------*/
1285 NPT_Result
1286 PLT_CtrlPoint::FetchDeviceSCPDs(PLT_CtrlPointGetSCPDsTask* task,
1287 PLT_DeviceDataReference& device,
1288 NPT_Cardinal level)
1290 if (level == 5 && device->m_EmbeddedDevices.GetItemCount()) {
1291 NPT_LOG_FATAL("Too many embedded devices depth! ");
1292 return NPT_FAILURE;
1295 ++level;
1297 // fetch embedded devices services scpds first
1298 for (NPT_Cardinal i = 0;
1299 i<device->m_EmbeddedDevices.GetItemCount();
1300 i++) {
1301 NPT_CHECK_SEVERE(FetchDeviceSCPDs(task, device->m_EmbeddedDevices[i], level));
1304 // Get SCPD of device services now and bail right away if one fails
1305 return device->m_Services.ApplyUntil(
1306 PLT_AddGetSCPDRequestIterator(*task, device),
1307 NPT_UntilResultNotEquals(NPT_SUCCESS));
1310 /*----------------------------------------------------------------------
1311 | PLT_CtrlPoint::ProcessGetDescriptionResponse
1312 +---------------------------------------------------------------------*/
1313 NPT_Result
1314 PLT_CtrlPoint::ProcessGetDescriptionResponse(NPT_Result res,
1315 const NPT_HttpRequest& request,
1316 const NPT_HttpRequestContext& context,
1317 NPT_HttpResponse* response,
1318 NPT_TimeInterval leasetime,
1319 NPT_String uuid)
1321 NPT_COMPILER_UNUSED(request);
1323 NPT_AutoLock lock(m_Lock);
1325 PLT_CtrlPointGetSCPDsTask* task = NULL;
1326 NPT_String desc;
1327 PLT_DeviceDataReference root_device;
1328 PLT_DeviceDataReference device;
1330 // Add a delay, some devices need it (aka Rhapsody)
1331 NPT_TimeInterval delay(0.1f);
1333 NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessGetDescriptionResponse @ %s (result = %d, status = %d)",
1334 (const char*)request.GetUrl().ToString(),
1335 res,
1336 response?response->GetStatusCode():0);
1338 // Remove pending inspection
1339 m_PendingInspections.Remove(uuid);
1341 // verify response was ok
1342 NPT_CHECK_LABEL_FATAL(res, bad_response);
1343 NPT_CHECK_POINTER_LABEL_FATAL(response, bad_response);
1345 // log response
1346 PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, prefix, response);
1348 // get response body
1349 res = PLT_HttpHelper::GetBody(*response, desc);
1350 NPT_CHECK_SEVERE(res);
1352 // create new root device
1353 NPT_CHECK_SEVERE(PLT_DeviceData::SetDescription(root_device, leasetime, request.GetUrl(), desc, context));
1355 // make sure root device was not previously queried
1356 if (NPT_FAILED(FindDevice(root_device->GetUUID(), device))) {
1357 m_RootDevices.Add(root_device);
1359 NPT_LOG_INFO_3("Device \"%s\" is now known as \"%s\" (%s)",
1360 (const char*)root_device->GetUUID(),
1361 (const char*)root_device->GetFriendlyName(),
1362 (const char*)root_device->GetDescriptionUrl(NULL));
1364 // create one single task to fetch all scpds one after the other
1365 task = new PLT_CtrlPointGetSCPDsTask(this, root_device);
1366 NPT_CHECK_LABEL_SEVERE(res = FetchDeviceSCPDs(task, root_device, 0),
1367 cleanup);
1369 // if device has embedded devices, we want to delay fetching scpds
1370 // just in case there's a chance all the initial NOTIFY bye-bye have
1371 // not all been received yet which would cause to remove the devices
1372 // as we're adding them
1373 if (root_device->m_EmbeddedDevices.GetItemCount() > 0) {
1374 delay = 1.f;
1376 NPT_CHECK_LABEL_SEVERE(res = m_TaskManager->StartTask(task, &delay),
1377 failure);
1380 return NPT_SUCCESS;
1382 bad_response:
1383 NPT_LOG_SEVERE_2("Bad Description response @ %s: %s",
1384 (const char*)request.GetUrl().ToString(),
1385 (const char*)desc);
1387 cleanup:
1388 if (task) delete task;
1390 failure:
1391 return res;
1394 /*----------------------------------------------------------------------
1395 | PLT_CtrlPoint::ProcessGetSCPDResponse
1396 +---------------------------------------------------------------------*/
1397 NPT_Result
1398 PLT_CtrlPoint::ProcessGetSCPDResponse(NPT_Result res,
1399 const NPT_HttpRequest& request,
1400 const NPT_HttpRequestContext& context,
1401 NPT_HttpResponse* response,
1402 PLT_DeviceDataReference& device)
1404 NPT_COMPILER_UNUSED(context);
1406 NPT_AutoLock lock(m_Lock);
1408 PLT_DeviceReadyIterator device_tester;
1409 NPT_String scpd;
1410 PLT_DeviceDataReference root_device;
1411 PLT_Service* service;
1413 NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessGetSCPDResponse for a service of device \"%s\" @ %s (result = %d, status = %d)",
1414 (const char*)device->GetFriendlyName(),
1415 (const char*)request.GetUrl().ToString(),
1416 res,
1417 response?response->GetStatusCode():0);
1419 // verify response was ok
1420 NPT_CHECK_LABEL_FATAL(res, bad_response);
1421 NPT_CHECK_POINTER_LABEL_FATAL(response, bad_response);
1423 PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, prefix, response);
1425 // make sure root device hasn't disappeared
1426 NPT_CHECK_LABEL_WARNING(FindDevice(device->GetUUID(), root_device, true),
1427 bad_response);
1429 res = device->FindServiceBySCPDURL(request.GetUrl().ToRequestString(), service);
1430 NPT_CHECK_LABEL_SEVERE(res, bad_response);
1432 // get response body
1433 res = PLT_HttpHelper::GetBody(*response, scpd);
1434 NPT_CHECK_LABEL_FATAL(res, bad_response);
1436 // DIAL support
1437 if (root_device->GetType().Compare("urn:dial-multiscreen-org:device:dial:1") == 0) {
1438 AddDevice(root_device);
1439 return NPT_SUCCESS;
1442 // set the service scpd
1443 res = service->SetSCPDXML(scpd);
1444 NPT_CHECK_LABEL_SEVERE(res, bad_response);
1446 // if root device is ready, notify listeners about it and embedded devices
1447 if (NPT_SUCCEEDED(device_tester(root_device))) {
1448 AddDevice(root_device);
1451 return NPT_SUCCESS;
1453 bad_response:
1454 NPT_LOG_SEVERE_2("Bad SCPD response for device \"%s\":%s",
1455 (const char*)device->GetFriendlyName(),
1456 (const char*)scpd);
1458 if (!root_device.IsNull()) RemoveDevice(root_device);
1459 return res;
1462 /*----------------------------------------------------------------------
1463 | PLT_CtrlPoint::RenewSubscriber
1464 +---------------------------------------------------------------------*/
1465 PLT_ThreadTask*
1466 PLT_CtrlPoint::RenewSubscriber(PLT_EventSubscriberReference subscriber)
1468 NPT_AutoLock lock(m_Lock);
1470 PLT_DeviceDataReference root_device;
1471 if (NPT_FAILED(FindDevice(subscriber->GetService()->GetDevice()->GetUUID(),
1472 root_device,
1473 true))) {
1474 return NULL;
1477 NPT_LOG_FINE_3("Renewing subscriber \"%s\" for service \"%s\" of device \"%s\"",
1478 (const char*)subscriber->GetSID(),
1479 (const char*)subscriber->GetService()->GetServiceID(),
1480 (const char*)subscriber->GetService()->GetDevice()->GetFriendlyName());
1482 // create the request
1483 NPT_HttpRequest* request = new NPT_HttpRequest(
1484 subscriber->GetService()->GetEventSubURL(true),
1485 "SUBSCRIBE",
1486 NPT_HTTP_PROTOCOL_1_1);
1488 PLT_UPnPMessageHelper::SetSID(*request, subscriber->GetSID());
1489 PLT_UPnPMessageHelper::SetTimeOut(*request,
1490 (NPT_Int32)PLT_Constants::GetInstance().GetDefaultSubscribeLease()->ToSeconds());
1492 // Prepare the request
1493 // create a task to post the request
1494 return new PLT_CtrlPointSubscribeEventTask(
1495 request,
1496 this,
1497 root_device,
1498 subscriber->GetService());
1501 /*----------------------------------------------------------------------
1502 | PLT_CtrlPoint::Subscribe
1503 +---------------------------------------------------------------------*/
1504 NPT_Result
1505 PLT_CtrlPoint::Subscribe(PLT_Service* service,
1506 bool cancel,
1507 void* userdata)
1509 NPT_AutoLock lock(m_Lock);
1511 if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
1513 NPT_HttpRequest* request = NULL;
1515 // make sure service is subscribable
1516 if (!service->IsSubscribable()) return NPT_FAILURE;
1518 // event url
1519 NPT_HttpUrl url(service->GetEventSubURL(true));
1521 // look for the corresponding root device & sub
1522 PLT_DeviceDataReference root_device;
1523 PLT_EventSubscriberReference sub;
1524 NPT_CHECK_WARNING(FindDevice(service->GetDevice()->GetUUID(),
1525 root_device,
1526 true));
1528 // look for the subscriber with that service to decide if it's a renewal or not
1529 NPT_ContainerFind(m_Subscribers,
1530 PLT_EventSubscriberFinderByService(service),
1531 sub);
1533 if (cancel == false) {
1534 // renewal?
1535 if (!sub.IsNull()) {
1536 PLT_ThreadTask* task = RenewSubscriber(sub);
1537 return m_TaskManager->StartTask(task);
1540 NPT_LOG_INFO_2("Subscribing to service \"%s\" of device \"%s\"",
1541 (const char*)service->GetServiceID(),
1542 (const char*)service->GetDevice()->GetFriendlyName());
1544 // prepare the callback url
1545 NPT_String uuid = service->GetDevice()->GetUUID();
1546 NPT_String service_id = service->GetServiceID();
1547 NPT_String callback_uri = "/" + uuid + "/" + service_id;
1549 // create the request
1550 request = new NPT_HttpRequest(url, "SUBSCRIBE", NPT_HTTP_PROTOCOL_1_1);
1551 // specify callback url using ip of interface used when
1552 // retrieving device description
1553 NPT_HttpUrl callbackUrl(
1554 service->GetDevice()->m_LocalIfaceIp.ToString(),
1555 m_EventHttpServer->GetPort(),
1556 callback_uri);
1558 // set the required headers for a new subscription
1559 PLT_UPnPMessageHelper::SetNT(*request, "upnp:event");
1560 PLT_UPnPMessageHelper::SetCallbacks(*request,
1561 "<" + callbackUrl.ToString() + ">");
1562 PLT_UPnPMessageHelper::SetTimeOut(*request,
1563 (NPT_Int32)PLT_Constants::GetInstance().GetDefaultSubscribeLease()->ToSeconds());
1564 } else {
1565 NPT_LOG_INFO_3("Unsubscribing subscriber \"%s\" for service \"%s\" of device \"%s\"",
1566 (const char*)(!sub.IsNull()?sub->GetSID().GetChars():"unknown"),
1567 (const char*)service->GetServiceID(),
1568 (const char*)service->GetDevice()->GetFriendlyName());
1570 // cancellation
1571 if (sub.IsNull()) return NPT_FAILURE;
1573 // create the request
1574 request = new NPT_HttpRequest(url, "UNSUBSCRIBE", NPT_HTTP_PROTOCOL_1_1);
1575 PLT_UPnPMessageHelper::SetSID(*request, sub->GetSID());
1577 // remove from list now
1578 m_Subscribers.Remove(sub, true);
1581 // verify we have request to send just in case
1582 NPT_CHECK_POINTER_FATAL(request);
1584 // Prepare the request
1585 // create a task to post the request
1586 PLT_ThreadTask* task = new PLT_CtrlPointSubscribeEventTask(
1587 request,
1588 this,
1589 root_device,
1590 service,
1591 userdata);
1592 m_TaskManager->StartTask(task);
1594 return NPT_SUCCESS;
1597 /*----------------------------------------------------------------------
1598 | PLT_CtrlPoint::ProcessSubscribeResponse
1599 +---------------------------------------------------------------------*/
1600 NPT_Result
1601 PLT_CtrlPoint::ProcessSubscribeResponse(NPT_Result res,
1602 const NPT_HttpRequest& request,
1603 const NPT_HttpRequestContext& context,
1604 NPT_HttpResponse* response,
1605 PLT_Service* service,
1606 void* /* userdata */)
1608 NPT_COMPILER_UNUSED(context);
1610 NPT_AutoLock lock(m_Lock);
1612 const NPT_String* sid = NULL;
1613 NPT_Int32 seconds = -1;
1614 PLT_EventSubscriberReference sub;
1615 bool subscription = (request.GetMethod().ToUppercase() == "SUBSCRIBE");
1617 NPT_String prefix = NPT_String::Format("PLT_CtrlPoint::ProcessSubscribeResponse %ubscribe for service \"%s\" (result = %d, status code = %d)",
1618 (const char*)subscription?"S":"Uns",
1619 (const char*)service->GetServiceID(),
1620 res,
1621 response?response->GetStatusCode():0);
1622 PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, prefix, response);
1624 // if there's a failure or it's a response to a cancellation
1625 // we get out (any 2xx status code ok)
1626 if (NPT_FAILED(res) || response == NULL || response->GetStatusCode()/100 != 2) {
1627 goto failure;
1630 if (subscription) {
1631 if (!(sid = PLT_UPnPMessageHelper::GetSID(*response)) ||
1632 NPT_FAILED(PLT_UPnPMessageHelper::GetTimeOut(*response, seconds))) {
1633 NPT_CHECK_LABEL_SEVERE(res = NPT_ERROR_INVALID_SYNTAX, failure);
1636 // Look for subscriber
1637 NPT_ContainerFind(m_Subscribers,
1638 PLT_EventSubscriberFinderBySID(*sid),
1639 sub);
1641 NPT_LOG_INFO_5("%s subscriber \"%s\" for service \"%s\" of device \"%s\" (timeout = %d)",
1642 !sub.IsNull()?"Updating timeout for":"Creating new",
1643 (const char*)*sid,
1644 (const char*)service->GetServiceID(),
1645 (const char*)service->GetDevice()->GetFriendlyName(),
1646 seconds);
1648 // create new subscriber if sid never seen before
1649 // or update subscriber expiration otherwise
1650 if (sub.IsNull()) {
1651 sub = new PLT_EventSubscriber(m_TaskManager, service, *sid, seconds);
1652 m_Subscribers.Add(sub);
1653 } else {
1654 sub->SetTimeout(seconds);
1657 // Process any pending notifcations for that subscriber we got a bit too early
1658 ProcessPendingEventNotifications();
1660 return NPT_SUCCESS;
1663 goto remove_sub;
1665 failure:
1666 NPT_LOG_SEVERE_4(
1667 "%subscription failed of sub \"%s\" for service \"%s\" of device \"%s\"",
1668 (const char*)subscription ? "S" : "Uns", (const char*)(sid ? sid->GetChars() : "Unknown"),
1669 (const char*)service->GetServiceID(), (const char*)service->GetDevice()->GetFriendlyName());
1670 res = NPT_FAILED(res) ? res : NPT_FAILURE;
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);
1680 return res;
1683 /*----------------------------------------------------------------------
1684 | PLT_CtrlPoint::InvokeAction
1685 +---------------------------------------------------------------------*/
1686 NPT_Result
1687 PLT_CtrlPoint::InvokeAction(PLT_ActionReference& action,
1688 void* userdata)
1690 if (!m_Started) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE);
1692 PLT_Service* service = action->GetActionDesc().GetService();
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);
1698 // create a memory stream for our request body
1699 NPT_MemoryStreamReference stream(new NPT_MemoryStream);
1700 action->FormatSoapRequest(*stream);
1702 // set the request body
1703 NPT_HttpEntity* entity = NULL;
1704 PLT_HttpHelper::SetBody(*request, (NPT_InputStreamReference)stream, &entity);
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 + "\"");
1711 // create a task to post the request
1712 PLT_CtrlPointInvokeActionTask* task = new PLT_CtrlPointInvokeActionTask(
1713 request,
1714 this,
1715 action,
1716 userdata);
1718 // queue the request
1719 m_TaskManager->StartTask(task);
1721 return NPT_SUCCESS;
1724 /*----------------------------------------------------------------------
1725 | PLT_CtrlPoint::ProcessActionResponse
1726 +---------------------------------------------------------------------*/
1727 NPT_Result
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)
1735 NPT_COMPILER_UNUSED(request);
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();
1748 // reset the error code and desc
1749 action->SetError(0, "");
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;
1763 PLT_LOG_HTTP_RESPONSE(NPT_LOG_LEVEL_FINER, "PLT_CtrlPoint::ProcessActionResponse:", response);
1765 NPT_LOG_FINER("Reading/Parsing Action Response Body...");
1766 if (NPT_FAILED(PLT_HttpHelper::ParseBody(*response, xml))) {
1767 goto failure;
1770 NPT_LOG_FINER("Analyzing Action Response Body...");
1772 // read envelope
1773 if (xml->GetTag().Compare("Envelope", true))
1774 goto failure;
1776 // check namespace
1777 if (!xml->GetNamespace() || xml->GetNamespace()->Compare("http://schemas.xmlsoap.org/soap/envelope/"))
1778 goto failure;
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;
1785 // read action
1786 soap_body = PLT_XmlHelper::GetChild(xml, "Body");
1787 if (soap_body == NULL)
1788 goto failure;
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;
1798 if (NPT_FAILED(PLT_XmlHelper::GetChild(soap_body, soap_action_response)))
1799 goto failure;
1801 // verify action name is identical to SOAPACTION header
1802 if (soap_action_response->GetTag().Compare(action_desc.GetName() + "Response", true))
1803 goto failure;
1805 // verify namespace
1806 if (!soap_action_response->GetNamespace() ||
1807 soap_action_response->GetNamespace()->Compare(action_desc.GetService()->GetServiceType()))
1808 goto failure;
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;
1817 action->SetArgumentValue(child->GetTag(),
1818 child->GetText() ? child->GetText()->GetChars() : "");
1819 if (NPT_FAILED(res)) goto failure;
1822 // create a buffer for our response body and call the service
1823 res = action->VerifyArguments(false);
1824 if (NPT_FAILED(res)) goto failure;
1826 goto cleanup;
1828 failure:
1829 // override res with failure if necessary
1830 if (NPT_SUCCEEDED(res)) res = NPT_FAILURE;
1831 // fallthrough
1833 cleanup:
1835 NPT_AutoLock lock(m_Lock);
1836 m_ListenerList.Apply(PLT_CtrlPointListenerOnActionResponseIterator(res, action, userdata));
1839 delete xml;
1840 return res;
1843 /*----------------------------------------------------------------------
1844 | PLT_CtrlPoint::ParseFault
1845 +---------------------------------------------------------------------*/
1846 NPT_Result
1847 PLT_CtrlPoint::ParseFault(PLT_ActionReference& action,
1848 NPT_XmlElementNode* fault)
1850 NPT_XmlElementNode* detail = fault->GetChild("detail");
1851 if (detail == NULL) return NPT_FAILURE;
1853 NPT_XmlElementNode *upnp_error, *error_code, *error_desc;
1854 upnp_error = detail->GetChild("upnp_error");
1856 // WMP12 Hack
1857 if (upnp_error == NULL) {
1858 upnp_error = detail->GetChild("UPnPError", NPT_XML_ANY_NAMESPACE);
1859 if (upnp_error == NULL) return NPT_FAILURE;
1862 error_code = upnp_error->GetChild("errorCode", NPT_XML_ANY_NAMESPACE);
1863 error_desc = upnp_error->GetChild("errorDescription", NPT_XML_ANY_NAMESPACE);
1864 NPT_Int32 code = 501;
1865 NPT_String desc;
1866 if (error_code && error_code->GetText()) {
1867 NPT_String value = *error_code->GetText();
1868 value.ToInteger(code);
1870 if (error_desc && error_desc->GetText()) {
1871 desc = *error_desc->GetText();
1873 action->SetError(code, desc);
1874 return NPT_SUCCESS;