diff deps/Platinum/Source/Core/PltSsdp.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 diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/deps/Platinum/Source/Core/PltSsdp.cpp	Mon Jul 06 08:06:28 2009 -0700
@@ -0,0 +1,512 @@
+/*****************************************************************
+|
+|   Platinum - SSDP
+|
+| 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 "PltSsdp.h"
+#include "PltDatagramStream.h"
+#include "PltDeviceHost.h"
+#include "PltUPnP.h"
+#include "PltHttp.h"
+#include "PltVersion.h"
+
+NPT_SET_LOCAL_LOGGER("platinum.core.ssdp")
+
+/*----------------------------------------------------------------------
+|   constants
++---------------------------------------------------------------------*/
+const int NPT_SSDP_MAX_LINE_SIZE  = 2048;
+const int NPT_SSDP_MAX_DGRAM_SIZE = 4096;
+
+/*----------------------------------------------------------------------
+|   PLT_SsdpSender::SendSsdp
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_SsdpSender::SendSsdp(NPT_HttpRequest&   request,
+                         const char*        usn,
+                         const char*        target,
+                         NPT_UdpSocket&     socket,
+                         bool               notify,
+                         const NPT_SocketAddress* addr /* = NULL */)
+{
+    NPT_CHECK_SEVERE(FormatPacket(request, usn, target, socket, notify));
+
+    // logging
+    NPT_LOG_FINE_2("Sending SSDP %s for %s",
+        (const char*)request.GetMethod(), 
+        usn);
+    PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &request);
+
+    // use a memory stream to write all the data
+    NPT_MemoryStream stream;
+    NPT_Result res = request.Emit(stream);
+    if (NPT_FAILED(res)) return res;
+
+    // copy stream into a data packet and send it
+    NPT_LargeSize size;
+    stream.GetSize(size);
+    if (size != (NPT_Size)size) return NPT_ERROR_OUT_OF_RANGE;
+
+    NPT_DataBuffer packet(stream.GetData(), (NPT_Size)size);
+    return socket.Send(packet, addr);
+}
+
+/*----------------------------------------------------------------------
+|   PLT_SsdpSender::SendSsdp
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_SsdpSender::SendSsdp(NPT_HttpResponse&  response,
+                         const char*        usn,
+                         const char*        target,
+                         NPT_UdpSocket&     socket,
+                         bool               notify, 
+                         const NPT_SocketAddress* addr /* = NULL */)
+{
+    NPT_CHECK_SEVERE(FormatPacket(response, usn, target, socket, notify));
+
+    // logging
+    NPT_LOG_FINE("Sending SSDP:");
+    PLT_LOG_HTTP_MESSAGE(NPT_LOG_LEVEL_FINE, &response);
+
+    // use a memory stream to write all the data
+    NPT_MemoryStream stream;
+    NPT_Result res = response.Emit(stream);
+    if (NPT_FAILED(res)) return res;
+
+    // copy stream into a data packet and send it
+    NPT_LargeSize size;
+    stream.GetSize(size);
+    if (size != (NPT_Size)size) return NPT_ERROR_OUT_OF_RANGE;
+
+    NPT_DataBuffer packet(stream.GetData(), (NPT_Size)size);
+    return socket.Send(packet, addr);
+}
+
+/*----------------------------------------------------------------------
+|   PLT_SsdpSender::FormatPacket
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_SsdpSender::FormatPacket(NPT_HttpMessage& message, 
+                             const char*      usn,
+                             const char*      target,
+                             NPT_UdpSocket&   socket,
+                             bool             notify)
+{
+    NPT_COMPILER_UNUSED(socket);
+
+    PLT_UPnPMessageHelper::SetUSN(message, usn);
+    if (notify) {
+        PLT_UPnPMessageHelper::SetNT(message, target);
+    } else {
+        PLT_UPnPMessageHelper::SetST(message, target);
+    }
+    PLT_HttpHelper::SetContentLength(message, 0);
+
+    return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+|   PLT_SsdpDeviceSearchResponseInterfaceIterator class
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_SsdpDeviceSearchResponseInterfaceIterator::operator()(NPT_NetworkInterface*& net_if) const 
+{
+    NPT_Result res;
+    const NPT_SocketAddress* remote_addr = &m_RemoteAddr;
+
+    NPT_List<NPT_NetworkInterfaceAddress>::Iterator netaddr = net_if->GetAddresses().GetFirstItem();
+    if (!netaddr) {
+        return NPT_SUCCESS;
+    }
+
+    // don't respond on loopback
+    // windows media player on vista sends it's M-SEARCH to loopback interface if it's a local clients sometimes
+    //if (net_if->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_LOOPBACK) {
+    //    return NPT_SUCCESS;
+    //}
+
+    NPT_SocketAddress local_addr((*netaddr).GetPrimaryAddress(), 0); // 1900?
+    NPT_UdpSocket     socket;
+    //if (NPT_FAILED(res = socket.Bind(local_addr))) {
+    //    return res;
+    //}
+
+    // get the output socket stream
+    NPT_OutputStreamReference stream;
+    if (NPT_FAILED(res = socket.GetOutputStream(stream))) {
+        return res;
+    }
+
+    NPT_HttpResponse response(200, "OK", NPT_HTTP_PROTOCOL_1_1);
+
+    // get location URL based on ip address of interface
+    // by connecting, the kernel chooses which interface to use to route to the remote
+    // this is the IP we should use in our Location
+    if (NPT_FAILED(res = socket.Connect(m_RemoteAddr, 5000))) {
+        return res;
+    }
+    NPT_SocketInfo info;
+    socket.GetInfo(info);
+
+    // did we successfully connect and found out which interface is used?
+    if (info.local_address.GetIpAddress().AsLong()) {
+        // check that the interface the kernel chose matches the interface
+        // we wanted to send on
+        // FIXME: Should we fail instead and stop sending a response once we get NPT_SUCCESS?
+        if (local_addr.GetIpAddress().AsLong() != info.local_address.GetIpAddress().AsLong()) {
+            return NPT_SUCCESS;
+        }
+
+        // already connected, so we don't need to specify where to go
+        remote_addr = NULL;
+    }
+
+    PLT_UPnPMessageHelper::SetLocation(response, m_Device->GetDescriptionUrl(local_addr.GetIpAddress().ToString()));
+    PLT_UPnPMessageHelper::SetLeaseTime(response, (NPT_Timeout)((float)m_Device->GetLeaseTime()));
+    PLT_UPnPMessageHelper::SetServer(response, NPT_HttpServer::m_ServerHeader, false);
+    response.GetHeaders().SetHeader("EXT", "");
+
+    // process search response twice to be NMPR compliant
+    NPT_CHECK_SEVERE(m_Device->SendSsdpSearchResponse(response, socket, m_ST, remote_addr));
+    NPT_CHECK_SEVERE(m_Device->SendSsdpSearchResponse(response, socket, m_ST, remote_addr));
+
+    return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+|   PLT_SsdpDeviceSearchResponseTask::DoRun()
++---------------------------------------------------------------------*/
+void
+PLT_SsdpDeviceSearchResponseTask::DoRun() 
+{
+    NPT_List<NPT_NetworkInterface*> if_list;
+    NPT_CHECK_LABEL_WARNING(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list), 
+                            done);
+
+    if_list.Apply(PLT_SsdpDeviceSearchResponseInterfaceIterator(
+        m_Device, 
+        m_RemoteAddr, 
+        m_ST));
+    if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
+
+done:
+    return;
+}
+
+/*----------------------------------------------------------------------
+|   PLT_SsdpAnnounceInterfaceIterator class
++---------------------------------------------------------------------*/
+NPT_Result
+PLT_SsdpAnnounceInterfaceIterator::operator()(NPT_NetworkInterface*& net_if) const 
+{
+    // don't use this interface address if it's not broadcast capable
+    if (m_Broadcast && !(net_if->GetFlags() & NPT_NETWORK_INTERFACE_FLAG_BROADCAST)) {
+        return NPT_FAILURE;
+    }
+
+    NPT_List<NPT_NetworkInterfaceAddress>::Iterator niaddr = 
+        net_if->GetAddresses().GetFirstItem();
+    if (!niaddr) return NPT_FAILURE;
+
+    // Remove disconnected interfaces
+    NPT_IpAddress addr =  (*niaddr).GetPrimaryAddress();
+    if (!addr.ToString().Compare("0.0.0.0")) return NPT_FAILURE;
+
+    NPT_HttpUrl            url;
+    NPT_UdpMulticastSocket multicast_socket;
+    NPT_UdpSocket          broadcast_socket;
+    NPT_UdpSocket*         socket;
+
+    if (m_Broadcast) {
+        //url = NPT_HttpUrl("255.255.255.255", 1900, "*");
+        url = NPT_HttpUrl((*niaddr).GetBroadcastAddress().ToString(), 1900, "*");
+        socket = &broadcast_socket;
+    } else {
+        url = NPT_HttpUrl("239.255.255.250", 1900, "*");
+        socket = &multicast_socket;
+        NPT_CHECK_SEVERE(((NPT_UdpMulticastSocket*)socket)->SetInterface(addr));
+    }
+
+    NPT_HttpRequest req(url, "NOTIFY", NPT_HTTP_PROTOCOL_1_1);
+    PLT_HttpHelper::SetHost(req, "239.255.255.250:1900");
+    
+    // put a location only if alive message
+    if (m_IsByeBye == false) {
+        PLT_UPnPMessageHelper::SetLocation(req, m_Device->GetDescriptionUrl(addr.ToString()));
+    }
+
+    NPT_CHECK_SEVERE(m_Device->Announce(req, *socket, m_IsByeBye));
+    NPT_CHECK_SEVERE(m_Device->Announce(req, *socket, m_IsByeBye));
+
+    return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+|   PLT_SsdpDeviceAnnounceUnicastTask::DoRun
++---------------------------------------------------------------------*/
+void
+PLT_SsdpDeviceAnnounceTask::DoRun()
+{
+    NPT_Result res = NPT_SUCCESS;
+    NPT_List<NPT_NetworkInterface*> if_list;
+
+    while (1) {
+        NPT_CHECK_LABEL_FATAL(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list),
+                              cleanup);
+
+        // if we're announcing our arrival, sends a byebye first (NMPR compliance)
+        if (m_IsByeByeFirst == true) {
+            m_IsByeByeFirst = false;
+            res = if_list.Apply(PLT_SsdpAnnounceInterfaceIterator(m_Device, true, m_IsBroadcast));
+            if (NPT_FAILED(res)) goto cleanup;
+
+            // schedule to announce alive in 300 ms
+            if (NPT_FAILED(res) || IsAborting((NPT_Timeout)300)) break;
+        }
+            
+        res = if_list.Apply(PLT_SsdpAnnounceInterfaceIterator(m_Device, false, m_IsBroadcast));
+        
+cleanup:
+        if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
+        if_list.Clear();
+
+        if (NPT_FAILED(res) || IsAborting(m_Repeat.m_Seconds*1000)) break;
+    };
+}
+
+/*----------------------------------------------------------------------
+|    PLT_SsdpListenTask::DoInit
++---------------------------------------------------------------------*/
+void
+PLT_SsdpListenTask::DoInit() 
+{
+    if (m_IsMulticast) {
+        NPT_List<NPT_NetworkInterface*> if_list;
+        NPT_CHECK_LABEL_FATAL(PLT_UPnPMessageHelper::GetNetworkInterfaces(if_list), 
+                              done);
+
+        /* Join multicast group for every interface we found */
+        if_list.ApplyUntil(
+            PLT_SsdpInitMulticastIterator((NPT_UdpMulticastSocket*)m_Socket), 
+            NPT_UntilResultNotEquals(NPT_SUCCESS));
+
+        if_list.Apply(NPT_ObjectDeleter<NPT_NetworkInterface>());
+    }
+
+done: 
+    return;
+}
+
+/*----------------------------------------------------------------------
+|    PLT_SsdpListenTask::GetInputStream
++---------------------------------------------------------------------*/
+NPT_Result 
+PLT_SsdpListenTask::GetInputStream(NPT_InputStreamReference& stream) 
+{
+    if (!m_Datagram.IsNull()) {
+        stream = m_Datagram;
+        return NPT_SUCCESS;
+    } else {
+        NPT_InputStreamReference input_stream;
+        NPT_Result res = m_Socket->GetInputStream(input_stream);
+        if (NPT_FAILED(res)) {
+            return res;
+        }
+        // for datagrams, we can't simply write to the socket directly
+        // we need to write into a datagramstream (buffer) that redirects to the real stream when flushed
+        m_Datagram = new PLT_InputDatagramStream((NPT_UdpSocket*)m_Socket);
+        stream = m_Datagram;
+        return NPT_SUCCESS;
+    }
+}
+
+/*----------------------------------------------------------------------
+|    PLT_SsdpListenTask::GetInfo
++---------------------------------------------------------------------*/
+NPT_Result 
+PLT_SsdpListenTask::GetInfo(NPT_SocketInfo& info) 
+{
+    if (m_Datagram.IsNull()) return NPT_FAILURE;
+    return m_Datagram->GetInfo(info);
+}
+
+/*----------------------------------------------------------------------
+|    PLT_SsdpListenTask::ProcessRequest
++---------------------------------------------------------------------*/
+NPT_Result 
+PLT_SsdpListenTask::ProcessRequest(NPT_HttpRequest&              request, 
+                                   const NPT_HttpRequestContext& context,
+                                   NPT_HttpResponse*&            response,
+                                   bool&                         headers_only) 
+{
+    NPT_COMPILER_UNUSED(headers_only);
+
+    NPT_AutoLock lock(m_Mutex);
+    m_Listeners.Apply(PLT_SsdpPacketListenerIterator(request, context));
+
+    // set response to NULL since we don't have anything to respond
+    // as we use a separate task to respond with ssdp
+    response = NULL;
+    return NPT_SUCCESS;
+}
+
+/*----------------------------------------------------------------------
+|    PLT_SsdpSearchTask::PLT_SsdpSearchTask
++---------------------------------------------------------------------*/
+PLT_SsdpSearchTask::PLT_SsdpSearchTask(NPT_UdpSocket*                  socket,
+                                       PLT_SsdpSearchResponseListener* listener, 
+                                       NPT_HttpRequest*                request,
+                                       NPT_Timeout                     timeout,
+                                       bool                            repeat     /* = true */) : 
+    m_Listener(listener), 
+    m_Request(request),
+    m_Timeout(timeout),
+    m_Repeat(repeat),
+    m_Socket(socket)
+{
+    m_Socket->SetReadTimeout(timeout);
+    m_Socket->SetWriteTimeout(10000);
+}
+
+/*----------------------------------------------------------------------
+|   PLT_SsdpSearchTask::~PLT_SsdpSearchTask
++---------------------------------------------------------------------*/
+PLT_SsdpSearchTask::~PLT_SsdpSearchTask()
+{
+    delete m_Socket;
+    delete m_Request;
+}
+
+/*----------------------------------------------------------------------
+|   PLT_HttpServerSocketTask::DoAbort
++---------------------------------------------------------------------*/
+void
+PLT_SsdpSearchTask::DoAbort()
+{
+     m_Socket->Disconnect();
+}
+
+/*----------------------------------------------------------------------
+|   PLT_HttpServerSocketTask::DoRun
++---------------------------------------------------------------------*/
+void
+PLT_SsdpSearchTask::DoRun()
+{
+    NPT_HttpResponse*      response = NULL;
+    PLT_HttpClient         client;
+    NPT_Timeout            timeout = 30;
+    NPT_HttpRequestContext context;
+
+    do {
+        // get the address of the server
+        NPT_IpAddress server_address;
+        NPT_CHECK_LABEL_SEVERE(server_address.ResolveName(
+                                   m_Request->GetUrl().GetHost(), 
+                                   timeout), 
+                               done);
+        NPT_SocketAddress address(server_address, 
+                                  m_Request->GetUrl().GetPort());
+
+        // send 2 requests in a row
+        NPT_OutputStreamReference output_stream(
+            new PLT_OutputDatagramStream(m_Socket, 
+                                         4096, 
+                                         &address));
+        NPT_CHECK_LABEL_SEVERE(client.SendRequest(
+                                   output_stream, 
+                                   *m_Request), 
+                               done);
+        NPT_CHECK_LABEL_SEVERE(client.SendRequest(
+                                   output_stream, 
+                                   *m_Request), 
+                               done);
+        output_stream = NULL;
+
+        // keep track of when we sent the request
+        NPT_TimeStamp last_send;
+        NPT_System::GetCurrentTimeStamp(last_send);
+
+        while (!IsAborting(0)) {
+            // read response
+            PLT_InputDatagramStreamReference input_stream(
+                new PLT_InputDatagramStream(m_Socket));
+
+            NPT_InputStreamReference stream = input_stream;
+            NPT_Result res = client.WaitForResponse(stream, 
+                                                    *m_Request, 
+                                                    context, 
+                                                    response);
+            // callback to process response
+            if (NPT_SUCCEEDED(res)) {
+                // get source info    
+                NPT_SocketInfo info;
+                input_stream->GetInfo(info);
+
+                context.SetLocalAddress(info.local_address);
+                context.SetRemoteAddress(info.remote_address);
+
+                // process response
+                ProcessResponse(NPT_SUCCESS, m_Request, context, response);
+                delete response;
+                response = NULL;
+            } else if (res != NPT_ERROR_TIMEOUT) {
+                NPT_LOG_WARNING_1("PLT_SsdpSearchTask got an error (%d) waiting for response", res);
+            }
+
+            input_stream = NULL;
+
+            // check if it's time to resend request
+            NPT_TimeStamp now;
+            NPT_System::GetCurrentTimeStamp(now);
+            if (now >= last_send + (long)m_Timeout/1000)
+                break;
+        }
+    } while (!IsAborting(0) && m_Repeat);
+
+done:
+    return;
+}
+
+/*----------------------------------------------------------------------
+|    PLT_CtrlPointGetDescriptionTask::ProcessResponse
++---------------------------------------------------------------------*/
+NPT_Result 
+PLT_SsdpSearchTask::ProcessResponse(NPT_Result                    res, 
+                                    NPT_HttpRequest*              request,  
+                                    const NPT_HttpRequestContext& context,
+                                    NPT_HttpResponse*             response)
+{
+    NPT_COMPILER_UNUSED(request);
+    return m_Listener->ProcessSsdpSearchResponse(res, context, response);
+}