#include "main.h"

/*
 * net.c Copyright 1999 Christopher M Sedore. All Rights Reserved.
 * Please see the "COPYING" file for license information. 
 *
 * This file is something of a mess, unfortunately.  The code below
 * is essentially responsible for io on sockets.  It provides 3
 * main functions: 1) it provides upcalls with each line of input
 * when not reading an article. 2) it will read an entire article
 * then do an upcall. 3) it will copy an article from a store to 
 * a socket.
 *
 * All these functions are async--there is no blocking anywhere in
 * the upcall functions.  Introducing any blocking system calls
 * would be detrimental to performance.
 */


void ReadComplete(struct context *cc, int len);

/*
 * NextIo()
 *
 * NextIo() is the routine normally called when another async operation
 * (read or write) should be queued on a socket.  It also will close
 * the socket now, or after the next operation completes.
 */

void
NextIo(struct context *cc)
{
	if (cc->flags & NNTP_CLOSE_CONNECTION) {
		
		if (cc->incoming) {
			cc->incoming->connected--;
			TAILQ_REMOVE(&cc->incoming->activeConns,cc,clist);
		}
		printf("closing\n");
		if (cc->errorCallback) 
			cc->errorCallback(cc);
		
		if (cc->flags & NNTP_READ_ARTICLE)
			CleanupAfterReadArticle(cc->art);
		close(cc->cb.cb.aio_fildes);
		free(cc);
		return;
	}
	
	if (cc->obuflen) {
		cc->cb.state="nio-wr";
		WriteConn(cc);
	} else {
		cc->cb.state="nio-rd";
		if (cc->flags & NNTP_READ_ARTICLE) 
			ReadConnArt(cc);
		else
			ReadConn(cc);
	}
	
	if (cc->flags & NNTP_CLOSE_CONNECTION_AFTER_OP) {
		cc->flags|=NNTP_CLOSE_CONNECTION;
	}
	
	return;
}

/*
 * ReadConnArt()
 *
 * ReadConnArt() is a special case function.  It handles reading from a
 * socket when we are reading an article.  Special handling is needed
 * because the buffering is different.
 */

int
ReadConnArt(struct context *cc)
{
	cc->cb.cb.aio_buf=&cc->art->buf[cc->art->len];
	
	cc->cb.cb.aio_nbytes=
		cc->art->bufsz-(cc->art->len+255)>CONTEXT_BUF_SIZE ? CONTEXT_BUF_SIZE-16 
		: cc->art->bufsz-(cc->art->len+255);
	
	assert(cc->cb.cb.aio_nbytes>0);
	
	cc->flags|=NNTP_ASYNC_ACT;
	cc->cb.state="rc-art";
	cc->cb.callback=(void(*)(void *,int))ReadComplete;	
	cc->cb.cb._aiocb_private.privatemodes=0;
	
	if (aio_read((struct aiocb *)cc)) {
		perror("ReadComplete (art)");
	}
}

/*
 * ReadComplete()
 * 
 * ReadComplete() is the low level upcall that runs after a aio_read call
 * completes.  This is where it gets ugly :(.  This code handles two
 * cases: 1) we're reading an article.  2) we're reading commands 
 * (line at a time).
 */

void
ReadComplete(struct context *cc, int len)
{
	int noNewOp=0;
	char *s,*e;
	int r;
	
	cc->flags&=~NNTP_ASYNC_ACT;
	assert(cc->callback!=NULL);
	
	if ((cc->cb.logfd) && (len>0)) {
		write(cc->cb.logfd,(char *)cc->cb.cb.aio_buf,len);
	}
	
	if (cc->flags & NNTP_FEED_START) {
		if (len>-1) {
			cc->buflen+=len;
			cc->cb.state="rd-cb";
			cc->callback(cc);
		} else {
			cc->flags|=NNTP_CLOSE_CONNECTION;
			perror("rc/fs");
		}
		NextIo(cc);
		return;
	}
	
	if (len<0) {
		printf("connclose/rc (%i) (%s/%s)\r\n",len,cc->state,cc->cb.state);
		perror("cc/rc");
		cc->flags|=NNTP_CLOSE_CONNECTION;
		cc->connErrorCode=errno;
		NextIo(cc);
		return;
	}

	/*
	 * A zero length read should mean a remote connection closed
	 */

	if (len==0) {
		cc->flags|=NNTP_CLOSE_CONNECTION;
		cc->connErrorCode=0;
		NextIo(cc);
		return;
	}
	
	if (len==0x7FFFFFFF)
		len=0; // XXX This is how we do it recursively with no chars
	
	while (cc->flags & NNTP_READ_ARTICLE) {
		s=&cc->art->buf[cc->art->len];
		
		if (cc->art->len>4) {
			s-=4;
		} else {
			s=cc->art->buf;
		}
		
		cc->art->len+=len;
		
		/*
		 * is the article empty?
		 */
		
		if ((cc->art->buf[0]=='.') &&
			(cc->art->buf[1]=='\r') && 
			(cc->art->buf[2]=='\n')) {
			int l;
			
			l=cc->art->len-3;
			cc->art->len=0;
			
			cc->cb.state="rda-cb";
			if (l) {
				cc->buflen=l;
				memcpy(cc->buf,&cc->art->buf[3],l);
			} else {
				cc->buflen=0;
			}
			
			if (cc->callback(cc)) {
				noNewOp=1;
			} else {
				noNewOp=0;
			}
			
			cc->bp=cc->buf;
			len=0;
			continue;
		}
		
		e=NULL;
		
		/*
 	 	 * is the article complete?
		 */
		
		while (s-cc->art->buf<cc->art->len-4) {
			if (((s[0]=='\r') &&
					(s[1]=='\n') &&
					(s[2]=='.') &&
					(s[3]=='\r') &&
					(s[4]=='\n'))) {
				e=s;
				break;
			}
			s++;
		}
		
		/*
		 * If the article is not complete, we need to make sure
		 * that our buffer hasn't overrun.  Note that the 2 level
		 * extent-like allocator is a target for replacement.
		 * If we haven't got the whole artice, read again.
		 */
		if (!e) {
			if (cc->art->len>=cc->art->bufsz-265) {
				if (cc->art->bufsz==BASE_ARTSIZE) {
					cc->art->buf=
						realloc(cc->art->bufp,MAX_ARTSIZE);
					assert(cc->art->buf!=NULL);
					cc->art->bufsz=MAX_ARTSIZE;
					cc->art->bufp=cc->art->buf;
					cc->art->buf+=255;
				} else {
					cc->flags|=NNTP_BAD_ARTICLE;	
					printf("oversize\n");
					memcpy(cc->art->buf,
						&cc->art->buf[cc->art->len-5],5);
					cc->art->len=5;
				}
			}
			
			ReadConnArt(cc);
			return;
			
		} 
		
		/*
		 * The whole article has been received.  Mess with the
		 * buffer, upcall, and then copy any remaining data
		 * back in to the normal connection buffer. 
		 */
		
		if (e) {
			int l;
			
			l=cc->art->len-((s+5)-cc->art->buf);
			cc->art->len=s-cc->art->buf;
//			*s=0;
			
			if (l) {
				cc->buflen=l;
				assert(l>-1);
				assert(l<cc->bufsz);
				memcpy(cc->buf,s+5,l);
			} else {
				cc->buflen=0;
			}
			
			cc->cb.state="rda-cb";
			if (cc->callback(cc)) {
				noNewOp=1;
			} else {
				noNewOp=0;
			}
			
			cc->bp=cc->buf;
			len=0;
		}
		
	}
	
	/*
	 * We get here when not reading an article.  The code below
	 * takes data read from a socket, finds the \r\n, replaces \r
	 * with a NUL, then upcalls to process the command.
	 */
	
	cc->buflen+=len;
	
	if (noNewOp) 
		return;
	
	if (!cc->buflen) {
		NextIo(cc);
		return;
	}
	
	while (1) {
		s=cc->bp;
		
		while ((*s!='\r') && (s-cc->buf<cc->buflen)) s++;
		
		if (s-cc->buf==cc->bufsz) {
			if (cc->bp>cc->buf) {
				cc->buflen-=cc->bp-cc->buf;
				memcpy(cc->buf,cc->bp,cc->buflen);
				cc->bp=cc->buf;
				NextIo(cc);
				cc->cb.state="rd-ovf";
				return;
			} else {
				cc->flags|=NNTP_CLOSE_CONNECTION;
				printf("overflow");
				NextIo(cc);
				return;
			}
		}
		
		if ((s-cc->buf==cc->buflen) || (*s!='\r')) {
			NextIo(cc);	
			return;
		}
		
		*s=0;
		
		cc->cb.state="rd-cb";
		
		r=cc->callback(cc);
		
		s+=2;
		cc->bp=s;
		if (cc->buflen<cc->bp-cc->buf)
			cc->bp=&cc->buf[cc->buflen];
		
		if (cc->buflen>cc->bufsz/2) 
			cc->flags|=NNTP_BUF_COMPRESS;
		
		if (cc->flags & NNTP_BUF_COMPRESS) {
			if (cc->bp>cc->buf) {
				cc->buflen-=cc->bp-cc->buf;
				memcpy(cc->buf,cc->bp,cc->buflen);
			} else {
				printf("why are we here %s %u\n",
					__FILE__,__LINE__);
				cc->buflen=0;
			}
			cc->bp=cc->buf;
			cc->flags&=~NNTP_BUF_COMPRESS;
		}	
		
		if (cc->bp==&cc->buf[cc->buflen]) {
			cc->bp=cc->buf;
			cc->buflen=0;
		}
		
		/*
		 * if the return from the upcall was nonzero, then
		 * we don't initiate another async operation. 
		 */
		
		if (r)
			return;
		
		if (cc->flags & NNTP_READ_ARTICLE) {
			cc->buflen-=cc->bp-cc->buf;
			assert(cc->buflen>-1);
			assert((cc->bp>=cc->buf) &&
				(cc->bp<cc->buf+cc->bufsz));
			cc->art->len=0;
			
			if (cc->buflen) {
				memcpy(cc->art->buf,cc->bp,cc->buflen);
				ReadComplete(cc,cc->buflen);
			} else {
				NextIo(cc);
			}
			return;
		}
		
		
	}
	
}

/*
 * WriteComplete()
 *
 * WriteComplete() is a low level upcall executed upon the completion of
 * an aio_write operation.  It doesn't do much since we driven exclusively
 * by reads.
 */

void
WriteComplete(struct context *cc,int len)
{
	
	cc->flags&=~NNTP_ASYNC_ACT;
	
	if (len<1) {
		printf("connclose/wc (%i) (%s/%s) (%u %s)\r\n",len,cc->state,cc->cb.state,cc->obuflen,cc->obuf);
		cc->flags|=NNTP_CLOSE_CONNECTION;
		cc->connErrorCode=errno;		
		NextIo(cc);
		return;
	}
	
	cc->obuflen-=len;
	
	assert(cc->callback!=NULL);
	
//	if (cc->callback(cc))
//		return;
	
	NextIo(cc);
}

/*
 * WriteConn()
 *
 * WriteConn is called to send the contents of cc->obuf.  Writes take 
 * precedence over reads, so if there are bytes to be sent, NextIo()
 * will call WriteConn.
 */

int
WriteConn(struct context *cc)
{
	
	assert(!(cc->flags & NNTP_ASYNC_ACT));
	assert(cc->obuflen<10000000);
	
	cc->flags|=NNTP_ASYNC_ACT;
	
	cc->cb.cb.aio_buf=cc->obuf;
	cc->cb.cb.aio_nbytes=cc->obuflen;
	cc->cb.callback=(void(*)(void *,int))WriteComplete;	
	cc->cb.cb._aiocb_private.privatemodes=0;
	
	if (cc->cb.logfd) {
//	write(1,cc->obuf,cc->obuflen);
		write(cc->cb.logfd,cc->obuf,cc->obuflen);
	}
	
	if (aio_write((struct aiocb *)cc)) 
		perror("WriteConn");
}

/*
 * ReadConn()
 *
 * ReadConn reads from the socket into the command buffer.
 */

int 
ReadConn(struct context *cc)
{
	assert(!(cc->flags & NNTP_ASYNC_ACT));
	
	cc->flags|=NNTP_ASYNC_ACT;
	
	cc->cb.cb.aio_buf=&cc->buf[cc->buflen];
	cc->cb.cb.aio_nbytes=cc->bufsz-cc->buflen;
	cc->cb.callback=(void(*)(void *,int))ReadComplete;	
	cc->cb.cb._aiocb_private.privatemodes=0;
	
	if (aio_read((struct aiocb *)cc))
		perror("ReadConn");
}		

/*
 * CopyArtDone()
 * CopyArtSendArt()
 * CopyArt()
 *
 * These three functions work together to async send an article.
 * CopyArt() sets up the context aiocb to read an article and
 * asks the store functions to read it.  CopyArtSendArt is the
 * callback routine for CopyArt, and takes the (just read) article
 * and aio_write's it to the wire.  CopyArtDone is the callback
 * for CopyArtSendArt, and makes sure the article went, then does
 * cleanup and resumes normal processing.
 */

void
CopyArtDone(struct context *cc, int len)
{
	
	
	cc->cb.cb.aio_nbytes-=len;
	
//printf("sent=%i,len=%i,left=%i\n",cc->obuflen,len,cc->cb.cb.aio_nbytes);
	
	if (cc->cb.cb.aio_nbytes) {
		printf("cad-connclose left=%i sent=%i\r\n",cc->cb.cb.aio_nbytes,len);
		perror("cad");
		cc->connErrorCode=errno;		
		cc->flags|=NNTP_CLOSE_CONNECTION;
		free(cc->hold);
		NextIo(cc);
		return;
	}
	
/* 
 * This block should have only been necessary when I forgot to turn off
 * FIONBIO on the socket 
 */
	
/*	if (cc->cb.cb.aio_nbytes) {
		cc->obuflen+=len;
		cc->cb.cb.aio_buf=cc->hold+cc->obuflen;		
		if (aio_write((struct aiocb *)cc))
			perror("aio_write in CopyArtDone");
		return;
	}
*/
	
	cc->flags&=~NNTP_ASYNC_ACT;
	
	cc->cb.cb.aio_buf-=cc->obuflen;
	assert(cc->cb.cb.aio_buf==cc->hold);
	cc->obuflen=0;
	
	free((void *)cc->cb.cb.aio_buf);
	cc->cb.cb.aio_buf=&cc->buf[cc->buflen];
	cc->cb.cb.aio_nbytes=cc->bufsz-cc->buflen;
	
	assert(cc->callback!=NULL);
	
	if (cc->buflen) {
//		printf("copy done,rc\n");
		ReadComplete(cc,0x7fffffff);
	} else {
//		printf("copy done,cc\n");
		if (!cc->callback(cc)) {
//			printf("nio o=%i\n",cc->obuflen);
//			printf("<=%s\n<=%s\n",cc->buf,cc->bp);
			NextIo(cc);
		}
	}
}		

void
CopyArtSendArt(struct context *cc,int len)
{
	int store;
	u_quad_t offset;
	
	assert(!(cc->flags & NNTP_ASYNC_ACT));
	
	cc->flags|=NNTP_ASYNC_ACT;
	
	if (len<0) { 
		abort();
	}
	
	if (cc->obuflen) {
		cc->cb.cb.aio_buf-=cc->obuflen;
		memcpy((void *)cc->cb.cb.aio_buf,cc->obuf,cc->obuflen);
		cc->cb.cb.aio_nbytes+=cc->obuflen;
		cc->obuflen=0;
	}
	
	if (DecipherKey(cc->key,&store,&offset)) {
		printf("Missing after READ!?!\n");
		cc->cb.cb.aio_nbytes=cc->obuflen;
	}
	
	((char *)cc->cb.cb.aio_buf)[cc->cb.cb.aio_nbytes++]='\r';	
	((char *)cc->cb.cb.aio_buf)[cc->cb.cb.aio_nbytes++]='\n';	
	((char *)cc->cb.cb.aio_buf)[cc->cb.cb.aio_nbytes++]='.';	
	((char *)cc->cb.cb.aio_buf)[cc->cb.cb.aio_nbytes++]='\r';	
	((char *)cc->cb.cb.aio_buf)[cc->cb.cb.aio_nbytes++]='\n';	
	
	cc->cb.cb.aio_fildes=cc->fd;
	cc->cb.callback=(void(*)(void *,int))CopyArtDone;
	cc->cb.state="sndart";
	
	if (aio_write((struct aiocb *)cc)) {
		cc->flags|=NNTP_CLOSE_CONNECTION;
		free(cc->hold);
		printf("close on send\n");
		NextIo(cc);
	}
}


void
MapAndSendCleanup(struct context *cc, int len) 
{

	cc->flags&=~NNTP_ASYNC_ACT;
	assert(len==cc->artlen);
	UnMapStore((void *)cc->cb.cb.aio_buf,cc->artlen);

	cc->cb.cb.aio_buf=&cc->buf[cc->buflen];
	cc->cb.cb.aio_nbytes=cc->bufsz-cc->buflen;

	cc->obuf[cc->obuflen++]='\r';	
	cc->obuf[cc->obuflen++]='\n';	
	cc->obuf[cc->obuflen++]='.';	
	cc->obuf[cc->obuflen++]='\r';	
	cc->obuf[cc->obuflen++]='\n';

	NextIo(cc);	

}


void
MapAndSendArt(struct context *cc, int len)
{

	cc->flags&=~NNTP_ASYNC_ACT;
	if (len<0) {
		printf("connclose/masa (%i) (%s/%s) (%u %s)\r\n",len,cc->state,cc->cb.state,cc->obuflen,cc->obuf);
		cc->flags|=NNTP_CLOSE_CONNECTION;
		NextIo(cc);
		return;
	}

	if (len!=cc->obuflen) {
		printf("lenghts not equal?  %u!=%u\n",len,cc->obuflen);
	}
//	assert(len==cc->obuflen);
	
	cc->obuflen-=len;

	cc->hold=MapFromStore(cc->key,cc->artlen);
	
	if (cc->hold==NULL) {
		printf("no mmap\n");
		cc->obuf[cc->obuflen++]='.';	
		cc->obuf[cc->obuflen++]='\r';	
		cc->obuf[cc->obuflen++]='\n';
                NextIo(cc);
                return;
        }

	cc->cb.cb.aio_buf=cc->hold;
	cc->cb.cb.aio_nbytes=cc->artlen;
	cc->cb.callback=(void(*)(void *,int))MapAndSendCleanup;
	cc->cb.state="mapsnd";
	cc->flags|=NNTP_ASYNC_ACT;

	if (aio_write((struct aiocb *)cc)) {
		cc->flags|=NNTP_CLOSE_CONNECTION;
		UnMapStore(cc->hold,cc->artlen);
		printf("close on send\n");
		NextIo(cc);
	}
}
	


int
CopyArt(struct context *cc,struct artent *pae)
{

#ifndef USEMMAPSENDS
	
	if (pae->magic!=ARTENTRY_MAGIC) 
		return -1;
	
	assert((pae->artlen+16+cc->obuflen)<MAX_ARTSIZE+1024);
	cc->hold=(void *)cc->cb.cb.aio_buf=malloc(pae->artlen+16+cc->obuflen);
	assert(cc->hold!=NULL);
	cc->cb.cb.aio_buf+=cc->obuflen;
	cc->cb.cb.aio_nbytes=pae->artlen;
	cc->cb.callback=(void(*)(void *,int))CopyArtSendArt;
	cc->cb.state="cpart";	
	
	cc->key=pae->key;
	
	if (GetFromStore(pae->key,(struct myaiocb *)cc)) {
		free(cc->hold);
		return -1;
	}
#else

	cc->key=pae->key;
	cc->artlen=pae->artlen;	

	if (cc->obuflen) {
		WriteConn(cc);
		cc->cb.callback=MapAndSendArt;
		return 0;
	}

	MapAndSendArt(cc,0);

	return 0;
#endif

}
