/*
 *  lib/conn_list.c
 *
 *  Connection Management
 *
 *  Copyright (C) 2004  Phoenix Grid Computing Project
 *                      ( http://www.logos.ic.i.u-tokyo.ac.jp/phoenix/ )
 *
 *  2004-12-07  Conn_pending () added 
 *              by Kaneda Kenji
 *  2004-11-26  Implemented basic functions 
 *              by Kaneda Kenji
 */


#include "common.h"
#include "debug.h"
#include "std.h"
#include "xssl.h"
#include "conn/common.h"


/* Connect the BIO <bio> for the read and write operations
 * of the TLS/SSL (encrypted) side of <ssl>. */
static void
set_ssl_read_write ( SSL *ssl, BIO *bio )
{
	assert ( ssl != NULL );
	assert ( bio != NULL );
	
	SSL_set_bio ( ssl, 
		      bio /* rbio */, 
		      bio /* wbio */ );
}

struct conn_t *
Conn_new ( SSL_CTX *ctx, int sockfd )
{
	struct conn_t *x;

	assert ( ctx != NULL );

	x = ( struct conn_t *) Malloc ( sizeof ( struct conn_t ) );

	x->sockfd = sockfd;
	x->ssl = xSSL_new ( ctx );
	x->bio = BIO_new_socket ( sockfd, BIO_NOCLOSE ); /* [???] what does NOCLOSE mean? */

	set_ssl_read_write ( x->ssl, x->bio );

	return x;
}

void
Conn_destroy ( struct conn_t *x )
{
	assert ( x != NULL );
	assert ( x->ssl != NULL );
	assert ( x->bio != NULL );

	SSL_free ( x->ssl );
	/* close <x->bio> ??? */

	Free ( x );
}

struct conn_t *
Conn_connect ( SSL_CTX *ctx, int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen, int *ret )
{
	struct conn_t *conn;

	assert ( ctx != NULL );
	assert ( ret != NULL );
	
	*ret = connect ( sockfd, serv_addr, addrlen );
	if ( *ret == -1 ) /* [TODO] non-blocking */
		return NULL;

	conn = Conn_new ( ctx, sockfd );

	*ret = SSL_connect ( conn->ssl );
	
	if ( *ret == -1 ) {
		ssl_failure ( "SSL_connect" ); /* [DEBUG] */
		Conn_destroy ( conn );
		return NULL;
	}

	return conn;
}

struct conn_t *
Conn_accept ( SSL_CTX *ctx, int s, struct sockaddr *addr, socklen_t *addrlen, int *ret )
{
	struct conn_t *conn;
	int new_sockfd;

	assert ( ctx != NULL );
	assert ( ret != NULL );

	*ret = accept ( s, addr, addrlen );
	if ( *ret == -1 )
		return NULL;

	new_sockfd = *ret;
	conn = Conn_new ( ctx, new_sockfd );

	*ret = SSL_accept ( conn->ssl );

	if ( *ret == -1 ) {
		ssl_failure ( "SSL_accept" ); /* [DEBUG] */
		Conn_destroy ( conn );
		return NULL;
	}

	*ret = new_sockfd;
	return conn;
}

ssize_t
Conn_send ( struct conn_t *conn, const void *buf, size_t len, int flags )
{
	ssize_t ret;
	int err;

	assert ( conn != NULL );

	ret = SSL_write ( conn->ssl, buf, len );

	err = SSL_get_error ( conn->ssl, ret );
	switch ( err ) {
	case SSL_ERROR_NONE:
		break;
	case SSL_ERROR_WANT_READ:
		ssl_failure ( "Conn_send: SSL_ERROR_WANT_READ\n" );
		break;
	case SSL_ERROR_WANT_WRITE:
		ssl_failure ( "Conn_send: SSL_ERROR_WANT_WRITE\n" );
		break;
	default:
		break;
	}


	return ret;
}

ssize_t
Conn_recv ( struct conn_t *conn, void *buf, size_t len, int flags )
{
	ssize_t ret;
	int err;

	assert ( conn != NULL );

	ret = SSL_read ( conn->ssl, buf, len );

	err = SSL_get_error ( conn->ssl, ret );
	switch ( err ) {
	case SSL_ERROR_NONE:
		break;
	case SSL_ERROR_WANT_READ:
		ssl_failure ( "Conn_recv: SSL_ERROR_WANT_READ\n" );
	case SSL_ERROR_WANT_WRITE:
		ssl_failure ( "Conn_recv: SSL_ERROR_WANT_WRITE\n" );
		break;
	default:
		break;
	}

	return ret;
}


int
Conn_shutdown ( struct conn_t *conn, int how )
{
	assert ( conn != NULL );

        return SSL_shutdown ( conn->ssl ); /* ??? */
}

int
Conn_pending ( struct conn_t *conn )
{
	assert ( conn != NULL );

	return SSL_pending ( conn->ssl );
}

/*******************************************************/

struct conn_list_entry_t *
ConnListEntry_new ( struct conn_t *conn )
{
	struct conn_list_entry_t *x;

	assert ( conn != NULL );

	x = ( struct conn_list_entry_t *) Malloc ( sizeof ( struct conn_list_entry_t ) );

	x->conn = conn;
	x->next = NULL;

	return x;
}

void
ConnListEntry_destroy ( struct conn_list_entry_t *x )
{
	assert ( x != NULL );
	
	Conn_destroy ( x->conn );

	Free ( x );
}

/*******************************************************/

struct conn_list_t *
ConnList_new ( void )
{
	struct conn_list_t *x;

	x = ( struct conn_list_t *) Malloc ( sizeof ( struct conn_list_t ) );

	x->head = NULL;

	return x;
}

struct conn_t *
ConnList_find ( const struct conn_list_t *t, int sockfd )
{
	struct conn_list_entry_t *e;
	
	assert ( t != NULL );
	
	for ( e = t->head; e != NULL; e = e->next ) {
		if ( e->conn->sockfd == sockfd )
			return e->conn;
	}

	return NULL;
}

void
ConnList_add ( struct conn_list_t *t, struct conn_t *conn )
{
	struct conn_list_entry_t *e;

	assert ( t != NULL );
	assert ( conn != NULL );

	/* Assert that <e> has not already been inserted in <t>. */
	assert ( ConnList_find ( t, conn->sockfd ) == NULL );

	e = ConnListEntry_new ( conn );

	/* Add <e> to the head of <t>. */
	e->next = t->head;
	t->head = e;
}

bool_t
ConnList_delete ( struct conn_list_t *t, int sockfd )
{
	struct conn_list_entry_t *p, *prev;

	assert ( t != NULL );

	/* Find <p> such that <p->sockfd> is equal to <sockfd>. */
	p = t->head;
	prev = NULL;
	while ( p != NULL ) {
		if ( p->conn->sockfd == sockfd )
			break;

		prev = p;
		p = p->next;
	}
	
	if ( p == NULL ) /* Cannot find. */
		return FALSE;
	
	/* Remove <p> from <t>. */
	if ( prev == NULL ) {
		t->head = p->next;
	} else {
		prev->next = p->next;
	}

	ConnListEntry_destroy(p);
	
	return TRUE;
}

void
ConnList_destroy ( struct conn_list_t *t )
{
	struct conn_list_entry_t *e;

	assert ( t != NULL );

	e = t->head;
	while ( e != NULL ) {
		struct conn_list_entry_t *next;

		next = e->next;
		ConnListEntry_destroy ( e );
		e = next;
	}

	Free ( t );
}
