1 /*****************************************************************
3 | Platinum - Control Point
5 | Copyright (c) 2004-2010, Plutinosoft, LLC.
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 /*----------------------------------------------------------------------
37 +---------------------------------------------------------------------*/
38 #include "PltCtrlPoint.h"
40 #include "PltDeviceData.h"
41 #include "PltUtilities.h"
42 #include "PltCtrlPointTask.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
55 PLT_CtrlPointListenerOnDeviceAddedIterator(PLT_DeviceDataReference
& device
) :
58 NPT_Result
operator()(PLT_CtrlPointListener
*& listener
) const {
59 return listener
->OnDeviceAdded(m_Device
);
63 PLT_DeviceDataReference
& m_Device
;
66 /*----------------------------------------------------------------------
67 | PLT_CtrlPointListenerOnDeviceRemovedIterator class
68 +---------------------------------------------------------------------*/
69 class PLT_CtrlPointListenerOnDeviceRemovedIterator
72 PLT_CtrlPointListenerOnDeviceRemovedIterator(PLT_DeviceDataReference
& device
) :
75 NPT_Result
operator()(PLT_CtrlPointListener
*& listener
) const {
76 return listener
->OnDeviceRemoved(m_Device
);
80 PLT_DeviceDataReference
& m_Device
;
83 /*----------------------------------------------------------------------
84 | PLT_CtrlPointListenerOnActionResponseIterator class
85 +---------------------------------------------------------------------*/
86 class PLT_CtrlPointListenerOnActionResponseIterator
89 PLT_CtrlPointListenerOnActionResponseIterator(NPT_Result res
,
90 PLT_ActionReference
& action
,
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
);
100 PLT_ActionReference
& m_Action
;
104 /*----------------------------------------------------------------------
105 | PLT_CtrlPointListenerOnEventNotifyIterator class
106 +---------------------------------------------------------------------*/
107 class PLT_CtrlPointListenerOnEventNotifyIterator
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
);
119 PLT_Service
* m_Service
;
120 NPT_List
<PLT_StateVariable
*>* m_Vars
;
123 /*----------------------------------------------------------------------
124 | PLT_AddGetSCPDRequestIterator class
125 +---------------------------------------------------------------------*/
126 class PLT_AddGetSCPDRequestIterator
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
);
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
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
);
187 PLT_CtrlPoint
* m_CtrlPoint
;
190 /*----------------------------------------------------------------------
191 | PLT_ServiceReadyIterator class
192 +---------------------------------------------------------------------*/
193 class PLT_ServiceReadyIterator
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
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) {
232 /*----------------------------------------------------------------------
233 | PLT_CtrlPoint::PLT_CtrlPoint
234 +---------------------------------------------------------------------*/
235 PLT_CtrlPoint::PLT_CtrlPoint(const char* search_criteria
/* = "upnp:rootdevice" */) :
236 m_EventHttpServer(NULL
),
239 m_SearchCriteria(search_criteria
),
244 /*----------------------------------------------------------------------
245 | PLT_CtrlPoint::~PLT_CtrlPoint
246 +---------------------------------------------------------------------*/
247 PLT_CtrlPoint::~PLT_CtrlPoint()
251 /*----------------------------------------------------------------------
252 | PLT_CtrlPoint::IgnoreUUID
253 +---------------------------------------------------------------------*/
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 +---------------------------------------------------------------------*/
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;
289 return m_SearchCriteria
.GetLength()?Search(NPT_HttpUrl("239.255.255.250", 1900, "*"), m_SearchCriteria
):NPT_SUCCESS
;
292 /*----------------------------------------------------------------------
293 | PLT_CtrlPoint::GetPort
294 +---------------------------------------------------------------------*/
296 PLT_CtrlPoint::GetPort(NPT_UInt16
& port
)
298 if (!m_Started
) return NPT_ERROR_INVALID_STATE
;
300 port
= m_EventHttpServer
->GetPort();
304 /*----------------------------------------------------------------------
305 | PLT_CtrlPoint::Stop
306 +---------------------------------------------------------------------*/
308 PLT_CtrlPoint::Stop(PLT_SsdpListenTask
* task
)
310 if (!m_Started
) NPT_CHECK_WARNING(NPT_ERROR_INVALID_STATE
);
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();
322 NotifyDeviceRemoved(*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
;
337 /*----------------------------------------------------------------------
338 | PLT_CtrlPoint::AddListener
339 +---------------------------------------------------------------------*/
341 PLT_CtrlPoint::AddListener(PLT_CtrlPointListener
* listener
)
343 NPT_AutoLock
lock(m_Lock
);
344 if (!m_ListenerList
.Contains(listener
)) {
345 m_ListenerList
.Add(listener
);
350 /*----------------------------------------------------------------------
351 | PLT_CtrlPoint::RemoveListener
352 +---------------------------------------------------------------------*/
354 PLT_CtrlPoint::RemoveListener(PLT_CtrlPointListener
* listener
)
356 NPT_AutoLock
lock(m_Lock
);
357 m_ListenerList
.Remove(listener
);
361 /*----------------------------------------------------------------------
362 | PLT_CtrlPoint::CreateSearchTask
363 +---------------------------------------------------------------------*/
365 PLT_CtrlPoint::CreateSearchTask(const NPT_HttpUrl
& url
,
368 NPT_TimeInterval frequency
,
369 const NPT_IpAddress
& address
)
371 // make sure mx is at least 1
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
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
),
394 NPT_LOG_SEVERE("Couldn't bind socket for Search Task");
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());
406 PLT_SsdpSearchTask
* task
= new PLT_SsdpSearchTask(
410 (frequency
.ToMillis()>0 && frequency
.ToMillis()<5000)?NPT_TimeInterval(5.):frequency
); /* repeat no less than every 5 secs */
416 /*----------------------------------------------------------------------
417 | PLT_CtrlPoint::Search
418 +---------------------------------------------------------------------*/
420 PLT_CtrlPoint::Search(const NPT_HttpUrl
& url
,
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();
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
)) {
443 for (net_if_addr
= (*net_if
)->GetAddresses().GetFirstItem();
447 PLT_SsdpSearchTask
* task
= CreateSearchTask(url
,
451 (*net_if_addr
).GetPrimaryAddress());
452 m_TaskManager
->StartTask(task
, &initial_delay
);
456 if_list
.Apply(NPT_ObjectDeleter
<NPT_NetworkInterface
>());
460 /*----------------------------------------------------------------------
461 | PLT_CtrlPoint::Discover
462 +---------------------------------------------------------------------*/
464 PLT_CtrlPoint::Discover(const NPT_HttpUrl
& url
,
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
476 NPT_UdpSocket
* socket
= new NPT_UdpSocket(NPT_SOCKET_FLAG_CANCELLABLE
);
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");
490 PLT_ThreadTask
* task
= new PLT_SsdpSearchTask(
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 +---------------------------------------------------------------------*/
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)
519 NPT_System::GetCurrentTimeStamp(now
);
520 if (now
> last_update
+ NPT_TimeInterval((double)lease_time
*2)) {
521 devices_to_remove
.Add(device
);
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())
537 // remove old devices
539 NPT_AutoLock
lock(m_Lock
);
541 for (NPT_List
<PLT_DeviceDataReference
>::Iterator device
=
542 devices_to_remove
.GetFirstItem();
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();
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
);
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();
573 PLT_ThreadTask
* _task
= *task
++;
574 m_TaskManager
->StartTask(_task
);
580 /*----------------------------------------------------------------------
581 | PLT_CtrlPoint::FindDevice
582 +---------------------------------------------------------------------*/
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();
590 // device uuid found immediately as root device
591 if ((*iter
)->GetUUID().Compare(uuid
) == 0) {
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
);
603 return NPT_ERROR_NO_SUCH_ITEM
;
606 /*----------------------------------------------------------------------
607 | PLT_CtrlPoint::FindActionDesc
608 +---------------------------------------------------------------------*/
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
);
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
);
633 /*----------------------------------------------------------------------
634 | PLT_CtrlPoint::CreateAction
635 +---------------------------------------------------------------------*/
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
,
652 PLT_DeviceDataReference root_device
;
653 NPT_CHECK_SEVERE(FindDevice(device
->GetUUID(), root_device
, true));
655 action
= new PLT_Action(*action_desc
, root_device
);
659 /*----------------------------------------------------------------------
660 | PLT_CtrlPoint::SetupResponse
661 +---------------------------------------------------------------------*/
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");
678 /*----------------------------------------------------------------------
679 | PLT_CtrlPoint::DecomposeLastChangeVar
680 +---------------------------------------------------------------------*/
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"),
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()) {
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
)))
709 if (!child
->GetTag().Compare("InstanceID", true)) {
710 // extract the "val" attribute value
712 if (NPT_SUCCEEDED(PLT_XmlHelper::GetAttribute(child
, "val", value
)) &&
713 !value
.Compare("0")) {
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
)))
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
))) {
736 NPT_LOG_FINE_2("LastChange var change for (%s): %s",
737 (const char*)var
->GetName(),
738 (const char*)var
->GetValue());
750 /*----------------------------------------------------------------------
751 | PLT_CtrlPoint::ProcessEventNotification
752 +---------------------------------------------------------------------*/
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
);
779 if (NPT_FAILED(PLT_XmlHelper::Parse(notification
->m_XmlBody
, xml
))) {
780 NPT_CHECK_LABEL_WARNING(NPT_FAILURE
, failure
);
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();
795 NPT_XmlElementNode
* child
= (*children
)->AsElementNode();
796 if (!child
) continue;
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
);
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
);
827 NPT_LOG_SEVERE("CtrlPoint failed to process event notification");
832 /*----------------------------------------------------------------------
833 | PLT_CtrlPoint::AddPendingEventNotification
834 +---------------------------------------------------------------------*/
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
);
845 m_PendingNotifications
.Add(notification
);
849 /*----------------------------------------------------------------------
850 | PLT_CtrlPoint::ProcessPendingEventNotifications
851 +---------------------------------------------------------------------*/
853 PLT_CtrlPoint::ProcessPendingEventNotifications()
855 NPT_Cardinal count
= m_PendingNotifications
.GetItemCount();
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
),
868 m_PendingNotifications
.Add(notification
);
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
);
880 if (NPT_FAILED(result
)) continue;
884 if (service
&& vars
.GetItemCount()) {
885 m_ListenerList
.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service
, &vars
));
892 /*----------------------------------------------------------------------
893 | PLT_CtrlPoint::ProcessHttpNotify
894 +---------------------------------------------------------------------*/
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
;
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
),
923 NPT_LOG_WARNING_1("Subscriber %s not found, delaying notification process.\n", (const char*)notification
->m_SID
);
924 AddPendingEventNotification(notification
);
928 // Process notification for subscriber
929 service
= sub
->GetService();
930 result
= ProcessEventNotification(sub
, notification
, vars
);
933 NPT_CHECK_LABEL_WARNING(result
, bad_request
);
936 if (vars
.GetItemCount()) {
937 m_ListenerList
.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service
, &vars
));
943 NPT_LOG_SEVERE("CtrlPoint received bad event notify request\r\n");
944 if (response
.GetStatusCode() == 200) {
945 response
.SetStatus(412, "Precondition Failed");
950 /*----------------------------------------------------------------------
951 | PLT_CtrlPoint::ProcessSsdpSearchResponse
952 +---------------------------------------------------------------------*/
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
);
980 // if we get an advertisement other than uuid
981 // verify it's formatted properly
983 NPT_List
<NPT_String
> components
= usn
->Split("::");
984 if (components
.GetItemCount() != 2)
987 if (st
->Compare(*components
.GetItem(1), true))
990 uuid
= components
.GetItem(0)->SubString(5);
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
);
1000 return ProcessSsdpMessage(*response
, context
, uuid
);
1006 /*----------------------------------------------------------------------
1007 | PLT_CtrlPoint::OnSsdpPacket
1008 +---------------------------------------------------------------------*/
1010 PLT_CtrlPoint::OnSsdpPacket(const NPT_HttpRequest
& request
,
1011 const NPT_HttpRequestContext
& context
)
1013 return ProcessSsdpNotify(request
, context
);
1016 /*----------------------------------------------------------------------
1017 | PLT_CtrlPoint::ProcessSsdpNotify
1018 +---------------------------------------------------------------------*/
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))
1043 NPT_CHECK_POINTER_SEVERE(nts
);
1044 NPT_CHECK_POINTER_SEVERE(nt
);
1045 NPT_CHECK_POINTER_SEVERE(usn
);
1049 // if we get an advertisement other than uuid
1050 // verify it's formatted properly
1052 NPT_List
<NPT_String
> components
= usn
->Split("::");
1053 if (components
.GetItemCount() != 2)
1056 if (nt
->Compare(*components
.GetItem(1), true))
1059 uuid
= components
.GetItem(0)->SubString(5);
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
);
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
);
1083 return ProcessSsdpMessage(request
, context
, uuid
);
1089 /*----------------------------------------------------------------------
1090 | PLT_CtrlPoint::AddDevice
1091 +---------------------------------------------------------------------*/
1093 PLT_CtrlPoint::AddDevice(PLT_DeviceDataReference
& data
)
1095 NPT_AutoLock
lock(m_Lock
);
1097 return NotifyDeviceReady(data
);
1100 /*----------------------------------------------------------------------
1101 | PLT_CtrlPoint::NotifyDeviceReady
1102 +---------------------------------------------------------------------*/
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
]);
1118 /*----------------------------------------------------------------------
1119 | PLT_CtrlPoint::RemoveDevice
1120 +---------------------------------------------------------------------*/
1122 PLT_CtrlPoint::RemoveDevice(PLT_DeviceDataReference
& data
)
1124 NPT_AutoLock
lock(m_Lock
);
1126 NotifyDeviceRemoved(data
);
1127 CleanupDevice(data
);
1132 /*----------------------------------------------------------------------
1133 | PLT_CtrlPoint::NotifyDeviceRemoved
1134 +---------------------------------------------------------------------*/
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
]);
1150 /*----------------------------------------------------------------------
1151 | PLT_CtrlPoint::CleanupDevice
1152 +---------------------------------------------------------------------*/
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));
1179 /*----------------------------------------------------------------------
1180 | PLT_CtrlPoint::ProcessSsdpMessage
1181 +---------------------------------------------------------------------*/
1183 PLT_CtrlPoint::ProcessSsdpMessage(const NPT_HttpMessage
& message
,
1184 const NPT_HttpRequestContext
& context
,
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);
1225 // renew expiration time
1226 data
->SetLeaseTime(leasetime
);
1227 NPT_LOG_FINE_1("Device \"%s\" expiration time renewed..",
1228 (const char*)data
->GetFriendlyName());
1234 return InspectDevice(location
, uuid
, leasetime
);
1237 /*----------------------------------------------------------------------
1238 | PLT_CtrlPoint::InspectDevice
1239 +---------------------------------------------------------------------*/
1241 PLT_CtrlPoint::InspectDevice(const NPT_HttpUrl
& location
,
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
),
1255 NPT_LOG_INFO_2("Inspecting device \"%s\" detected @ %s",
1257 (const char*)location
.ToString());
1259 if (!location
.IsValid()) {
1260 NPT_LOG_INFO_1("Invalid device description url: %s",
1261 (const char*) location
.ToString());
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(
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
);
1282 /*----------------------------------------------------------------------
1283 | PLT_CtrlPoint::FetchDeviceSCPDs
1284 +---------------------------------------------------------------------*/
1286 PLT_CtrlPoint::FetchDeviceSCPDs(PLT_CtrlPointGetSCPDsTask
* task
,
1287 PLT_DeviceDataReference
& device
,
1290 if (level
== 5 && device
->m_EmbeddedDevices
.GetItemCount()) {
1291 NPT_LOG_FATAL("Too many embedded devices depth! ");
1297 // fetch embedded devices services scpds first
1298 for (NPT_Cardinal i
= 0;
1299 i
<device
->m_EmbeddedDevices
.GetItemCount();
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 +---------------------------------------------------------------------*/
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
,
1321 NPT_COMPILER_UNUSED(request
);
1323 NPT_AutoLock
lock(m_Lock
);
1325 PLT_CtrlPointGetSCPDsTask
* task
= NULL
;
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(),
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
);
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),
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) {
1376 NPT_CHECK_LABEL_SEVERE(res
= m_TaskManager
->StartTask(task
, &delay
),
1383 NPT_LOG_SEVERE_2("Bad Description response @ %s: %s",
1384 (const char*)request
.GetUrl().ToString(),
1388 if (task
) delete task
;
1394 /*----------------------------------------------------------------------
1395 | PLT_CtrlPoint::ProcessGetSCPDResponse
1396 +---------------------------------------------------------------------*/
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
;
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(),
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),
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
);
1437 if (root_device
->GetType().Compare("urn:dial-multiscreen-org:device:dial:1") == 0) {
1438 AddDevice(root_device
);
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
);
1454 NPT_LOG_SEVERE_2("Bad SCPD response for device \"%s\":%s",
1455 (const char*)device
->GetFriendlyName(),
1458 if (!root_device
.IsNull()) RemoveDevice(root_device
);
1462 /*----------------------------------------------------------------------
1463 | PLT_CtrlPoint::RenewSubscriber
1464 +---------------------------------------------------------------------*/
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(),
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),
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(
1498 subscriber
->GetService());
1501 /*----------------------------------------------------------------------
1502 | PLT_CtrlPoint::Subscribe
1503 +---------------------------------------------------------------------*/
1505 PLT_CtrlPoint::Subscribe(PLT_Service
* service
,
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
;
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(),
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
),
1533 if (cancel
== false) {
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(),
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());
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());
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(
1592 m_TaskManager
->StartTask(task
);
1597 /*----------------------------------------------------------------------
1598 | PLT_CtrlPoint::ProcessSubscribeResponse
1599 +---------------------------------------------------------------------*/
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(),
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) {
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
),
1641 NPT_LOG_INFO_5("%s subscriber \"%s\" for service \"%s\" of device \"%s\" (timeout = %d)",
1642 !sub
.IsNull()?"Updating timeout for":"Creating new",
1644 (const char*)service
->GetServiceID(),
1645 (const char*)service
->GetDevice()->GetFriendlyName(),
1648 // create new subscriber if sid never seen before
1649 // or update subscriber expiration otherwise
1651 sub
= new PLT_EventSubscriber(m_TaskManager
, service
, *sid
, seconds
);
1652 m_Subscribers
.Add(sub
);
1654 sub
->SetTimeout(seconds
);
1657 // Process any pending notifcations for that subscriber we got a bit too early
1658 ProcessPendingEventNotifications();
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
;
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
),
1677 m_Subscribers
.Remove(sub
);
1683 /*----------------------------------------------------------------------
1684 | PLT_CtrlPoint::InvokeAction
1685 +---------------------------------------------------------------------*/
1687 PLT_CtrlPoint::InvokeAction(PLT_ActionReference
& action
,
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(
1718 // queue the request
1719 m_TaskManager
->StartTask(task
);
1724 /*----------------------------------------------------------------------
1725 | PLT_CtrlPoint::ProcessActionResponse
1726 +---------------------------------------------------------------------*/
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
,
1735 NPT_COMPILER_UNUSED(request
);
1737 NPT_String service_type
;
1739 NPT_XmlElementNode
* xml
= NULL
;
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(),
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
))) {
1770 NPT_LOG_FINER("Analyzing Action Response Body...");
1773 if (xml
->GetTag().Compare("Envelope", true))
1777 if (!xml
->GetNamespace() || xml
->GetNamespace()->Compare("http://schemas.xmlsoap.org/soap/envelope/"))
1781 attr
= xml
->GetAttribute("encodingStyle", "http://schemas.xmlsoap.org/soap/envelope/");
1782 if (!attr
|| attr
->Compare("http://schemas.xmlsoap.org/soap/encoding/"))
1786 soap_body
= PLT_XmlHelper::GetChild(xml
, "Body");
1787 if (soap_body
== NULL
)
1790 // check if an error occurred
1791 fault
= PLT_XmlHelper::GetChild(soap_body
, "Fault");
1792 if (fault
!= NULL
) {
1794 ParseFault(action
, fault
);
1798 if (NPT_FAILED(PLT_XmlHelper::GetChild(soap_body
, soap_action_response
)))
1801 // verify action name is identical to SOAPACTION header
1802 if (soap_action_response
->GetTag().Compare(action_desc
.GetName() + "Response", true))
1806 if (!soap_action_response
->GetNamespace() ||
1807 soap_action_response
->GetNamespace()->Compare(action_desc
.GetService()->GetServiceType()))
1810 // read all the arguments if any
1811 for (NPT_List
<NPT_XmlNode
*>::Iterator args
= soap_action_response
->GetChildren().GetFirstItem();
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
;
1829 // override res with failure if necessary
1830 if (NPT_SUCCEEDED(res
)) res
= NPT_FAILURE
;
1835 NPT_AutoLock
lock(m_Lock
);
1836 m_ListenerList
.Apply(PLT_CtrlPointListenerOnActionResponseIterator(res
, action
, userdata
));
1843 /*----------------------------------------------------------------------
1844 | PLT_CtrlPoint::ParseFault
1845 +---------------------------------------------------------------------*/
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");
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;
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
);