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:
2018-11-19
Revision:
7:1e8c677e3d28
Parent:
6:9df748509c3d
Child:
8:e8f0dc2b78c4

File content as of revision 7:1e8c677e3d28:

//
// 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"

#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
extern void ShowSignOfLife(int which);
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/1.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, myIPasString, myPort);
// Requires IP address as a string
static const char * SSDP_NOTIFY = 
    "NOTIFY * HTTP/1.1\r\n"
    "HOST: 239.255.255.250:1900\r\n"
    "CACHE-CONTROL: max-age=1800\r\n"
    "LOCATION: http://%s:%u/setup.xml\r\n"
    "NTS: ssdp:alive\r\n\r\n"
    "";

// Addr:    "###.###.###.###"                   [15]
// Port:    12345                               [5]
//
#define SSDP_NOTIFY_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);
    if (server.join_multicast_group(MCAST_GRP) != 0) {
        ERR("Error joining the multicast group");
        while (true) {}
    }
    server.set_blocking(false, 50); // non-blocking with 50ms timeout
    Endpoint client;
    char buffer[256];
    while (true) {
        //INFO("Wait for packet...");
        ShowSignOfLife(2);
        int n = server.receiveFrom(client, buffer, sizeof(buffer)-1);
        if (n > 0) {
            char * p = buffer;
            volatile int delay = 0;
            uint8_t mask = 0x00;    // fragments we found in the received packet
            
            buffer[n] = '\0';
            INFO("SSDP receiveFrom %d bytes from %s:%d", n, 
                client.get_address(), client.get_port());
            INFO("SSDP\n%s", buffer);
            while (*p) {
                char * e = strstr(p, "\r\n");
                if (e && (e - buffer) < n) {
                    *e = '\0';
                    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;
                    } else if (sw_stristr(p, "HOST: ")) {
                        mask |= 0x10;                               // HOST: 239.255.255.250:49152
                        char * pColon = strchr(p+6, ':');
                        if (pColon) {
                            pColon = '\0';
                        }
                    }
                    p = e + 1;
                }
                p++;
            }
            INFO("     ***** %02X", mask);
            if ((mask & 0x1F) == 0x1F) {
                char * out_buffer = (char *)malloc(strlen(SSDP_HTTP) + SSDP_HTTP_OVERHEAD);
                
                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 2 seconds.
                    // Send the response twice, to improve reliability of node discovery
                    for (int i=0; i<1; i++) {
                        INFO("SSDPListener: reply >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
                        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() {
    if (pThr)
        pThr->terminate();
    pThr = NULL;
    DelFriendlyName();
    DelIdentity();
    DelIPAddress();
}

void SSDP::SendNotify() {
    char * out_buffer = (char *)malloc(strlen(SSDP_NOTIFY) + SSDP_NOTIFY_OVERHEAD);
    if (out_buffer) {
        UDPSocket sock;
        Endpoint broadcast;
        int i;
        
        i = sock.init();
        printf(" %d = sock.init()\n", i);
        i = sock.set_broadcasting();
        printf(" %d = sock.set_broadcasting()\n", i);
        i = broadcast.set_address(MCAST_GRP, MCAST_PORT);
        printf(" %d = sock.set_address(%s,%d)\n", i, MCAST_GRP, MCAST_PORT);
        sprintf(out_buffer, SSDP_NOTIFY, _config.ipAddr, _config.port);
        printf("SendNotify:\n%s\n", out_buffer);
        i = sock.sendTo(broadcast, out_buffer, strlen(out_buffer));
        printf(" %d = sendTo(%s, ..., %d)\n", 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, osPriorityLow, 768);
}