Mercurial > projects > hoofbaby
view deps/Platinum/Source/Core/PltCtrlPoint.cpp @ 0:3425707ddbf6
Initial import (hopefully this mercurial stuff works...)
author | fraserofthenight |
---|---|
date | Mon, 06 Jul 2009 08:06:28 -0700 |
parents | |
children |
line wrap: on
line source
/***************************************************************** | | Platinum - Control Point | | Copyright (c) 2004-2008, Plutinosoft, LLC. | All rights reserved. | http://www.plutinosoft.com | | This program is free software; you can redistribute it and/or | modify it under the terms of the GNU General Public License | as published by the Free Software Foundation; either version 2 | of the License, or (at your option) any later version. | | OEMs, ISVs, VARs and other distributors that combine and | distribute commercially licensed software with Platinum software | and do not wish to distribute the source code for the commercially | licensed software under version 2, or (at your option) any later | version, of the GNU General Public License (the "GPL") must enter | into a commercial license agreement with Plutinosoft, LLC. | | This program is distributed in the hope that it will be useful, | but WITHOUT ANY WARRANTY; without even the implied warranty of | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | GNU General Public License for more details. | | You should have received a copy of the GNU General Public License | along with this program; see the file LICENSE.txt. If not, write to | the Free Software Foundation, Inc., | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | http://www.gnu.org/licenses/gpl-2.0.html | ****************************************************************/ /*---------------------------------------------------------------------- | includes +---------------------------------------------------------------------*/ #include "PltCtrlPoint.h" #include "PltUPnP.h" #include "PltDeviceData.h" #include "PltXmlHelper.h" #include "PltCtrlPointTask.h" #include "PltSsdp.h" #include "PltHttpServer.h" NPT_SET_LOCAL_LOGGER("platinum.core.ctrlpoint") /*---------------------------------------------------------------------- | typedef +---------------------------------------------------------------------*/ typedef PLT_HttpRequestHandler<PLT_CtrlPoint> PLT_HttpCtrlPointRequestHandler; /*---------------------------------------------------------------------- | PLT_CtrlPointListenerOnDeviceAddedIterator class +---------------------------------------------------------------------*/ class PLT_CtrlPointListenerOnDeviceAddedIterator { public: PLT_CtrlPointListenerOnDeviceAddedIterator(PLT_DeviceDataReference& device) : m_Device(device) {} NPT_Result operator()(PLT_CtrlPointListener*& listener) const { return listener->OnDeviceAdded(m_Device); } private: PLT_DeviceDataReference& m_Device; }; /*---------------------------------------------------------------------- | PLT_CtrlPointListenerOnDeviceRemovedIterator class +---------------------------------------------------------------------*/ class PLT_CtrlPointListenerOnDeviceRemovedIterator { public: PLT_CtrlPointListenerOnDeviceRemovedIterator(PLT_DeviceDataReference& device) : m_Device(device) {} NPT_Result operator()(PLT_CtrlPointListener*& listener) const { return listener->OnDeviceRemoved(m_Device); } private: PLT_DeviceDataReference& m_Device; }; /*---------------------------------------------------------------------- | PLT_CtrlPointListenerOnActionResponseIterator class +---------------------------------------------------------------------*/ class PLT_CtrlPointListenerOnActionResponseIterator { public: PLT_CtrlPointListenerOnActionResponseIterator(NPT_Result res, PLT_ActionReference& action, void* userdata) : m_Res(res), m_Action(action), m_Userdata(userdata) {} NPT_Result operator()(PLT_CtrlPointListener*& listener) const { return listener->OnActionResponse(m_Res, m_Action, m_Userdata); } private: NPT_Result m_Res; PLT_ActionReference& m_Action; void* m_Userdata; }; /*---------------------------------------------------------------------- | PLT_CtrlPointListenerOnEventNotifyIterator class +---------------------------------------------------------------------*/ class PLT_CtrlPointListenerOnEventNotifyIterator { public: PLT_CtrlPointListenerOnEventNotifyIterator(PLT_Service* service, NPT_List<PLT_StateVariable*>* vars) : m_Service(service), m_Vars(vars) {} NPT_Result operator()(PLT_CtrlPointListener*& listener) const { return listener->OnEventNotify(m_Service, m_Vars); } private: PLT_Service* m_Service; NPT_List<PLT_StateVariable*>* m_Vars; }; /*---------------------------------------------------------------------- | PLT_AddGetSCPDRequestIterator class +---------------------------------------------------------------------*/ class PLT_AddGetSCPDRequestIterator { public: PLT_AddGetSCPDRequestIterator(PLT_TaskManager* task_manager, PLT_CtrlPoint* ctrl_point, PLT_DeviceDataReference& device) : m_TaskManager(task_manager), m_CtrlPoint(ctrl_point), m_Device(device) {} NPT_Result operator()(PLT_Service*& service) const { // look for the host and port of the device PLT_DeviceData* device = service->GetDevice(); NPT_String scpd_url = service->GetSCPDURL(); NPT_LOG_INFO_2("Fetching SCPD for service \"%s\" of device \"%s\"", (const char*)service->GetServiceID(), (const char*)device->GetFriendlyName()); // if the SCPD Url starts with a "/", this means we should not append it to the base URI // but instead go there directly if (!scpd_url.StartsWith("/")) { scpd_url = device->GetURLBase().GetPath() + scpd_url; } NPT_HttpUrl url(device->GetURLBase().GetHost(), device->GetURLBase().GetPort(), scpd_url); // Add a delay, some devices need it (aka Rhapsody) NPT_TimeInterval delay(0.1f); return m_TaskManager->StartTask( new PLT_CtrlPointGetSCPDTask(url, m_CtrlPoint, (PLT_DeviceDataReference&)m_Device), &delay); } private: PLT_TaskManager* m_TaskManager; PLT_CtrlPoint* m_CtrlPoint; PLT_DeviceDataReference m_Device; }; /*---------------------------------------------------------------------- | PLT_EventSubscriberRemoverIterator class +---------------------------------------------------------------------*/ class PLT_EventSubscriberRemoverIterator { public: PLT_EventSubscriberRemoverIterator(PLT_CtrlPoint* ctrl_point) : m_CtrlPoint(ctrl_point) { m_CtrlPoint->m_Subscribers.Lock(); } ~PLT_EventSubscriberRemoverIterator() { m_CtrlPoint->m_Subscribers.Unlock(); } NPT_Result operator()(PLT_Service*& service) const { PLT_EventSubscriber* sub = NULL; if (NPT_SUCCEEDED(NPT_ContainerFind(m_CtrlPoint->m_Subscribers, PLT_EventSubscriberFinderByService(service), sub))) { m_CtrlPoint->m_Subscribers.Remove(sub); delete sub; } return NPT_SUCCESS; } private: PLT_CtrlPoint* m_CtrlPoint; }; /*---------------------------------------------------------------------- | PLT_ServiceReadyIterator class +---------------------------------------------------------------------*/ class PLT_ServiceReadyIterator { public: PLT_ServiceReadyIterator() {} NPT_Result operator()(PLT_Service*& service) const { return service->IsInitted()?NPT_SUCCESS:NPT_FAILURE; } }; /*---------------------------------------------------------------------- | PLT_DeviceReadyIterator class +---------------------------------------------------------------------*/ class PLT_DeviceReadyIterator { public: PLT_DeviceReadyIterator() {} NPT_Result operator()(PLT_DeviceDataReference& device) const { NPT_CHECK(device->m_Services.ApplyUntil( PLT_ServiceReadyIterator(), NPT_UntilResultNotEquals(NPT_SUCCESS))); NPT_CHECK(device->m_EmbeddedDevices.ApplyUntil( PLT_DeviceReadyIterator(), NPT_UntilResultNotEquals(NPT_SUCCESS))); // a device must have at least one service or embedded device // otherwise it's not ready if (device->m_Services.GetItemCount() == 0 && device->m_EmbeddedDevices.GetItemCount() == 0) { return NPT_FAILURE; } return NPT_SUCCESS; } }; /*---------------------------------------------------------------------- | PLT_CtrlPoint::PLT_CtrlPoint +---------------------------------------------------------------------*/ PLT_CtrlPoint::PLT_CtrlPoint(const char* autosearch /* = "upnp:rootdevice" */) : m_EventHttpServer(new PLT_HttpServer()), m_AutoSearch(autosearch) { m_EventHttpServerHandler = new PLT_HttpCtrlPointRequestHandler(this); m_EventHttpServer->AddRequestHandler(m_EventHttpServerHandler, "/", true); } /*---------------------------------------------------------------------- | PLT_CtrlPoint::~PLT_CtrlPoint +---------------------------------------------------------------------*/ PLT_CtrlPoint::~PLT_CtrlPoint() { delete m_EventHttpServer; delete m_EventHttpServerHandler; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::IgnoreUUID +---------------------------------------------------------------------*/ void PLT_CtrlPoint::IgnoreUUID(const char* uuid) { if (!m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) { m_UUIDsToIgnore.Add(uuid); } } /*---------------------------------------------------------------------- | PLT_CtrlPoint::Start +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::Start(PLT_SsdpListenTask* task) { m_EventHttpServer->Start(); // house keeping task m_TaskManager.StartTask(new PLT_CtrlPointHouseKeepingTask(this)); task->AddListener(this); return m_AutoSearch.GetLength()?Search(NPT_HttpUrl("239.255.255.250", 1900, "*"), m_AutoSearch):NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::Stop +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::Stop(PLT_SsdpListenTask* task) { task->RemoveListener(this); m_TaskManager.StopAllTasks(); m_EventHttpServer->Stop(); // we can safely clear everything without a lock // as there are no more tasks pending m_Devices.Clear(); m_Subscribers.Apply(NPT_ObjectDeleter<PLT_EventSubscriber>()); m_Subscribers.Clear(); return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::AddListener +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::AddListener(PLT_CtrlPointListener* listener) { NPT_AutoLock lock(m_ListenerList); if (!m_ListenerList.Contains(listener)) { m_ListenerList.Add(listener); } return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::RemoveListener +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::RemoveListener(PLT_CtrlPointListener* listener) { NPT_AutoLock lock(m_ListenerList); m_ListenerList.Remove(listener); return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::CreateSearchTask +---------------------------------------------------------------------*/ PLT_SsdpSearchTask* PLT_CtrlPoint::CreateSearchTask(const NPT_HttpUrl& url, const char* target, NPT_Cardinal mx, const NPT_IpAddress& address) { // make sure mx is at least 1 if (mx<1) mx=1; // create socket NPT_UdpMulticastSocket* socket = new NPT_UdpMulticastSocket(); socket->SetInterface(address); socket->SetTimeToLive(4); // create request NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1); PLT_UPnPMessageHelper::SetMX(*request, mx); PLT_UPnPMessageHelper::SetST(*request, target); PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\""); request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, NPT_HttpClient::m_UserAgentHeader); // create task PLT_SsdpSearchTask* task = new PLT_SsdpSearchTask( socket, this, request, mx*10000); return task; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::Search +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::Search(const NPT_HttpUrl& url, const char* target, NPT_Cardinal mx /* = 5 */) { NPT_List<NPT_NetworkInterface*> if_list; NPT_List<NPT_NetworkInterface*>::Iterator net_if; NPT_List<NPT_NetworkInterfaceAddress>::Iterator net_if_addr; NPT_CHECK_SEVERE(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list)); for (net_if = if_list.GetFirstItem(); net_if; net_if++) { // make sure the interface is at least broadcast or multicast if (!((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_MULTICAST) && !((*net_if)->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_BROADCAST)) { continue; } for (net_if_addr = (*net_if)->GetAddresses().GetFirstItem(); net_if_addr; net_if_addr++) { // create task PLT_SsdpSearchTask* task = CreateSearchTask(url, target, mx, (*net_if_addr).GetPrimaryAddress()); m_TaskManager.StartTask(task); } } // { // // create task on 127.0.0.1 // NPT_IpAddress address; // address.ResolveName("127.0.0.1"); // // PLT_ThreadTask* task = CreateSearchTask(url, // target, // mx, // address); // m_TaskManager.StartTask(task); // } if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>()); return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::Discover +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::Discover(const NPT_HttpUrl& url, const char* target, NPT_Cardinal mx /* = 5 */, NPT_Timeout repeat /* = 50000 */) { // make sure mx is at least 1 if (mx<1) mx = 1; // create socket NPT_UdpSocket* socket = new NPT_UdpSocket(); // create request NPT_HttpRequest* request = new NPT_HttpRequest(url, "M-SEARCH", NPT_HTTP_PROTOCOL_1_1); PLT_UPnPMessageHelper::SetMX(*request, mx); PLT_UPnPMessageHelper::SetST(*request, target); PLT_UPnPMessageHelper::SetMAN(*request, "\"ssdp:discover\""); request->GetHeaders().SetHeader(NPT_HTTP_HEADER_USER_AGENT, NPT_HttpClient::m_UserAgentHeader); // force HOST to be the regular multicast address:port // Some servers do care (like WMC) otherwise they won't respond to us request->GetHeaders().SetHeader(NPT_HTTP_HEADER_HOST, "239.255.255.250:1900"); // create task PLT_ThreadTask* task = new PLT_SsdpSearchTask( socket, this, request, repeat<(NPT_Timeout)mx*5000?(NPT_Timeout)mx*5000:repeat); /* repeat no less than every 5 secs */ return m_TaskManager.StartTask(task); } /*---------------------------------------------------------------------- | PLT_CtrlPoint::DoHouseKeeping +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::DoHouseKeeping() { NPT_AutoLock lock_devices(m_Devices); NPT_TimeStamp now; int count = m_Devices.GetItemCount(); NPT_System::GetCurrentTimeStamp(now); PLT_DeviceDataReference device; while (count--) { NPT_Result res = m_Devices.PopHead(device); if (NPT_SUCCEEDED(res)) { NPT_TimeStamp last_update = device->GetLeaseTimeLastUpdate(); NPT_TimeInterval lease_time = device->GetLeaseTime(); // check if device lease time has expired if (now > last_update + NPT_TimeInterval((unsigned long)(((float)lease_time)*2), 0)) { RemoveDevice(device); } else { // add the device back to our list since it is still alive m_Devices.Add(device); } } else { NPT_LOG_SEVERE("DoHouseKeeping failure!"); return NPT_FAILURE; } } return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::FindDevice +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::FindDevice(const char* uuid, PLT_DeviceDataReference& device) { NPT_AutoLock lock(m_Devices); return NPT_ContainerFind(m_Devices, PLT_DeviceDataFinder(uuid), device); } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ProcessHttpRequest +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ProcessHttpRequest(NPT_HttpRequest& request, const NPT_HttpRequestContext& context, NPT_HttpResponse& response) { NPT_COMPILER_UNUSED(context); if (!request.GetMethod().Compare("NOTIFY")) { return ProcessHttpNotify(request, context, response); } NPT_LOG_SEVERE("CtrlPoint received bad http request\r\n"); response.SetStatus(412, "Precondition Failed"); return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ProcessHttpNotify +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ProcessHttpNotify(NPT_HttpRequest& request, const NPT_HttpRequestContext& context, NPT_HttpResponse& response) { NPT_COMPILER_UNUSED(context); NPT_List<PLT_StateVariable*> vars; PLT_EventSubscriber* sub = NULL; NPT_String str; NPT_XmlElementNode* xml = NULL; NPT_String callback_uri; NPT_String uuid; NPT_String service_id; NPT_UInt32 seq = 0; PLT_Service* service = NULL; PLT_DeviceData* device = NULL; NPT_String content_type; NPT_String method = request.GetMethod(); NPT_String uri = request.GetUrl().GetPath(); // NPT_LOG_FINE_3("CtrlPoint received %s request from %s:%d\r\n", // request.GetMethod(), // client_info.remote_address.GetIpAddress(), // client_info.remote_address.GetPort()); // PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, &request); const NPT_String* sid = PLT_UPnPMessageHelper::GetSID(request); const NPT_String* nt = PLT_UPnPMessageHelper::GetNT(request); const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request); PLT_HttpHelper::GetContentType(request, content_type); { NPT_AutoLock lock_subs(m_Subscribers); // look for the subscriber with that subscription url if (!sid || NPT_FAILED(NPT_ContainerFind(m_Subscribers, PLT_EventSubscriberFinderBySID(*sid), sub))) { NPT_LOG_FINE_1("Subscriber %s not found\n", (const char*)sid); goto bad_request; } // verify the request is syntactically correct service = sub->GetService(); device = service->GetDevice(); uuid = device->GetUUID(); service_id = service->GetServiceID(); // callback uri for this sub callback_uri = "/" + uuid + "/" + service_id; if (uri.Compare(callback_uri, true) || !nt || nt->Compare("upnp:event", true) || !nts || nts->Compare("upnp:propchange", true)) { goto bad_request; } // if the sequence number is less than our current one, we got it out of order // so we disregard it PLT_UPnPMessageHelper::GetSeq(request, seq); if (sub->GetEventKey() && seq <= sub->GetEventKey()) { goto bad_request; } // parse body if (NPT_FAILED(PLT_HttpHelper::ParseBody(request, xml))) { goto bad_request; } // check envelope if (xml->GetTag().Compare("propertyset", true)) goto bad_request; // check namespace // xml.GetAttrValue("xmlns:e", str); // if (str.Compare("urn:schemas-upnp-org:event-1-0")) // goto bad_request; // check property set // keep a vector of the state variables that changed NPT_XmlElementNode* property; PLT_StateVariable* var; for (NPT_List<NPT_XmlNode*>::Iterator children = xml->GetChildren().GetFirstItem(); children; children++) { NPT_XmlElementNode* child = (*children)->AsElementNode(); if (!child) continue; // check property if (child->GetTag().Compare("property", true)) goto bad_request; if (NPT_FAILED(PLT_XmlHelper::GetChild(child, property))) { goto bad_request; } var = service->FindStateVariable(property->GetTag()); if (var == NULL) { goto bad_request; } if (NPT_FAILED(var->SetValue(property->GetText()?*property->GetText():""))) { goto bad_request; } vars.Add(var); } // update sequence sub->SetEventKey(seq); } // notify listener we got an update if (vars.GetItemCount()) { NPT_AutoLock lock(m_ListenerList); m_ListenerList.Apply(PLT_CtrlPointListenerOnEventNotifyIterator(service, &vars)); } delete xml; return NPT_SUCCESS; bad_request: NPT_LOG_SEVERE("CtrlPoint received bad request\r\n"); response.SetStatus(412, "Precondition Failed"); delete xml; return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ProcessSsdpSearchResponse +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ProcessSsdpSearchResponse(NPT_Result res, const NPT_HttpRequestContext& context, NPT_HttpResponse* response) { NPT_CHECK_SEVERE(res); NPT_CHECK_POINTER_SEVERE(response); NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString(); NPT_String protocol = response->GetProtocol(); NPT_LOG_FINE_2("CtrlPoint received SSDP search response from %s:%d", (const char*)context.GetRemoteAddress().GetIpAddress().ToString() , context.GetRemoteAddress().GetPort()); PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, response); if (response->GetStatusCode() == 200) { const NPT_String* st = response->GetHeaders().GetHeaderValue("st"); const NPT_String* usn = response->GetHeaders().GetHeaderValue("usn"); const NPT_String* ext = response->GetHeaders().GetHeaderValue("ext"); NPT_CHECK_POINTER_SEVERE(st); NPT_CHECK_POINTER_SEVERE(usn); NPT_CHECK_POINTER_SEVERE(ext); NPT_String uuid; // if we get an advertisement other than uuid // verify it's formatted properly if (usn != st) { char tmp_uuid[200]; char tmp_st[200]; int ret; // FIXME: We can't use sscanf directly! ret = sscanf(((const char*)*usn)+5, "%[^::]::%s", tmp_uuid, tmp_st); if (ret != 2) return NPT_FAILURE; if (st->Compare(tmp_st, true)) return NPT_FAILURE; uuid = tmp_uuid; } else { uuid = ((const char*)*usn)+5; } if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) { NPT_LOG_FINE_1("CtrlPoint received a search response from ourselves (%s)\n", (const char*)uuid); return NPT_SUCCESS; } return ProcessSsdpMessage(response, context, uuid); } return NPT_FAILURE; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::OnSsdpPacket +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::OnSsdpPacket(NPT_HttpRequest& request, const NPT_HttpRequestContext& context) { return ProcessSsdpNotify(request, context); } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ProcessSsdpNotify +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ProcessSsdpNotify(NPT_HttpRequest& request, const NPT_HttpRequestContext& context) { // get the address of who sent us some data back NPT_String ip_address = context.GetRemoteAddress().GetIpAddress().ToString(); NPT_String method = request.GetMethod(); NPT_String uri = (const char*)request.GetUrl().GetPath(); NPT_String protocol = request.GetProtocol(); if (method.Compare("NOTIFY") == 0) { NPT_LOG_INFO_2("Received SSDP NOTIFY from %s:%d", context.GetRemoteAddress().GetIpAddress().ToString().GetChars(), context.GetRemoteAddress().GetPort()); PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &request); if ((uri.Compare("*") != 0) || (protocol.Compare("HTTP/1.1") != 0)) return NPT_FAILURE; const NPT_String* nts = PLT_UPnPMessageHelper::GetNTS(request); const NPT_String* nt = PLT_UPnPMessageHelper::GetNT(request); const NPT_String* usn = PLT_UPnPMessageHelper::GetUSN(request); NPT_CHECK_POINTER_SEVERE(nts); NPT_CHECK_POINTER_SEVERE(nt); NPT_CHECK_POINTER_SEVERE(usn); NPT_String uuid; // if we get an advertisement other than uuid // verify it's formatted properly if (*usn != *nt) { char tmp_uuid[200]; char tmp_nt[200]; int ret; //FIXME: no sscanf! ret = sscanf(((const char*)*usn)+5, "%[^::]::%s", tmp_uuid, tmp_nt); if (ret != 2) return NPT_FAILURE; if (nt->Compare(tmp_nt, true)) return NPT_FAILURE; uuid = tmp_uuid; } else { uuid = ((const char*)*usn)+5; } if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) { NPT_LOG_FINE_1("Received a NOTIFY request from ourselves (%s)\n", (const char*)uuid); return NPT_SUCCESS; } // if it's a byebye, remove the device and return right away if (nts->Compare("ssdp:byebye", true) == 0) { NPT_LOG_INFO_1("Received a byebye NOTIFY request from %s\n", (const char*)uuid); PLT_DeviceDataReference data; if (NPT_SUCCEEDED(FindDevice(uuid, data))) { NPT_AutoLock lock_devices(m_Devices); RemoveDevice(data); } return NPT_SUCCESS; } return ProcessSsdpMessage(&request, context, uuid); } return NPT_FAILURE; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::RemoveDevice +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::RemoveDevice(PLT_DeviceDataReference& data) { NPT_LOG_INFO_1("Removing %s from device list\n", (const char*)data->GetUUID()); /* recursively remove embedded devices */ NPT_Array<PLT_DeviceDataReference> embedded_devices = data->GetEmbeddedDevices(); for(NPT_Cardinal i=0;i>embedded_devices.GetItemCount();i++) { RemoveDevice(embedded_devices[i]); } /* remove from list */ m_Devices.Remove(data); /* unsubscribe from services */ data->m_Services.Apply(PLT_EventSubscriberRemoverIterator(this)); /* notify listeners */ { NPT_AutoLock lock(m_ListenerList); m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceRemovedIterator(data)); } return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ProcessSsdpMessage +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ProcessSsdpMessage(NPT_HttpMessage* message, const NPT_HttpRequestContext& context, NPT_String& uuid) { NPT_COMPILER_UNUSED(context); NPT_CHECK_POINTER_SEVERE(message); if (m_UUIDsToIgnore.Find(NPT_StringFinder(uuid))) return NPT_SUCCESS; const NPT_String* location = PLT_UPnPMessageHelper::GetLocation(*message); NPT_CHECK_POINTER_SEVERE(location); // be nice and assume a default lease time if not found NPT_Timeout leasetime; if (NPT_FAILED(PLT_UPnPMessageHelper::GetLeaseTime(*message, leasetime))) { leasetime = 1800; } return InspectDevice(*location, uuid, leasetime); } /*---------------------------------------------------------------------- | PLT_CtrlPoint::InspectDevice +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::InspectDevice(const char* location, const char* uuid, NPT_Timeout leasetime) { NPT_HttpUrl url(location); if (!url.IsValid()) return NPT_FAILURE; // is it a new device? PLT_DeviceDataReference data; if (NPT_FAILED(FindDevice(uuid, data))) { NPT_AutoLock lock(m_Devices); NPT_LOG_INFO_2("New device \"%s\" detected @ %s", uuid, location); data = new PLT_DeviceData(url, uuid, NPT_TimeInterval(leasetime, 0)); m_Devices.Add(data); // Start a task to retrieve the description PLT_CtrlPointGetDescriptionTask* task = new PLT_CtrlPointGetDescriptionTask( url, this, data); // Add a delay, some devices need it (aka Rhapsody) NPT_TimeInterval delay(0.2f); m_TaskManager.StartTask(task, &delay); return NPT_SUCCESS; } // in case we missed the byebye and the device description has changed (ip or port) // reset base and assumes device is the same (same number of services and SCPDs) // FIXME: The right way is to remove the device and rescan it though PLT_DeviceReadyIterator device_tester; if (NPT_SUCCEEDED(device_tester(data)) && data->GetDescriptionUrl().Compare(location, true)) { NPT_LOG_INFO_2("Old device \"%s\" detected @ new location %s", (const char*)data->GetFriendlyName(), location); data->SetURLBase(url); } // renew expiration time data->SetLeaseTime(NPT_TimeInterval(leasetime, 0)); NPT_LOG_FINE_1("Device (%s) expiration time renewed..", (const char*)uuid); return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ProcessGetDescriptionResponse +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ProcessGetDescriptionResponse(NPT_Result res, const NPT_HttpRequestContext& context, NPT_HttpResponse* response, PLT_DeviceDataReference& device) { NPT_LOG_INFO_2("Received device description for %s (result = %d)", (const char*)device->GetUUID(), res); NPT_CHECK_FATAL(res); NPT_CHECK_POINTER_FATAL(response); PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, response); // make sure we have seen this device before PLT_DeviceDataReference root_device; res = FindDevice(device->GetUUID(), root_device); NPT_CHECK_WARNING(res); // get body NPT_String desc; res = PLT_HttpHelper::GetBody(*response, desc); NPT_CHECK_LABEL_SEVERE(res, bad_response); // set the device description res = root_device->SetDescription(desc, context.GetLocalAddress().GetIpAddress()); NPT_CHECK_LABEL_SEVERE(res, bad_response); NPT_LOG_INFO_2("Device \"%s\" is now known as \"%s\"", (const char*)device->GetUUID(), (const char*)device->GetFriendlyName()); // add embedded devices to list of devices // and fetch their services scpd for (NPT_Cardinal i = 0; i<root_device->m_EmbeddedDevices.GetItemCount(); i++) { PLT_DeviceDataReference embedded_device = root_device->m_EmbeddedDevices[i]; PLT_DeviceDataReference data; if (NPT_FAILED(FindDevice(embedded_device->GetUUID(), data))) { NPT_AutoLock lock(m_Devices); m_Devices.Add(embedded_device); } res = embedded_device->m_Services.Apply(PLT_AddGetSCPDRequestIterator( &m_TaskManager, this, embedded_device)); NPT_CHECK_LABEL_SEVERE(res, bad_response); } // Get SCPD of root device services now res = root_device->m_Services.Apply(PLT_AddGetSCPDRequestIterator( &m_TaskManager, this, root_device)); NPT_CHECK_LABEL_SEVERE(res, bad_response); return NPT_SUCCESS; bad_response: NPT_LOG_SEVERE_2("Bad Description response for device \"%s\": %s", (const char*)device->GetUUID(), (const char*)desc); if (!root_device.IsNull()) { NPT_AutoLock lock(m_Devices); RemoveDevice(root_device); } return res; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ProcessGetSCPDResponse +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ProcessGetSCPDResponse(NPT_Result res, NPT_HttpRequest* request, NPT_HttpResponse* response, PLT_DeviceDataReference& device) { PLT_DeviceReadyIterator device_tester; PLT_Service* service = NULL; NPT_LOG_INFO_2("Received SCPD response for %s (result = %d)", (const char*)device->GetUUID(), res); NPT_CHECK_FATAL(res); NPT_CHECK_POINTER_FATAL(request); NPT_CHECK_POINTER_FATAL(response); PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, response); PLT_DeviceDataReference data; NPT_CHECK_WARNING(FindDevice(device->GetUUID(), data)); // get body NPT_String scpd; res = PLT_HttpHelper::GetBody(*response, scpd); NPT_CHECK_LABEL_FATAL(res, bad_response); // look for the service based on the SCPD uri res = data->FindServiceByDescriptionURI(request->GetUrl().GetPath(), service); NPT_CHECK_LABEL_FATAL(res, bad_response); { // lock using listener list before testing // to make sure an scpd is not getting set while we test NPT_AutoLock lock(m_ListenerList); // set the service scpd res = service->SetSCPDXML(scpd); NPT_CHECK_LABEL_FATAL(res, bad_response); if (NPT_SUCCEEDED(device_tester(data))) { // notify that the device is ready to use m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceAddedIterator(data)); } // if device is not root, notify listeners now if parent is ready if (!data->GetParentUUID().IsEmpty()) { PLT_DeviceDataReference parent; NPT_CHECK_WARNING(FindDevice(data->GetParentUUID(), parent)); // lock using listener list before testing // to make sure an scpd is not getting set while we test if (NPT_SUCCEEDED(device_tester(parent))) { // notify that the root device is ready to use m_ListenerList.Apply(PLT_CtrlPointListenerOnDeviceAddedIterator(parent)); } } } return NPT_SUCCESS; bad_response: NPT_LOG_SEVERE_2("Bad SCPD response for device \"%s\":%s", (const char*)device->GetFriendlyName(), (const char*)scpd); if (!data.IsNull()) { NPT_AutoLock lock(m_Devices); RemoveDevice(data); } return res; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::Subscribe +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::Subscribe(PLT_Service* service, bool cancel, void* userdata) { NPT_AutoLock lock(m_Subscribers); if (!service->IsSubscribable()) return NPT_FAILURE; // look for the host and port of the device PLT_DeviceData* device = service->GetDevice(); // get the relative event subscription url // if URL starts with '/', it's not to be appended to base URL NPT_String event_sub_url = service->GetEventSubURL(); if (!event_sub_url.StartsWith("/")) { event_sub_url = device->GetURLBase().GetPath() + event_sub_url; } NPT_HttpUrl url(device->GetURLBase().GetHost(), device->GetURLBase().GetPort(), event_sub_url); // look for the subscriber with that service to decide if it's a renewal or not PLT_EventSubscriber* sub = NULL; NPT_ContainerFind(m_Subscribers, PLT_EventSubscriberFinderByService(service), sub); // create the request NPT_HttpRequest* request = NULL; if (cancel == false) { // renewal? if (sub) { NPT_LOG_FINE_3("Renewing subscriber \"%s\" for service \"%s\" of device \"%s\"", (const char*)sub->GetSID(), (const char*)service->GetServiceID(), (const char*)device->GetFriendlyName()); // create the request request = new NPT_HttpRequest(url, "SUBSCRIBE"); PLT_UPnPMessageHelper::SetSID(*request, sub->GetSID()); PLT_UPnPMessageHelper::SetTimeOut(*request, 1800); } else { NPT_LOG_INFO_2("Subscribing to service \"%s\" of device \"%s\"", (const char*)service->GetServiceID(), (const char*)service->GetDevice()->GetFriendlyName()); // prepare the callback url NPT_String uuid = device->GetUUID(); NPT_String service_id = service->GetServiceID(); NPT_String callback_uri = "/" + uuid + "/" + service_id; // create the request request = new NPT_HttpRequest(url, "SUBSCRIBE"); // specify callback url using ip of interface used when // retrieving device description NPT_HttpUrl callbackUrl(device->m_LocalIfaceIp.ToString(), m_EventHttpServer->GetPort(), callback_uri); // set the required headers for a new subscription PLT_UPnPMessageHelper::SetNT(*request, "upnp:event"); PLT_UPnPMessageHelper::SetCallbacks(*request, "<" + callbackUrl.ToString() + ">"); PLT_UPnPMessageHelper::SetTimeOut(*request, 1800); } } else { NPT_LOG_INFO_3("Unsubscribing subscriber \"%s\" for service \"%s\" of device \"%s\"", (const char*)(sub?sub->GetSID().GetChars():"unknown"), (const char*)service->GetServiceID(), (const char*)service->GetDevice()->GetFriendlyName()); // cancellation if (!sub)return NPT_FAILURE; // create the request request = new NPT_HttpRequest(url, "UNSUBSCRIBE"); PLT_UPnPMessageHelper::SetSID(*request, sub->GetSID()); // remove from list now m_Subscribers.Remove(sub, true); delete sub; } NPT_CHECK_POINTER_FATAL(request); // Prepare the request // create a task to post the request PLT_ThreadTask* task = new PLT_CtrlPointSubscribeEventTask( request, this, service, userdata); m_TaskManager.StartTask(task); return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ProcessSubscribeResponse +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ProcessSubscribeResponse(NPT_Result res, NPT_HttpResponse* response, PLT_Service* service, void* /* userdata */) { const NPT_String* sid = NULL; NPT_Int32 timeout; PLT_EventSubscriber* sub = NULL; NPT_AutoLock lock(m_Subscribers); NPT_LOG_INFO_2("Received subscription response for service \"%s\" (result = %d)", (const char*)service->GetServiceID(), res); PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINER, response); // if there's a failure or it's a response to a cancellation // we get out if (NPT_FAILED(res) || response == NULL || response->GetStatusCode() != 200) { NPT_CHECK_LABEL_SEVERE(NPT_FAILED(res)?res:NPT_FAILURE, failure); } if (!(sid = PLT_UPnPMessageHelper::GetSID(*response)) || NPT_FAILED(PLT_UPnPMessageHelper::GetTimeOut(*response, timeout))) { NPT_CHECK_LABEL_SEVERE(NPT_ERROR_INVALID_SYNTAX, failure); } // look for the subscriber with that sid if (NPT_FAILED(NPT_ContainerFind(m_Subscribers, PLT_EventSubscriberFinderBySID(*sid), sub))) { NPT_LOG_INFO_3("Creating new subscriber \"%s\" for service \"%s\" of device \"%s\"", (const char*)*sid, (const char*)service->GetServiceID(), (const char*)service->GetDevice()->GetFriendlyName()); sub = new PLT_EventSubscriber(&m_TaskManager, service, *sid); m_Subscribers.Add(sub); } sub->SetTimeout(timeout); return NPT_SUCCESS; failure: NPT_LOG_SEVERE_3("(un)subscription failed of sub \"%s\" for service \"%s\" of device \"%s\"", (const char*)(sid?*sid:"?"), (const char*)service->GetServiceID(), (const char*)service->GetDevice()->GetFriendlyName()); // in case it was a renewal look for the subscriber with that service and remove it from the list if (NPT_SUCCEEDED(NPT_ContainerFind(m_Subscribers, PLT_EventSubscriberFinderByService(service), sub))) { m_Subscribers.Remove(sub); delete sub; } return NPT_FAILURE; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::InvokeAction +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::InvokeAction(PLT_ActionReference& action, void* userdata) { PLT_Service* service = action->GetActionDesc()->GetService(); PLT_DeviceData* device = service->GetDevice(); // look for the service control url NPT_String control_url = service->GetControlURL(); // if URL starts with a "/", it's not to be appended to base URL if (!control_url.StartsWith("/")) { control_url = device->GetURLBase().GetPath() + control_url; } // create the request // FIXME: hack use HTTP/1.0 for now because of WMC that returning 100 Continue when using HTTP/1.1 // and this screws up the http processing right now NPT_HttpUrl url(device->GetURLBase().GetHost(), device->GetURLBase().GetPort(), control_url); NPT_HttpRequest* request = new NPT_HttpRequest(url, "POST", NPT_HTTP_PROTOCOL_1_0); // create a memory stream for our request body NPT_MemoryStreamReference stream(new NPT_MemoryStream); action->FormatSoapRequest(*stream); // set the request body NPT_InputStreamReference input = stream; PLT_HttpHelper::SetBody(*request, input); PLT_HttpHelper::SetContentType(*request, "text/xml; charset=\"utf-8\""); NPT_String service_type = service->GetServiceType(); NPT_String action_name = action->GetActionDesc()->GetName(); request->GetHeaders().SetHeader("SOAPAction", "\"" + service_type + "#" + action_name + "\""); // create a task to post the request PLT_CtrlPointInvokeActionTask* task = new PLT_CtrlPointInvokeActionTask( request, this, action, userdata); // queue the request m_TaskManager.StartTask(task); return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ProcessActionResponse +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ProcessActionResponse(NPT_Result res, NPT_HttpResponse* response, PLT_ActionReference& action, void* userdata) { NPT_String service_type; NPT_String str; NPT_XmlElementNode* xml = NULL; NPT_String name; NPT_String soap_action_name; NPT_XmlElementNode* soap_action_response; NPT_XmlElementNode* soap_body; NPT_XmlElementNode* fault; const NPT_String* attr = NULL; PLT_ActionDesc* action_desc = action->GetActionDesc(); // reset the error code and desc action->SetError(0, ""); // check context validity if (NPT_FAILED(res) || response == NULL) { goto failure; } NPT_LOG_FINE("Received Action Response:"); PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, response); NPT_LOG_FINER("Reading/Parsing Action Response Body..."); if (NPT_FAILED(PLT_HttpHelper::ParseBody(*response, xml))) { goto failure; } NPT_LOG_FINER("Analyzing Action Response Body..."); // read envelope if (xml->GetTag().Compare("Envelope", true)) goto failure; // check namespace if (!xml->GetNamespace() || xml->GetNamespace()->Compare("http://schemas.xmlsoap.org/soap/envelope/")) goto failure; // check encoding attr = xml->GetAttribute("encodingStyle", "http://schemas.xmlsoap.org/soap/envelope/"); if (!attr || attr->Compare("http://schemas.xmlsoap.org/soap/encoding/")) goto failure; // read action soap_body = PLT_XmlHelper::GetChild(xml, "Body"); if (soap_body == NULL) goto failure; // check if an error occurred fault = PLT_XmlHelper::GetChild(soap_body, "Fault"); if (fault != NULL) { // we have an error ParseFault(action, fault); goto failure; } if (NPT_FAILED(PLT_XmlHelper::GetChild(soap_body, soap_action_response))) goto failure; // verify action name is identical to SOAPACTION header if (soap_action_response->GetTag().Compare(action_desc->GetName() + "Response", true)) goto failure; // verify namespace if (!soap_action_response->GetNamespace() || soap_action_response->GetNamespace()->Compare(action_desc->GetService()->GetServiceType())) goto failure; // read all the arguments if any for (NPT_List<NPT_XmlNode*>::Iterator args = soap_action_response->GetChildren().GetFirstItem(); args; args++) { NPT_XmlElementNode* child = (*args)->AsElementNode(); if (!child) continue; action->SetArgumentValue(child->GetTag(), child->GetText()?*child->GetText():""); if (NPT_FAILED(res)) goto failure; } // create a buffer for our response body and call the service res = action->VerifyArguments(false); if (NPT_FAILED(res)) goto failure; goto cleanup; failure: // override res with failure if necessary if (NPT_SUCCEEDED(res)) res = NPT_ERROR_INVALID_FORMAT; // fallthrough cleanup: { NPT_AutoLock lock(m_ListenerList); m_ListenerList.Apply(PLT_CtrlPointListenerOnActionResponseIterator(res, action, userdata)); } delete xml; return res; } /*---------------------------------------------------------------------- | PLT_CtrlPoint::ParseFault +---------------------------------------------------------------------*/ NPT_Result PLT_CtrlPoint::ParseFault(PLT_ActionReference& action, NPT_XmlElementNode* fault) { NPT_XmlElementNode* detail = fault->GetChild("detail"); if (detail == NULL) return NPT_FAILURE; NPT_XmlElementNode *upnp_error, *error_code, *error_desc; upnp_error = detail->GetChild("upnp_error"); if (upnp_error == NULL) return NPT_FAILURE; error_code = upnp_error->GetChild("errorCode"); error_desc = upnp_error->GetChild("errorDescription"); NPT_Int32 code = 501; NPT_String desc; if (error_code && error_code->GetText()) { NPT_String value = *error_code->GetText(); value.ToInteger(code); } if (error_desc && error_desc->GetText()) { desc = *error_desc->GetText(); } action->SetError(code, desc); return NPT_SUCCESS; }