SSDP Server - working version provides SSDP based network discovery, and with a companion web server, may provide other functionalities.

Dependents:   X10Svr SSDP_Server

SSDP.cpp

Committer:
WiredHome
Date:
2020-07-19
Revision:
14:ad92261497ca
Parent:
13:bcabd901d344

File content as of revision 14:ad92261497ca:

//
// SSDP Server 
//
// This is an SSDP server. It relies on a web server running on this same node.
//
// 
#include "SSDP.h"
#include "EthernetInterface.h"
#include "SW_String.h"

// Normal discovery
//      ST: upnp:rootdevice
//      ST: ssdp:all
// Some devices query directly for a Belkin device (like Amazon dot)
//      ST: urn:Belkin:device:**
//      define the following to support that discovery
#define ST_DISCOVER_BELKIN_DEVICE

#define DEBUG "SSDP"      //Debug is disabled by default

#include <cstdio>
#if (defined(DEBUG) && !defined(TARGET_LPC11U24))
#define DBG(x, ...)  std::printf("[DBG %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define WARN(x, ...) std::printf("[WRN %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define ERR(x, ...)  std::printf("[ERR %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#define INFO(x, ...) std::printf("[INF %s %4d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__);
#else
#define DBG(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)
#define INFO(x, ...)
#endif
static const char* MCAST_GRP = "239.255.255.250";
static const int MCAST_PORT = 1900;
static Thread * pThr;

// sprintf(buffer, SSDP_HTTP, "myIPString", myPort, "myIdentity", "myIdentity");
// Requires IP address as a string
static const char * SSDP_HTTP = 
    "HTTP/1.1 200 OK\r\n"
    "CACHE-CONTROL: max-age=1800\r\n"
    "DATE: Mon, 22 Jun 2015 17:24:01 GMT\r\n"
    "EXT:\r\n"
    "LOCATION: http://%s:%d/setup.xml\r\n"                      // "my.ip.string", portNum
    "OPT: \"http://schemas.upnp.org/upnp/1/0/\"; ns=01\r\n"
    "01-NLS: %s\r\n"                                            // "Unique Identity"
    "SERVER: Smartware, UPnP/2.0, Smartware\r\n"
    "ST: upnp:rootdevice\r\n"
    "USN: uuid:Node-1_0-%s::upnp:rootdevice\r\n"                // "Unique Identity"
    "X-User-Agent: Smartware\r\n"
    "\r\n";

// Addr:    "###.###.###.###"                   [15]
// Port:    12345                               [5]
// Ident:   "#########0#########0#########0"    [30] x 2
//
#define SSDP_HTTP_OVERHEAD 50   // Number of bytes to fill in the information


// sprintf(buffer, SSDP_NOTIFY_ALIVE, myIPasString, myPort);
// Requires IP address as a string
static const char * SSDP_NOTIFY_ALIVE = 
    "NOTIFY * HTTP/1.1\r\n"
    "HOST: 239.255.255.250:1900\r\n"
    "CACHE-CONTROL: max-age=86400\r\n"
    "LOCATION: http://%s:%u/setup.xml\r\n"
    "NT: upnp:rootdevice\r\n"
    "NTS: ssdp:alive\r\n\r\n"
    "SERVER: Smartware, UPnP/2.0, Smartware\r\n"
    "";

// Addr:    "###.###.###.###"                   [15]
// Port:    12345                               [5]
//
#define SSDP_NOTIFY_ALIVE_OVERHEAD 25   // Number of bytes to fill in the information (+5)


// The SSDP listener thread
static void SSDPListener(void const * args) {
    UDPSocket server;
    SSDP_Config_T * cfg = (SSDP_Config_T*)args;
    
    server.bind(MCAST_PORT);
    int r = server.join_multicast_group(MCAST_GRP);
    if (r != 0) {
        ERR("Error joining the multicast group, ret: %d", r);
        return;
    }
    server.set_blocking(false, 50); // non-blocking with 50ms timeout
    Endpoint client;
    char buffer[256];
    while (true) {
        //INFO("Wait for packet...");
        int n = server.receiveFrom(client, buffer, sizeof(buffer)-1);
        if (n > 0) {
            volatile int delay = 0;
            uint8_t mask = 0x00;    // fragments we found in the received packet
            
            buffer[n] = '\0';
            INFO("SSDP Recv %d bytes from %s:%d", n, 
                client.get_address(), client.get_port());
            INFO("RecvMessage:\r\n<<<<<<<<<<<<<<\r\n%s>>>>>>>>>>>>>>>", buffer);
            char * p = buffer;
            while (*p) {
                char * e = strstr(p, "\r\n");
                if (e && (e - buffer) < n) {
                    *e = '\0';
                    //INFO("EVAL '%s'", p);
                    if (sw_stristr(p, "M-SEARCH * HTTP/1.1")) {
                        mask |= 0x01;                               // M-SEARCH * HTTP/1.1
                    } else if (sw_stristr(p, "MAN:") && sw_stristr(p,"\"ssdp:discover\"")) {
                        mask |= 0x02;                               // MAN: "ssdp:discover"
                    } else if (sw_stristr(p, "MX:")) {
                        mask |= 0x04;                               // MX: \d
                        delay = atoi(p + 3);
                    } else if (sw_stristr(p, "ST:") && sw_stristr(p, "upnp:rootdevice")) {
                        mask |= 0x08;
                    } else if (sw_stristr(p, "ST:") && sw_stristr(p, "ssdp:all")) {
                        mask |= 0x08;
                    #ifdef ST_DISCOVER_BELKIN_DEVICE
                    } else if (sw_stristr(p, "ST:") && sw_stristr(p, "urn:Belkin:device:**")) {
                        mask |= 0x08;
                    } else if (sw_stristr(p, "ST:") && sw_stristr(p, "urn:Belkin:service:")) {
                        mask |= 0x08;
                    #endif
                    } else if (sw_stristr(p, "HOST: ")) {
                        mask |= 0x10;                               // HOST: 239.255.255.250:49152
                        char * pColon = strchr(p+6, ':');
                        if (pColon) {
                            pColon = '\0';
                        }
                    }
                    //INFO("    mask: %02X", mask);
                    p = e + 1;
                }
                p++;
            }
            if ((mask & 0x1F) == 0x1F) {
                char * out_buffer = (char *)malloc(strlen(SSDP_HTTP) + SSDP_HTTP_OVERHEAD);
                
                INFO("\r\n\r\n    ***** %02X", mask);
                if (out_buffer) {
                    sprintf(out_buffer, SSDP_HTTP, cfg->ipAddr, cfg->port, cfg->ident, cfg->ident);
                    // It would be polite to delay, but the recommendation is from 1 to 5 seconds,
                    // and that will stall this thread.
                    INFO("SSDPListener: reply >>>>>>> %s:%d >>>>>>>>>>>>>", client.get_address(),
                        client.get_port());
                    INFO("\r\n%s", out_buffer);
                    int i = server.sendTo(client, out_buffer, strlen(out_buffer));
                    INFO("  sendTo %3d: reply", i);
                    free(out_buffer);
                    INFO("SSDPListener: stack-used: %d, total: %d", pThr->max_stack(), pThr->stack_size());
                } else {
                    ERR("Can't get memory for response");
                }
            }
        }
    }
}

SSDP::SSDP(const char * name, const char * ident, const char * ipAddr, int port) {
    pThr = NULL;
    _config.name = NULL;
    SetFriendlyName(name);
    _config.ident = NULL;
    SetFriendlyName(ident);
    _config.ipAddr = NULL;
    SetIPAddress(ipAddr);
    _config.port = port;
    StartListener();
    INFO("SSDP(......) constructor done. Listener Started.");
    SendNotify();
}

SSDP::SSDP(const SSDP_Config_T * config) {
    pThr = NULL;
    memcpy(&_config, config, sizeof(SSDP_Config_T));
    StartListener();
    INFO("SSDP(.) constructor done. Listener Started.");
    SendNotify();
}

SSDP::~SSDP() {
    INFO("Terminate SSDP object, pThr: %p", pThr);
    if (pThr)
        pThr->terminate();  // should terminate SSDPListener
    pThr = NULL;
    DelFriendlyName();
    DelIdentity();
    DelIPAddress();
}

void SSDP::SendNotify(NotifyType_t nt) {
    char * out_buffer = (char *)malloc(strlen(SSDP_NOTIFY_ALIVE) + SSDP_NOTIFY_ALIVE_OVERHEAD);
    (void)nt;
    
    if (out_buffer) {
        UDPSocket sock;
        Endpoint broadcast;
        volatile int i;
        
        i = sock.init();
        INFO(" %d = sock.init()", i);
        i = sock.set_broadcasting();
        INFO(" %d = sock.set_broadcasting()", i);
        i = broadcast.set_address(MCAST_GRP, MCAST_PORT);
        INFO(" %d = sock.set_address(%s,%d)", i, MCAST_GRP, MCAST_PORT);
        sprintf(out_buffer, SSDP_NOTIFY_ALIVE, _config.ipAddr, _config.port);
        INFO("SendNotify:  >>>>>>>>>>>>>>\r\n%s>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", out_buffer);
        i = sock.sendTo(broadcast, out_buffer, strlen(out_buffer));
        INFO(" %d = sendTo(%s, ..., %d)", i, broadcast.get_address(), strlen(out_buffer));
        free(out_buffer);
    }
}

bool SSDP::SetFriendlyName(const char * name) {
    DelFriendlyName();
    _config.name = (char *)malloc(strlen(name) + 1);
    if (_config.name) {
        strcpy(_config.name, name);
        return true;
    } else {
        return false;
    }
}

void SSDP::DelFriendlyName() {
    if (_config.name)
        free(_config.name);
    _config.name = NULL;
}

bool SSDP::SetIdentity(const char * ident) {
    DelIdentity();
    _config.ident = (char *)malloc(strlen(ident) + 1);
    if (_config.ident) {
        strcpy(_config.ident, ident);
        return true;
    } else {
        return false;
    }
}

void SSDP::DelIdentity() {
    if (_config.ident)
        free(_config.ident);
    _config.ident = NULL;
}

bool SSDP::SetIPAddress(const char * ipAddr) {
    DelIPAddress();
    _config.ipAddr = (char *)malloc(strlen(ipAddr) + 1);
    if (_config.ipAddr) {
        strcpy(_config.ipAddr, ipAddr);
        return true;
    } else {
        return false;
    }
}

void SSDP::DelIPAddress() {
    if (_config.ipAddr)
        free(_config.ipAddr);
    _config.ipAddr = NULL;
}

bool SSDP::SetPort(int port) {
    _config.port = port;
    return true;
}

void SSDP::StartListener() {
    pThr = new Thread(SSDPListener, (void *)&_config, osPriorityNormal, 768);
    INFO("SSDP Listener Started: %p", pThr);
}


#if 0   // simple UUID generator is needed
char GUID[40];

// srand (clock());
// UUIDGenerator(GUID, sizeof(GUID));
// printf ("%s\r\n", GUID);

/// Simple UUID Generator.
///
/// This is not claimed to be particularly good, but it does generally meet
/// the minimum requirements for a valid UUID.
///
/// @param UUID is a pointer to a buffer into which the UUID will be written.
/// @param UUID_size is that size of that buffer, and is used to ensure the
///         buffer is large enough.
/// @returns true if a UUID was successfully generated into the provided buffer.
///
bool UUIDGenerator(char * UUID, size_t UUID_size) {
    int t = 0;
    char *szTemp = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx";
    char *szHex = "0123456789ABCDEF-";
    int nLen = strlen (szTemp);
    
    if (UUID_size < 40)
        return false;
    
    for (t=0; t<nLen+1; t++)
    {
        int r = rand () % 16;
        char c = ' ';   
    
        switch (szTemp[t])
        {
            case 'x' : { c = szHex [r]; } break;
            case 'y' : { c = szHex [r & 0x03 | 0x08]; } break;
            case '-' : { c = '-'; } break;
            case '4' : { c = '4'; } break;
        }
        UUID[t] = ( t < nLen ) ? c : 0x00;
    }
    return true;
}
#endif