/* File: C:\WACKER\xfer\hpr_rcv0.c (created: 24-Jun-1994)
 * created from HAWIN source file:
 * hpr_rcv0.c -- Routines to implement HyperProtocol receiver.
 *
 *	Copyright 1989,1994 by Hilgraeve Inc. -- Monroe, MI
 *	All rights reserved
 *
 *	$Revision: 1.6 $
 *	$Date: 1994/02/18 08:01:04 $
 */

#include <windows.h>
#include <setjmp.h>
#include <time.h>
#include <term\res.h>
#include <sys\types.h>
#include <sys\utime.h>

#include <tdll\stdtyp.h>
#include <tdll\mc.h>
// #include <tdll\com.h>
#include <tdll\session.h>
#include <tdll\load_res.h>
#include <tdll\xfer_msc.h>
#include <tdll\globals.h>
#include <tdll\file_io.h>

#if !defined(BYTE)
#define	BYTE	unsigned char
#endif

#include "cmprs.h"

#include "xfr_dsp.h"
#include "xfr_todo.h"
#include "xfr_srvc.h"

#include "xfer.h"
#include "xfer.hh"
#include "xfer_tsc.h"

#include "hpr.h"
#include "hpr.hh"
#include "hpr_sd.hh"

/* 
 *  not all event codes in this table are needed here, but having a complete
 *	table simplifies the lookup code substantially.
 */
int hr_result_codes[] = /* maps HyperProtocol event codes to result codes */
	{
	TSC_OK, 			/* HRE_NONE 	   */
	TSC_ERROR_LIMIT,	/* HRE_DATAERR	   */
	TSC_OUT_OF_SEQ, 	/* HRE_LOSTDATA    */
	TSC_NO_RESPONSE,	/* HRE_NORESP	   */
	TSC_ERROR_LIMIT,	/* HRE_RETRYERR    */
	TSC_BAD_FORMAT, 	/* HRE_ILLEGAL	   */
	TSC_OK, 			/* HRE_ERRFIXED    */
	TSC_RMT_CANNED, 	/* HRE_RMTABORT    */
	TSC_USER_CANNED,	/* HRE_USRCANCEL   */
	TSC_NO_RESPONSE,	/* HRE_TIMEOUT	   */
	TSC_ERROR_LIMIT,	/* HRE_DCMPERR	   */
	TSC_LOST_CARRIER,	/* HRE_LOST_CARR   */
	TSC_TOO_MANY,		/* HRE_TOO_MANY    */
	TSC_DISK_FULL,		/* HRE_DISK_FULL   */
	TSC_CANT_OPEN,		/* HRE_CANT_OPEN   */
	TSC_DISK_ERROR, 	/* HRE_DISK_ERR    */
	TSC_OLDER_FILE, 	/* HRE_OLDER_FILE  */
	TSC_NO_FILETIME,	/* HRE_NO_FILETIME */
	TSC_VIRUS_DETECT,	/* HRE_VIRUS_DET   */
	TSC_USER_SKIP,		/* HRE_USER_SKIP   */
	TSC_REFUSE			/* HRE_REFUSE	   */
	};


/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 * hpr_rcv
 *
 * DESCRIPTION:
 *	Receives files using the Hyperprotocol transfer method.
 *
 * ARGUMENTS:
 *	attended	-- True if the program determines that a user is likely to be
 *					 present at the computer keyboard. FALSE if a user is NOT
 *					 likely to be present (such as host and script modes)
 *	single_file -- TRUE if user specified only a file name to receive the
 *					result of the transfer as opposed to naming a dirctory.
 *
 * RETURNS:
 *	TRUE if the transfer successfully completes. FALSE otherwise.
 */
int hpr_rcv(HSESSION hSession, int attended, int single_file)
	{
	struct s_hc *hc;
	HCOM	  hCom;
	int	      usRetVal;
	int		  iret;
	int 	  status;
	int 	  result;
	int 	  timeout_cnt = 0;
	int       mtype;
	BYTE     *mdata;
	char	  str[20];
	long	  timer;
	struct st_rcv_open stRcv;
	BYTE	  tmp_name[FNAME_LEN];

	hCom = sessQueryComHdl(hSession);

	hc = malloc(sizeof(struct s_hc));
	if (hc == NULL)
		return TSC_NO_MEM;

	memset(hc, 0, sizeof(struct s_hc));

	hc->hSession = hSession;

	/* initialize stuff */
	if (!hr_setup(hc))
		{
		free(hc);
		return TSC_NO_MEM;
		}

	/* initialize control variables */

	/* blocksize depends on the speed of the connection. Larger block sizes
	 * can be used for faster connections. If the blocksize is too large,
	 * error detection will be slow. If too small, there is unnecessary overhead
	 */
	hc->blocksize = 2048;
	// hc->blocksize = xfer_blocksize(hSession);

	hc->current_filen = 0;
	hc->datacnt = hc->blocksize;
	hc->deadmantime = 600;
	hc->total_tries = 0;
	hc->total_thru = 0L;
	hc->total_dsp = 0L;
	hc->ucancel = FALSE;
	hc->usecrc = TRUE;		/* first messages in will use CRC */
	hc->fhdl = NULL;

	hc->rc.checkpoint = 0L;
	hc->rc.files_expected = 0;
	hc->rc.bytes_expected = -1L;
	hc->rc.filesize = -1L;
	hc->rc.expected_msg = 0;
	hc->rc.cancel_reason = HRE_NONE;
	hc->rc.using_compression = FALSE;
	hc->rc.virus_detected = FALSE;

	hc->rc.hr_ptr_putc = hr_toss;
	hc->h_crc = hc->h_checksum = 0;

	omsg_init(hc, TRUE, FALSE);
	hc->rc.single_file = single_file; 	/*	 to the sender					 */


	/* Receiver begins the transfer by transmitting a starting message
		repeatedly until the sender begins */

	/* prepare the initial message */
	omsg_new(hc, 'c');

	/* tell sender who we are */
	hpr_id_get(hc, str);
	omsg_add(hc, str);

	/* we can express our opinion about what checktype and blocksize to use
	 *	but it will be up to the sender to make the final choice
	 */
	wsprintf(str, "T%d", hc->h_chkt == H_CRC ? H_CRC : H_CHECKSUM);
	omsg_add(hc, str);

	wsprintf(str, "B%d", hc->blocksize);
	omsg_add(hc, str);

	/* let sender know whether we can handle compression */
	if (hc->h_trycompress & compress_enable());
		{
		omsg_add(hc, "C");
		}

	/* A restart 0,0 request causes sender to start */
	omsg_add(hc, "R0,0");

	/* send first response packet at intervals until first H_MSGCHAR
	 *	 is received
	 */
	status = H_OK;
	hrdsp_status(hc, HRS_REQSTART);
	timer = startinterval();

	stRcv.pszSuggestedName = "junk.jnk";
	stRcv.pszActualName = tmp_name;
	// stRcv.pstFtCompare = NULL;
	stRcv.lFileTime = 0;
	// stRcv.pfnVscanOutput = NULL;
	// stRcv.ssmchVscanHdl = (SSHDLMCH)0;

	// hc->rc.pfVirusCheck = MakeProcInstance((FARPROC)hr_virus_detect,
	//								hSession->hInstance);

	// stRcv.pfnVscanOutput = (VOID (FAR *)(void *, int))hc->rc.pfVirusCheck;

	// transfer_build_rcv_name(&stRcv);
	xfer_build_rcv_name(hSession, &stRcv);

	// hc->rc.ssmchVscan = stRcv.ssmchVscanHdl;

	hc->xfertimer = -1;

#if FALSE
	/* if we are the host, don't send an immediate start request because
	 * the user probably had to start us first and then set himself up. If
	 * we are the attended machine, though, the other end has probably already
	 * been started.
	 */
	// sendnext = (attended ? 0 : 40);

	// Changed to always try to start immediately since we may be responding
	// to auto-start in which case sender is already waiting
	sendnext = 0;

	repeat
		{
		hc->xfertimer = startinterval();
		if (mComRcvBufrPeek(hCom, &rcode) != 0)
			{
			if (rcode == H_MSGCHAR)
				{
				if (!hr_resynch(hSession, HRE_NONE))
					status = H_NOSTART;
				break;
				}
			// RemoteGet(); 	   /* wrong character, remove it from buffer */
			mComRcvChar(hCom, &rcode);

			/* Other end can send us an ESC to cancel the transfer before
			 *	it ever gets started.
			 */
			if ((rcode == ESC) || (rcode == CAN))
				{
				status = H_RMTABORT;
				hrdsp_event(hSession, hc->rc.cancel_reason = HRE_RMTABORT);
				break;
				}
			}

		/* We can't wait forever to get started. If we haven't seen a start
		 *	character in H_START_WAIT seconds, give up.
		 */
		if ((time = interval(timer)) > H_START_WAIT * 10)
			{
			status = H_NOSTART;
			hc->rc.cancel_reason = HRE_NORESP;
			break;
			}

		/* see if it's time to send another startup request */
		else if (time > sendnext || (rcode & 0x7F) == '\r')
			{
			sendnext = time + 40;	/* send again in 4 seconds */
			omsg_send(hc, 1, FALSE, TRUE);
			}

		/* finally, see if someone at keyboard want's us to stop trying */
		iret = xfer_user_interrupt(hSession);
		if (iret == XFER_ABORT)
			{
			status = H_USERABORT;
			hrdsp_event(hSession, hc->rc.cancel_reason = HRE_USRCANCEL);
			break;
			}
		else if (iret == XFER_SKIP)
			{
			hr_reject_file(hSession, HRE_USER_SKIP);
			}

		hpr_idle(hSession);

		}

#endif

	omsg_send(hc, 1, FALSE, TRUE);
	if (!hr_resynch(hc, HRE_NONE))
		status = H_NOSTART;

	/* If status is still H_OK, it means we've synched with sender.
	 * We'll stay in this loop now until the transfer is finished.
	 */
	while (status == H_OK)
		{
		hr_still_alive(hc, FALSE, FALSE);   /* check whether deadman msg
											is in order */
		hrdsp_progress(hc, 0);		  /* keep user notified */

		/* Collect blocks of data, which may be interrupted by messages
		 * from the sender.
		 */
		result = hr_collect_data(hc, &hc->datacnt, TRUE, H_CHARTIME);
		if (result != HR_TIMEOUT)
			timeout_cnt = 0;
		switch(result)
			{
		case HR_VIRUS_FOUND:
			goto virus_found;

		case HR_COMPLETE:
			/* got all chars. we asked for, setup to receive another
			 * full block
			 */
			hc->rc.checkpoint = hc->h_filebytes;
			hc->h_checksum = hc->h_crc = 0;
			hc->datacnt = hc->blocksize;
			break;

		case HR_DCMPERR :
			/* data error caused decompression algorithm to fail */
			if (!hr_restart(hc, HRE_DCMPERR))
				status = H_NORESYNCH;
			break;

		case HR_BADCHECK :
			/* got complete block but checksum or CRC didn't match */
			if (!hr_restart(hc, HRE_DATAERR))
				status = H_NORESYNCH;
			break;

		case HR_LOSTDATA :
			/* received block n+1 before block n */
			if (!hr_restart(hc, HRE_LOSTDATA))
				status = H_NORESYNCH;
			break;

		case HR_MESSAGE:
			/* Block of data was interrupted by a message. All that's
			 * actually been detected is a message character in the data,
			 * we must now extract and analyze the message
			 */
			switch(result = hr_collect_msg(hc, &mtype, &mdata, H_CHARTIME))
				{
			case HR_KBDINT:
				/* local user interrupted us while receiving the message
				 * if user had interrupted us once and is doing it again
				 * while we are attempting to tell the other end what we're
				 * doing, drop out immediately and leave the sender to fend
				 * for himself.
				 */
				if (hc->ucancel)
					status = H_USERABORT;
				else
					{
					hr_kbdint(hc);
					/* try to let sender know what we're doing */
					if (!hr_cancel(hc, HRE_USRCANCEL))
						status = H_USERABORT;
					}
				break;

			case HR_TIMEOUT:
			case HR_BADMSG:
			case HR_BADCHECK:
				/* message was scrambled, try to resynch */
				if (!hr_restart(hc, HRE_DATAERR))
					status = H_NORESYNCH;
				break;

			case HR_LOSTDATA:
				/* message was recevied, but it was the wrong one */
				if (!hr_restart(hc, HRE_LOSTDATA))
					status = H_NORESYNCH;
				break;

			case HR_COMPLETE:
				/* message received ok, figure out what sender wants */
				status = hr_decode_msg(hc, mdata);
				break;
				}
			break;

		case HR_TIMEOUT:
			/* sender stopped sending to us, try to prod him into restartting */
			if (timeout_cnt++ < TIMEOUT_LIMIT)
				{
				/* TODO: generalize this
				if (cnfg.save_xprot)
					RemoteSendChar(cnfg.save_xon);
				*/
				hr_still_alive(hc, TRUE, TRUE); /* send file ack and timeout msg */
				}
			else
				{
				status = H_TIMEOUT;
				hc->rc.cancel_reason = HRE_TIMEOUT;
				}
			break;

		case HR_KBDINT:
			/* user is trying to interrupt the transfer */
			if (hc->ucancel)
				status = H_USERABORT;
			else
				{
				hr_kbdint(hc);
				/* try to inform sender about what we are doing */
				if (!hr_cancel(hc, HRE_USRCANCEL))
					status = H_USERABORT;
				}
			break;

		case HR_LOST_CARR:
			/* we lost carrier while trying to transfer */
			if (!hr_cancel(hc, HRE_LOST_CARR))
				status = H_TIMEOUT;
			break;

		case HR_FILEERR:
			/* A file error occurred while trying to save incoming data */
			if (!hr_cancel(hc, HRE_DISK_ERR))
				status = H_FILEERR;
			break;
			}

		/* during full-bore transfers, the data collection routines won't
		 *	waste time checking the keyboard for an interrupt request from
		 *	the user or carrier loss so we'll check here at least once per
		 *	data block
		 */
		iret = xfer_user_interrupt(hSession);
		if (iret == XFER_ABORT)
			{
			if (hc->ucancel)
				status = H_USERABORT;
			else
				{
				hr_kbdint(hc);
				if (!hr_cancel(hc, HRE_USRCANCEL))
					status = H_USERABORT;
				}
			}
		else if (iret == XFER_SKIP)
			{
			hr_reject_file(hc, HRE_USER_SKIP);
			}

		if (xfer_carrier_lost(hSession))
			if (!hr_cancel(hc, HRE_LOST_CARR))
					status = H_TIMEOUT;

		/* Actual virus detection occurs deep in the bowels of a transfer.
		 * Therefore, the detecting routine merely sets a flag and begins
		 * tossing data. We actually shut down here
		 */
virus_found:
		if (hc->rc.virus_detected)
			{
			hc->rc.virus_detected = FALSE;	/* don't come in here again */
			if (!hr_cancel(hc, HRE_VIRUS_DET))
				status = H_USERABORT;
			}
		}

	/* Transfer is all done, 'status' indicates the final result. */
	hrdsp_progress(hc, TRANSFER_DONE);
	compress_disable();

	// if (stRcv.ssmchVscanHdl != (SSHDLMCH)0)
	//	StrSrchStopSrch(stRcv.ssmchVscanHdl);

	// if (hc->rc.pfVirusCheck != NULL)
	//	{
	//	FreeProcInstance(hc->rc.pfVirusCheck);
	//	hc->rc.pfVirusCheck = NULL;
	//	}

	usRetVal = (int)hr_result_codes[hc->rc.cancel_reason];

	/* clear display, free memory, etc. */
	status = hr_wrapup(hc, attended, status);
	free(hc);

	return usRetVal;
	}


/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 * hr_collect_msg
 *
 * DESCRIPTION:
 *	Called when a message has been detected within a block of data. Messages
 *	start with H_MSGCHAR (0x01). If an H_MSGCHAR occurs as part of the data
 *	being sent, it will be doubled. When it is encountered alone, this routine
 *	is called to extract the following message from the stream of data.
 *
 * ARGUMENTS:
 *	mtype	-- pointer to a variable to be updated with the message type
 *	mdata	-- pointer to a variable to be updated with the address of the
 *				message data
 *	timeout -- amount of time (in tenths of seconds) to wait for the data
 *				to complete the message.
 *
 * RETURNS:
 *	Returns a status code:
 *	  HR_COMPLETE -- message successfully received
 *	  HR_BADCHECK -- crc or checksum error on message data
 *	  HR_TIMEOUT  -- time out exceeded while waiting for data
 *	  HR_KBDINT   -- user interrupted from keyboard
 *	  HR_BADMSG   -- message data was not in recognized format
 *	  HR_LOSTDATA -- message was complete but message number was not the
 *						expected one.
 *	  HR_LOST_CARR --Lost carrier while collecting message
 */
// char FAR *storageptr;	 /* place to put data as we receive it */

int hr_collect_msg(struct s_hc *hc,
					int *mtype,
					BYTE **mdata,
					long timeout)
	{
	unsigned hold_checksum;
	unsigned hold_crc;
	int gotlen = FALSE;
	int count;
	int result = HR_UNDECIDED;
	int (*holdptr)(void *, int);
	int msgn;

	/* since a message is embedded within a data block, we need to preserve
	 * a few values for the interrupted data collection routine.
	 */
	holdptr = hc->rc.hr_ptr_putc;
	/* set collection routine to store data for us */
	hc->rc.hr_ptr_putc = hr_storedata;
	hold_checksum = hc->h_checksum;
	hold_crc = hc->h_crc;
	hc->h_checksum = 0; 			/* messages have their own check bytes */
	hc->h_crc = 0;

	/* We will retrieve the message in two parts, first we'll get the type and
	 *	length fields, then, based on those, we can collect the rest of the
	 *	message.
	 */
	hc->storageptr = hc->rc.rmsg_bufr;
	count = 2;
	while (result == HR_UNDECIDED)
		{
		switch (result = hr_collect_data(hc, &count, FALSE, timeout))
			{
		case HR_COMPLETE:
			if (!gotlen)
				{
				/* got first part, set up to get rest of message */
				result = HR_UNDECIDED;
				*mtype = hc->rc.rmsg_bufr[0];
				count = hc->rc.rmsg_bufr[1];
				if (count < 3)
					result = HR_BADMSG;
				gotlen = TRUE;
				}
			else
				{
				/* got everything, check for valid message */
				msgn = hc->rc.rmsg_bufr[2];
				count = hc->rc.rmsg_bufr[1];
				if (hc->usecrc)
					{
					if (hc->h_crc != 0)
						result = HR_BADCHECK;
					}
				else
					{
					hc->h_checksum -= hc->rc.rmsg_bufr[count];
					hc->h_checksum -= hc->rc.rmsg_bufr[count + 1];
					if (hc->rc.rmsg_bufr[count] != (BYTE)(hc->h_checksum % 256) ||
							hc->rc.rmsg_bufr[count + 1] !=
							(BYTE)(hc->h_checksum / 256))
						result = HR_BADCHECK;
					}
				hc->rc.rmsg_bufr[count] = '\0';
				}
			break;

		case HR_LOST_CARR:
		case HR_TIMEOUT:
		case HR_KBDINT:
			/* return same result */
			break;

		case HR_MESSAGE:
			/* we encountered what looked like a message within a message
			 *	but that is illegal
			 */
			result = HR_BADMSG;
			break;
			}
		}

	/* we're done, restore details for overlying data collection routine */
	hc->rc.hr_ptr_putc = holdptr;
	hc->h_checksum = hold_checksum;
	hc->h_crc = hold_crc;
	*mdata = &hc->rc.rmsg_bufr[3];
	if (result == HR_COMPLETE)
		{
		if (msgn != hc->rc.expected_msg)
			result = HR_LOSTDATA;
		else
			hc->rc.expected_msg = ++hc->rc.expected_msg % 256;
		}
	return(result);
	}



/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 * hr_still_alive
 *
 * DESCRIPTION:
 *	This routine is called periodically during receiving. It determines
 *	whether it is time to send the sender a 'deadman' message. Since there
 *	is no regular response from the receiver to the sender unless errors
 *	occur, the deadman message prevents the sender from sending into a void
 *	for long periods of time. If the sender doesn't receive ANYTHING from
 *	the receiver for the negotiated deadman time, it can assume the receiver
 *	is no longer active.
 *
 * ARGUMENTS:
 *	force	  -- TRUE if a deadman notification should be sent whether it is
 *					officially time for one or not.
 *	timed_out -- TRUE if receiver has timeout and we want sender to know that.
 *
 * RETURNS:
 *	nothing
 */
void hr_still_alive(struct s_hc *hc, int force, int timed_out)
	{
	char msg[20];

	if (force || (long)interval(omsg_last(hc)) >= hc->deadmantime)
		{
		omsg_new(hc, 'c');
		if (timed_out)
			omsg_add(hc, "t");

		/* While we're talking to the sender, we'll let him know how much
		 *	we've actually received. This lets him clear the table of
		 *	unacknowledged files that he keeps.
		 */
		// StrFmt(msg, "f%d,%lu", hc->current_filen, hc->rc.checkpoint);
		wsprintf(msg, "f%d,%lu", hc->current_filen, hc->rc.checkpoint);
		omsg_add(hc, msg);
		omsg_send(hc, BURSTSIZE, FALSE, FALSE);
		}
	}

/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 * hr_kbdint
 *
 * DESCRIPTION:
 *
 *
 * ARGUMENTS:
 *
 *
 * RETURNS:
 *
 */
void hr_kbdint(struct s_hc *hc)
	{
	/* TODO: fix this somehow
	if (!hc->ucancel)
		errorline(FALSE, strld(TM_WAIT_CONF));
	*/
	hc->ucancel = TRUE;
	}


/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 * hr_suspend_input
 *
 * DESCRIPTION:
 *
 *
 * ARGUMENTS:
 *
 *
 * RETURNS:
 *
 */
void hr_suspend_input(void *hS, int suspend)
	{
#if FALSE
	if (suspend)
		suspendinput(FLG_DISK_ACTIVE, 5);
	else
		allowinput(FLG_DISK_ACTIVE);
#endif
	}

/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
 * hr_check_input
 *
 * DESCRIPTION:
 *
 * ARGUEMENTS:
 *
 * RETURNS:
 *
 */
void	hr_check_input(void *hS, int suspend)
	{
	}



/********************** end of hpr_rcv0.c ***************************/