/* $Id: tdu_log.c,v 1.3 2007/03/05 18:04:35 keil 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 <unistd.h>
#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <tdu_user.h>
#include "tdu.h"
#include "fileheader.h"

char * (*tdu_extended_reason)(int) = NULL;

/* Table with various information on tdu protocol data units */

static struct tdu_descr cmd_descr[] = {
{"T_RESPONSE_POSITIVE", TDU_CI_T_RESPONSE_POSITIVE,0},
{"T_RESPONSE_NEGATIVE", TDU_CI_T_RESPONSE_NEGATIVE,0},
{"T_ASSOCIATE",		TDU_CI_T_ASSOCIATE,0},
{"T_RELEASE",		TDU_CI_T_RELEASE,0},
{"T_ABORT",		TDU_CI_T_ABORT,0},
{"T_ACCESS",		TDU_CI_T_ACCESS,1},
{"T_END_ACCESS",	TDU_CI_T_END_ACCESS,1},
{"T_DIRECTORY",		TDU_CI_T_DIRECTORY,1},
{"T_LOAD",		TDU_CI_T_LOAD,1},
{"T_SAVE",		TDU_CI_T_SAVE,1},
{"T_RENAME",		TDU_CI_T_RENAME,1},
{"T_DELETE",		TDU_CI_T_DELETE,1},
{"T_TYPED_DATA",	TDU_CI_T_TYPED_DATA,1},
{"T_WRITE",		TDU_CI_T_WRITE,0},
{"T_TRANSFER_REJECT",	TDU_CI_T_TRANSFER_REJECT,0},
{"T_READ_RESTART",      TDU_CI_T_READ_RESTART,0},
{"T_P_EXCEPTION",	TDU_CI_T_P_EXCEPTION,1},
{"RESPONSE_TIMEOUT",	TDU_CI_RESPONSE_TIMEOUT,2},
{"SOCKET_READ_ERROR",	TDU_CI_SOCK_READ_ERROR,2},
{"TC disconnected",	TDU_CI_TC_DISCONNECT,2},
{"Empty TDU",		TDU_CI_EMPTY,2},
{NULL, 			0,0}
};

/* Table with various information on result/reason codes.
   According to ETS 300 075, 7.2.1 and 7.2.2*/

static struct tdu_descr tdu_res_descr[] = {
{"provider: called address incorrect",	TDU_RE_CALLED_ADDR_INCOR_P,0},
{"user: called address incorrect",	TDU_RE_CALLED_ADDR_INCOR_U,0},
{"provider: calling address incorrect", TDU_RE_CALLING_ADDR_INCOR_P,0},
{"user: calling address incorrect",	TDU_RE_CALLING_ADDR_INCOR_U,0},
{"provider: role refused",		TDU_RE_ROLE_REFUSED_P,0},
{"user: role refused",			TDU_RE_ROLE_REFUSED_U,0},
{"insufficient primitives handled",	TDU_RE_INSUFF_PRIMITIVES,0},
{"application name unknown",		TDU_RE_APPL_NAME_UNKNOWN,0},
{"provider: service class refused",	TDU_RE_SVC_CLASS_REFUSED_P,0},
{"user: service class refused",		TDU_RE_SVC_CLASS_REFUSED_U,0},
{"erroneous revovery point",		TDU_RE_ERRON_RECOV_POINT,0},
{"erroneous designation",		TDU_RE_ERRON_DESIGN,0},
{"no answer to the request",		TDU_RE_NO_ANSWER,0},
{"unknown file",			TDU_RE_UNKNOWN_FILE,0},
{"already existing file",		TDU_RE_FILE_EXISTS,0},
{"erroneous file",			TDU_RE_ERRON_FILE,0},
{"erroneous new name",			TDU_RE_ERRON_NEW_NAME,0},
{"new name already in use",		TDU_RE_NEW_NAME_IN_USE,0},
{"wrong identity/identification",	TDU_RE_WRONG_ID,0},
{"erroneous user data",			TDU_RE_ERRON_UDATA,0},
{"service unknown",			TDU_RE_SVC_UNKNOWN,0},
{"group forbidden",			TDU_RE_GROUP_FORBIDDEN,0},
{"other reason",			TDU_RE_OTHER_REASON,0},
{"repeated errors/negative acknowledges",TDU_RE_REPEATED_ERROR,0},
{"delay expired",			TDU_RE_DELAY_EXPIRED,0},
{"unknown message",			TDU_RE_UNKNOWN_MESSAGE,0},
{"syntax error/missing parameter",	TDU_RE_SYNTAX_ERROR,0},
{"unrecoverable lower layer error",	TDU_RE_LOWER_LAYER_ERROR,0},
{"protocol conflict",			TDU_RE_PROTOCOL_CONFLICT,0},
{"primitive not handled",		TDU_RE_PRIMITIVE_NOT_HANDLED,0},
{NULL, 			0,0}
};

/* Table with various information on tdu parameters */

static struct tdu_descr tdu_par_descr[] = {
{"USER_DATA",			TDU_PI_USER_DATA,0},
{"CALLED_ADDRESS",		TDU_PI_CALLED_ADDRESS,1},
{"CALLING_ADDRESS",		TDU_PI_CALLING_ADDRESS,1},
{"Result/Reason",		TDU_PI_RESULT_REASON,1},
{"Role/Function",		TDU_PI_ROLE_FUNCTION,0},
{"APPLICATION_NAME",		TDU_PI_APPLICATION_NAME,0},
{"APPL_RESPONSE_TIMOUT",	TDU_PI_APPL_RESPONSE_TIMEOUT,0},
{"Size/Recovery/Window",	TDU_PI_SIZE_RECOVERY_WINDOW,1},
{"DESIGNATION",			TDU_PI_DESIGNATION,1},
{"NEW_NAME",			TDU_PI_NEW_NAME,1},
{"REQUEST_IDENT",		TDU_PI_REQUEST_IDENT,1},
{"Identification",		TDU_PI_IDENTIFICATION,1},
{"ExplCnf/1st/last/bl#",	TDU_PI_EX_CONF_1ST_LAST_BLNO,0},
{"TRANSFER_MODE",		TDU_PI_TRANSFER_MODE,1},
{"RECOVERY_POINT",		TDU_PI_RECOVERY_POINT,1},
{"SERVICE_CLASS",		TDU_PI_SERVICE_CLASS,0},
/* not a parameter but compatible */ 
{"File_Header",			TDU_PI_FILE_HEADER,0},
{NULL,				0,0}
};


const char * tdu_cmd_descr(int ci)
{
	static const char unknown[] = "Unknown_TDU_Command";
	char * des = tdu_des(cmd_descr,ci);
	return des ? des : unknown;
}


const char * tdu_param_descr(int pi)
{
	static const char unknown[] = "Unknown_TDU_Parameter";
	char* des = tdu_des(tdu_par_descr,pi);
	return des ? des : unknown;
}

FILE * tdu_logfile;
unsigned int tdu_stderr_mask = 0,
	tdu_logfile_mask = 0,
	tdu_stderr_dirty = 0,
	nl_err=1,
	nl_log=1;
pid_t	log_pid=0;
char 	log_prefix[20]="e4l[%d]:%s: ", 
	time_format[20]="%Y-%m-%d %H:%M:%S";


int tdu_open_log(const char * fname)
{
	tdu_logfile = fopen(fname,"a");
	if( ! tdu_logfile ) {
		perror("fopen log file");
		tdu_logfile_mask = 0;
	} else {
		setvbuf(tdu_logfile,NULL,_IOLBF,4098);
	}
	return tdu_logfile != NULL;
}

/*
 * set/change log prefix and date format 
 */
int tdu_log_prefix(const char * id_fmt, const char *tm_fmt)
{
	if( id_fmt) strncpy(log_prefix, id_fmt, sizeof(log_prefix));
	if( tm_fmt)  strncpy(time_format, tm_fmt, sizeof(time_format));
	log_pid = getpid();
	return 0;
}


int tdu_printf( int context, char * fmt, ... )
{
	int ret = 0, nl, i;
	va_list ap;
	char tst[20];

	/*
	 * Check for trailing newline might fail if newline is part of last
	 * argument. It is also not bullet proof if several processes
	 * are writing to the same output channel in parallel. Setting
	 * line buffered io mode should help for most scenarios.
	 */ 
	nl = ( fmt[strlen(fmt)-1] == '\n' );
	if( nl_err || nl_log ){ 
		struct tm * tm_time;
		time_t t=0;
		time(&t);
		tm_time = localtime(&t);
		i = strftime(tst, sizeof(tst), time_format, tm_time);
	}

	va_start(ap, fmt);
	if( context & tdu_stderr_mask ){
		if( nl_err ) fprintf(stderr, log_prefix, log_pid, tst); 
		ret = vfprintf(stderr, fmt, ap);
		tdu_stderr_dirty = 1;
		nl_err = nl;
	}
	if( ret < 0 ) return ret;
	
	va_start(ap, fmt);
	if( context & tdu_logfile_mask ){
		if( nl_log ) fprintf(tdu_logfile, log_prefix, log_pid, tst); 
		ret = vfprintf(tdu_logfile, fmt, ap);
		nl_log = nl;
	}
	va_end(ap);
	return ret;
}


void tdu_prompt(char * prompt)
{
	fprintf(stderr,prompt);
	tdu_stderr_dirty = 0;
}      


unsigned char * tdu_print_cmd( int ct, unsigned char *pkt, unsigned char *end,
			       int level )
{
	unsigned char ci, ci2;
	int len=0, garbage=0;

	if( pkt >= end ) goto packet_too_small;

	if( level < 1 ) { 
		tdu_printf(ct,"\n");
		return pkt;
	}
	ci = *(pkt++);
	tdu_printf(ct, " %s(", tdu_cmd_descr(ci));

	if( pkt < end ) len = *(pkt++);
	if( len == 0xff ) {
		if( pkt+2 >= end ) goto  packet_too_small;
		len = 0x100 * *(pkt)  +  *(pkt+1);
		pkt += 2;
	}

	if( pkt+len < end ){
		garbage = end - pkt - len; 
		/* remove trailing garbage */
		end = pkt + len;
	}
	if( ci == TDU_CI_T_RESPONSE_POSITIVE ||
	    ci == TDU_CI_T_RESPONSE_NEGATIVE ){
		if( pkt+2 > end ) goto  packet_too_small;
		ci2 = *(pkt+2);
		tdu_printf(ct, "%s,", tdu_cmd_descr(ci2));
	}
	tdu_printf(ct, "len=%d", len);
	if ( garbage ) tdu_printf(ct,"[+%d]", garbage);
	tdu_printf(ct, ")" );

	if( level < 2 ) { 
		tdu_printf(ct,"\n");
		return pkt;
	}
	tdu_printf(ct, ":  " );

	/* Never log t-write contents */
	if( ci == TDU_CI_T_WRITE ) {
		pkt = tdu_print_par( ct, pkt, end, ci, level );
		tdu_printf(ct,"\n");
		return end;
	}

	while( pkt < end ) pkt = tdu_print_par( ct, pkt, end, ci, level );
	tdu_printf(ct,"\n");
	return pkt;
	
	packet_too_small : tdu_printf(ct, "CMD packet to small!\n" );
	return end;
}

void tdu_print_hex( int ct, unsigned char * pkt, unsigned char * end)
{
	tdu_printf(ct,"0x");
	while ( pkt < end ) 
		tdu_printf(ct, "%02x ", (int) *(pkt++) );
}


void tdu_print_txt( int ct, unsigned char * pkt, unsigned char * end)
{
	unsigned char tmp;

	while ( pkt < end ){
		tmp = *pkt & 0x7f;
		if( tmp < 32 ){
			tdu_printf(ct, "^%c", (int) ( *pkt + 64 ) );
		} else if ( *pkt == 0x7f ) {
			tdu_printf(ct, "^?" );
		} else {
			tdu_printf(ct, "%c", (int) *pkt );
		}
		pkt++;
	};
}


void tdu_print_li( int ct, unsigned char * pkt, unsigned char * end)
{
	int i = 0;
	while ( pkt < end ) {
		i = i * 256;
		i += *(pkt++);
	}
	tdu_printf(ct, "%d", i );
}


static void tdu_print_srw( int ct, unsigned char * pkt, unsigned char * end)
{
	int log2, size=512;

	if ( pkt >= end ) return;

	/* packet size */
	log2 = (0x0e & *pkt) >> 1;
	while( log2-- ) size <<= 1;
	tdu_printf(ct,"%d/", size);

	/* recovery */
	tdu_printf(ct,"%sAccepted/", (*pkt & 0x01) ? "" : "Not");

	/* window size */
	tdu_printf(ct,"%d", ((*pkt & 0xe0) >> 5) + 1 );
}


static void tdu_print_mode( int ct, unsigned char * pkt, unsigned char * end)
{
	if ( pkt >= end ) return;
	tdu_printf(ct,"%sBasicMode", (*pkt & 0x01) ? "" : "No");
}


static void tdu_print_role( int ct, unsigned char * pkt, unsigned char * end)
{
	if ( pkt >= end ) return;
	if( *pkt & 0x01 ) {
		tdu_printf(ct, "Master(" );
		if (*pkt & 0x02) tdu_printf(ct," ReadRestart");
		if (*pkt & 0x04) tdu_printf(ct," TypedData");
	} else {
		tdu_printf(ct, "Slave(" );
		if (*pkt & 0x02) tdu_printf(ct," ReadRestart");
		if (*pkt & 0x04) tdu_printf(ct," TypedData");
		if (*pkt & 0x08) tdu_printf(ct," Directory");
		if (*pkt & 0x10) tdu_printf(ct," Delete");
		if (*pkt & 0x20) tdu_printf(ct," Rename");
		if (*pkt & 0x40) tdu_printf(ct," Save");
		if (*pkt & 0x80) tdu_printf(ct," Load");
	}
	tdu_printf(ct," )");
}

/* FIXME: should not go into this source file */
#include "eft.h"

static void tdu_print_reason( int ct, unsigned char * pkt, unsigned char * end, int ci)
{
	int rci;

	if( pkt < end && ( ci == TDU_CI_T_RESPONSE_NEGATIVE ||
			   ci == TDU_CI_T_RESPONSE_POSITIVE )  ) {
		rci = *(pkt++);
		tdu_printf(ct, "%s ", tdu_cmd_descr( rci )  );
		if( rci == TDU_CI_T_WRITE ){
			int count = 0;
			if( pkt < end ) count += *(pkt++); 
			if( pkt < end ) { count *= 256; count += *(pkt++);}
			tdu_printf(ct, "packet No %d",count);
		}
	}	
	while ( pkt < end ){
		tdu_printf(ct, "%s ", tdu_str_reason(*pkt) );
		if( *pkt == TDU_RE_ERRON_UDATA && (pkt+1 < end)){
			tdu_print_reason( ct, pkt+1, end, ci);
			return;
		}
		if( *pkt == TDU_RE_OTHER_REASON ){
			char * ext_re = NULL;

			pkt++;
			/* FIXME: should not belong here */
			if( (pkt+1) == end && tdu_extended_reason) 
				ext_re = tdu_extended_reason(*pkt);
			while ( pkt < end ) 
				tdu_printf(ct, "%c", (int) *(pkt++) );
			tdu_printf(ct," ");
			if( ext_re ) tdu_printf(ct, "[%s] ",ext_re);
		};
		pkt++;
	};
}

unsigned char * tdu_print_par( int ct, unsigned char *pkt, unsigned char *end, int ci, int level )
{
	unsigned char pi, *ret;
	int len=0;

	if( pkt >= end ) goto packet_too_small;
	
	pi = *(pkt++);
	tdu_printf(ct, " %s(", tdu_param_descr(pi));

	if( pkt >= end )  goto  packet_too_small; 
	if( (len = *(pkt++)) == 0xff ) {
		if( pkt+2 > end )  goto packet_too_small; 
		len = 0x100 * pkt[0] + pkt[1];
		pkt += 2;
	}
	tdu_printf(ct,"len=%d)", len);
	ret = pkt + len;
	if( level < 3 ) {
		return ret;
	}

	tdu_printf(ct,"=");
	switch ( pi ) {
	case TDU_PI_RESULT_REASON:
		tdu_print_reason(ct,pkt,ret,ci);
		break;
	case TDU_PI_SIZE_RECOVERY_WINDOW:
		tdu_print_srw(ct,pkt,ret);
		break;
	case TDU_PI_ROLE_FUNCTION:
		tdu_print_role(ct,pkt,ret);
		break;
	case TDU_PI_TRANSFER_MODE:
		tdu_print_mode(ct,pkt,ret);
		break;
	case TDU_PI_APPLICATION_NAME:
	case TDU_PI_CALLED_ADDRESS:
	case TDU_PI_CALLING_ADDRESS:
	case TDU_PI_DESIGNATION:
	case TDU_PI_NEW_NAME:
	case TDU_PI_IDENTIFICATION:
		tdu_print_txt(ct,pkt,ret);
		break;
	default:
		tdu_print_hex(ct,pkt,ret );
	};
	return ret;
	
	packet_too_small : tdu_printf(ct, "PAR packet to small!\n" );
	return end;
}

const char * tdu_str_reason(int reason)
{
	static const char unknown[] = "Unknown_TDU_ReasonCode";
	char * des = tdu_des(tdu_res_descr,reason);
	return des ? des : unknown;
}

/*
 * Replace unprintable characters from string type tdu parameters.
 * Thus should be applied before printing those as strings in order
 * to prevent them from screwing up your terminal state.
 *
 * The data array must be at least dimensioned max(len+1,1), which is essential
 * to be verified before calling in order to prevent buffer overflow attacks.
 * The last data[len] (or data[0] if len<0) will be terminated by zero on exit.
 */
int tdu_mk_printable(unsigned char * pr_data, const unsigned char * in_data,
		     int len){
	int i;

	if( len < 0 ) {
		pr_data[0]=0;
		return 0;
	}
	pr_data[len]=0;
	
	memcpy(pr_data, in_data, len);
	for(i=0; i < len; i++){
		if( ((0x7f & pr_data[i]) < 32) || (pr_data[i] == 0x7f) )
			pr_data[i]='?';
		
	}
	return 0;
}

