#include <stdlib.h>
#include <string.h>
#include "statetbl.h"
#include "xutil.h"
#include "lutil.h"
#include "ttyio.h"
#include "session.h"
#include "ftscprod.h"
#include "config.h"
#include "ftn.h"
#include "emsi.h"
#include "nodelist.h"
#include "version.h"

#define PRODCODE 0xfe

/*------------------------------------------------------------------------*/
/* YOOHOO<tm> CAPABILITY VALUES                                           */
/*------------------------------------------------------------------------*/
#define Y_DIETIFNA 0x0001  /* Can do fast "FTS-0001"  0000 0000 0000 0001 */
#define FTB_USER   0x0002  /* Full-Tilt Boogie        0000 0000 0000 0010 */
#define ZED_ZIPPER 0x0004  /* Does ZModem, 1K blocks  0000 0000 0000 0100 */
#define ZED_ZAPPER 0x0008  /* Can do ZModem variant   0000 0000 0000 1000 */
#define DOES_IANUS 0x0010  /* Can do Janus            0000 0000 0001 0000 */
#define Bit_5      0x0020  /* reserved by FTSC        0000 0000 0010 0000 */
#define Bit_6      0x0040  /* reserved by FTSC        0000 0000 0100 0000 */
#define Bit_7      0x0080  /* reserved by FTSC        0000 0000 1000 0000 */
#define Bit_8      0x0100  /* reserved by FTSC        0000 0001 0000 0000 */
#define Bit_9      0x0200  /* reserved by FTSC        0000 0010 0000 0000 */
#define Bit_a      0x0400  /* reserved by FTSC        0000 0100 0000 0000 */
#define Bit_b      0x0800  /* reserved by FTSC        0000 1000 0000 0000 */
#define Bit_c      0x1000  /* reserved by FTSC        0001 0000 0000 0000 */
#define Bit_d      0x2000  /* reserved by FTSC        0010 0000 0000 0000 */
#define Bit_e      0x4000  /* reserved by FTSC        0100 0000 0000 0000 */
#define WZ_FREQ    0x8000  /* WZ file req. ok         1000 0000 0000 0000 */

#define LOCALCAPS (Y_DIETIFNA|ZED_ZIPPER|ZED_ZAPPER)

extern unsigned short crc16(char*,int);
extern int janus(void);
extern int rxwazoo(void);
extern int txwazoo(void);
extern int rxdietifna(void);
extern int txdietifna(void);
extern void rdoptions(node*);
extern int nodelock(faddr*);

static int rxyoohoo(void);
static int txyoohoo(void);
static void fillhello(unsigned short,char*);
static void checkhello(void);

static int iscaller;
static struct _hello {
	unsigned char data[128];
	unsigned char crc[2];
} hello;

int rx_yoohoo(void)
{
	int rc;
	unsigned short capabilities,localcaps;
	char *pwd;
	fa_list *tmp;
	node *nlent;

	loginf("start inbound WaZOO session");

	pwd=NULL;
	localcaps=LOCALCAPS;
	if (localoptions & NOZMODEM) localcaps &= ~(ZED_ZAPPER|ZED_ZIPPER);
	if (localoptions & NOZEDZAP) localcaps &= ~ZED_ZAPPER;
	if (localoptions & NOJANUS) localcaps &= ~DOES_IANUS;
	emsi_local_opts=0;
	emsi_remote_opts=0;
	iscaller=0;
	if ((rc=rxyoohoo()) == 0)
	{
		checkhello();
		capabilities=hello.data[114]+(hello.data[115]<<8);
		if (capabilities & WZ_FREQ) session_flags |= SESSION_WAZOO;
		else session_flags &= ~SESSION_WAZOO;
		localcaps&=capabilities;
		if (localcaps & DOES_IANUS) localcaps &= DOES_IANUS;
		else if (localcaps & ZED_ZAPPER) localcaps &= ZED_ZAPPER;
		else if (localcaps & ZED_ZIPPER) localcaps &= ZED_ZIPPER;
		else if (localcaps & FTB_USER)   localcaps &= FTB_USER;
		else if (localcaps & Y_DIETIFNA) localcaps &= Y_DIETIFNA;
		if ((localoptions & NOFREQS) == 0) localcaps |= WZ_FREQ;
		else emsi_local_opts |= NRQ;

		if (((nlent=getnlent(remote->addr))) &&
		    (nlent->pflag != NL_DUMMY))
		{
			loginf("remote is a listed system");
			inbound=listinbound;
		}
		if (nlent) rdoptions(nlent);

		for (tmp=pwlist;tmp;tmp=tmp->next)
		if (metric(remote->addr,tmp->addr) == 0)
		{
			if (strncasecmp((char*)hello.data+98,tmp->addr->name,8) != 0)
			{
				pwd="BAD_PASS";
				loginf("remote gave password \"%s\", expected \"%s\"",
					(char*)hello.data+98,S(tmp->addr->name));
				localcaps=0;
			}
			else /* password did match, return it back */
			{
				loginf("remote gave correct password, protected WaZOO session");
				inbound=protinbound;
				pwd=tmp->addr->name;
				break;
			}
		}

		fillhello(localcaps,pwd);

		rc=txyoohoo();
	}
	if ((rc == 0) && ((localcaps & LOCALCAPS) == 0))
	{
		loginf("No common WaZOO protocols or bad password");
		return 0;
	}
	if (rc) return rc;
	session_flags |= SESSION_WAZOO;
	if (localcaps & DOES_IANUS) return janus();
	else if ((localcaps & ZED_ZAPPER) || (localcaps & ZED_ZIPPER))
	{
		if (localcaps & ZED_ZAPPER) emsi_local_protos=ZAP;
		else emsi_local_protos=ZMO;
		return rxwazoo();
	}
	else if (localcaps & Y_DIETIFNA) return rxdietifna();
	else logerr("WaZOO internal error - no proto for 0x%04xh",localcaps);
	return 1;
}

int tx_yoohoo(void)
{
	int rc;
	unsigned short capabilities;
	char *pwd;
	fa_list *tmp;

	loginf("start outbound WaZOO session");

	pwd=NULL;
	for (tmp=pwlist;tmp;tmp=tmp->next)
	if (metric(remote->addr,tmp->addr) == 0)
	{
		pwd=tmp->addr->name;
		break;
	}
	capabilities=LOCALCAPS;
	if (localoptions & NOZMODEM) capabilities &= ~(ZED_ZAPPER|ZED_ZIPPER);
	if (localoptions & NOZEDZAP) capabilities &= ~ZED_ZAPPER;
	if (localoptions & NOJANUS) capabilities &= ~DOES_IANUS;
	if ((localoptions & NOFREQS) == 0) capabilities |= WZ_FREQ;
	else emsi_local_opts |= NRQ;
	fillhello(capabilities,pwd);
	iscaller=1;
	if ((rc=txyoohoo()) == 0)
	{
		rc=rxyoohoo();
		checkhello();
		capabilities=hello.data[114]+(hello.data[115]<<8);
		if (capabilities & WZ_FREQ) session_flags |= SESSION_WAZOO;
		else session_flags &= ~SESSION_WAZOO;
	}
	if ((rc == 0) && ((capabilities & LOCALCAPS) == 0))
	{
		loginf("No common WaZOO protocols");
		return 0;
	}
	if (rc) return rc;
	session_flags |= SESSION_WAZOO;
	if (capabilities & DOES_IANUS) return janus();
	else if ((capabilities & ZED_ZAPPER) || (capabilities & ZED_ZIPPER))
	{
		if (capabilities & ZED_ZAPPER) emsi_local_protos=ZAP;
		else emsi_local_protos=ZMO;
		return txwazoo();
	}
	else if (capabilities & Y_DIETIFNA) return txdietifna();
	else logerr("WaZOO internal error - no proto for 0x%04xh",capabilities);
	return 1;
}

SM_DECL(rxyoohoo,"rxyoohoo")
SM_STATES
	sendenq,waitchar,getpacket,sendnak,sendack
SM_NAMES
	"sendenq","waitchar","getpacket","sendnak","sendack"
SM_EDECL

	int c;
	int count=0;
	unsigned short lcrc,rcrc;

SM_START(sendenq)

SM_STATE(sendenq)

	if (count++ > 9)
	{
		loginf("too many tries to get hello packet");
		SM_ERROR;
	}
	PUTCHAR(ENQ);
	SM_PROCEED(waitchar)

SM_STATE(waitchar)

	c=GETCHAR(10);
	if (c == TIMEOUT) {SM_PROCEED(sendenq);}
	else if (c < 0)
	{
		SM_ERROR;
	}
	else switch (c)
	{
	case 0x1f:	SM_PROCEED(getpacket); break;
	case YOOHOO:	SM_PROCEED(sendenq); break;
	default:	debug(10,"got '%s' waiting hello",
				printablec(c));
			SM_PROCEED(waitchar);
			break;
	}

SM_STATE(getpacket)

	GET((char*)&hello,sizeof(hello),30);
	if (STATUS)
	{
		SM_ERROR;
	}
	lcrc=crc16((char*)hello.data,sizeof(hello.data));
	rcrc=(hello.crc[0]<<8)+hello.crc[1];
	if (lcrc != rcrc)
	{
		debug(10,"crc does not match in hello packet: %04xh/%04xh",
			rcrc,lcrc);
		SM_PROCEED(sendnak);
	}
	else {SM_PROCEED(sendack);}

SM_STATE(sendnak)

	if (count++ > 9)
	{
		loginf("too many tries to get hello packet");
		SM_ERROR;
	}
	PUTCHAR('?');
	SM_PROCEED(waitchar);

SM_STATE(sendack)

	PUTCHAR(ACK);
	SM_SUCCESS;

SM_END
SM_RETURN


SM_DECL(txyoohoo,"txyoohoo")
SM_STATES
	sendyoohoo,waitenq,sendpkt,waitchar
SM_NAMES
	"sendyoohoo","waitenq","sendpkt","waitchar"
SM_EDECL

	int c;
	int count=0;
	int startstate;
	unsigned short lcrc;

	if (iscaller) startstate=sendpkt;
	else startstate=sendyoohoo;
	lcrc=crc16((char*)hello.data,sizeof(hello.data));
	hello.crc[0]=lcrc>>8;
	hello.crc[1]=lcrc&0xff;

SM_START(startstate)

SM_STATE(sendyoohoo)

	PUTCHAR(YOOHOO);
	SM_PROCEED(waitenq);

SM_STATE(waitenq)

	c=GETCHAR(10);
	if (c == TIMEOUT)
	{
		if (count++ > 9)
		{
			loginf("timeout waiting ENQ");
			SM_ERROR;
		}
		else
		{
			SM_PROCEED(sendyoohoo);
		}
	}
	else if (c < 0)
	{
		SM_ERROR;
	}
	else switch (c)
	{
	case ENQ:	SM_PROCEED(sendpkt);
	case YOOHOO:	SM_PROCEED(waitenq);
	default:	debug(10,"got '%s' waiting hello ACK",
				printablec(c));
			SM_PROCEED(waitchar);
			break;
	}

SM_STATE(sendpkt)

	if (count++ > 9)
	{
		loginf("too many tries to send hello packet");
		SM_ERROR;
	}

	PUTCHAR(0x1f);
	PUT((char*)&hello,sizeof(hello));
	if (STATUS) {SM_ERROR;}
	SM_PROCEED(waitchar);

SM_STATE(waitchar)

	c=GETCHAR(30);
	if (c == TIMEOUT)
	{
		loginf("timeout waiting hello ACK");
		SM_ERROR;
	}
	else if (c < 0)
	{
		SM_ERROR;
	}
	else switch (c)
	{
	case ACK:	SM_SUCCESS; break;
	case '?':	SM_PROCEED(sendpkt); break;
	case ENQ:	SM_PROCEED(sendpkt); break;
	default:	debug(10,"got '%s' waiting hello ACK",
				printablec(c));
			SM_PROCEED(waitchar);
			break;
	}

SM_END
SM_RETURN


void fillhello(capabilities,password)
unsigned short capabilities;
char *password;
{
	faddr *best;
	unsigned short majver,minver;

	debug(10,"fillhello(0x%04hx)",capabilities);

	best=bestaka_s(remote->addr);

	sscanf(version,"%hd.%hd",&majver,&minver);
	memset(&hello,0,sizeof(hello));
	hello.data[0]='o'; /* signal */
	hello.data[2]=1; /* hello-version */
	hello.data[4]=PRODCODE; /* product */
	hello.data[6]=majver&0xff; /* prod-ver-major */
	hello.data[7]=majver>>8; /* prod-ver-major */
	hello.data[8]=minver&0xff; /* prod-ver-minor */
	hello.data[9]=minver>>8; /* prod-ver-minor */
	strncpy((char*)hello.data+10,name?name:"Unavailable",59); /* name */
	strncpy((char*)hello.data+70,sysop?sysop:"Unavailable",19); /* sysop */
	hello.data[90]=best->zone&0xff; /* zone */
	hello.data[91]=best->zone>>8; /* zone */
	hello.data[92]=best->net&0xff; /* net */
	hello.data[93]=best->net>>8; /* net */
	hello.data[94]=best->node&0xff; /* node */
	hello.data[95]=best->node>>8; /* node */
	hello.data[96]=best->point&0xff; /* point */
	hello.data[97]=best->point>>8; /* point */

	if (password) strncpy((char*)hello.data+98,password,8);

	hello.data[114]=capabilities&0xff; /* capabilities */
	hello.data[115]=capabilities>>8; /* capabilities */
	debug(10,"filled hello \"%s\"",printable((char*)hello.data,128));
}

void checkhello(void)
{
	unsigned short i,majver,minver;
	fa_list **tmpl,*tmpn;
	faddr remaddr;
	char *prodnm;

	debug(10,"check hello \"%s\"",printable((char*)hello.data,128));

	if ((hello.data[0] != 'o') ||
	    (hello.data[1] != 0) ||
	    (hello.data[2] != 1) ||
	    (hello.data[3] != 0))
	{
		loginf("got \"%s\" instead of \"o\\000\\001\000\"",
			printable((char*)hello.data,3));
	}
	for (i=0;ftscprod[i].name;i++)
		if (ftscprod[i].code == hello.data[4]) break;
	prodnm=ftscprod[i].name;
	majver=hello.data[6]+(hello.data[7]<<8);
	minver=hello.data[8]+(hello.data[9]<<8);
	remaddr.zone=hello.data[90]+(hello.data[91]<<8);
	remaddr.net=hello.data[92]+(hello.data[93]<<8);
	remaddr.node=hello.data[94]+(hello.data[95]<<8);
	remaddr.point=hello.data[96]+(hello.data[97]<<8);
	remaddr.name=NULL;
	remaddr.domain=NULL;
	if (remote)
		debug(10,"remote known     address: %s",ascfnode(remote->addr,0x0f));
	debug(10,"remote presented address: %s",ascfnode(&remaddr,0x0f));
	for (tmpl=&remote;*tmpl;tmpl=&((*tmpl)->next));
	if ((remote == NULL) || (metric(remote->addr,&remaddr) != 0))
	{
		(*tmpl)=(fa_list*)xmalloc(sizeof(fa_list));
		(*tmpl)->next=NULL;
		(*tmpl)->addr=(faddr*)xmalloc(sizeof(faddr));
		(*tmpl)->addr->zone=remaddr.zone;
		(*tmpl)->addr->net=remaddr.net;
		(*tmpl)->addr->node=remaddr.node;
		(*tmpl)->addr->point=remaddr.point;
		(*tmpl)->addr->domain=NULL;
	}
	else
	{
		tmpl=&remote;
		debug(10,"using single remote address");
	}
	if ((*tmpl)->addr->name == NULL)
		(*tmpl)->addr->name=xstrcpy((char*)hello.data+70);
	if ((*tmpl)->addr->domain == NULL)
	{
		for (tmpn=nllist;tmpn;tmpn=tmpn->next)
			if (tmpn->addr->zone == (*tmpl)->addr->zone) break;
		if (tmpn) (*tmpl)->addr->domain=xstrcpy(tmpn->addr->domain);
	}

	for (tmpn=remote;tmpn;tmpn=tmpn->next)
		(void)nodelock(tmpn->addr);
		/* lock all remotes, ignore locking result */

	loginf("remote  address: %s",ascfnode(remote->addr,0x0f));
	loginf("remote password: %s",(char*)hello.data+98);
	loginf("remote     uses: %s [%02X] version %d.%d",
		prodnm?prodnm:"<unknown program>",hello.data[4],
		majver,minver);
	loginf("remote   system: %s",(char*)hello.data+10);
	loginf("remote operator: %s",(char*)hello.data+70);
}
