/* program to display alog file
** Displays an ALyymmdd file.
*/
/* Displays from FTL0 versions later than 13 March 								*/
/* This program should be used for UO-14 AL files beginning with	*/
/* AL910403 																											*/
/*------------------------------------------------------------------------
	JW Ward, G0/K8KA
	HE Price, NK6k
	15 April 1991
------------------------------------------------------------------------*/

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>

#include "viewlog.h"
#include "alog.h"

char buffer[40];
struct ALOG_1F *alog_1f;
struct ALOG_2F *alog_2f;

struct tm *utc;

struct CODE_MESSAGE
{
	int code;
	char *message;
};

/* PROTOTYPES */
void pcallsession(void);
void pcalla(void);
void pcall(void);
char *search_for_str(struct CODE_MESSAGE *code_array, int  code);
char *short_time(long utc);
int  get_alog_struct(FILE *fp, unsigned char *buffer);
int  alog_struct_type(void);

/* CAUSES FOR FTL0 SESSION TERMINATION
** Used in calls to ftl_close_session()
*/
#define	FTC_FRMR_REM	1													/* Remote frame reject 				*/
#define	FTC_FRMR_LOCAL	2												/* Locally generated frmr			*/
#define	FTC_DISC_LOCAL	3												/* Locally requested disc			*/
#define	FTC_DISC_REM	4													/* Remote requested disc			*/
#define	FTC_TIMEOUT	5														/* AX25 timeout.							*/
#define	FTC_RECONNECT	6													/* Client reconnected.				*/
#define	FTC_QUIT	7															/* BBS closed down.						*/

/* Reasons for closing an FTL0 session
*/
struct CODE_MESSAGE close_reasons[] =
{
	{FTC_FRMR_REM,		"user FRMR"},
	{FTC_FRMR_LOCAL,	"server FRMR"},
	{FTC_DISC_LOCAL,	"server disconnect"},
	{FTC_DISC_REM,		"user disconnect"},
	{FTC_TIMEOUT,		"FTL0 timeout"},
	{FTC_RECONNECT,		"reconnection"},
	{FTC_QUIT,		"quit"},
	{0, 			"Undefined error"}
};

struct CODE_MESSAGE disc_reasons[] =
{
	{DC_TIMEOUT,		"Inactivity timeout"},
	{DC_IN_ULOK,		"Unexpected input (uplink command)"},
	{DC_IN_DLOK,		"Unexpected input (downlink command)"},
	{DC_IN_ULRX,		"Unexpected input (uplink data)"},
	{DC_IN_DLEND,		"Unexpected input (downlink end)"},
	{DC_UNKNOWN_PKT,	"FTL0 packet type unknown."},
	{DC_PKT_TOO_BIG,	"FTL0 command packet too long."},
	{0, 			"Undefined error"}
};

/* General FTL0 error messages
*/

struct CODE_MESSAGE ftl0_errors[]=
{
	{1,	"e:PG command bug"},
	{2,	"e:cannot continue"},
	{3,	"e:PACSAT err"},
	{4,	"e:No such file"},
	{5,	"e:Selection empty"},
	{6,	"e:Mandatory PFH err"},
	{7,	"e:PHF err"},
	{8,	"e:Bad selection"},
	{9,	"e:File locked"},
	{10,	"e:No such destination"},
	{11,	"e:File partial."},
	{12,	"e:File complete."},
	{13,	"e:PACSAT file system full"},
	{14,	"e:PFH err"},
	{15,	"e:PFH checksum failure"},
	{16,	"e:body checksum failure"},
	{0,	""}
};

/* Events logged in the ALOG, in numerical order of their event code,
** beginning with event 0 (nothing).
*/

char *event_text[] =
{
	" ",
	"STARTUP",
	"SHUTDOWN",
	"LOGIN",
	"LOGOUT",
	"BLOWOFF",
	"REFUSED",
	"BCST ON",
	"BCST OFF",
	"FREE DISK",
	"DELETE",
	"DOWNLOAD",
	"UPLOAD",
	"SHUT",
	"OPEN",
	"DIR",
	"SELECT",
	"ADEL OK",
	"ADEL FAIL",
	"DL DONE",
	"UL DONE",
	"DIR DONE",
	"SEL DONE",
};

#define MAX_SESSION 20

/* keep last MAX_SESSION sessions, for printing callsign */

struct
{
	unsigned char  call[6];
	unsigned char  ssid;
	unsigned short session;
}
session[MAX_SESSION];

char writebuf[200];

int sidx = 0;

#define msmin  (long) (60l)
#define mshour (long) (60l * 60l)
#define msday  (long) (60l * 60l *24l)

int alogdisp(FILE *fp)
{
	int first = 1;
	char *err_str;
	int err;
	int day, hour, min, sec;
	long tmpl;
	char tmptxt1[25];

	/* Effectively unions with the input buffer */
	alog_1f = (struct ALOG_1F *)buffer;
	alog_2f = (struct ALOG_2F *)buffer;

	while (1)
	{
		err = get_alog_struct(fp, buffer);

		if (err != 0)
		{
			fclose(fp);
			return(err - 1);
		}
		
		/* Print the header once per log file */
		if (first)
		{
			sprintf(writebuf, "FTL0 Activity Log for %s", asctime(gmtime(&alog_1f->tstamp)));
			writetext(writebuf);
			sprintf(writebuf, "\nTime      Activity   Call      Rx Session\n");
			writetext(writebuf);
			first = 0;
		}

		if (alog_1f->var3 != 0L)
			err_str = search_for_str(ftl0_errors, (int)alog_1f->var3);
		else
			err_str = "";

		switch (alog_1f->event)
		{
		case ALOG_FTL0_STARTUP:
			/* FTL0 BBS code executed */
			sprintf(writebuf, "%s  %-9.9s  ", short_time(alog_1f->tstamp), event_text[alog_1f->event]);
			writetext(writebuf);
			break;

		case ALOG_FTL0_SHUTDOWN:
			/* FTL0 BBS task exits. */
			sprintf(writebuf, "%s  %-9.9s  ", short_time(alog_1f->tstamp), event_text[alog_1f->event]);
			writetext(writebuf);
			break;
		
		case ALOG_BBS_SHUT:
			/* Ground command shuts bbs */
			pcalla();
			break;

		case ALOG_BBS_OPEN:
			/* Ground command opens bbs */
			pcalla();
			break;

		case ALOG_BCAST_START:
			/* User starts a broadcast
				 var1 = file number
				 var2 = number of seconds for broadcast
				 var3 = error
				 var4 = length of data packets
			*/
			pcalla();
			if (alog_2f->var3 != 0L)
				err_str = search_for_str(ftl0_errors, (int)alog_2f->var3);
			else
				err_str = "";

			sprintf(writebuf, "        f#%lx dur:%lu l:%ld %s", alog_2f->var1,
				alog_2f->var2,alog_2f->var4,err_str);
			writetext(writebuf);
			break;

		case ALOG_BCAST_STOP:
			/* User stops a broadcast
				 var1 = file number
				 var2 = unused
				 var3 = error
			*/
			pcalla();
			if (alog_2f->var3 != 0L)
				err_str = search_for_str(ftl0_errors, (int)alog_2f->var3);
			else
				err_str = "";
			sprintf(writebuf, "        f#%lx  %s", alog_2f->var1, err_str);
			writetext(writebuf);
			break;

		case ALOG_DISKSPACE:
			/* Diskspace is logged hourly
			 	var1 = number of disk clusters free (1008 bytes of data each)
			 	var2 = number of directory entries free
			*/
			sprintf(writebuf, "%s  %-9.9s  ", short_time(alog_1f->tstamp), event_text[alog_1f->event]);
			writetext(writebuf);
			sprintf(writebuf, "                     %ld bytes \\", alog_1f->var1 * 1008l);
			writetext(writebuf);
			sprintf(writebuf, " %ld dirs", alog_1f->var2);
			writetext(writebuf);
			break;

		case ALOG_FILE_DELETE:
			/* Command station deletes file
				 var1 = file number
				 var2 = unused
				 var3 = error code
			*/
			pcalla();
			if (alog_1f->var3 != 0L)
				err_str = search_for_str(ftl0_errors, (int)alog_1f->var3);
			else
				err_str = "";
			sprintf(writebuf, "        f#%lx", alog_1f->var1);
			writetext(writebuf);
			break;

		case ALOG_USER_REFUSED:
			sprintf(writebuf, "%s  %-9.9s  ",short_time(alog_1f->tstamp), event_text[alog_1f->event]);
			writetext(writebuf);
			/* BBS was full.
			*/
			break;

		case ALOG_START_SESSION:
			/* User logs on. This is the only time we get callsign
				 information for connected mode users; must use serial_no
				 from now on.
			*/
			pcallsession();
			sprintf(writebuf, " %06u", alog_1f->serial_no);
			writetext(writebuf);
			break;

		case ALOG_CLOSE_SESSION:
			/* Server closes session.
				var1 = reason for closing session, see close_reasons array.
				var2 = bytes downloaded, if download in progress
				var3 = bytes uploaded, if upload in progress
				var2 and var3 are 0 if no transfer in progress.
				download includes directory commands.
			*/
			pcall();
			sprintf(writebuf, " %06u", alog_1f->serial_no);
			writetext(writebuf);
			sprintf(writebuf, " %s  ", search_for_str(close_reasons, (int)alog_1f->var1));
			writetext(writebuf);
			if (alog_1f->var2)
			{
				sprintf(writebuf, "\n%41.41s Incomplete D/L @ %lu bytes", "", alog_1f->var2);
				writetext(writebuf);
			}
			if (alog_1f->var3)
			{
				sprintf(writebuf, "\n%41.41s Incomplete U/L @ %lu bytes", "", alog_1f->var3);
				writetext(writebuf);
			}
			break;

		case ALOG_DISCONNECT :
			/* Server is disconnecting user.
				 var1 = reason for disconnect, see disc_reasons array.
			*/
			pcall();
			sprintf(writebuf, " %s  ", search_for_str(disc_reasons, (int)alog_1f->var1));
			writetext(writebuf);
			break;
		
		case ALOG_FILE_DOWNLOAD:
			/* Server receives download command.
				 var1 = file number
				 var2 = continue offset
				 var3 = error code
				 var4 = flag indicating that download was from select list.
			*/
			pcall();
			if (alog_1f->var3 != 0L)
				err_str = search_for_str(ftl0_errors, (int)alog_1f->var3);
			else
				err_str = "";
			sprintf(writebuf, "        f#%lx off:%lu %s",alog_1f->var1,
				alog_1f->var2, err_str);
			writetext(writebuf);
			if (alog_1f->var4)
				writetext("(selected)");
			break;

		case ALOG_END_DOWNLOAD:
			/* User has indicated end of download to server. May be good or bad.
				 var1 = number of bytes downloaded. Not necessarily file length,
				        if this was a continued download.
				 var2 = FTL_DL_ACK_CMD if user acked.
				        FTL_DL_NAK_CMD if user n'acked.
				 User may n'ack if checksum fails.
			*/
			pcall();
			sprintf(writebuf, "        %ld bytes ", alog_1f->var1);
			writetext(writebuf);
			if ((unsigned char)alog_1f->var2 == 0x0c)
				writetext("Ack'd");
			else
				writetext("Nak'd");
			break;

		case ALOG_FILE_UPLOAD:
			/* Server receives client's upload command.
				 var1 = file number
				 var2 = continue offset
				 var3 = error code
				 var4 = file length as told by client to server.
			*/
			pcall();
			if (alog_1f->var3 != 0L)
				err_str = search_for_str(ftl0_errors, (int)alog_1f->var3);
			else
				err_str = "";
			sprintf(writebuf, "        f#%lx off:%lu l#%lu %s", alog_1f->var1,
				alog_1f->var2,alog_1f->var4, err_str);
			writetext(writebuf);
			break;

		case ALOG_END_UPLOAD:
			/* Server has received, and finished checksumming, the file.
				 var1 = time checksum started. Subtractr from alog_1f->time
				        to figure out how many seconds to checksum the file.
				 var2 = error code.
				 var3 = number of bytes uploaded; not necessarily same as file size,
				        since this might be a continued upload.
			*/
			pcall();
			sprintf(writebuf, "        %ld bytes %ld seconds cksum", alog_1f->var3,
				alog_1f->tstamp - alog_1f->var1);
			writetext(writebuf);
			if (alog_1f->var2)
				writetext(" Nak'd");
			break;

		case ALOG_DIR:
			/* Server receives directory request from client.
				 var1 = file number (0xffffffff or 0 indicate dir from selection.)
				 var2 = error code.
			*/
			pcall();
			if (alog_1f->var2 != 0L)
				err_str = search_for_str(ftl0_errors, (int)alog_1f->var2);
			else
				err_str = "";
			if (alog_1f->var1 != 0xffffffff && alog_1f->var1 != 0L)
				sprintf(writebuf, "        f#%lx  %s", alog_1f->var1, err_str);
			else
				sprintf(writebuf, "        from selection.");
			writetext(writebuf);
			break;

		case ALOG_END_DIR:
			/* Server sends last packet of directory to client.
				 var1 = total number of bytes in directory.
			  */
				pcall();
				sprintf(writebuf, "        %ld bytes", alog_1f->var1);
				writetext(writebuf);
				break;

			/* Automatic file removal
				 var1 = file number
				 var2 = free directory entries in system
				 var3 = free file clusters in system
				 var4 = expiry time (earliest at which file could be deleted)
			*/
		case ALOG_FILE_REMOVED:
			sprintf(writebuf, "%s  %-9.9s  ",short_time(alog_1f->tstamp), event_text[alog_1f->event]);
			writetext(writebuf);
			sprintf(writebuf, "        f#%lx (exp: (%lx) %24.24s)",alog_1f->var1,alog_1f->var4,
				asctime(gmtime(&(alog_1f->var4))));
			writetext(writebuf);
			break;

		case ALOG_FILE_NOT_REMOVED:
			sprintf(writebuf, "%s  %-9.9s  ", short_time(alog_1f->tstamp), event_text[alog_1f->event]);
			writetext(writebuf);
			sprintf(writebuf, "        f#%lx (exp: (%lx) %24.24s)", alog_1f->var1,alog_1f->var4,
				asctime(gmtime(&(alog_1f->var4))));
			writetext(writebuf);
			break;

		case ALOG_SELECT:
		/* Select command received by server. No vars used */
			pcall();
			break;

		case ALOG_SELECT_DONE:
		/* Finished building select list
			 var1 = length of user's selection equation
			 var2 = oldest file considered for the selection
			 var3 = newest file considered for the selection
			 var4 = number of files selected
		*/
			pcall();
			if (alog_1f->var2 == 0x131d1741) 
				strcpy(tmptxt1, "start");
			else
			{
				/* compute time before "now" */
				tmpl = alog_1f->tstamp - alog_1f->var2;
				if (tmpl < 0)
					strcpy(tmptxt1, "future?");
				else
				{
					day = tmpl / msday;
					tmpl= tmpl % msday;
					hour = tmpl / mshour;
					tmpl = tmpl % mshour;
					min = tmpl / msmin;
					tmpl = tmpl % msmin;
					sec = tmpl;
					sprintf(tmptxt1,"%03u/%02u:%02u:%02u",
						day, hour, min, sec);
				}
			}
			sprintf(writebuf, "        %s len:%ld selected:%ld",
				tmptxt1, alog_1f->var1, alog_1f->var4);
			writetext(writebuf);
			break;

		default:
			sprintf(writebuf, "%s  %-9.9s  ", short_time(alog_1f->tstamp), event_text[alog_1f->event]);
			writetext(writebuf);
			sprintf(writebuf, "Unknown (%d)", alog_1f->event);
			writetext(writebuf);
			break;
		}

		writetext("\n");
	}

	return(0);
}

/*------------------------------------------------------------------------
 Prints callsign for the beginning of a session, and puts the
 call and ssid into the structure array holding each session.
------------------------------------------------------------------------*/

void pcallsession(void)
{
	memcpy(session[sidx].call, alog_2f->call, 6);
	session[sidx].ssid = alog_2f->ssid;
	session[sidx].ssid = alog_2f->ssid;
	session[sidx].session = alog_2f->serial_no;

	/* Move circularly through session array. */
	sidx = (sidx + 1) % MAX_SESSION;

	sprintf(writebuf, "%s  %-9.9s  ", short_time(alog_1f->tstamp), event_text[alog_1f->event]);
	writetext(writebuf);
	sprintf(writebuf, "%6.6s-%-2u  %u ", alog_2f->call,alog_2f->ssid, alog_2f->rxchan);
	writetext(writebuf);
}

/*------------------------------------------------------------------------
  Prints callsign for action not related to a connected session.
	Takes call, ssid and receiver out of current alog_2f structure.
------------------------------------------------------------------------*/

void pcalla(void)
{
	sprintf(writebuf, "%s  %-9.9s  ", short_time(alog_1f->tstamp), event_text[alog_1f->event]);
	writetext(writebuf);
	sprintf(writebuf, "%6.6s-%-2u  %u ", alog_2f->call,alog_2f->ssid, alog_2f->rxchan); 
	writetext(writebuf);
}

/*------------------------------------------------------------------------
  Prints callsign for action within a connected session.
	Looks up correct session in session[] using serial_no from current
	alog_1f structure.
	Takes call and ssid out of session[].
------------------------------------------------------------------------*/

void pcall(void)
{
	int i;
	/* Find the session looking up serial numbers */

	for (i = 0; i < MAX_SESSION; i++)
		if (session[i].session == alog_1f->serial_no)
			break;

	/* Found it */
	sprintf(writebuf, "%s  %-9.9s  ",short_time(alog_1f->tstamp), event_text[alog_1f->event]);
	writetext(writebuf);

	if (i < MAX_SESSION) 
	{
		sprintf(writebuf, "%6.6s-%-2u    ", session[i].call, session[i].ssid);
		writetext(writebuf);
	}
	else
	{
		writetext("******-**    "); 
	}
}

char *search_for_str(struct CODE_MESSAGE code_array[], int code)
{
	int i = 0;

	if (code == -1) return("Can't add");

	while (code_array[i].code != code && code_array[i].code != 0)
		++i;

	return(code_array[i].message);
}

/*------------------------------------------------------------------------
	provide an HH:MM:SS timestamp.
------------------------------------------------------------------------*/

char time_stamp[9];

char *short_time(time_t utc)
{
	struct tm *tp;

	tp = gmtime(&utc);

	sprintf(time_stamp, "%02d:%02d:%02d", tp->tm_hour, tp->tm_min, tp->tm_sec);

	return time_stamp;
}
	

/*------------------------------------------------------------------------
	Read a variable length record from the file.
	The first byte is always record type.
	The second byte is always record length.
	See alog.h for further details of the two variable length structs
	which can be in the file.
------------------------------------------------------------------------*/

int get_alog_struct(FILE *fp, unsigned char *buffer)
{
	int cnt;
	int error = 0;

	/* First two bytes contain structure length */
	cnt = fread(buffer, 1, 2, fp);

	if (cnt != 2)
	{
		error = 1;
	}
	/* Alog structure never greater then 40 bytes */
	else if (alog_1f->len > 40)
	{
		error = 2;
	}
	else
	{
		/* Get the rest of the structure, now length is known */
		cnt = fread(buffer+2, 1, alog_1f->len-2, fp);

		if (cnt + 2 != alog_1f->len)
		{
			error = 3;
		}
	}

	return error;
}

/*------------------------------------------------------------------------
	Checks the type of record in the global alog structure and returns
	an appropriate integer. Generally, this tells if the record type is
	alog1 or alog2, though some other divisions have been introduced.
	
	START_SESSION MUST RETURN 2
	CONNECTED MODE OPERATIONS MUST RETURN 1
	ADMINISTRATIVE OPERATIONS MUST RETURN 0
------------------------------------------------------------------------*/

int alog_struct_type(void)
{
	int type;

	switch (alog_1f->event)
	{
		/* All of these have callsigns in them */
		case ALOG_START_SESSION:
		case ALOG_BCAST_START:
		case ALOG_BCAST_STOP:
		case ALOG_FILE_DELETE:
		case ALOG_BBS_SHUT:
		case ALOG_BBS_OPEN:
			type = 2;
			break;

		/* These don't have or refer to callsigns. This is the */
		/* group containing all automatic s/c generated events.*/
		case ALOG_FTL0_STARTUP:
		case ALOG_FTL0_SHUTDOWN:
		case ALOG_DISKSPACE:
		case ALOG_FILE_REMOVED:
		case ALOG_FILE_NOT_REMOVED:
			type = 0;
			break;

		/* All connected mode events */
		case ALOG_CLOSE_SESSION:
		case ALOG_DISCONNECT:
		case ALOG_FILE_DOWNLOAD:
		case ALOG_FILE_UPLOAD:
		case ALOG_DIR:
		case ALOG_SELECT:
		case ALOG_END_DOWNLOAD:
		case ALOG_END_UPLOAD:
		case ALOG_END_DIR:
		case ALOG_SELECT_DONE:
			type = 1;
			break;

		/* Put refusals in a class by themselves, because they are anoying */
		case ALOG_USER_REFUSED:
			type = 4;
			break;

		default:
			type = 3;
			break;
	}

	return type;
}

