//
//sock.c
//
//Functions for handling sockets.
//
//
//-UserX 2001/11/07

#include "misc/compat.h"
#include <string.h>

//#include "isproxy.h"
#include "net/sock.h"
#include "base/mem.h"
#include "net/noderef.h"
#include "base/logger.h"
#include "crypt/random.h"

#include "pipe/dummy.h"

#ifndef _WINDOZE_
#include <errno.h>
#endif

//#ifdef _WINDOZE_
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
//#endif

/**
Functions for handling \Ref{SockHandle}s. <P>
Normally SockHandle's are maniuplated else where (\Ref{sockserv})
but to manual/direct use of \Ref{SockHandle} can be done in this manner.<P>
<B>Opening out bound connections</B><BR>
<CODE>
	SockHandle *sh; <BR>
	NodeRef *nr; <BR>
	int er; <BR>
	char *s; <BR>
<BR>
	nr = noderefMake(); <BR>
	nr->host = "myhost"; <BR>
	nr->port = 1234; <BR>
<BR>
	sh = \Ref{sockMake}(); <BR>
	er = sockSetNodeRef(sh, nr); <BR>
	noderefFree(nr); <BR>
	if(er != SOCK_ERR_NONE) { <BR>
		return; //insert error action <BR>
	} <BR>
<BR>
	er = sockOpen(sh); <BR>
	if(er != SOCK_ERR_NONE) { <BR>
		return; //insert error action <BR>
	} <BR>
<BR>
	sh->IOPipe->OutBuffer = dblockAppenString(sh->IOPipe->OutBuffer, "Hello World!"); <BR>
	sockWrite(sh); <BR>
<BR>
	sockRead(sh); <BR>
	s = dblockToString(sh->IOPipe->InBuffer); <BR>
<BR>
	sockClose(sh); <BR>
	sockFree(sh); <BR>
<BR>
</CODE>
<B>Inbound connections:</B><BR>
<CODE>
	er = sockSetLocalNodeRef(sh, nr); <BR>
	er = sockListen(sh); <BR>
	nsh = sockAccept(sh); <BR>
</CODE>
//todo: Better support for detecting inbound connections. <BR>
//todo: Allow options for non-blocking sockets.<BR>
//todo: Look into what needs to be done for supporting other transports.<BR>
@author UserX
@name sock
*/
//@{
struct sockaddr_in blanksockaddr_in = BLANKSOCKADDR_IN;
struct sockaddr_in localsockaddr_in = LOCALSOCKADDR_IN;

SockHandle blanksockhandle = {
		BLANKPIPE_SOCK,
		//NULL,				// in datablock
		//NULL,				// out datablock
		BLANKSOCKADDR_IN,	//remote sockaddress
		LOCALSOCKADDR_IN,	//local sockaddress
		//BLANKSOCKADDR_IN,	//remote sockaddress
		//LOCALSOCKADDR_IN,	//local sockaddress
		(SOCKET) SOCKET_ERROR,		//socket
		NULL,				//noderef
		NULL,				//localnoderef
		SOCK_STATUS_NEW,	//status
		SOCK_DIR_UNKNOWN,	//connection direction
		0,					//socket error
		0					//reg count
	};

/**
Initializes sock functions. Doesn't actually do anything now. 
Will probably be deleted later.
*/
void SocketAtStart() {
	LOGDEBUG ("SocketAtStart called.");
	return;
}

/**
Initializes an existing SockHandle. 
@param sh The SockHandle to initialize.
*/
void sockInit(SockHandle *sh) {
	LOGDEBUG (stringJoin("sockInit:", ptrToString(sh)));
	if(sh == NULL) {
		return;
	}
	sh->RegCount = 1;
	sh->status = SOCK_STATUS_NEW;
	pipeInit(&sh->IOPipe);
	pipeInitFunctions(&sh->IOPipe, 
		NULL, NULL,
		(PipeFuncAttach) sockPipeAttach, (PipeFuncDetach) sockPipeDetach,
		(PipeFuncClose) sockPipeClose, NULL);
	//sh->IOPipe.attachFunction = sockPipeAttach;
	//sh->IOPipe.detachFunction = sockPipeDetach;
	//sh->IOPipe.closeFunction = sockPipeClose;
	sh->IOPipe.status = PSTATUS_NORMAL;


}

/**
Creates and initializes a new SockHandle. 
@return Pointer to the new SockHandle. 
*/
SockHandle *sockMake() {
	SockHandle *sh;
	LOGMEMDUMP();
	sh = memCopy(&blanksockhandle, sizeof(SockHandle), "Sock", NULL);
	LOGDEBUG (stringJoin("sockMake:", ptrToString(sh)));
	sockInit(sh);
	return sh;
}

/**
Creates a new SockHandle and saves the pointer to the specified address. 
@param sh A pointer to where the pointer to a SockHandle will be stored. 
*/
void sockMakeAt(SockHandle **sh) {
	LOGDEBUG (stringJoin("sockMakeAt:", ptrToString(sh)));
	if(sh == NULL) {
		return;
	}
	*sh = sockMake();
}

/**
Increase the reference count to this sock. 
@param sh Pointer to the SockHandle. 
*/
void sockRegister(SockHandle *sh) {
	LOGDEBUG (stringJoin("sockRegister:", ptrToString(sh)));
	++sh->RegCount;
	return;
}

/**
Decrements the reference count to this sock and releases it when it reaches zero.
@param sh Pointer to the SockHandle to release.
*/
void sockFree(SockHandle *sh) {
	LOGDEBUG (stringJoin("sockFree:", ptrToString(sh)));
	LOGDEBUG (stringJoin("- reference count:", intToString(sh->RegCount)));
	if(--sh->RegCount != 0) {
		return;
	}
	noderefFree(sh->noderef);
	noderefFree(sh->localnoderef);
	sockClose(sh);
	pipeFree(&sh->IOPipe);
	memFree(sh);
	LOGMEMDUMP();
}

/**
Internal - Gets the error code of the last socket operation. 
@param so The socket handle.
*/
int socketGetError(SOCKET so) {
	int err = SOCKET_ERROR;
#ifdef _WINDOZE_
	err = WSAGetLastError();
#else
	err = errno;
#endif
	LOGDEBUG (stringJoinMany(
			"sockerGetError: socket(", 
			intToString(so),
			") error(",
			intToString(err),
			")",
			NULL));
	return err;
}

/**
Internal - Gets and saves the last socket error, also returns the error code.
@param sh Pointer to the SockHandle. 
@return The error code.
*/
int sockRecordError(SockHandle *sh) {
	LOGDEBUG (stringJoin("sockRecordError:", ptrToString(sh)));
	if(sh == NULL) {
		return SOCKET_ERROR;
	}
	return sh->socketerror = socketGetError(sh->Socket);
}

/**
Logs an error related to a SockHandle. 
@param sh Pointer to the SockHandle. 
@param msg Releasable string, descriptive context of the error. 
*/
void sockPrintError(SockHandle *sh, char *msg) {
	LOGERROR(stringJoinMany(
			"Socket(",
			intToString(sh->Socket),
			"), Error (",
			intToString(sh->socketerror),
			"):",
			msg,
			NULL
		));
}


/**
Internal - Converts a NodeRef to a network socket address.
@param nr Pointer to the NodeRef to lookup. 
@param sai Pointer to the address structure. 
@return Zero on success, non-zero error code on failure.
*/
int nodeRefToSockAddr(NodeRef *nr, struct sockaddr_in *sai) {

	if (sai == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}
	if (nr == NULL) {
		return SOCK_ERR_BADNODEREF;
	}

	sai->sin_port = htons((unsigned short)nr->PortNum);
	if(sai->sin_port == 0 && nr->PortNum != 0) {
		//noderefFree(nr);
		return SOCK_ERR_BADPORT;
	}
	sai->sin_addr.s_addr = inet_addr(nr->HostName); //try #.#.#.# first
	if(sai->sin_addr.s_addr == INADDR_NONE) {
		struct hostent *nameresult = gethostbyname(nr->HostName); // try a DNS lookup
		if (nameresult == NULL) {
			LOGERROR(stringJoinMany("nodeRefToSockAddr: bad host name: host(", 
						stringCopy(nr->HostName),
						"), port(", intToString(nr->PortNum) ,")",
						_("Failed to lookup host name."),
					NULL));
			return SOCK_ERR_BADHOST;
		}
		memcpy(&sai->sin_addr, nameresult->h_addr, nameresult->h_length);
	}

	return SOCK_ERR_NONE;

}

/**
Set the remote NodeRef, does a look up. A copy of the NodeRef is made.
@param sh The SockHandle.
@param nr The NodeRef to copy.
@return Zero if successful, or non-zero error code.
*/
int sockSetNodeRef(SockHandle *sh, NodeRef *nr) {
	struct sockaddr_in tmpin = BLANKSOCKADDR_IN;

	int er;
	if (sh == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}
	if (sh->noderef != NULL) {
		noderefFree(sh->noderef);
		sh->noderef = NULL;
	}

	if(nr == NULL) {
		return SOCK_ERR_BADNODEREF;
	}

	nr = noderefCopy(nr);
	//nodeRefFree(nr);

	er = nodeRefToSockAddr(nr, &tmpin);
	if(er != SOCK_ERR_NONE) {
		noderefFree(nr);
		return er;
	}

	sh->SockAddress.sin_port = tmpin.sin_port;
	sh->SockAddress.sin_addr.s_addr = tmpin.sin_addr.s_addr;
	sh->noderef = nr;
	return SOCK_ERR_NONE;

}

/**
Set the local NodeRef, does a look up. A copy of the NodeRef is made.
@param sh Pointer to the SockHandle.
@param nr Pointer to the NodeRef to copy.
@return Zero if successful, or non-zero error code.
*/
int sockSetLocalNodeRef(SockHandle *sh, NodeRef *nr) {
	struct sockaddr_in tmpin = BLANKSOCKADDR_IN;

	int er;
	if (sh == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}
	if (sh->localnoderef != NULL) {
		noderefFree(sh->localnoderef);
		sh->localnoderef = NULL;
	}

	if(nr == NULL) {
		return SOCK_ERR_BADNODEREF;
	}

	nr = noderefCopy(nr);
	//nodeRefFree(nr);

	er = nodeRefToSockAddr(nr, &tmpin);
	if(er != SOCK_ERR_NONE) {
		noderefFree(nr);
		return er;
	}

	sh->LocalSockAddress.sin_port = tmpin.sin_port;
	sh->LocalSockAddress.sin_addr.s_addr = tmpin.sin_addr.s_addr;
	sh->localnoderef = nr;
	return SOCK_ERR_NONE;

}

/**
Internal - Sets the remote network socket address. Does not update any NodeRef
@param sh Pointer to the SockHandle.
@param sain Pointer to the network address to set.
@return Zero if successful, or non-zero error code.
*/
int sockSetSockAddress(SockHandle *sh, struct sockaddr_in *sain) {
	if(sh == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}
	if(sain == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}

	memcpy(&sh->SockAddress, sain, sizeof(struct sockaddr_in));
	return SOCK_ERR_NONE;
}

/**
Internal - Sets the local network socket address. Does not update any NodeRef
@param sh Pointer to the SockHandle.
@param sain Pointer to the network address to set.
@return Zero if successful, or non-zero error code.
*/
int sockSetLocalSockAddress(SockHandle *sh, struct sockaddr_in *sain) {
	if(sh == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}
	if(sain == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}

	memcpy(&sh->SockAddress, sain, sizeof(struct sockaddr_in));
	return SOCK_ERR_NONE;
}



/**
Opens an outbound connection.
@param sh Pointer to the SockHandle to use.
@return Zero if successful, or non-zero error code.
*/
int sockOpen(SockHandle *sh) {
	int er;
	LOGDEBUG (stringJoin("sockOpen:", ptrToString(sh)));
	if(sh == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}
	sh->direction = SOCK_DIR_OUT;
	//todo: check status
	//todo: check return error codes
#ifdef __FreeBSD__
        sh->Socket = socket(PF_INET, SOCK_STREAM, 0);
#else
	sh->Socket = socket(AF_INET, SOCK_STREAM, 0);
#endif

	LOGDEBUG(stringJoin("- socket:", intToString(sh->Socket)));

	if(sh->Socket == SOCKET_ERROR) {
		return SOCK_ERR_SOCKETERROR;
	}
	er = bind(sh->Socket, (const struct sockaddr *) &sh->LocalSockAddress, sizeof(struct sockaddr_in));
	if(er == SOCKET_ERROR) {
		sockRecordError(sh);
		sockPrintError(sh, "Failed to bind new connection.");
		sockClose(sh);
		return SOCK_ERR_BINDFAILED;
	}

	er = connect(sh->Socket, (const struct sockaddr *) &sh->SockAddress, sizeof(struct sockaddr_in));
	//sh
	if(er == SOCKET_ERROR) {
		sockRecordError(sh);
		sockPrintError(sh, "Failed to open new connection.");
		LOGERROR(stringJoinMany("sockOpen: open failed: host(", stringCopy(sh->noderef->HostName),
					"), port(", intToString(sh->noderef->PortNum) ,")",
					_("Failed to open an outbound connection."),
				NULL));
		sockClose(sh);
		return SOCK_ERR_NOCONNECT;
	}

	sh->status = SOCK_STATUS_OPEN;
	sh->IOPipe.status |= PSTATUS_OPENED;
	
	randomAddEntropyClock(2);

	return SOCK_ERR_NONE;
}


/**
Closes a connection.
@param sh Pointer to the SockHandle to use.
@return Zero if successful, or non-zero error code.
*/
int sockClose(SockHandle *sh) {
	LOGDEBUG (stringJoin("sockClose:", ptrToString(sh)));
	if(sh == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}
	//todo: check status
	//todo: check return error codes

	if(sh->Socket != SOCKET_ERROR) {
		closesocket(sh->Socket);
	}

	sh->direction = SOCK_DIR_UNKNOWN;

	sh->status = SOCK_STATUS_CLOSED;
	sh->IOPipe.status |= PSTATUS_CLOSED;
	sh->Socket = (SOCKET) SOCKET_ERROR;

	randomAddEntropyClock(1);

	return SOCK_ERR_NONE;
}

/**
Starts a SockHandle listening for connections. 
@param sh Pointer to the SockHandle to use.
@return Zero if successful, or non-zero error code.
*/
int sockListen(SockHandle *sh) {
	int er;
	LOGDEBUG (stringJoin("sockListen:", ptrToString(sh)));
	if(sh == NULL) {
		return SOCK_ERR_NOSOCKHANDLE;
	}
	sh->direction = SOCK_DIR_IN;
	//todo: check status
	//todo: check return error codes
#ifdef __FreeBSD__
        sh->Socket = socket(PF_INET, SOCK_STREAM, 0);
#else
	sh->Socket = socket(AF_INET, SOCK_STREAM, 0);
#endif
	if(sh->Socket == SOCKET_ERROR) {
		return SOCK_ERR_SOCKETERROR;
	}
	er = bind(sh->Socket, (struct sockaddr *) &sh->LocalSockAddress, sizeof(struct sockaddr_in));
	
	if(er == SOCKET_ERROR) {
		sockRecordError(sh);
		sockPrintError(sh, "Failed to bind listening socket.");
		LOGERROR(stringJoinMany("sockListen: bind failed: host(", stringCopy(sh->localnoderef->HostName),
					"), port(", intToString(sh->localnoderef->PortNum) ,")",
					_("Failed to bind listening socket."),
				NULL));
		sockClose(sh);
		return SOCK_ERR_BINDFAILED;
	}

	er = listen(sh->Socket, SOMAXCONN);

	if(er == SOCKET_ERROR) {
		sockRecordError(sh);
		sockPrintError(sh, "Failed to establish listening socket.");
		LOGERROR(stringJoinMany("sockListen: listen failed: host(", stringCopy(sh->localnoderef->HostName),
					"), port(", intToString(sh->localnoderef->PortNum) ,")",
					_("Failed to establish a listening socket."),
				NULL));
		sockClose(sh);
		return SOCK_ERR_LISTENFAILED;
	}

	randomAddEntropyClock(1);

	sh->status = SOCK_STATUS_LISTENING;

	LOGDEBUG(stringJoin("- socket:", intToString(sh->Socket)));

	return SOCK_ERR_NONE;
}

/**
Accepts an incoming connection from a listening SockHandle.
@param sh Pointer to the SockHandle to use.
@return A pointer to the SockHandle of the new connection. 
*/
SockHandle *sockAccept(SockHandle *sh) {
	SockHandle *nsh;
	int i = sizeof(struct sockaddr_in);
	LOGDEBUG(stringJoin("sockAccept:", ptrToString(sh)));
	if(sh == NULL) {
		return NULL;
	}
	//todo: check status
	nsh = sockMake();
	LOGDEBUG(stringJoin("- SockHandle:", ptrToString(nsh)));
	nsh->direction = SOCK_DIR_IN;

	nsh->Socket = accept(sh->Socket, (struct sockaddr *) &sh->SockAddress, &i);

	LOGDEBUG(stringJoin("- socket:", intToString(sh->Socket)));

	if(nsh->Socket == SOCKET_ERROR) {
		sockFree(nsh);
		return NULL;
	}


	memcpy(&nsh->LocalSockAddress, &sh->LocalSockAddress, sizeof(struct sockaddr_in));

	nsh->status = SOCK_STATUS_OPEN;
	nsh->IOPipe.status |= PSTATUS_OPENED;
	
	nsh->localnoderef = noderefCopy(sh->localnoderef);

	randomAddEntropyClock(2);

	return nsh;
	
}


/**
Receives notice that a pipe has been attached to this SockHandle. 
@param sh Pointer to the SockHandle in question. 
@param newpipe Pointer to the Pipe being attached. 
*/
void sockPipeAttach(SockHandle *sh, Pipe *newpipe) {
	LOGDEBUG(stringJoin("sockPipeAttach:", ptrToString(sh)));
	sockRegister(sh);
}

/**
Receives notice that a pipe has been detached from this SockHandle. 
@param sh Pointer to the SockHandle in question. 
*/
void sockPipeDetach(SockHandle *sh) {
	LOGDEBUG(stringJoin("sockPipeDetach:", ptrToString(sh)));
	sockFree(sh);
}

/**
Receives notice that a pipe that this SockHandle is part of is closing. 
@param sh Pointer to the SockHandle in question. 
*/
void sockPipeClose(SockHandle *sh) {
	LOGDEBUG(stringJoin("sockPipeClose:", ptrToString(sh)));
	sockClose(sh);
}

/**
Internal - Read data from a socket into a DataBlock. 
@param so The socket handle to read from.
@param db The DataBlock to store read data. 
@param status Pointer to a SockHandle status.
@return The new pointer for <B>db</B>. 
*/
DataBlock *socketRead(SOCKET so, DataBlock *db, int *status) {
	int i;
	if(so == SOCKET_ERROR) {
		return db;
	}
	db = dblockExpand(db, db->size + 1024);

	randomAddEntropyClock(2);

	i = recv(so, SOCKETBUFFERCAST db->data + db->size, db->maxSize - db->size, MSG_NOSIGNAL);
	if(i == SOCKET_ERROR || i == 0) {
		if(status != NULL) {
			*status = SOCK_STATUS_CLOSING;
		}
		//sockRecordError(sh);
	} else {
		db->size += i;
		LOGDEBUGTRAFFIC(stringJoinMany("socketRead: socket ", 
				intToString(so),
				" got bytes ", 
				intToHexString(i),
			NULL));
	}
	return db;
}

/**
Internal - Writes data to a socket from a DataBlock. 
@param so The socket handle to read from.
@param db The DataBlock with the data to be sent. 
@param status Pointer to a SockHandle status.
@return The new pointer for <B>db</B> cotaining the unsent portion. 
*/
DataBlock *socketWrite(SOCKET so, DataBlock *db, int *status) {
	int i = SOCK_MAX_WRITE;
	int er;
	if(so == SOCKET_ERROR || db == NULL || db->size == 0) {
		return db;
	}
	if(db->size < i) {
		if(db->size < 0) {
			db->size = 0;
		}
		i = db->size;
		
	}
	if(i == 0) {
		return db;
	}

	randomAddEntropyClock(2);

	er = send(so, SOCKETBUFFERCAST db->data, i, MSG_NOSIGNAL);
	if(er == SOCKET_ERROR || er == 0) {
		if(status != NULL) {
			*status = SOCK_STATUS_CLOSING;
		}
		//sockRecordError(sh);
	} else {
		db = dblockDelete(db, 0, er);
		LOGDEBUGTRAFFIC(stringJoinMany("socketWrite: socket ", 
				intToString(so),
				" sent bytes ", 
				intToHexString(er),
			NULL));
	}
	return db;
}

/**
Reads data from the SockHandle's socket and into its Pipe. 
@param sh Pointer to the SockHandle to use. 
*/
void sockRead(SockHandle *sh) {
	sh->IOPipe.inBuffer = socketRead(sh->Socket, sh->IOPipe.inBuffer, &sh->status);
}

/**
Writes the data queued in the SockHandle's Pipe out to it's corresponding socket.
@param sh Pointer to the SockHandle to use. 
*/
void sockWrite(SockHandle *sh) {
	sh->IOPipe.outBuffer = socketWrite(sh->Socket, sh->IOPipe.outBuffer, &sh->status);
}

//@}
