/*-
 * Copyright (c) 1993-1994 The Regents of the University of California.
 * All rights reserved.
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and the Network Research Group at
 *      Lawrence Berkeley Laboratory.
 * 4. Neither the name of the University nor of the Laboratory may be used
 *    to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 */
static const char rcsid[] =
    "@(#) $Header: /work/projects/tove/cvs/src/testing/vat/net.cc,v 1.1 1997/12/08 17:22:52 parnanen Exp $ (LBL)";

#include <stdlib.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#ifdef WIN32
#include <winsock.h>
#else
#include <sys/socket.h>
#include <sys/uio.h>
#endif
#include "net.h"
#include "crypt.h"

/*
 * Linux does not have sendmsg
 */
#if defined(__linux__) || defined(WIN32)
#define MAXPACKETSIZE (1500-28)

static int
sendmsg(int s, struct msghdr* mh, int flags)
{
	u_char wrkbuf[MAXPACKETSIZE];
	int len = mh->msg_iovlen;
	struct iovec* iov = mh->msg_iov;
	u_char* cp;
	u_char* ep;

	for (cp = wrkbuf, ep = wrkbuf + MAXPACKETSIZE; --len >= 0; ++iov) {
		int plen = iov->iov_len;
		if (cp + plen >= ep) {
			errno = E2BIG;
			return (-1);
		}
		memcpy(cp, iov->iov_base, plen);
		cp += plen;
	}
	return (send(s, (char*)wrkbuf, cp - wrkbuf, flags));
}
#endif

Network::Network() :
	addr_(0),
	local_(0),
	lport_(0),
	port_(0),
	ttl_(0),
	rsock_(-1),
	ssock_(-1),
	noloopback_broken_(0),
	crypt_(0)
{
}

Network::~Network()
{
}

int Network::command(int argc, const char*const* argv)
{
	if (argc == 2) {
		Tcl& tcl = Tcl::instance();
		char* cp = tcl.buffer();
		if (strcmp(argv[1], "addr") == 0 || 
		    strcmp(argv[1], "interface") == 0 ||
		    strcmp(argv[1], "port") == 0 ||
		    strcmp(argv[1], "ttl") == 0 ||
		    strcmp(argv[1], "ismulticast") == 0)
			strcpy(cp, "0");
		else
			return (TclObject::command(argc, argv));
		tcl.result(cp);
		return (TCL_OK);
	} else if (argc == 3) {
		if (strcmp(argv[1], "crypt") == 0) {
			/*
			 * 'crypt ""' will turn of encryption because
			 * lookup() will return null.
			 */
			crypt_ = (Crypt*)TclObject::lookup(argv[2]);
			return (TCL_OK);
		}
	}
	return (TclObject::command(argc, argv));
}

void Network::nonblock(int fd)
{       
#ifdef WIN32
	u_long flag = 1;
	if (ioctlsocket(fd, FIONBIO, &flag) == -1) {
		fprintf(stderr, "ioctlsocket: FIONBIO: %lu\n", GetLastError());
		exit(1);
	}
#else
        int flags = fcntl(fd, F_GETFL, 0);
#if defined(hpux) || defined(__hpux)
        flags |= O_NONBLOCK;
#else
        flags |= O_NONBLOCK|O_NDELAY;
#endif
        if (fcntl(fd, F_SETFL, flags) == -1) {
                perror("fcntl: F_SETFL");
                exit(1);
        }
#endif
}

u_char* Network::wrkbuf_;
int Network::wrkbuflen_;

void Network::expand_wrkbuf(int len)
{
	if (wrkbuflen_ == 0)
		wrkbuf_ = (u_char*)malloc(len);
	else
		wrkbuf_ = (u_char*)realloc((u_char*)wrkbuf_, len);
	wrkbuflen_ = len;
}

void Network::dosend(u_char* buf, int len, int fd)
{
	int cc = ::send(fd, (char*)buf, len, 0);
	if (cc < 0) {
		switch (errno) {
		case ECONNREFUSED:
			/* no one listening at some site - ignore */
#if defined(__osf__) || defined(_AIX)
			/*
			 * Due to a bug in kern/uipc_socket.c, on several
			 * systems, datagram sockets incorrectly persist
			 * in an error state on receipt of an ICMP
			 * port-unreachable.  This causes unicast connection
			 * rendezvous problems, and worse, multicast
			 * transmission problems because several systems
			 * incorrectly send port unreachables for 
			 * multicast destinations.  Our work around
			 * is to simply close and reopen the socket
			 * (by calling reset() below).
			 *
			 * This bug originated at CSRG in Berkeley
			 * and was present in the BSD Reno networking
			 * code release.  It has since been fixed
			 * in 4.4BSD and OSF-3.x.  It is know to remain
			 * in AIX-4.1.3.
			 *
			 * A fix is to change the following lines from
			 * kern/uipc_socket.c:
			 *
			 *	if (so_serror)
			 *		snderr(so->so_error);
			 *
			 * to:
			 *
			 *	if (so->so_error) {
			 * 		error = so->so_error;
			 *		so->so_error = 0;
			 *		splx(s);
			 *		goto release;
			 *	}
			 *
			 */
			reset();
#endif
			break;

		case ENETUNREACH:
		case EHOSTUNREACH:
			/*
			 * These "errors" are totally meaningless.
			 * There is some broken host sending
			 * icmp unreachables for multicast destinations.
			 * UDP probably aborted the send because of them --
			 * try exactly once more.  E.g., the send we
			 * just did cleared the errno for the previous
			 * icmp unreachable, so we should be able to
			 * send now.
			 */
			(void)::send(ssock_, (char*)buf, len, 0);
			break;

		default:
			perror("send");
			return;
		}
	}
}

void Network::send(u_char* buf, int len)
{
	if (crypt_)
		buf = crypt_->Encrypt(buf, len);
	dosend(buf, len, ssock_);
}

/*
 * Copy a scatter/gather packet to the work buffer and
 * return it's length.
 */
int Network::cpmsg(const msghdr& mh)
{
	int len, i;
	for (len = 0, i = 0; i < int(mh.msg_iovlen); ++i)
		len += mh.msg_iov[i].iov_len;

	if (len > wrkbuflen_)
		expand_wrkbuf(len);
	u_char* cp = wrkbuf_;
	for (i = 0; i < int(mh.msg_iovlen); ++i) {
		int cc = mh.msg_iov[i].iov_len;
		memcpy(cp, mh.msg_iov[i].iov_base, cc);
		cp += cc;
	}
	return (len);
}

void Network::sendmsg(const msghdr& mh)
{
	if (crypt_) {
		int cc = cpmsg(mh);
		send(wrkbuf_, cc);
		return;
	}
	int cc = ::sendmsg(ssock_, (msghdr*)&mh, 0);
	if (cc < 0) {
		switch (errno) {
		case ECONNREFUSED:
			/* no one listening at some site - ignore */
#if defined(__osf__) || defined(_AIX)
			reset();
#endif
			break;

		case ENETUNREACH:
		case EHOSTUNREACH:
			/*
			 * These "errors" are totally meaningless.
			 * There is some broken host sending
			 * icmp unreachables for multicast destinations.
			 * UDP probably aborted the send because of them --
			 * try exactly once more.  E.g., the send we
			 * just did cleared the errno for the previous
			 * icmp unreachable, so we should be able to
			 * send now.
			 */
			(void)::sendmsg(ssock_, (msghdr*)&mh, 0);
			break;

		default:
			perror("sendmsg");
			return;
		}
	}
}

int Network::dorecv(u_char* buf, int len, u_int32_t& from, int fd)
{
	sockaddr_in sfrom;
	int fromlen = sizeof(sfrom);
	int cc = ::recvfrom(fd, (char*)buf, len, 0,
			    (sockaddr*)&sfrom, &fromlen);
	if (cc < 0) {
		if (errno != EWOULDBLOCK)
			perror("recvfrom");
		return (-1);
	}
	from = sfrom.sin_addr.s_addr;
	if (noloopback_broken_ && from == local_ && sfrom.sin_port == lport_)
		return (0);

	return (cc);
}

int Network::recv(u_char* buf, int len, u_int32_t& from)
{
	if (crypt_) {
		if (len > wrkbuflen_)
			expand_wrkbuf(len);
		int cc = dorecv(wrkbuf_, len, from, rsock_);
		return (crypt_->Decrypt(wrkbuf_, cc, buf));
	}
	return (dorecv(buf, len, from, rsock_));
}

void Network::reset()
{
}
