/*
 * libkarma/ssdp.c
 *
 * Copyright (c) Nicolas Justin+Gregrory Kokanosky 2004
 *           (c) Frank Zschockelt <libkarma@freakysoft.de> 2004
 *
 * You may distribute and modify this program under the terms of 
 * the GNU GPL, version 2 or later.
 *
 */
/*
 * This file is rudimentary implementation of the 
 * Simple Service Discovery Protocol/1.0, also known
 * as Universal Plug'n'Play. Ths implementation is based
 * on the draft v1.03 which can be found at:
 * http://www.upnp.org/download/draft_cai_ssdp_v1_03.txt
 *
 * This feature is enabled by default on the Karma.
 */
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/socket.h>
#ifdef __NetBSD__
#include <netinet/in.h>
#endif
#include <netdb.h>

#include "lkarma.h"
#include "ssdp.h"

static int get_host_from_ssdp(char *buffer, char **hostname, uint16_t * port);

/* Parse the URL found at the "LOCATION:" entry of
 * the SSDP reply in 'buffer'. 'hostname' and 'port'
 * allocated, the caller is responsible to free them.
 * 'hostname' is guaranteed to be null terminated.
 * Return 0 on success, -1 otherwise.
 */
static int get_host_from_ssdp(char *buffer, char **hostname, uint16_t * port)
{
    char *pos1, *pos2;
    char tmp;

    if((pos1 = strstr(buffer, "LOCATION:")) == NULL) {
        return -1;
    }

    /* Find http:// */
    if((pos1 = index(pos1, '/')) == NULL)
        return -1;
    pos1 += 2;            /* jump to one char after http:// */
    /* search the ':' in "hostname:4444" */
    if((pos2 = index(pos1, ':')) == NULL)
        return -1;
    tmp = *pos2;
    *pos2 = '\0';
    /*copy the hostname */
    if((*hostname = strdup(pos1)) == NULL)
        return -1;
    *pos2 = tmp;
    /* Copy the port number */
    /* Set pos2 to the end of the line */
    pos1 = pos2 + 1;
    if((pos2 = index(pos1, '\r')) == NULL)
        return -1;
    tmp = *pos2;
    *pos2 = '\0';
    *port = atoi(pos1);
    *pos2 = tmp;

    return 0;
}

/* Call this function if you want automatic discovery
 * of a Karma on your network. A SSDP:discover is sent
 * to the multicast address 239.255.255.250:1900, if
 * a response is received, hostname and port are set
 * respectivly to the Karma address (may be a hostname
 * or an IP address), and the port wich listen to the
 * Pearl protocol.
 * Return 0 if succeed, or -1 on error.
 */
int lk_ssdp_discover(char **host, uint16_t * port)
{
    int sock;
    size_t ret;
    unsigned int socklen;
    struct sockaddr_in sockname;
    struct sockaddr clientsock;
    struct hostent *hostname;
    char data[] =
        "M-SEARCH * HTTP/1.1\r\n"
        "Host: 239.255.255.250:1900\r\n"
        "Man: \"ssdp:discover\"\r\n"
        "ST: urn:empeg-com:protocol2\r\n" "MX: 3\r\n" "\r\n";
    char buffer[RESPONSE_BUFFER_LEN];
    unsigned int len = RESPONSE_BUFFER_LEN;
    fd_set fds;
    struct timeval timeout;

    hostname = gethostbyname(SSDP_MULTICAST);
    hostname->h_addrtype = AF_INET;

    if((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1) {
        lk_errors_set(E_SOCKET);       /*err: socket() failed */
        return -1;
    }

    memset((char *)&sockname, 0, sizeof(struct sockaddr_in));
    sockname.sin_family = AF_INET;
    sockname.sin_port = htons(SSDP_PORT);
    sockname.sin_addr.s_addr = *((unsigned long *)(hostname->h_addr_list[0]));

    ret = sendto(sock, data, strlen(data), 0, (struct sockaddr *)&sockname,
                 sizeof(struct sockaddr_in));
    if(ret != strlen(data)) {
        lk_errors_set(E_SENDTO);       /*err: sendto() failed */
        return -1;
    }
    /* Get response */
    FD_ZERO(&fds);
    FD_SET(sock, &fds);
    timeout.tv_sec = 3;
    timeout.tv_usec = 0;
    if(select(sock + 1, &fds, NULL, NULL, &timeout) < 0) {
        lk_errors_set(E_SELECT);
        return -1;
    }
    if(FD_ISSET(sock, &fds)) {
        socklen = sizeof(clientsock);
        if((len = recvfrom(sock, buffer, len, MSG_PEEK,
                           &clientsock, &socklen)) == (size_t) - 1) {
            lk_errors_set(E_RECVFROM); /*err: recvfrom() failed */
            return -1;
        }
        buffer[len-1] = '\0';
        close(sock);

        /* Check the HTTP response code */
        if(strncmp(buffer, "HTTP/1.1 200 OK", 12) != 0) {
            lk_errors_set(E_SSDPPARSE);/*err: ssdp parsing failed */
            return -1;
        }

        if(get_host_from_ssdp(buffer, host, port) == -1) {
            lk_errors_set(E_SSDPPARSE);/*err: ssdp parsing failed */
            return -1;
        }
        return 0;

    } else {
        lk_errors_set(E_NOSSDP);       /*err: no ssdp answer */
        return -1;
    }
}
