/* crypto/bio/bss_bio.c  -*- Mode: C; c-file-style: "eay" -*- */

/*  *** Not yet finished (or even tested). *** */

/* Special method for a BIO where the other endpoint is also a BIO
 * of this kind, handled by the same thread.
 * Such "BIO pairs" are mainly for using the SSL library with I/O interfaces
 * for which no specific BIO method is available. */

#include <assert.h>
#include <stdlib.h>

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/crypto.h>

static int bio_new(BIO *bio);
static int bio_free(BIO *bio);
static int bio_read(BIO *bio, char *buf, int size);
static int bio_write(BIO *bio, char *buf, int num);
static long bio_ctrl(BIO *bio, int cmd, long num, char *ptr);
static int bio_puts(BIO *bio, char *str);

static int bio_make_pair(BIO *bio1, BIO *bio2);
static void bio_destroy_pair(BIO *bio);

static BIO_METHOD methods_biop =
{
	BIO_TYPE_BIO,
	"BIO pair",
	bio_write,
	bio_read,
	bio_puts,
	NULL /* no bio_gets */,
	bio_ctrl,
	bio_new,
	bio_free
};

BIO_METHOD *BIO_s_bio(void)
	{
	return &methods_biop;
	}

struct bio_bio_st
{
	BIO *peer;     /* NULL if buf == NULL.
	                * If peer != NULL, then peer->ptr is also a bio_bio_st,
	                * and its "peer" member points back to us.
	                * peer != NULL iff init != 0 in the BIO. */
	
	/* This is for what we write (i.e. reading uses peer's struct): */
	int closed;     /* valid iff peer != NULL */
	size_t len;     /* valid iff buf != NULL; 0 if peer == NULL */
	size_t offset;  /* valid iff buf != NULL; 0 if len == 0 */
	size_t size;
	char *buf;      /* "size" elements (if != NULL) */

	size_t request; /* valid iff peer != NULL; 0 if len != 0;
	                 * otherwise set by peer to number of bytes
	                 * it (unsuccesfully) tried to read. */
};

static int bio_new(BIO *bio)
	{
	struct bio_bio_st *b;
	
	b = Malloc(sizeof *b);
	if (b == NULL)
		return 0;

	b->peer = NULL;
	b->size = 17*1024; /* enough for one TLS record (just a default) */
	b->buf = NULL;

	return 1;
	}


static int bio_free(BIO *bio)
	{
	struct bio_bio_st *b;

	if (bio == NULL)
		return 0;
	b = bio->ptr;

	assert(b != NULL);

	if (b->peer)
		bio_destroy_pair(bio);
	
	if (b->buf != NULL)
		{
		Free(b->buf);
		}

	Free(b);

	return 1;
	}



static int bio_read(BIO *bio, char *buf, int size)
	{
	/* XXX */
	return -1;
	}

static int bio_write(BIO *bio, char *buf, int num)
	{
	/* XXX */
	return -1;
	}
	
static long bio_ctrl(BIO *bio, int cmd, long num, char *ptr)
	{
	long ret;
	struct bio_bio_st *b = bio->ptr;
	
	assert(b != NULL);

	switch (cmd)
		{
		/* XXX Additional commands: */
		/* - Set buffer size */
		/* - make pair */
		/* - destroy pair */
		/* - get number of bytes that the next write will accept */
		/* - get number of bytes requested by peer */
		/* - send "close" */

	case BIO_CTRL_RESET:
		if (b->buf != NULL)
			{
			b->len = 0;
			b->offset = 0;
			}
		ret = 0;
		break;		

	case BIO_CTRL_GET_CLOSE:
		ret = bio->shutdown;
		break;

	case BIO_CTRL_SET_CLOSE:
		bio->shutdown = (int) num;
		ret = 1;
		break;

	case BIO_CTRL_PENDING:
		if (b->peer != NULL)
			{
			struct bio_bio_st *peer_b =b->peer->ptr;
			
			ret = (long) peer_b->len;
			}
		else
			ret = 0;
		break;

	case BIO_CTRL_WPENDING:
		if (b->buf != NULL)
			ret = (long) b->len;
		else
			ret = 0;
		break;

	case BIO_CTRL_DUP:
		/* XXX */
		ret = 1;
		break;

	case BIO_CTRL_FLUSH:
		ret = 1;
		break;

	default:
		ret = 0;
		}
	return ret;
	}

static int bio_puts(BIO *bio, char *str)
	{
	return bio_write(bio, str, strlen(str));
	}

/* Until bio_make_pair is used, make a dummy function use it for -pedantic */
void dummy() { bio_make_pair(NULL,NULL); }

static int bio_make_pair(BIO *bio1, BIO *bio2)
	{
	struct bio_bio_st *b1, *b2;

	assert(bio1 != NULL);
	assert(bio2 != NULL);

	b1 = bio1->ptr;
	b2 = bio2->ptr;
	
	if (b1->peer != NULL || b2->peer != NULL)
		{
		BIOerr(BIO_F_BIO_MAKE_PAIR, BIO_R_IN_USE);
		return 0;
		}
	
	if (b1->buf == NULL)
		{
		b1->buf = Malloc(b1->size);
		if (b1->buf == NULL)
			{
			BIOerr(BIO_F_BIO_MAKE_PAIR, ERR_R_MALLOC_FAILURE);
			return 0;
			}
		b1->len = 0;
		b1->offset = 0;
		}
	
	if (b2->buf == NULL)
		{
		b2->buf = Malloc(b2->size);
		if (b2->buf == NULL)
			{
			BIOerr(BIO_F_BIO_MAKE_PAIR, ERR_R_MALLOC_FAILURE);
			return 0;
			}
		b2->len = 0;
		b2->offset = 0;
		}
	
	b1->peer = bio2;
	b1->closed = 0;
	b1->request = 0;
	b2->peer = bio1;
	b2->closed = 0;
	b2->request = 0;

	bio1->init = 1;
	bio2->init = 1;

	return 1;
	}

static void bio_destroy_pair(BIO *bio)
	{
	struct bio_bio_st *b = bio->ptr;

	if (b != NULL)
		{
		BIO *peer_bio = b->peer;

		if (peer_bio != NULL)
			{
			struct bio_bio_st *peer_b = peer_bio->ptr;

			assert(peer_b != NULL);
			assert(peer_b->peer == bio);

			peer_b->peer = NULL;
			peer_bio->init = 0;
			assert(peer_b->buf != NULL);
			peer_b->len = 0;
			peer_b->offset = 0;
			
			b->peer = NULL;
			bio->init = 0;
			assert(b->buf != NULL);
			b->len = 0;
			b->offset = 0;
			}
		}
	}
