/* ============================================================================
 * << File: protocol.c >>
 * -------------------------
 *  Authors: Emiliano Castagnari (aka Torian) <ecastag@fi.uba.ar>
 *           Diego Essaya <dessaya@fi.uba.ar>
 *     Date: 31/12/03
 *
 *  Description:
 *    Functions related to message I/O, using the gems protocol specification
 *    (see doc/Protocol).
 *
 *    Also, some global buffers are declared here, which can be used in other
 *    modules to save I/O data. This is to improve general efficiency, because
 *    it is not necessary to declare a different buffer for each function, and
 *    to copy data between buffers. All functions use the same I/O buffer.
 * ============================================================================
 * ============================================================================
 *
 * ChangeLog: View Changelog
 *
 * ============================================================================
 * Copyright (C) 2003, 2004 Diego Essaya, Emiliano Castagnari
 * 
 * This file is part of gems.
 * 
 * gems is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * gems 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 Library General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 * ============================================================================
 */

#include <stdio.h>			/* perror          */
#include <sys/types.h>		/* recv() y send() */
#include <sys/socket.h>		/* recv() y send() */
#include <pty.h>			/* struct winsize  */
#include <errno.h>			/* int errno       */
#include <string.h>			/* memcpy()        */

#include "protocol.h"
#include "common.h"

/* I/O buffers: One buffer to hold an entire message (msg_buffer), and pointers
 * to the header and message data. */
char gems_msg_buffer[MSG_HEADER_SIZE + MSG_MAX_DATA_SIZE];
char *gems_header_buffer = gems_msg_buffer;
char *gems_data_buffer = gems_msg_buffer + MSG_HEADER_SIZE;
/* Pointers to the header fields: */
gems_msg_type_t *gems_type_buffer = (gems_msg_type_t *) gems_msg_buffer;
gems_msg_len_t *gems_len_buffer = 
	(gems_msg_len_t *) (gems_msg_buffer + MSG_TYPE_SIZE);

/* Buffer to hold version string: Let's suppose 20 characters is enough. */
#define VERSION_BUFFER_LEN 20
char gems_version_buffer[VERSION_BUFFER_LEN];


/* get_stream() is a wrapper for recv().
 * 
 * Reads data from the specified fd until all expected bytes have arrived,
 * writing them into the buffer.
 * Returns FALSE if a disconnection occurs. 
 */
fbool_t get_stream(int fd, char *buffer, int bytes)
{
	int n;

	do
	{
		n = recv(fd, buffer, bytes, MSG_WAITALL);
	}
	while ((n == -1) && (errno == EINTR));
	/* EINTR means that a signal has been caught and handled. We don't mind
	 * about this, so continue reading. */

	if (n == bytes)
		return TRUE;
	if (n == 0)
		return FALSE;  /* Disconnection. */

	/* Error: */
	perror("recv");
	return FAIL;
}


/* get_msg()
 * 
 * Reads data from the specified fd until an entire message has arrived.
 * Returns FALSE if a disconnection occurs, or if the message received is
 * DISCONNECT.
 * NOTE: The data (msg->data) is not copied into a new array; therefore 
 * succesive calls to this function will overwrite that section of memory. 
 */
fbool_t get_msg(int fd, t_gems_msg * msg)
{
	fbool_t r;

	/* Receive message header: */
	if ((r = get_stream(fd, gems_header_buffer, MSG_HEADER_SIZE)) != TRUE)
		return r;

	/* Get message type: */
	msg->type = *gems_type_buffer;

	/* Get data size: */
	msg->len = *gems_len_buffer;

	/* Get data: */
	if (msg->len > 0)
	{
		if ((r = get_stream(fd, gems_data_buffer, msg->len)) != TRUE)
			return r;
		msg->data = gems_data_buffer;
	}

	if (msg->type == GEMS_DISCONNECT)
		return FALSE;

	return TRUE;
}


/* send_msg()
 *
 * Sends the message, following the protocol specifications. The global buffer
 * gems_data_buffer can be used for msg->data. This is encouraged, because 
 * otherwise an extra memcpy() has to be done.
 */
fbool_t send_msg(int fd, t_gems_msg * msg)
{
	/* Armo el paquete: */
	*gems_type_buffer = msg->type;
	*gems_len_buffer = msg->len;
	if (msg->len > 0)
		if (msg->data != gems_data_buffer)
			memcpy(gems_data_buffer, msg->data, msg->len);

	/* Envio el mensaje: */
	if (send(fd, gems_msg_buffer, MSG_HEADER_SIZE + msg->len, 0) == -1)
	{
		perror("send");
		return FAIL;
	}

	return TRUE;
}


/* send_version()
 *
 * Sends the protocol version.
 */
fbool_t send_version(int fd)
{
	t_gems_msg msg;

	msg.type = GEMS_VERSION;
	msg.len = strlen(PROTOCOL_VERSION);
	msg.data = PROTOCOL_VERSION;

	return send_msg(fd, &msg);
}


/* get_peer_version()
 *
 * Receives the remote protocol version. Returns a zero-terminated string
 * in *version.
 * If DISCONNECT is received instead of VERSION, or if the connection is
 * closed, this function returns FALSE.
 * NOTE: Succesive calls to this function will overwrite the string buffer.
 */
fbool_t get_peer_version(int fd, char **version)
{
	t_gems_msg msg;
	fbool_t r = TRUE;

	if ((r = get_msg(fd, &msg)) != TRUE)
		return r;

	if (msg.type != GEMS_VERSION)
		return FAIL;

	if (msg.len > VERSION_BUFFER_LEN - 1)
		return FAIL;

	memcpy(gems_version_buffer, msg.data, msg.len);
	gems_version_buffer[msg.len] = '\0';
	*version = gems_version_buffer;

	return TRUE;
}


/* send_ack()
 *
 * Sends an ACK message.
 */
fbool_t send_ack(int fd)
{
	t_gems_msg msg;

	msg.type = GEMS_ACK;
	msg.len = 0;

	return send_msg(fd, &msg);
}


/* get_ack()
 *
 * Waits until an ACK message is received. Returns FALSE if a disconnection
 * occurs.
 */
fbool_t get_ack(int fd)
{
	t_gems_msg msg;
	fbool_t r;

	if ((r = get_msg(fd, &msg)) != TRUE)
		return r;

	if (msg.type != GEMS_ACK)
		return FAIL;

	return TRUE;
}


/* send_disconnect()
 *
 * Sends a DISCONNECT message.
 */
fbool_t send_disconnect(int fd)
{
	t_gems_msg msg;

	msg.type = GEMS_DISCONNECT;
	msg.len = 0;

	return send_msg(fd, &msg);
}


/* send_winsize()
 *
 * Sends the local terminal size.
 */
fbool_t send_winsize(int fd)
{
	struct winsize win;
	t_gems_msg msg;
	int data[2];

	/* Obtengo el tamaņo de la ventana: */
	ioctl(0, TIOCGWINSZ, (char *) &win);
	data[0] = (int) win.ws_col;
	data[1] = (int) win.ws_row;

	msg.type = GEMS_WINSIZE;
	msg.len = 2 * sizeof(int);
	msg.data = (char *) data;

	return send_msg(fd, &msg);
}


/* compare_winsize()
 *
 * Compares the remote and local terminal sizes. Returns TRUE if local size
 * is greater. Otherwise prints appropriate error message and returns FALSE.
 */
fbool_t compare_winsize(int remote_cols, int remote_rows, char *appname)
{
	struct winsize win;

	/* Obtengo el tamaņo de la ventana local: */
	ioctl(0, TIOCGWINSZ, (char *) &win);

	if ((win.ws_col < remote_cols) || (win.ws_row < remote_rows))
	{
		if (appname)
		{
			fputc('\n', stderr);
			fprintf(stderr, 
				_("%s: Terminal size too small "
					"(minimum required: %dx%d).\n"), 
				appname, remote_cols, remote_rows);
		}
		return FALSE;
	}

	return TRUE;
}


/* get_cols_rows()
 *
 * Extracts the columns and rows values from a GEMS_WINSIZE message.
 */
fbool_t get_cols_rows(const t_gems_msg * msg, int *cols, int *rows)
{
	if ((msg->type != GEMS_WINSIZE) || (msg->len != 2 * sizeof(int)))
		return FAIL;

	*cols = ((int *) msg->data)[0];
	*rows = ((int *) msg->data)[1];

	return TRUE;
}


/* get_winsize()
 *
 * Receives the remote terminal size, compares it with the local size and
 * returns TRUE if local size is greater. Returns FALSE if a disconnection
 * occurs.
 */
fbool_t get_winsize(int fd, int *cols, int *rows)
{
	t_gems_msg msg;
	fbool_t r;

	if ((r = get_msg(fd, &msg)) != TRUE)
		return r;

	if (msg.type != GEMS_WINSIZE)
		return FAIL;

	if ((r = get_cols_rows(&msg, cols, rows)) != TRUE)
		return r;

	return compare_winsize(*cols, *rows, NULL);
}

/* vim: set ts=4 sw=4: */
