/* Copyright (c) 2003 Tungsten Graphics, Inc.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files ("the
 * Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:  The above copyright notice, the Tungsten
 * Graphics splash screen, and this permission notice shall be included
 * in all copies or substantial portions of the Software.  THE SOFTWARE
 * IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT
 * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/*
 * Simple IPC API
 * Brian Paul
 */


#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include "ipc.h"

#if defined(IRIX) || defined(irix)
typedef int socklen_t;
#endif

#define NO_DELAY 1

#define DEFAULT_MASTER_PORT 7011


/*
 * Return my hostname in <nameOut>.
 * Return 1 for success, 0 for error.
 */
int
MyHostName(char *nameOut, int maxNameLength)
{
    int k = gethostname(nameOut, maxNameLength);
    return k==0;
}


/*
 * Create a socket attached to a port.  Later, we can call AcceptConnection
 * on the socket returned from this function.
 * Return the new socket number or -1 if error.
 */
int
CreatePort(int *port)
{
    char hostname[1000];
    struct sockaddr_in servaddr;
    struct hostent *hp;
    int so_reuseaddr = 1;
    int tcp_nodelay = 1;
    int sock, k;

    /* create socket */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    assert(sock > 2);

    /* get my host name */
    k = gethostname(hostname, 1000);
    assert(k == 0);

    /* get hostent info */
    hp = gethostbyname(hostname);
    assert(hp);

    /* initialize the servaddr struct */
    memset(&servaddr, 0, sizeof(servaddr) );
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons((unsigned short) (*port));
    memcpy((char *) &servaddr.sin_addr, hp->h_addr,
	   sizeof(servaddr.sin_addr));

    /* deallocate when we exit */
    k = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
		   (char *) &so_reuseaddr, sizeof(so_reuseaddr));
    assert(k==0);

    /* send packets immediately */
#if NO_DELAY
    k = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
		   (char *) &tcp_nodelay, sizeof(tcp_nodelay));
    assert(k==0);
#endif

    if (*port == 0)
        *port = DEFAULT_MASTER_PORT;

    k = 1;
    while (k && (*port < 65534)) {
    	/* bind our address to the socket */
    	servaddr.sin_port = htons((unsigned short) (*port));
    	k = bind(sock, (struct sockaddr *) &servaddr, sizeof(servaddr));
        if (k)
           *port = *port + 1;
    }

#if 0
    printf("###### Real Port: %d\n", *port);
#endif

    /* listen for connections */
    k = listen(sock, 100);
    assert(k == 0);

    return sock;
}


/*
 * Accept a connection on the named socket.
 * Return a new socket for the new connection, or -1 if error.
 */
int
AcceptConnection(int socket)
{
    struct sockaddr addr;
    socklen_t addrLen;
    int newSock;

    addrLen = sizeof(addr);
    newSock = accept(socket, &addr, &addrLen);
    if (newSock == 1)
	return -1;
    else
	return newSock;
}


/*
 * Contact the server running on the given host on the named port.
 * Return socket number or -1 if error.
 */
int
Connect(const char *hostname, int port)
{
    struct sockaddr_in servaddr;
    struct hostent *hp;
    int sock, k;
    int tcp_nodelay = 1;

    assert(port);

    sock = socket(AF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);

    hp = gethostbyname(hostname);
    assert(hp);

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons((unsigned short) port);
    memcpy((char *) &servaddr.sin_addr, hp->h_addr, sizeof(servaddr.sin_addr));

    k = connect(sock, (struct sockaddr *) &servaddr, sizeof(servaddr));
    if (k != 0) {
       perror("Connect:");
       return -1;
    }

#if NO_DELAY
    /* send packets immediately */
    k = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
		   (char *) &tcp_nodelay, sizeof(tcp_nodelay));
    assert(k==0);
#endif

    return sock;
}


void
CloseSocket(int socket)
{
    close(socket);
}


int
SendData(int socket, const void *data, int bytes)
{
    int sent = 0;
    int b;

    while (sent < bytes) {
        b = write(socket, (char *) data + sent, bytes - sent);
        if (b <= 0)
            return -1; /* something broke */
        sent += b;
    }
    return sent;
}


int
ReceiveData(int socket, void *data, int bytes)
{
    int received = 0, b;

    while (received < bytes) {
        b = read(socket, (char *) data + received, bytes - received);
        if (b <= 0)
            return -1;
        received += b;
    }
    return received;
}


int
SendString(int socket, const char *str)
{
    const int len = strlen(str);
    int sent, b;

    /* first, send a 4-byte length indicator */
    b = write(socket, &len, sizeof(len));
    if (b <= 0)
	return -1;

    sent = SendData(socket, str, len);
    assert(sent == len);
    return sent;
}


int
ReceiveString(int socket, char *str, int maxLen)
{
    int len, received, b;

    /* first, read 4 bytes to see how long of string to receive */
    b = read(socket, &len, sizeof(len));
    if (b <= 0)
	return -1;

    assert(len <= maxLen);  /* XXX fix someday */
    assert(len >= 0);
    received = ReceiveData(socket, str, len);
    assert(received != -1);
    assert(received == len);
    str[len] = 0;
    return received;
}