/*
 * bridge.cpp - Monitored socket bridge
 * $Id: bridge.cpp,v 1.10 2004/06/05 15:15:17 rdenisc Exp $
 */

/***********************************************************************
 *  Copyright (C) 2002-2004 Remi Denis-Courmont.                       *
 *  This program is free software; you can redistribute and/or modify  *
 *  it under the terms of the GNU General Public License as published  *
 *  by the Free Software Foundation; version 2 of the license.         *
 *                                                                     *
 *  This program is distributed in the hope that it will be useful,    *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of     *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.               *
 *  See the GNU General Public License for more details.               *
 *                                                                     *
 *  You should have received a copy of the GNU General Public License  *
 *  along with this program; if not, you can get it from:              *
 *  http://www.gnu.org/copyleft/gpl.html                               *
 ***********************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include <string.h> // memmove()
#include <limits.h> // LONG_MAX
#include <sys/types.h>
#include <sys/time.h> // struct timeval (unused, but needed for select)
#if HAVE_SYS_SELECT_H
# include <sys/select.h>
#endif
#include <unistd.h> // close()
#ifdef HAVE_SYS_SOCKET_H
# include <sys/socket.h> // shutdown(), send(), recv()
#endif

#include "log.h"

#include "bridge.h"

#define BRIDGE_BUFFER_SIZE	16384 // bytes

typedef struct
{
	int infd, outfd;
	char buffer[BRIDGE_BUFFER_SIZE];
	size_t buflen;
	OnewayLogList *logs;
} bridge;


/*
 * Registers file descriptors in various fd_set for select().
 * Returns the biggest fd found.
 */
inline int
register_bridge (const bridge& b, fd_set *readset, fd_set *writeset,
			fd_set *exceptset)
{
	if (b.infd == -1)
		return -1; // bridge was shut down

	int maxfd = b.infd;

	FD_SET (maxfd, exceptset);
	if (b.buflen)
	{
		/* Data pending in the buffer */
		FD_SET (b.outfd, writeset);
		if (b.outfd > b.infd)
			maxfd = b.outfd;
	}
	else
	{
		/* Buffer empty */
		FD_SET (b.infd, readset);
	}

	return maxfd;
}


/*
 * Shutdowns both sides of a bridge.
 */
inline void
shutdown_bridge (bridge& b)
{
	b.logs->Shutdown ();

	shutdown (b.infd, SHUT_RD);
	shutdown (b.outfd, SHUT_WR);
	b.infd = (b.outfd = -1);
	b.buflen = 0;
}


/*
 * Operates a full-duplex bridge between fd[0] and fd[1] and display any
 * transmitted data to each streams in the NULL-terminated stream list <logs>.
 *
 * Note: no assumption is made about the transport protocol used,
 * but the use of shutdown() assumes we work with sockets
 * (shutdown() will otherwise silently fail -- not a big problem).
 *
 * fd should be closed upon return.
 * fd should probably be in non-blocking I/O mode so that the function operates
 * fine.
 */

int
monitor_bridge(const int *fds, DataLogList *logs, long limit)
{
	long totalcount = 0;
	bridge b[2];

	b[0].outfd = b[1].infd = fds[0];
	b[0].infd = b[1].outfd = fds[1];
	b[0].buflen = b[1].buflen = 0;
	b[0].logs = &logs->ServerSide ();
	b[1].logs = &logs->ClientSide ();

	do
	{
		fd_set rdset, wrset, exset;

		FD_ZERO (&rdset);
		FD_ZERO (&wrset);
		FD_ZERO (&exset);

		int val = -1;
		for (int i = 0; i < 2; i++)
		{
			int f = register_bridge (b[i], &rdset, &wrset, &exset);
			if (val < f)
				val = f;
		}

		if (val == -1)
			return 0; // no more active bridge -> exit nicely

		// What should we do?
		val = select (val + 1, &rdset, &wrset, &exset, NULL);
		if (val == -1)
			return -1; // most likely EINTR -> die

		for (int i = 0; val > 0; i++)
		{
			int fd = b[i].infd;

			if (fd == -1)
				continue; // bridge was shut down earlier

			if (FD_ISSET (fd, &exset))
			{
				val--;
				/* transmit OOB byte */
				char oob;

				if (recv (fd, &oob, 1, MSG_OOB) != 1)
					return -1; // strange error
				if ((b[i].logs->WriteData (&oob, 1, 1) != 1)
				 || (send (b[i].outfd, &oob, 1, MSG_OOB) != 1))
				{
					shutdown_bridge (b[i]);
					shutdown_bridge (b[i ^ 1]);
					return -1;
				}
				totalcount ++;
			}

			if (FD_ISSET (fd, &rdset))
			{
				val--;
				/* receive data (b[i].buflen MUST be zero) */
				int check = recv (fd, b[i].buffer,
							sizeof (b[i].buffer),
							0);
				switch (check)
				{
					case -1:
					case 0:
						if (FD_ISSET (b[i].outfd, &wrset))
							val --;
						shutdown_bridge (b[i]);
						continue;

					default:
						b[i].buflen = check;
				}
			}

			fd = b[i].outfd;
			if (FD_ISSET (fd, &wrset))
			{
				val--;
				/* send data (buflen MUST be non-zero) */
				int check = send (b[i].outfd, b[i].buffer,
							b[i].buflen, 0);
				switch (check)
				{
					case -1:
					case 0:
						shutdown_bridge (b[i]);
						shutdown_bridge (b[i ^ 1]);
						return check;

					default:
						b[i].logs->WriteData (
								b[i].buffer,
								check);
						b[i].buflen -= check;
						memmove (b[i].buffer,
							 b[i].buffer + check,
							 b[i].buflen);
						totalcount += check;
				}
			}
		}

		if (totalcount < 0)
			totalcount = LONG_MAX;
	}
	while ((limit == -1) || (totalcount < limit));

	/* limit reached */
	return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1