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-01-12
- Revision:
- 11:b7f8070014d8
- Parent:
- 10:26f5a66f05a4
- Child:
- 12:4e7c5e1fd32b
File content as of revision 11:b7f8070014d8:
// // 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); 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..."); 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 <<<<<<< %d bytes from %s:%d <<<<<<<<<<<", n, client.get_address(), client.get_port()); INFO("SSDP\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() { if (pThr) pThr->terminate(); 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); printf("SendNotify:\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, osPriorityLow, 768); } #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