/*
 * $Id$
 *
 * cmp.c:  Pilot Connection Management Protocol
 *
 * Copyright (c) 1996, Kenneth Albanowski.
 * Copyright (c) 1999, Tilo Christ
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This library 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 Library General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
 */

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

#include <stdio.h>

#include "pi-debug.h"
#include "pi-source.h"
#include "pi-socket.h"
#include "pi-padp.h"
#include "pi-cmp.h"
#include "pi-error.h"

/* Declare prototypes */
static int cmp_flush(pi_socket_t *ps, int flags);
static int cmp_getsockopt(pi_socket_t *ps, int level, int option_name, 
			  void *option_value, size_t *option_len);
static int cmp_setsockopt(pi_socket_t *ps, int level, int option_name, 
			  const void *option_value, size_t *option_len);

static pi_protocol_t *cmp_protocol_dup (pi_protocol_t *prot);
static void cmp_protocol_free (pi_protocol_t *prot);

/* Protocol Functions */
/***********************************************************************
 *
 * Function:    cmp_protocol_dup
 *
 * Summary:     clones an existing pi_protocol struct 
 *
 * Parameters:  pi_protocol_t*
 *
 * Returns:     pi_protocol_t* or NULL if operation failed
 *
 ***********************************************************************/
static pi_protocol_t*
cmp_protocol_dup (pi_protocol_t *prot)
{
	pi_protocol_t *new_prot;

	struct	pi_cmp_data	 *data,
				 *new_data;
	
	new_prot = (pi_protocol_t *)malloc (sizeof (pi_protocol_t));
	new_data = (struct pi_cmp_data *)malloc (sizeof (struct pi_cmp_data));

	if ( (new_prot != NULL) && (new_data != NULL) ) {
		new_prot->level 	= prot->level;
		new_prot->dup 		= prot->dup;
		new_prot->free 		= prot->free;
		new_prot->read 		= prot->read;
		new_prot->write 	= prot->write;
		new_prot->flush		= prot->flush;
		new_prot->getsockopt 	= prot->getsockopt;
		new_prot->setsockopt 	= prot->setsockopt;

		data = (struct pi_cmp_data *)prot->data;
		new_data->type 		= data->type;
		new_data->flags 	= data->flags;
		new_data->version 	= data->version;
		new_data->baudrate 	= data->baudrate;

		new_prot->data 		= new_data;

	} else if (new_prot != NULL) {
		free(new_prot);
		new_prot = NULL;
	} else if (new_data != NULL) {
		free(new_data);
		new_data = NULL;
	}

	return new_prot;
}


/***********************************************************************
 *
 * Function:    cmp_protocol_free
 *
 * Summary:     frees an existing pi_protocol struct
 *
 * Parameters:  pi_protocol_t*
 *
 * Returns:     void
 *
 ***********************************************************************/
static void
cmp_protocol_free (pi_protocol_t *prot)
{
	if (prot != NULL) {
		if (prot->data != NULL)
			free(prot->data);
		free(prot);
	}
}


/***********************************************************************
 *
 * Function:    cmp_protocol
 *
 * Summary:     creates and inits pi_protocol struct instance
 *
 * Parameters:  void
 *
 * Returns:     pi_protocol_t* or NULL if operation failed
 *
 ***********************************************************************/
pi_protocol_t*
cmp_protocol (void)
{
	pi_protocol_t *prot;
	struct 	pi_cmp_data *data;

	prot = (pi_protocol_t *)malloc (sizeof (pi_protocol_t));	
	data = (struct pi_cmp_data *)malloc (sizeof (struct pi_cmp_data));

	if (prot != NULL && data != NULL) {
		prot->level 		= PI_LEVEL_CMP;
		prot->dup 		= cmp_protocol_dup;
		prot->free 		= cmp_protocol_free;
		prot->read 		= cmp_rx;
		prot->write 		= cmp_tx;
		prot->flush		= cmp_flush;
		prot->getsockopt 	= cmp_getsockopt;
		prot->setsockopt 	= cmp_setsockopt;

		data->type 	= 0;
		data->flags 	= 0;
		data->version 	= 0;
		data->baudrate 	= 0;

		prot->data 	= data;

	} else if (prot != NULL) {
		free(prot);
		prot = NULL;
	} else if (data != NULL) {
		free(data);
		data = NULL;
	}

	return prot;
}


/***********************************************************************
 *
 * Function:    cmp_rx_handshake
 *
 * Summary:     establishes RX handshake
 *
 * Parameters:  pi_socket_t*, baudrate, hirate enable
 *
 * Returns:     0 for success, negative otherwise
 *
 ***********************************************************************/
int
cmp_rx_handshake(pi_socket_t *ps, int establishrate,
	int establishhighrate) 
{
	pi_protocol_t *prot;
	struct 	pi_cmp_data *data;
	pi_buffer_t *buf;
	int bytes;

	prot = pi_protocol(ps->sd, PI_LEVEL_CMP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	data = (struct pi_cmp_data *)prot->data;

	/* Read the cmp packet */
	buf = pi_buffer_new (PI_CMP_HEADER_LEN);
	if (buf == NULL) {
		errno = ENOMEM;
		return pi_set_error(ps->sd, PI_ERR_GENERIC_MEMORY);
	}
	
	bytes = cmp_rx(ps, buf, PI_CMP_HEADER_LEN, 0);

	pi_buffer_free (buf);
	if (bytes < 0)
		return bytes;

	if ((data->version & 0xFF00) == 0x0100) {
		if (establishrate != -1) {
			if (establishrate > data->baudrate) {
				if (establishhighrate) {
					LOG((PI_DBG_CMP, PI_DBG_LVL_INFO, 
					    "CMP Establishing higher rate %ul (%ul)\n",
					    establishrate, data->baudrate));
					data->baudrate = establishrate;
				}
			} else {
				data->baudrate = establishrate;
			}
		}
		
		if ((bytes = cmp_init(ps, data->baudrate)) < 0)
			return bytes;
	} else {
		/* 0x80 means the comm version wasn't compatible */
		LOG((PI_DBG_CMP, PI_DBG_LVL_ERR, "CMP Incompatible Version\n"));
		cmp_abort(ps, 0x80);
		errno = ECONNREFUSED;
		return pi_set_error(ps->sd, PI_ERR_PROT_INCOMPATIBLE);
	}

	return 0;
}


/***********************************************************************
 *
 * Function:    cmp_tx_handshake
 *
 * Summary:     establishes TX handshake
 *
 * Parameters:  pi_socket_t*
 *
 * Returns:     >= 0 for success, negative otherwise
 *
 ***********************************************************************/
int
cmp_tx_handshake(pi_socket_t *ps) 
{
	pi_protocol_t *prot;
	struct 	pi_cmp_data *data;
	int result;

	prot = pi_protocol(ps->sd, PI_LEVEL_CMP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	data = (struct pi_cmp_data *)prot->data;

	if ((result = cmp_wakeup(ps, 38400)) < 0)	/* Assume box can't go over 38400 */
		return result;

	if ((result = cmp_rx(ps, NULL, 0, 0)) < 0)
		return result;							/* failed to read, errno already set */

	switch (data->type) {
		case PI_CMP_TYPE_INIT:
			return 0;
		case PI_CMP_TYPE_ABRT:
			LOG((PI_DBG_CMP, PI_DBG_LVL_NONE,
				"CMP Aborted by other end\n"));
			errno = -EIO;
			return pi_set_error(ps->sd, PI_ERR_PROT_ABORTED);
	}

	return PI_ERR_PROT_INCOMPATIBLE;

}


/***********************************************************************
 *
 * Function:    cmp_tx
 *
 * Summary:     Transmit CMP Packets
 *
 * Parameters:  pi_socket_t*, char* to buf, buf length, flags
 *
 * Returns:     A negative number on error, 0 otherwise
 *
 ***********************************************************************/
ssize_t
cmp_tx(pi_socket_t *ps, const unsigned char *buf, size_t len, int flags)
{
	int 	bytes,
		type;
	size_t	size;
	pi_protocol_t	*prot,
			*next;
	struct 	pi_cmp_data *data;
	unsigned char cmp_buf[PI_CMP_HEADER_LEN];

	prot = pi_protocol(ps->sd, PI_LEVEL_CMP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	data = (struct pi_cmp_data *)prot->data;
	next = pi_protocol_next(ps->sd, PI_LEVEL_CMP);
	if (next == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	type = padData;
	size = sizeof(type);
	pi_setsockopt(ps->sd, PI_LEVEL_PADP, PI_PADP_TYPE, &type, &size);

	set_byte(&cmp_buf[PI_CMP_OFFSET_TYPE], data->type);
	set_byte(&cmp_buf[PI_CMP_OFFSET_FLGS], data->flags);
	set_short(&cmp_buf[PI_CMP_OFFSET_VERS], data->version > PI_CMP_VERSION ? PI_CMP_VERSION : data->version);
	set_short(&cmp_buf[PI_CMP_OFFSET_RESV], 0);
	set_long(&cmp_buf[PI_CMP_OFFSET_BAUD], data->baudrate);

	CHECK(PI_DBG_CMP, PI_DBG_LVL_INFO, cmp_dump(cmp_buf, 1));

	bytes = next->write(ps, cmp_buf, PI_CMP_HEADER_LEN, flags);
	if (bytes < 10)
		return (bytes < 0) ? bytes : pi_set_error(ps->sd, PI_ERR_PROT_ABORTED);

	return 0;
}

/***********************************************************************
 *
 * Function:    cmp_rx
 *
 * Summary:     Receive CMP packets
 *
 * Parameters:  None
 *
 * Returns:     A negative number on error, 0 otherwise
 *
 ***********************************************************************/
ssize_t
cmp_rx(pi_socket_t *ps, pi_buffer_t *msg, size_t len, int flags)
{
	int 	bytes;
	pi_protocol_t	*prot,
			*next;
	struct 	pi_cmp_data *data;

	LOG((PI_DBG_CMP, PI_DBG_LVL_DEBUG, "CMP RX len=%d flags=0x%02x\n",
		len, flags));

	prot = pi_protocol(ps->sd, PI_LEVEL_CMP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	data = (struct pi_cmp_data *)prot->data;
	next = pi_protocol_next(ps->sd, PI_LEVEL_CMP);
	if (next == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	bytes = next->read(ps, msg, len, flags);
	if (bytes < 10)
		return pi_set_error(ps->sd, (bytes < 0) ? bytes : PI_ERR_PROT_ABORTED);

	CHECK(PI_DBG_CMP, PI_DBG_LVL_INFO, cmp_dump(msg->data, 0));

	data->type 	= get_byte(&msg->data[PI_CMP_OFFSET_TYPE]);
	data->flags 	= get_byte(&msg->data[PI_CMP_OFFSET_FLGS]);
	data->version 	= get_short(&msg->data[PI_CMP_OFFSET_VERS]);
	data->baudrate 	= get_long(&msg->data[PI_CMP_OFFSET_BAUD]);

	return 0;
}

/***********************************************************************
 *
 * Function:    cmp_flush
 *
 * Summary:     Flush input and output buffers
 *
 * Parameters:  pi_socket_t*, flags
 *
 * Returns:     A negative number on error, 0 otherwise
 *
 ***********************************************************************/
static int
cmp_flush(pi_socket_t *ps, int flags)
{
	pi_protocol_t	*prot,
			*next;

	prot = pi_protocol(ps->sd, PI_LEVEL_CMP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	next = pi_protocol_next(ps->sd, PI_LEVEL_CMP);
	if (next == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	return next->flush(ps, flags);
}

/***********************************************************************
 *
 * Function:    cmp_init
 *
 * Summary:     Initialize the socket for CMP transmission
 *
 * Parameters:  pi_socket_t*, baudrate
 *
 * Returns:     Number of packets transmitted
 *
 ***********************************************************************/
int
cmp_init(pi_socket_t *ps, int baudrate)
{
	pi_protocol_t *prot;
	struct 	pi_cmp_data *data;

	prot = pi_protocol(ps->sd, PI_LEVEL_CMP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);
	data = (struct pi_cmp_data *)prot->data;

	data->type = PI_CMP_TYPE_INIT;
	data->flags = CMP_FL_LONG_PACKET_SUPPORT;
	if (baudrate != 9600)
		data->flags = CMP_FL_CHANGE_BAUD_RATE;
	data->baudrate = baudrate;

	return cmp_tx(ps, NULL, 0, 0);
}

/***********************************************************************
 *
 * Function:    cmp_abort
 *
 * Summary:     Abort a CMP session in progress
 *
 * Parameters:  pi_socket_t*
 *
 * Returns:     Number of PADP packets transmitted or negative on error
 *
 ***********************************************************************/
int
cmp_abort(pi_socket_t *ps, int reason)
{
	pi_protocol_t *prot;
	struct 	pi_cmp_data *data;
	
	prot = pi_protocol(ps->sd, PI_LEVEL_CMP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	data = (struct pi_cmp_data *)prot->data;
	data->type = PI_CMP_TYPE_ABRT;
	data->flags = reason;

	LOG((PI_DBG_CMP, PI_DBG_LVL_NONE, "CMP ABORT\n"));

	return cmp_tx (ps, NULL, 0, 0);
}

/***********************************************************************
 *
 * Function:    cmp_wakeup
 *
 * Summary:     Wakeup the CMP listener process
 *
 * Parameters:  pi_socket_t*
 *
 * Returns:     Number of PADP packets transmitted or negative on error
 *
 ***********************************************************************/
int
cmp_wakeup(pi_socket_t *ps, int maxbaud)
{
	pi_protocol_t *prot;
	struct 	pi_cmp_data *data;
	
	prot = pi_protocol(ps->sd, PI_LEVEL_CMP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);

	data = (struct pi_cmp_data *)prot->data;
	data->type 	= PI_CMP_TYPE_WAKE;
	data->flags 	= 0;
	data->version 	= PI_CMP_VERSION;
	data->baudrate 	= maxbaud;

	return cmp_tx(ps, NULL, 0, 0);
}


/***********************************************************************
 *
 * Function:    cmp_getsockopt
 *
 * Summary:     get options on socket
 *
 * Parameters:  pi_socket*, level, option name, option value, option length
 *
 * Returns:     0 for success, negative otherwise
 *
 ***********************************************************************/
static int
cmp_getsockopt(pi_socket_t *ps, int level, int option_name, 
	       void *option_value, size_t *option_len)
{
	pi_protocol_t *prot;
	struct 	pi_cmp_data *data;

	(void) level;

	prot = pi_protocol(ps->sd, PI_LEVEL_CMP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);
	data = (struct pi_cmp_data *)prot->data;

	switch (option_name) {
		case PI_CMP_TYPE:
			if (*option_len != sizeof (data->type))
				goto error;
			memcpy (option_value, &data->type,
				sizeof (data->type));
			*option_len = sizeof (data->type);
			break;

		case PI_CMP_FLAGS:
			if (*option_len != sizeof (data->flags))
				goto error;
			memcpy (option_value, &data->flags,
				sizeof (data->flags));
			*option_len = sizeof (data->flags);
			break;

		case PI_CMP_VERS:
			if (*option_len != sizeof (data->version))
				goto error;
			memcpy (option_value, &data->version,
				sizeof (data->version));
			*option_len = sizeof (data->version);
			break;

		case PI_CMP_BAUD:
			if (*option_len != sizeof (data->baudrate))
				goto error;
			memcpy (option_value, &data->baudrate,
				sizeof (data->baudrate));
			*option_len = sizeof (data->baudrate);
			break;
	}

	return 0;
	
 error:
	errno = EINVAL;
	return pi_set_error(ps->sd, PI_ERR_GENERIC_ARGUMENT);
}


/***********************************************************************
 *
 * Function:    cmp_setsockopt
 *
 * Summary:     set options on socket
 *
 * Parameters:  pi_socket*, level, option name, option value, option length
 *
 * Returns:     0 for success, negative otherwise
 *
 ***********************************************************************/
static int
cmp_setsockopt(pi_socket_t *ps, int level, int option_name, 
	       const void *option_value, size_t *option_len)
{
	pi_protocol_t *prot;
	struct 	pi_padp_data *data;

	(void) level;

	prot = pi_protocol(ps->sd, PI_LEVEL_PADP);
	if (prot == NULL)
		return pi_set_error(ps->sd, PI_ERR_SOCK_INVALID);
	data = (struct pi_padp_data *)prot->data;

	if (option_name == PI_PADP_TYPE) {
		if (*option_len != sizeof (data->type))
			goto error;
		memcpy (&data->type, option_value,
			sizeof (data->type));
		*option_len = sizeof (data->type);
	}

	return 0;
	
 error:
	errno = EINVAL;
	return pi_set_error(ps->sd, PI_ERR_GENERIC_ARGUMENT);
}


/***********************************************************************
 *
 * Function:    cmp_dump
 *
 * Summary:     Dump the CMP packet frames
 *
 * Parameters:  char* to cmp packet, TX boolean
 *
 * Returns:     void
 *
 ***********************************************************************/
void
cmp_dump(const unsigned char *cmp, int rxtx)
{
	char *type;

	(void) rxtx;

	switch (get_byte(&cmp[PI_CMP_OFFSET_TYPE])) {
		case PI_CMP_TYPE_WAKE:
			type = "WAKE";
			break;
		case PI_CMP_TYPE_INIT:
			type = "INIT";
			break;
		case PI_CMP_TYPE_ABRT:
			type = "ABRT";
			break;
		default:
			type = "UNK";
			break;
	}
	
	LOG((PI_DBG_CMP, PI_DBG_LVL_NONE,
	    "CMP %s %s Type: 0x%02x Flags: 0x%02x Version: 0x%04x Baud: %d\n",
		rxtx ? "TX" : "RX", type,
		get_byte(&cmp[PI_CMP_OFFSET_TYPE]), 
		get_byte(&cmp[PI_CMP_OFFSET_FLGS]),
		get_long(&cmp[PI_CMP_OFFSET_VERS]),
		get_long(&cmp[PI_CMP_OFFSET_BAUD])));
}

/* vi: set ts=8 sw=4 sts=4 noexpandtab: cin */
/* ex: set tabstop=4 expandtab: */
/* Local Variables: */
/* indent-tabs-mode: t */
/* c-basic-offset: 8 */
/* End: */
