/**************************************************************************** * Copyright (c) 2011 by Michael Fischer. All rights reserved. * * This work based on source from proconX Pty Ltd. Therefore * partial copyright by: Copyright (c) 2010 proconX Pty Ltd. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * ***************************************************************************** * History: * * 27.05.2011 mifi First Version based on source from proconX Pty Ltd. * Information about UPnP can be found at "www.upnp.org". * Note: I had no success with the UPnP Device Validator * from the "Open Software Projects". The old original * Intel tools from the book "UPnP Design by Example" * works a little bit better, but has problems too. ****************************************************************************/ #define __MAIN_C__ #include #include #include #include #include #include #include #include #include #include #include #include #include /*=========================================================================*/ /* DEFINE: All Structures and Common Constants */ /*=========================================================================*/ /* * Enable to fulfil the UPnP specification for notify messages. * I think this is not needed to advertise only the device on * windows networks. */ #define UPNP_FULFIL_SPEC 0 #ifndef UPNP_SERVICE_STACK #define UPNP_SERVICE_STACK ((1024 * NUT_THREAD_STACK_MULT) + NUT_THREAD_STACK_ADD) #endif #define UPNP_CGI_NAME "upnp.cgi" #define UPNP_UUID_PART1 "56F9C1D5-5083-4ee5-A6B3-" #define SSDP_IP 0xfaffffefU /* 239.255.255.250 */ #define SSDP_PORT 1900 #define SSDP_BUFFER_SIZE 512 #define SSDP_NOTIFY_TIMEOUT 300000U /* 5 minutes */ /* *INDENT-OFF* */ static char HTTP_TEXT_XML[] = "text/xml; charset=\"utf-8\""; static char NOTIFY_ALIVE[] = "NOTIFY * HTTP/1.1\r\n" "Host: 239.255.255.250:1900\r\n" "Cache-Control: max-age=900\r\n" "Location: http://%s/cgi-bin/" UPNP_CGI_NAME "\r\n" "NT: %s\r\n" "NTS: ssdp:alive\r\n" "USN: %s%s\r\n" "Server: %s\r\n" "Content-Length: 0\r\n" "\r\n"; static char NOTIFY_BYE[] = "NOTIFY * HTTP/1.1\r\n" "Host: 239.255.255.250:1900\r\n" "NT: upnp:rootdevice\r\n" "NTS: ssdp:byebye\r\n" "USN: %s::upnp:rootdevice\r\n" "Content-Length: 0\r\n" "\r\n"; static char MSEARCH_RESPONSE[] = "HTTP/1.1 200 OK\r\n" "Cache-Control: max-age=900\r\n" "Ext:\r\n" "Location: http://%s/cgi-bin/" UPNP_CGI_NAME "\r\n" "Server: %s\r\n" "ST: upnp:rootdevice\r\n" "USN: %s::upnp:rootdevice\r\n" "Content-Length: 0\r\n" "\r\n"; /* * All listed elements which are not tagged are "REQUIRED" as per * UPnP Device Architecture 1.0. Some other are "OPTIONAL" or "RECOMMENDED". */ static char HTML_UPNP[] = "\r\n" "\r\n" "\r\n" "1\r\n" "0\r\n" "\r\n" "http://%s\r\n" /* OPTIONAL */ "\r\n" "urn:schemas-upnp-org:device:Basic:1\r\n" "NutOS Server (%02X%02X%02X%02X%02X%02X)\r\n" "egnite\r\n" "http://www.egnite.de\r\n" /* OPTIONAL */ "NutOS HTTP Daemon\r\n" /* RECOMMENDED */ "Ethernut\r\n" "%02X%02X%02X%02X%02X%02X\r\n" /* RECOMMENDED */ "http://www.ethernut.de\r\n" /* OPTIONAL */ "%02X%02X%02X%02X%02X%02X\r\n" /* RECOMMENDED */ "uuid:" UPNP_UUID_PART1 "%02X%02X%02X%02X%02X%02X\r\n" /* * Despite being not required by the UPnP standard, we must * define at leat one service to make the device show up on Windows XP. */ "\r\n" "\r\n" "urn:schemas-upnp-org:service:Dummy:1\r\n" "urn:upnp-org:serviceId:Dummy\r\n" "/upnp/dummy.xml\r\n" "/upnp\r\n" "\r\n" "\r\n" "\r\n" "http://%s/index.html\r\n" /* RECOMMENDED */ "\r\n" "\r\n"; /* *INDENT-ON* */ /*=========================================================================*/ /* DEFINE: Prototypes */ /*=========================================================================*/ /*=========================================================================*/ /* DEFINE: Definition of all local Data */ /*=========================================================================*/ static NUTDEVICE *dev; static IFNET *nif; static char MyUSN[48]; static char ServerInfo[32]; /*=========================================================================*/ /* DEFINE: Definition of all local Procedures */ /*=========================================================================*/ /***************************************************************************/ /* GetRand */ /***************************************************************************/ static uint32_t GetRand(uint32_t MaxValue) { uint32_t Value; Value = rand() & MaxValue; return (Value); } /* GetRand */ /***************************************************************************/ /* LocationCGIHandler */ /***************************************************************************/ static int LocationCGIHandler(FILE * stream, REQUEST * req) { /* * Generate HTTP header */ NutHttpSendHeaderTop(stream, req, 200, "OK"); NutHttpSendHeaderBottom(stream, 0, HTTP_TEXT_XML, -1L); /* * Output XML data */ fprintf(stream, HTML_UPNP, inet_ntoa(nif->if_local_ip), /* URLBase */ nif->if_mac[0], nif->if_mac[1], /* friendlyName */ nif->if_mac[2], nif->if_mac[3], nif->if_mac[4], nif->if_mac[5], nif->if_mac[0], nif->if_mac[1], /* modelNumber */ nif->if_mac[2], nif->if_mac[3], nif->if_mac[4], nif->if_mac[5], nif->if_mac[0], nif->if_mac[1], /* serialNumber */ nif->if_mac[2], nif->if_mac[3], nif->if_mac[4], nif->if_mac[5], nif->if_mac[0], nif->if_mac[1], /* UDN */ nif->if_mac[2], nif->if_mac[3], nif->if_mac[4], nif->if_mac[5], inet_ntoa(nif->if_local_ip)); /* presentationURL */ return 0; } /* LocationCGIHandler */ /***************************************************************************/ /* SendNotifyAliveChunk */ /***************************************************************************/ static void SendNotifyAliveChunk(char *Buffer, UDPSOCKET * TxSock) { int Size; /* * Send device discovery messages. * The first message is really needed. * All other to fulfil the UPnP specification. */ /* Send first alive message */ Size = sprintf(Buffer, NOTIFY_ALIVE, inet_ntoa(nif->if_local_ip), "upnp:rootdevice", /* NT */ MyUSN, "::upnp:rootdevice", /* USN */ ServerInfo); NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size); NutSleep(25); #if (UPNP_FULFIL_SPEC >= 1) /* Send second alive message */ Size = sprintf(Buffer, NOTIFY_ALIVE, inet_ntoa(nif->if_local_ip), MyUSN, /* NT */ MyUSN, "", /* USN */ ServerInfo); NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size); NutSleep(25); /* Send third alive message */ Size = sprintf(Buffer, NOTIFY_ALIVE, inet_ntoa(nif->if_local_ip), "urn:schemas-upnp-org:device:Basic:1", /* NT */ MyUSN, "::urn:schemas-upnp-org:device:Basic:1", /* USN */ ServerInfo); NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size); NutSleep(25); /* * Send embedded device discovery messages */ /* No embedded device available */ /* * Send service discovery messages */ Size = sprintf(Buffer, NOTIFY_ALIVE, inet_ntoa(nif->if_local_ip), "urn:schemas-upnp-org:service:Dummy:1", /* NT */ MyUSN, "::urn:schemas-upnp-org:service:Dummy:1", /* USN */ ServerInfo); NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size); #endif } /* SendNotifyAliveChunk */ /***************************************************************************/ /* SendNotifyAlive */ /***************************************************************************/ static void SendNotifyAlive(char *Buffer, UDPSOCKET * TxSock) { uint32_t RandomDelay; /* * Send device discovery messages. Here two chunks will be send * with a random delay between each chunk. */ SendNotifyAliveChunk(Buffer, TxSock); /* Create random value between 500...2000ms */ RandomDelay = 500 + GetRand(1500); NutSleep(RandomDelay); SendNotifyAliveChunk(Buffer, TxSock); } /* SendNotifyAlive */ /***************************************************************************/ /* NotifyTask */ /***************************************************************************/ THREAD(NotifyTask, arg) { char *Buffer; UDPSOCKET *TxSock; /* * Alloc buffer and create sockets */ Buffer = malloc(SSDP_BUFFER_SIZE + 1); /* Add one for the string termination 0 */ TxSock = NutUdpCreateSocket(0); for (;;) { NutSleep(SSDP_NOTIFY_TIMEOUT); /* Send alive message. */ SendNotifyAlive(Buffer, TxSock); NutThreadYield(); } } /* NotifyTask */ /***************************************************************************/ /* SSDPTask */ /***************************************************************************/ THREAD(SSDPTask, arg) { int Size; char *Buffer; UDPSOCKET *RxSock; UDPSOCKET *TxSock; uint32_t RemoteIP; uint16_t Port; char *Start; char *End; uint32_t MaxDelay; uint32_t Delay; sprintf(MyUSN, "uuid:%s%02X%02X%02X%02X%02X%02X", UPNP_UUID_PART1, nif->if_mac[0], nif->if_mac[1], nif->if_mac[2], nif->if_mac[3], nif->if_mac[4], nif->if_mac[5]); sprintf(ServerInfo, "NutOS/%d.%d UPnP/1.0", NUT_VERSION_MAJOR, NUT_VERSION_MINOR); /* * Alloc buffer and create sockets */ Buffer = malloc(SSDP_BUFFER_SIZE + 1); /* Add one for the string termination 0 */ RxSock = NutUdpCreateSocket(SSDP_PORT); TxSock = NutUdpCreateSocket(0); /* * First send ByeBye to the network. */ Size = sprintf(Buffer, NOTIFY_BYE, MyUSN); NutUdpSendTo(TxSock, SSDP_IP, SSDP_PORT, Buffer, Size); NutSleep(500); /* * Now we can send a "hello" to the network, we are back. */ SendNotifyAlive(Buffer, TxSock); for (;;) { Size = NutUdpReceiveFrom(RxSock, &RemoteIP, &Port, Buffer, SSDP_BUFFER_SIZE, NUT_WAIT_INFINITE); if (Size > 0) { Buffer[Size] = 0; /* Terminate string */ /* * Check if a control point serach for a network device. */ if ((strstr(Buffer, "M-SEARCH") != NULL) && /* <= REQUIRED */ (strstr(Buffer, "ssdp:discover") != NULL)) { /* <= REQUIRED */ /* * Check if the control point search for "all", "root devices" * or for a special device. */ if ((strstr(Buffer, "ssdp:all") != NULL) || (strstr(Buffer, "upnp:rootdevice") != NULL) || (strstr(Buffer, MyUSN) != NULL)) { Start = strstr(Buffer, "MX:"); if (Start != NULL) { /* Jump over the "MX:" */ Start += 3; /* Get delay in seconds */ MaxDelay = strtol(Start, &End, 10); if (MaxDelay == 0) { MaxDelay = 1; } /* Change to millisecond */ MaxDelay *= 1000; /* Get random value */ Delay = GetRand(MaxDelay); } else { /* Ups, error */ Delay = 1234; } NutSleep(Delay); /* * We must send a M-SEARCH response */ Size = sprintf(Buffer, MSEARCH_RESPONSE, inet_ntoa(nif->if_local_ip), ServerInfo, MyUSN); NutUdpSendTo(TxSock, RemoteIP, Port, Buffer, Size); NutSleep(25); NutUdpSendTo(TxSock, RemoteIP, Port, Buffer, Size); } } } NutThreadYield(); } } /* SSDPTask */ /*=========================================================================*/ /* DEFINE: All code exported */ /*=========================================================================*/ /***************************************************************************/ /* upnp_Init */ /***************************************************************************/ void upnp_Init(void) { /* Set UPnP multicast address */ printf("Add UPnP multicast address..."); if (NutNetIfAddMcastAddr("eth0", SSDP_IP) != 0) { puts("failed"); } else { puts("OK"); /* * Multicast will be supported, * get device and network interface info for eth0. */ dev = NutDeviceLookup("eth0"); if (dev != NULL) { /* Get network interface pointer */ nif = dev->dev_icb; if (NutRegisterCgi(UPNP_CGI_NAME, LocationCGIHandler) == 0) { /* Start the UPnP threads */ NutThreadCreate("ssdp", SSDPTask, NULL, UPNP_SERVICE_STACK); NutThreadCreate("ssdp-notify", NotifyTask, NULL, UPNP_SERVICE_STACK); } } } } /* upnp_Init */ /*** EOF ***/