/* $Id: access.c,v 1.3 1999/10/06 18:16:22 he Exp $ */
/* Copyright 1997 by Henner Eisen

    This code 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 code 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., 675 Mass Ave, Cambridge, MA 02139, USA.

*/ 
#include <tdu_user.h>
#include "tdu.h"

/*
 * access regime event handler used during transfer regime by receiver
 * while loading (reading) data from peer.
 */
int tdu_access_loading( struct tdu_fsm *fsm, int event, struct tdu_buf * tb)
{

	tdu_printf( TDU_LOG_TRC, " tdu_access_loading(): unexpected event %s received"
		" while transfer regime is established\n",
		tdu_cmd_descr(event) );
	switch (event) {
	case TDU_CI_T_ABORT:
		fsm->assoc.handler (fsm, event, tb);
		break;
	case TDU_CI_RESPONSE_TIMEOUT:
		tdu_p_except_req( fsm,TDU_RE_DELAY_EXPIRED);
		break;
	case TDU_CI_T_P_EXCEPTION:
	default:
		tdu_printf( TDU_LOG_ERR, "tdu_access_loading(): unexpected command received by receiver\n" );
		tdu_transfer_abort(fsm);
	};
	return 0;
}

/*
 * access regime event handler used during transfer regime by sender 
 * while saving (writing) data to peer. 
 */
int tdu_access_saving( struct tdu_fsm *fsm, int event, struct tdu_buf * tb)
{

	tdu_printf( TDU_LOG_TRC, " tdu_access_saving(): unexpected event %s received"
		" while transfer regime is established\n",
		tdu_cmd_descr(event) );
	switch (event) {
	case TDU_CI_T_ABORT:
		fsm->assoc.handler (fsm, event, tb);
		break;
	case TDU_CI_RESPONSE_TIMEOUT:
		tdu_p_except_req( fsm,TDU_RE_DELAY_EXPIRED);
		break;
	case TDU_CI_T_P_EXCEPTION:
	default:
		tdu_printf( TDU_LOG_ERR, "tdu_access_saving(): unexpected command received by sender\n" );
		tdu_transfer_abort(fsm);
	};
	return 0;
}

int tdu_p_except_req( struct tdu_fsm * fsm, int reason )
{
	tdu_send_p_exception(fsm,reason);
	if( fsm->regime_handler == & fsm->transfer.handler )
		tdu_transfer_abort(fsm);
	tdu_access_set_idle(fsm);
	return reason>0 ? reason : -1;
}

/* 
 * Check if in idle state of access regime. returns 1 if in idle state,
 * 0 if not, but a negative value if the idle state is known never to be
 * reachable without user intervention.
 */
static int in_idle_access_regime( struct tdu_fsm * fsm )
{
	int ret;

	/* somewhat ugly, but we need this */
        if ( (fsm -> regime_handler == & fsm->access.handler) &&
	     (fsm -> access.handler == tdu_await_end_access)     ) return -2;

        ret =  ( fsm -> regime_handler == & fsm->access.handler )
		&& ( fsm->access.handler == fsm->access.idle_handler );

	if( ret ) return ret;
/* From the following states, the idle access regime cannot be reached
 * without additional local user intervention*/
	if (  fsm->regime_handler == &fsm->idle.handler ) return -3;
	if ( (fsm->regime_handler == &fsm->assoc.handler ) &&
	     (fsm->assoc.handler == fsm->assoc.master_handler)) return -2;
	return 0;
}

/* 
 * process events until the access regime's idle state is reached again.
 */
int tdu_wait_for_idle(struct tdu_fsm * fsm, struct timeval * timeout)
{
	return tdu_wait( fsm, timeout, in_idle_access_regime );
}


void tdu_access_set_idle(struct tdu_fsm * fsm)
{
		fsm -> access.handler = fsm->access.idle_handler;
		fsm -> regime_handler = & fsm->access.handler;
		tdu_del_timer(fsm);
		fsm->wait=0;
}

/*
 * access regime handler, waiting for confirmation of t_end_access 
 */
int tdu_await_end_access( struct tdu_fsm *fsm, int event, struct tdu_buf *tb)
{
	int err, pi;

	tdu_printf(TDU_LOG_TRC, "tdu_await_end_access(), event = %s\n",
			tdu_cmd_descr(event) ); 
	switch (event) {
	case TDU_CI_T_RESPONSE_POSITIVE:
		pi = tdu_get_next_pi(tb);
		err = tdu_check_response(tb,pi,fsm->wait);
		if( err ) goto error;
		fsm->assoc.handler = tdu_assoc_master;
		/* now fall through */
	case TDU_CI_T_END_ACCESS:
		/* if t_end_acces_req conflict, no change of 
		   role ./. association event handler handler */
		fsm -> regime_handler = & fsm->assoc.handler ;
		tdu_del_timer(fsm);
		break;
	case TDU_CI_RESPONSE_TIMEOUT:
		tdu_printf(TDU_LOG_ERR, "timeout while waiting for t_end_access confirmation\n");
		tdu_abort_req(fsm,TDU_RE_DELAY_EXPIRED,NULL);
		break;
	case TDU_CI_T_ABORT:
	default:
	error:
		tdu_printf(TDU_LOG_ERR, "unexpected event %s while waiting for t_end_access confirmation" 
		       " confirmation\n", tdu_cmd_descr(event) );
		tdu_abort_req(fsm,TDU_RE_PROTOCOL_CONFLICT,NULL);
	};
	fsm->wait = 0;
	return 0;
}

/*
 * Request termination of access regime.
 * Specifying user data and non-default reason parameter not yet supported.
 */
int tdu_end_access_req(struct tdu_fsm * fsm)
{
	int ret;
	struct tdu_buf tb[1];

	tdu_init_tb(tb);

	tdu_printf(TDU_LOG_TRC, "tdu_end_access_req(),\n" ); 

	ret = in_idle_access_regime( fsm );

	if( ret == 0 ) {
		tdu_printf( TDU_LOG_ERR, "tdu_end_access_req: not in idle state of "
			"access regime\n");
		return -1;
	}

	if( ret < 0 ) return ret;

	tdu_add_reason( tb, 0, TDU_RE_OTHER_REASON, "%");
	tdu_add_ci_header( tb, TDU_CI_T_END_ACCESS);
	ret = tdu_send_packet(tb,fsm);
	if ( ret < 0 ) { 
		perror("tdu_end_access_req:send_packet");
		tdu_abort(fsm);
		return ret;
	}

	fsm->access.handler = tdu_await_end_access;
	tdu_start_timer( fsm );
	fsm->wait = TDU_CI_T_END_ACCESS;
	return ret;
}       

/*
 * Fill in parameters in a t_access request packet
 */
int tdu_add_access_params( struct tdu_buf * tb, struct tdu_access_param * par)
{
	unsigned char byte = 0;
	int ret=0;
	
	if( par->udata && (par->udata_len >= 0) )
		ret += tdu_add_string_par(tb, TDU_PI_USER_DATA, par->udata
					  , LIMIT(par->udata_len,TDU_PLEN_UDATA) );

	ret += tdu_add_byte_par(tb,TDU_PI_TRANSFER_MODE,
				par->transfer_mode);

	byte = par->recovery ? 1 : 0;
	byte |= ( (  (par->transfer_size) >> 9 ) & 0x0e );
	byte |= ( (  (par->window - 1 )   << 5 ) & 0xe0 );
	ret += tdu_add_byte_par(tb,TDU_PI_SIZE_RECOVERY_WINDOW,byte);

	byte = par->role? TDU_ROLE_MASTER : TDU_ROLE_SLAVE;
	byte |= (par->functions) & 0xfe; /*  functions supported by us */
	ret += tdu_add_byte_par(tb,TDU_PI_ROLE_FUNCTION,byte);

	return ret;
}


int tdu_send_access( struct tdu_fsm * fsm, int resp ) 
{
	struct tdu_buf tb[1];

	tdu_printf(TDU_LOG_TRC, "tdu_send_access()\n");

	tdu_init_tb(tb);
	tdu_add_access_params( tb, & fsm->access.local ); 
	if( resp ) { 
		/*FIXME: the Role should be removed*/
		tdu_add_reason(tb, TDU_CI_T_ACCESS, 0, NULL);
		tdu_add_ci_header( tb, TDU_CI_T_RESPONSE_POSITIVE );
	} else {
		tdu_add_ci_header( tb, TDU_CI_T_ACCESS );
	}
	return tdu_send_packet( tb, fsm );
}

/*
 * Parse parameters of an access_request or positive response message.
 * returns:
 *   0 on success 
 *   an integer reason code from the TDU_RE_* set when an error is detected.
 */
int tdu_parse_access( struct tdu_access_param *par, struct tdu_buf *tb)
{
	int pi, pv;

	tdu_printf(TDU_LOG_TRC, "tdu_parse_access()\n");
	do {
		tdu_printf(TDU_LOG_DBG, "parsing parameter %d=%s\n",*tb->data,
			   tdu_param_descr(*tb->data) );
		pi = tdu_get_next_pi(tb);
		if( pi < 0 ) return -pi;
		switch( pi ){
		case TDU_PI_RESULT_REASON:
			tdu_printf(TDU_LOG_DBG, "result/reason\n");
			if( *tb->ci == TDU_CI_T_RESPONSE_POSITIVE ){
				if( (pv=tdu_parse_byte(tb)) < 0 ) 
					return TDU_RE_SYNTAX_ERROR;
				if( pv != TDU_CI_T_ACCESS )
					return TDU_RE_PROTOCOL_CONFLICT;
			};
			tb->data = tb->pn;
			break;
		case TDU_PI_ROLE_FUNCTION:
			if( (pv=tdu_parse_byte(tb)) < 0 ){
				if( tb->data + 1 == tb->pn ){
					tb->data++;
				} else {
					return TDU_RE_SYNTAX_ERROR;
				}
			}
			par->role = pv & TDU_BIT_ROLE;
			par->functions = pv & (~TDU_BIT_ROLE);
			break;
		case TDU_PI_SIZE_RECOVERY_WINDOW:
			tdu_printf(TDU_LOG_DBG, "size/recov/win\n");
			if( (pv=tdu_parse_byte(tb)) < 0 )
					return TDU_RE_SYNTAX_ERROR;
			par->transfer_size = 512 << ((pv & TDU_MASK_SIZE) >>1);
			par->window = 1 + ((pv & TDU_MASK_WINDOW) >>5);
			par->recovery = TDU_MASK_RECOVERY;
			tdu_printf(TDU_LOG_DBG,"peer's window size is %d\n",
				   par->window);
			break;
		case TDU_PI_TRANSFER_MODE:
			tdu_printf(TDU_LOG_DBG, "transfer mode\n");
			if( (pv=tdu_parse_byte(tb)) < 0 ) 
				return TDU_RE_SYNTAX_ERROR;
			par->transfer_mode = pv & TDU_BIT_TRANSFER_MODE;
			break;
		case TDU_PI_USER_DATA:
			tdu_printf(TDU_LOG_DBG, "user data\n");
			par->udata_len =
				tdu_parse_string(tb, par->udata,
						 TDU_PLEN_UDATA);
			break;
		default:
			tdu_printf(TDU_LOG_LOG, "tdu_parse_access(): unexpected"
				   " parameter '%s' ignored\n",
				   tdu_param_descr(pi) );
			tb->data = tb->pn;
		};
	} while( tb->pn < tb->tail );
	return 0;
}

/*
 * access regime handler, waiting for t_access confirmation 
 */
int tdu_await_access( struct tdu_fsm *fsm, int event, struct tdu_buf *tb)
{
	int err, pi;

	tdu_printf(TDU_LOG_TRC, "tdu_await_access(), event = %s\n",
	       tdu_cmd_descr(event) ); 
	switch (event) {
	case TDU_CI_T_RESPONSE_POSITIVE:
		tdu_del_timer(fsm);
		err = tdu_parse_access( & fsm->access.remote, tb );
		if( err ) {
			tdu_abort_req( fsm, err, NULL );
		} else {
			if( fsm->access.local.role == TDU_ROLE_SLAVE ) {
				/* requested new role is slave */
				fsm->assoc.handler 
					= fsm->assoc.slave_handler;
				fsm->access.idle_handler 
					= fsm->access.idle_slave_handler;
			} else {
				/* requested new role is master */
				fsm->assoc.handler 
					= fsm->assoc.master_handler;
				fsm->access.idle_handler 
					= fsm->access.idle_master_handler;
			}
			tdu_access_set_idle(fsm);
		}
		break;
	case TDU_CI_T_RESPONSE_NEGATIVE:
		pi = tdu_get_next_pi(tb);
		err = tdu_check_response(tb,pi,fsm->wait);
		if( err ) goto error;
		tdu_del_timer(fsm);
		fsm->assoc.handler = fsm->assoc.master_handler;
		break;
	case TDU_CI_T_ABORT:
		tdu_abort(fsm);
		break;
	default:
	error:
		tdu_printf(TDU_LOG_ERR,"unexpected event %s while waiting for t_access" 
		       " confirmation\n", tdu_cmd_descr(event) );
		tdu_abort_req(fsm,TDU_RE_PROTOCOL_CONFLICT,NULL);
	};
	fsm->wait = 0;
	return 0;
}

/*
 * establish access regime
 */
int tdu_access_req( struct tdu_fsm * fsm, struct tdu_access_param * par )
{

	if ( ! in_idle_assoc_regime_master( fsm ) ){
		tdu_printf(TDU_LOG_TRC, "tdu_access_req(): no idle association master state\n");
		return -1;
	}
	fsm->access.local = *par;
	/* FIXME: contents of *par needs checking */
	
	if( tdu_send_access( fsm, 0 ) > 0 ){
		fsm->assoc.handler = tdu_await_access;
		tdu_start_timer( fsm );
		fsm->wait = TDU_CI_T_ACCESS;
		return 1;
	}
	return 0;
}


void tdu_set_default_access_param( struct tdu_access_param * par, struct tdu_user *usr, struct tdu_stream * st )
{
	par->functions = 0;
	if( usr ){
		if( usr->t_typed_data ) par->functions |= TDU_BIT_TYPED_DATA;
		if( usr->t_dir )        par->functions |= TDU_BIT_DIRECTORY;
		if( usr->t_load)        par->functions |= TDU_BIT_LOAD;
		if( usr->t_save)        par->functions |= TDU_BIT_SAVE;
		if( usr->t_delete)      par->functions |= TDU_BIT_DELETE;
		if( usr->t_rename)      par->functions |= TDU_BIT_RENAME;
	}
	if( st && st->t_read_restart ){
		par->functions |= TDU_BIT_READ_RESTART;
	}
	par->transfer_size = 1024;
	par->window = 1;
	par->recovery = 0;
	par->transfer_mode = 0;
	par->udata_len = -1;
}

/* 
 * process events until the association regime's idle master state is reached.
 */
int tdu_wait_for_end_access(struct tdu_fsm * fsm, struct timeval * timeout)
{
	tdu_printf(TDU_LOG_TRC, "tdu_wait_for_end_access()\n");

	return tdu_wait( fsm, timeout, in_idle_assoc_regime_master );
}


/* 
 * perform the work requierd by a t_end_access request
 */
int tdu_end_access_received(struct tdu_fsm *fsm)
{
	tdu_printf(TDU_LOG_TRC, "tdu_end_access_received()\n");
	if( fsm->assoc.slave_handler ){
		tdu_send_response_pos(fsm,TDU_CI_T_END_ACCESS);
		fsm->assoc.handler = fsm->assoc.slave_handler;
		fsm->regime_handler = & fsm -> assoc.handler;
	} else {
		/* FIXME? is this the proper error code? */
		tdu_abort_req( fsm, TDU_RE_ROLE_REFUSED_U, NULL ); 
	}
	return 0;
}


void tdu_access_set_master(struct tdu_fsm * fsm)
{
	fsm->access.idle_handler = fsm->access.idle_master_handler;
}


void tdu_access_set_slave(struct tdu_fsm * fsm)
{
	fsm->access.idle_handler = fsm->access.idle_slave_handler;
}

/* 
 * perform the work required upon reception of a t_access_req tdu
 */
int tdu_access_received(struct tdu_fsm *fsm, struct tdu_buf * tb)
{
	struct tdu_param par;

	tdu_printf(TDU_LOG_TRC, "tdu_access_received()\n");
	par.reason = TDU_RE_ROLE_REFUSED_U;
	par.other_reason[0] = 0;

	tdu_set_default_access_param( &fsm->access.remote, 0, 0);
	tdu_set_default_access_param( &fsm->access.local, fsm->user,
				      fsm->stream);

	par.reason = tdu_parse_access(&fsm->access.remote,tb);
	if ( par.reason ) goto refuse;

	if( fsm->access.remote.role == TDU_ROLE_SLAVE ){
		fsm->access.local.role = TDU_ROLE_MASTER;
	} else {
		fsm->access.local.role = TDU_ROLE_SLAVE;
	}

	if( fsm->user->t_access){
		par.par.access = &fsm->access.remote;
		par.res.access = &fsm->access.local;
		par.reason = fsm->user->t_access(fsm->user,&par);
		if( par.reason ) goto refuse;
	}

	if( (! fsm->access.idle_slave_handler) && 
	    (fsm->access.local.role == TDU_ROLE_SLAVE)  )  goto refuse;
	if( (! fsm->access.idle_master_handler) && 
	    (fsm->access.local.role == TDU_ROLE_MASTER) )  goto refuse;

	if( tdu_send_access(fsm, 1) > 0 ){
		if (fsm->access.local.role == TDU_ROLE_SLAVE){
			tdu_access_set_slave(fsm);
		} else {
			tdu_access_set_master(fsm);
		}
		tdu_access_set_idle(fsm);
		return 0;
	} else {
		tdu_abort(fsm);
		return -3;
	}

refuse:
	tdu_send_response_neg(fsm,TDU_CI_T_ACCESS,
			      par.reason,par.other_reason);
	/* FIXME: is aborting here really appropriate ? */
	tdu_abort(fsm);
	return -3;
}


/*
 * Parse udata parameter of an arbitrary tdu.
 * returns:
 *   0 on success 
 *   an integer reason code from the TDU_RE_* set when an error is detected.
 */
int tdu_parse_udata( struct tdu_udata_param *par, struct tdu_buf *tb)
{
	int pi;

	tdu_printf(TDU_LOG_TRC, "tdu_parse_udata()\n");
	do {
		tdu_printf(TDU_LOG_DBG, "parsing parameter %d=%s\n",*tb->data,
			   tdu_param_descr(*tb->data) );
		pi = tdu_get_next_pi(tb);
		if( pi < 0 ) return -pi;
		switch( pi ){
		case TDU_PI_USER_DATA:
			tdu_printf(TDU_LOG_DBG, "user data\n");
			par->udata_len =
				tdu_parse_string(tb, par->udata,
						 TDU_PLEN_UDATA);
			break;
		default:
			tdu_printf(TDU_LOG_LOG, "tdu_parse_udata(): unexpected"
				   " parameter '%s' ignored\n",
				   tdu_param_descr(pi) );
			tb->data = tb->pn;
		};
	} while( tb->pn < tb->tail );
	return 0;
}


int tdu_typed_data_received(struct tdu_fsm * fsm, struct tdu_buf * tb)
{
	struct tdu_param param;
	struct tdu_udata_param udata;

	tdu_parse_udata(&udata,tb);
	param.par.udata = &udata;

	if( (fsm->access.local.functions & TDU_BIT_TYPED_DATA) &&
 	    fsm->user->t_typed_data )
		fsm->user->t_typed_data(fsm->user, &param);		
	return( param.reason );
}

int tdu_typed_data_req( struct tdu_fsm * fsm, unsigned char * msg){
	struct tdu_buf tb[1];

	tdu_init_tb(tb);
	tdu_printf(TDU_LOG_TRC,"typed data request\n");
	if( fsm->access.remote.functions & TDU_BIT_TYPED_DATA ){
		tdu_add_string_par(tb,TDU_PI_USER_DATA, msg,
				   LIMIT(strlen(msg),254));
		tdu_add_ci_header(tb,TDU_CI_T_TYPED_DATA);
		tdu_send_packet(tb,fsm);
		return 0;
	} else {
		return TDU_RE_PRIMITIVE_NOT_HANDLED;
	}
}


/*
 * send a t_p_execption report tdu to peer 
 */
int tdu_send_p_exception(struct tdu_fsm *fsm, int reason)
{
	struct tdu_buf tb[1];

	tdu_printf(TDU_LOG_TRC, "tdu_send_p_execption()\n");

	tdu_init_tb(tb);

	tdu_add_reason( tb, 0, reason, 0);
	tdu_add_ci_header( tb, TDU_CI_T_P_EXCEPTION);
	return tdu_send_packet(tb,fsm);
}

/*
 * check a generic positive or negative response for correctness
 * and requests a t_p_execption if errors are detected.
 * (semantics of pi as returned from first tdu_get_next_pi())
 */
int tdu_generic_response_received (struct tdu_fsm *fsm, struct tdu_buf * tb)
{
	int pi, err=0;

	tdu_printf(TDU_LOG_TRC, "tdu_generic_response_received");
	
	pi = tdu_get_next_pi(tb);
	err = tdu_check_response(tb,pi,fsm->wait);
	if( err ) {
		/* tb->data points to the parameter value of first pi */
		tdu_printf(TDU_LOG_ERR
			   , "Bad response %s(%s) does not match request %s!\n"
			   , tdu_cmd_descr(*tb->ci)
			   , tdu_cmd_descr(*tb->data)
			   , tdu_cmd_descr(fsm->wait)
			   );
		err = tdu_p_except_req(fsm,err);
	}
	return err;
}



