/*--------------------------------------------------------------------------*/
/*	File name:	XFER.C							Revision date:	2000.08.31	*/
/*	Revised by:	Ulf Ronald Andersson			Revision start:	1999.11.02	*/
/*	Created by:	Peter Rottengatter				Creation date:	1997.01.31	*/
/*--------------------------------------------------------------------------*/
/*	Project:	Serial port driver for STinG								*/
/*	Module:		Data transfer												*/
/*--------------------------------------------------------------------------*/
#include <tos.h>
#include <stdio.h>
#include <string.h>
#include <sting\transprt.h>
#include <sting\port.h>
/*--------------------------------------------------------------------------*/
#include "serial.h"
#include "ppp_fcs.h"
/*--------------------------------------------------------------------------*/
#define FIONREAD  (('F'<< 8) | 1)
#define FIONWRITE (('F'<< 8) | 2)
/*--------------------------------------------------------------------------*/
int32			sim_Bconstat(MAPTAB *map);
int32			sim_Bcostat(MAPTAB *map);
int32			sim_Fread(MAPTAB *map, int32 cnt, char *buf);
int32			sim_Fwrite(MAPTAB *map, int32 cnt, char *buf);

int				vjhc_compress (IP_DGRAM *datagram, VJHC *vjhc);
int				vjhc_uncompress (uint8 *buff, int16 len, int16 type, VJHC *vjhc, IP_DGRAM **dgram);

void			ppp_control (SERIAL_PORT *port, uint16 protocol, uint8 *data, int16 len);

void	cdecl	my_send (PORT *port);
int16			fetch_datagram (SERIAL_PORT *port);
void			ppp_do_cp (SERIAL_PORT *port);
int16			wrap_ip (SERIAL_PORT *port);
int16			slip_out (uint8 *dest, uint8 *source, int16 length);
int16			ppp_out (uint8 *dest, uint16 *fcs, uint32 accm, uint8 *source, uint16 length);
void	cdecl	my_receive (PORT *port);
void			process_datagram (SERIAL_PORT *port);
void			make_IP_dgram (uint8 *buffer, int16 buff_len, IP_DGRAM **dgram);
int16			slip_unwrap (SERIAL_PORT *port, int16 *type);
int16			ppp_unwrap (SERIAL_PORT *port, uint16 *prtcl, uint8 **data);


extern	DRIVER	my_driver;

uint8	ppp_header[4]  = {  PPP_ADDR, PPP_CNTRL, 0, 0  };
/*--------------------------------------------------------------------------*/
/* RA: my_send uses Fcntl, Fwrite for compatibility with device drivers		*/
/* if they do not have any BIOS interface, and we are running under MagiC.	*/
/* (If we are not running under MagiC, such ports are illegal (rejected).	*/
/* Normally it uses direct IOREC access, for superfast Fwrite emulation.	*/
/*--------------------------------------------------------------------------*/
void  cdecl  my_send (PORT  *port)
{	SERIAL_PORT	*serial;
	uint8		*walk;
	int16		remain, written;
	int32		count;

	if	(port->driver != & my_driver || ! port->active)
		return;

	serial = (SERIAL_PORT *) port;

	if	(serial->no_bios_f)
		Fcntl (serial->handle, (long)&count, FIONWRITE);
	else
		count = sim_Bcostat(serial->handler);

	if	(count)
	{	walk   = serial->send_buffer + serial->send_index;
		remain = serial->send_length - serial->send_index;
		
		do
		{	if	(remain == 0)
			{	if	(! fetch_datagram (serial))
					return;
				walk = serial->send_buffer;
				serial->send_index = 0;
				remain = serial->send_length;
			}

			if	(serial->no_bios_f)
				written = Fwrite (serial->handle, (long)remain, walk);
			else
				written = sim_Fwrite(serial->handler, (long)remain, walk);

			if	( written <= 0 )
				break;
			walk += written;
		} while ( ( remain -= written ) >= 0);
		serial->send_index = serial->send_length - remain;
	}
}
/*----------------------*/
/* ends:	my_send		*/
/*--------------------------------------------------------------------------*/
int16  fetch_datagram (port)

SERIAL_PORT  *port;

{	port->send_length = port->send_index = 0;

   if ((port->generic.flags & FLG_PRTCL) && port->ppp.cp_send_len)
        ppp_do_cp (port);
     else {
        if (! wrap_ip (port))   return (FALSE);
      }

   port->generic.stat_sd_data += port->send_length;

   return (TRUE);
 }
/*--------------------------*/
/* ends:	fetch_datagram	*/
/*--------------------------------------------------------------------------*/
void  ppp_do_cp (port)

SERIAL_PORT  *port;

{
   uint8   *work, chksum[2], *data;
   uint16  fcs, count, packet;

   work = port->send_buffer;

   for (count = 0; count < port->ppp.cp_send_len; count += packet + 2) {
        *work++ = PPP_FLAG;
        data = port->ppp.cp_send_data + count;
        packet = (data[4] << 8) | data[5];
        ppp_header[2] = data[0];   ppp_header[3] = data[1];
        fcs = 0xffffu;
        work += ppp_out (work, & fcs, 0xffffffffuL, ppp_header, 4);
        work += ppp_out (work, & fcs, 0xffffffffuL, & data[2], packet);
        fcs  ^= 0xffffu;
        chksum[0] = (uint8)  (fcs & 0x00ff);
        chksum[1] = (uint8) ((fcs >> 8) & 0x00ff);
        work += ppp_out (work, & fcs, 0xffffffffuL, chksum, 2);
      }

   *work++ = PPP_FLAG;
   port->send_length = work - port->send_buffer;
   KRfree (port->ppp.cp_send_data);   port->ppp.cp_send_len = 0;
 }
/*----------------------*/
/* ends:	ppp_do_cp	*/
/*--------------------------------------------------------------------------*/
int16  wrap_ip (SERIAL_PORT  *port)
{
   IP_DGRAM  *dgram;
   VJHC      *comp;
   int16     compressed, type, offs, hdr, len, posi;
   uint32    accm;
   uint16    fcs = 0xffffu;
   uint8     *work, chksum[2];

	port->send_length = 0;

	do
	{
		if ((dgram = port->generic.send) == NULL)
			return (FALSE);
		port->generic.send = dgram->next;
	} while (check_dgram_ttl (dgram) != E_NORMAL);

	compressed = ((port->generic.flags & FLG_VJHC) != 0) ? TRUE : FALSE;
	work = port->send_buffer;

	comp = port->vjhc;

	if ((port->generic.flags & FLG_PRTCL) == 0)
	{
		*work++ = SLIP_END;
		if (compressed)
		{
             switch (type = vjhc_compress (dgram, comp))
             {
                case VJHC_TYPE_IP :
                case VJHC_TYPE_UNCOMPR_TCP :
                  *((uint8 *) & dgram->hdr) |= type;
                  work += slip_out (work, (uint8 *) & dgram->hdr, 20);
                  work += slip_out (work, dgram->options, dgram->opt_length);
                  work += slip_out (work, dgram->pkt_data, dgram->pkt_length);
                  break;
                case VJHC_TYPE_COMPR_TCP :
                  comp->header[comp->begin] |= VJHC_TYPE_COMPR_TCP;
                  work += slip_out (work, comp->header + comp->begin, comp->length);
                  offs = ((TCP_HDR *) dgram->pkt_data)->offset << 2;
                  work += slip_out (work, (uint8*)dgram->pkt_data+offs, dgram->pkt_length-offs);
                  break;
             }
		}
		else
		{
			work += slip_out (work, (uint8 *) & dgram->hdr, 20);
			work += slip_out (work, dgram->options, dgram->opt_length);
			work += slip_out (work, dgram->pkt_data, dgram->pkt_length);
		}
		*work++ = SLIP_END;
	}
	else
	{
        hdr = ppp_header[2] = 0;
        len = posi = 4;
        if ((port->generic.flags & FLG_PRTCL_COMP) != 0) {
             len -= 1;   posi -= 1;
           }
        if ((port->generic.flags & FLG_A_C_COMP) != 0) {
             len -= 2;   hdr += 2;
           }
        *work++ = PPP_FLAG;
        accm    = port->ppp.send_accm;
        if (compressed) {
             switch (type = vjhc_compress (dgram, comp)) {
                case VJHC_TYPE_IP :
                case VJHC_TYPE_UNCOMPR_TCP :
                  ppp_header[posi - 1] = (type == VJHC_TYPE_IP) ? PPP_IP : PPP_VJHC_UNC;
                  work += ppp_out (work, & fcs, accm, & ppp_header[hdr], len);
                  work += ppp_out (work, & fcs, accm, (uint8 *) & dgram->hdr, 20);
                  work += ppp_out (work, & fcs, accm, dgram->options, dgram->opt_length);
                  work += ppp_out (work, & fcs, accm, dgram->pkt_data, dgram->pkt_length);
                  break;
                case VJHC_TYPE_COMPR_TCP :
                  ppp_header[posi - 1] = PPP_VJHC_C;
                  work += ppp_out (work, & fcs, accm, & ppp_header[hdr], len);
                  work += ppp_out (work, & fcs, accm, comp->header + comp->begin, comp->length);
                  offs = ((TCP_HDR *) dgram->pkt_data)->offset << 2;
                  work += ppp_out (work, & fcs, accm, (uint8*) dgram->pkt_data + offs,
                                   dgram->pkt_length - offs);
                  break;
                }
           }
          else {
             ppp_header[posi - 1] = PPP_IP;
             work += ppp_out (work, & fcs, accm, & ppp_header[hdr], len);
             work += ppp_out (work, & fcs, accm, (uint8 *) & dgram->hdr, 20);
             work += ppp_out (work, & fcs, accm, dgram->options, dgram->opt_length);
             work += ppp_out (work, & fcs, accm, dgram->pkt_data, dgram->pkt_length);
           }
        fcs ^= 0xffffu;
        chksum[0] = (uint8)  (fcs & 0x00ff);
        chksum[1] = (uint8) ((fcs >> 8) & 0x00ff);
        work += ppp_out (work, & fcs, accm, chksum, 2);
        *work++ = PPP_FLAG;
	}

	IP_discard (dgram, TRUE);
	port->send_length = work - port->send_buffer;

	return (TRUE);
}
/*----------------------*/
/* ends:	wrap_ip		*/
/*--------------------------------------------------------------------------*/
int16  slip_out (destination, source, length)

uint8  *destination, *source;
int16  length;

{
	int  count = 0;

	while (length--)
	{
		switch (*source)
		{
		case SLIP_END :
			*destination++ = SLIP_ESC;   *destination++ = SLIP_DATEND;
			count += 2;   break;
		case SLIP_ESC :
			*destination++ = SLIP_ESC;   *destination++ = SLIP_DATESC;
			count += 2;   break;
		default :
			*destination++ = *source;   count++;
		}
		source++;
	}
	return (count);
}
/*----------------------*/
/* ends:	slip_out		*/
/*--------------------------------------------------------------------------*/
int16  ppp_out (destination, fcs, send_accm, source, length)

uint8   *destination, *source;
uint16  *fcs, length;
uint32  send_accm;

{
   int16  count = 0;
   uint8  byte;

   while (length--) {
        *fcs = (*fcs >> 8) ^ fcs_tab[(*fcs ^ (byte = *source++)) & 0xff];

        if (byte < 32) {
             if (((1L << byte) & send_accm) == 0)
                  *destination++ = byte;
               else
                  *destination++ = PPP_ESC,  *destination++ = byte ^ 0x20,  count++;
             count++;
           }
          else {
             switch (byte) {
                case PPP_FLAG :
                  *destination++ = PPP_ESC;   *destination++ = PPP_FLAG ^ 0x20;
                  count += 2;   break;
                case PPP_ESC :
                  *destination++ = PPP_ESC;   *destination++ = PPP_ESC ^ 0x20;
                  count += 2;   break;
                default :
                  *destination++ = byte;   count++;
                }
           }
      }

   return (count);
 }
/*----------------------*/
/* ends:	ppp_out		*/
/*--------------------------------------------------------------------------*/
/* RA: my_receive uses Fcntl, Fread for compatibility with device drivers	*/
/* if they do not have any BIOS interface, and we are running under MagiC.	*/
/* (If we are not running under MagiC, such ports are illegal (rejected).	*/
/* Normally it uses direct IOREC access, for superfast Fread emulation.		*/
/* Partial tail packets remain in buffer for completion in future passes.	*/
/*--------------------------------------------------------------------------*/
void	cdecl	my_receive (PORT  *port)
{	SERIAL_PORT	*serial;
	uint8		*walk, *end, mark;
	uint8		*data;
	int32		remain;
	int16		status;
	
	if (port->driver != & my_driver || ! port->active)
		return;

	serial = (SERIAL_PORT *) port;

	if	(serial->no_bios_f)
		Fcntl (serial->handle, (long)&remain, FIONREAD);
	else
		remain = sim_Bconstat(serial->handler);

	if (remain)
	{	walk   = serial->recve_buffer + serial->recve_index;
		remain = serial->recve_buffer + 8190 - walk;
		
		if	(serial->no_bios_f)
			remain = Fread (serial->handle, remain, walk);
		else
			remain = sim_Fread (serial->handler, remain, walk);

		if	(remain <= 0)
			return;
		
		mark   = ((serial->generic.flags & FLG_PRTCL) == 0) ? SLIP_END : PPP_FLAG;
		end = walk + remain;
		data = serial->recve_buffer;

		do
		{	if	(*walk++ == mark)
			{	if	((walk - serial->recve_buffer) > 1)
				{	serial->recve_length = walk - serial->recve_buffer - 1;
					process_datagram (serial);
				}
				serial->recve_buffer = walk;
			}
		} while (walk < end);
		
		if	(serial->recve_buffer > data)	/* packet was processed above */
		{	serial->recve_index = walk - serial->recve_buffer;
			if	(serial->recve_index)
			{	status = serial->recve_index;
				walk = data;
				end = serial->recve_buffer;
				while (--status >= 0)
					*walk++ = *end++;
			}
			serial->recve_buffer = data;
		}
		else								/* no packets processed above */
		{	serial->recve_index = ( walk - data );
			if	(serial->recve_index >= 8190)	/* no more room ! */
			{	serial->generic.stat_dropped++;	/* drop garbage	! */
				serial->recve_index = 0;
			}
			/* NB: Any data which exceeds buffer size	*/
			/*     with no packet markers is garbage.	*/
		}
	}
}
/*--------------------------*/
/* ends:	my_receive		*/
/*--------------------------------------------------------------------------*/
void	process_datagram (SERIAL_PORT  *port)
{	IP_DGRAM	*dgram, *walk, **previous;
	uint8		*data;
	int16		len, compression, type;
	uint16		protocol;

	compression = (port->generic.flags & FLG_VJHC) ? TRUE : FALSE;
	port->generic.stat_rcv_data += port->recve_length;

	if (port->generic.flags & FLG_PRTCL)
	{	len = ppp_unwrap (port, & protocol, & data);
		if (len > 0)
		{	switch (protocol)
			{
			case PPP_IP :
				compression = FALSE;
				break;
			case PPP_VJHC_UNC :
				compression = TRUE;
				type = VJHC_TYPE_UNCOMPR_TCP;
				break;
			case PPP_VJHC_C :
				compression = TRUE;
				type = VJHC_TYPE_COMPR_TCP;
				break;
			default :
				ppp_control (port, protocol, data, len);
				return;
			}
		}
		else
		{	compression = TRUE;
			type = VJHC_TYPE_ERROR;
		}
	}
	else
	{	data = port->recve_buffer;
		len = slip_unwrap (port, & type);
	}
	
	if	((port->generic.flags & FLG_PRTCL) != 0 && port->ppp.ipcp.state != PPP_OPENED)
	{	port->generic.stat_dropped++;
		return;
	}

	if	(! compression)
		make_IP_dgram (data, len, & dgram);
	else
		vjhc_uncompress (data, len, type, port->vjhc, & dgram);

	if (dgram == NULL)
	{	port->generic.stat_dropped++;
		return;
	}

	dgram->recvd = & port->generic;
	dgram->next = NULL;
	set_dgram_ttl (dgram);

	previous = & port->generic.receive;

	for (walk = *previous; walk; walk = *(previous = &walk->next))
		;
	*previous = dgram;
}
/*------------------------------*/
/* ends:	process_datagram	*/
/*--------------------------------------------------------------------------*/
void  make_IP_dgram (buffer, buff_len, dgram)

uint8     *buffer;
int16     buff_len;
IP_DGRAM  **dgram;

{	IP_DGRAM  *temp;

	*dgram = NULL;

	if	(buff_len < sizeof (IP_HDR))
		return;

	if	((temp = KRmalloc (sizeof (IP_DGRAM))) == NULL)
		return;

	memcpy (& temp->hdr, buffer, sizeof (IP_HDR));
	buffer += sizeof (IP_HDR);

	if	(	temp->hdr.length > buff_len
		||	temp->hdr.hd_len < 5
		||	(temp->hdr.hd_len << 2) > buff_len
		)
	{	KRfree (temp);
		return;
	}

	temp->options  = KRmalloc (temp->opt_length = (temp->hdr.hd_len << 2) - sizeof (IP_HDR));
	temp->pkt_data = KRmalloc (temp->pkt_length = temp->hdr.length - (temp->hdr.hd_len << 2));

	if	(	temp->options == NULL
		||	temp->pkt_data == NULL
		)
	{	IP_discard (temp, TRUE);
		return;
	}

	memcpy (temp->options, buffer, temp->opt_length);
	memcpy (temp->pkt_data, buffer + temp->opt_length, temp->pkt_length);

	*dgram = temp;
}
/*--------------------------*/
/* ends:	make_IP_dgram	*/
/*--------------------------------------------------------------------------*/
int16  slip_unwrap (SERIAL_PORT  *port, int16 *type)

{	uint8  *p_read, *p_write, *p_last, first;
	int16  error = FALSE, len;

	p_last = (p_read = p_write = port->recve_buffer) + port->recve_length;

	if (*p_read == SLIP_END)
		p_read++;

	while (p_read < p_last)
	{
		if (*p_read == SLIP_ESC)
		{
			switch (*++p_read)
			{
				case SLIP_DATEND :
					*p_read = SLIP_END;
					break;
				case SLIP_DATESC :
					*p_read = SLIP_ESC;
					break;
				default :
					error = TRUE;
			}
		}
		*p_write++ = *p_read++;
	}

	if (*p_read != SLIP_END)
		error = TRUE;

	first = * port->recve_buffer;

	if (! error)
	{
		if (first & 0x80u)
			*type = VJHC_TYPE_COMPR_TCP;
		else
		{
			if (first >= 0x70u)
			{
				*type = VJHC_TYPE_UNCOMPR_TCP;
				*port->recve_buffer &= ~0x30u;
			}
			else
				*type = VJHC_TYPE_IP;
		}
	}
	else
		*type = VJHC_TYPE_ERROR;
	
	return ((int16) (p_write - port->recve_buffer) );
}
/*--------------------------*/
/* ends:	slip_unwrap		*/
/*--------------------------------------------------------------------------*/
int16  ppp_unwrap (port, protocol, data)

SERIAL_PORT	*port;
uint16		*protocol;
uint8		**data;

{	uint8	*p_read, *p_write, *p_last;
	uint16	fcs = 0xffffu;

	p_last = (p_read = p_write = port->recve_buffer) + port->recve_length;

	if	(*p_read == PPP_FLAG)
		p_read++;

	while (p_read < p_last)
	{	if	(*p_read < 32)
			if	(((1L << *p_read) & port->ppp.recve_accm) != 0)
			{	p_read++;
				continue;
			}
		if	(*p_read == PPP_ESC)
		{	if	(*++p_read == PPP_FLAG)
				return (0);
			*p_read ^= 0x20;
		}
		fcs = (fcs >> 8) ^ fcs_tab[(fcs ^ *p_read) & 0xff];
		*p_write++ = *p_read++;
	}

	if	(fcs != 0xf0b8 || p_write - port->recve_buffer < 6)
		return (0);

	p_read = port->recve_buffer;
	p_write -= 3;

	if	(p_read[0] == PPP_ADDR && p_read[1] == PPP_CNTRL)
		p_read += 2;

	if	((*p_read & 0x01) == 0)
		*protocol = (*p_read << 8) | *(p_read + 1);
	else
		*protocol =  *p_read;

	*data = p_read + ((*p_read & 0x01) ? 1 : 2);

	if ((*protocol & 0x01) == 0)
		return (0);

	return ((int16) (p_write - p_read));
}
/*----------------------*/
/* ends:	ppp_unwrap	*/
/*--------------------------------------------------------------------------*/
/* End of file:	XFER.C														*/
/*--------------------------------------------------------------------------*/
