#include <config.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <glib.h>
#include <glib/gprintf.h>

#include "giop-private.h"
#include "giop-debug.h"
#include <matecorba/GIOP/giop-types.h>
#include <matecorba/GIOP/giop-recv-buffer.h>

#undef DEBUG

#ifdef G_ENABLE_DEBUG
void (*giop_debug_hook_unexpected_reply) (GIOPRecvBuffer        *buf) = NULL;
void (*giop_debug_hook_spoofed_reply)    (GIOPRecvBuffer        *buf,
					  GIOPMessageQueueEntry *ent) = NULL;
void (*giop_debug_hook_incoming_mangler) (GIOPRecvBuffer        *buf) = NULL;
#endif


#define MORE_FRAGMENTS_FOLLOW(buf) ((buf)->msg.header.flags & GIOP_FLAG_FRAGMENTED)

/*
 * FIXME: pretty much this whole module,
 * and all the giop-types headers should be
 * auto-generated and generic ...
 */

/* A list of GIOPMessageQueueEntrys */
static GList  *giop_queued_messages = NULL;
static GMutex *giop_queued_messages_lock = NULL;

/* Don't do this genericaly, union's suck genericaly */
static gboolean
giop_GIOP_TargetAddress_demarshal (GIOPRecvBuffer     *buf,
				   GIOP_TargetAddress *value)
{
	gboolean do_bswap = giop_msg_conversion_needed (buf);

	buf->cur = ALIGN_ADDRESS(buf->cur, 2);
	if ((buf->cur + 2) > buf->end)
		return TRUE;
	if (do_bswap)
		value->_d = GUINT16_SWAP_LE_BE (
			*(guint16 *) buf->cur);
	else
		value->_d = *(guint16 *) buf->cur;
	buf->cur += 2;

	switch (value->_d) {
	case GIOP_KeyAddr:
		buf->cur = ALIGN_ADDRESS (buf->cur, 4);

		if ((buf->cur + 4) > buf->end)
			return TRUE;
		value->_u.object_key._release = CORBA_FALSE;
		if (do_bswap)
			value->_u.object_key._length = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
		else
			value->_u.object_key._length = *((guint32 *)buf->cur);
		buf->cur += 4;
		if ((buf->cur + value->_u.object_key._length) > buf->end ||
		    (buf->cur + value->_u.object_key._length) < buf->cur)
			return TRUE;
		value->_u.object_key._buffer = buf->cur;
		buf->cur += value->_u.object_key._length;

		break;
	case GIOP_ProfileAddr:
		g_warning ("XXX FIXME GIOP_ProfileAddr not handled");
		return TRUE;
		break;
	case GIOP_ReferenceAddr:
		g_warning ("XXX FIXME GIOP_ReferenceAddr not handled");
		return TRUE;
		break;
	}

	return FALSE;
}

static gboolean
giop_IOP_ServiceContextList_demarshal (GIOPRecvBuffer         *buf,
				       IOP_ServiceContextList *value)
{
	return MateCORBA_demarshal_value (
		TC_IOP_ServiceContextList,
		(gpointer *)&value, buf, NULL);
}

static void
giop_IOP_ServiceContextList_free (IOP_ServiceContextList *value)
{
	if (value)
		CORBA_free (value->_buffer);
}

static gboolean
giop_recv_buffer_demarshal_request_1_1(GIOPRecvBuffer *buf)
{
  gboolean do_bswap = giop_msg_conversion_needed(buf);
  CORBA_unsigned_long oplen;

  buf->msg.u.request_1_1.service_context._buffer = NULL;
  if(giop_IOP_ServiceContextList_demarshal(buf, &buf->msg.u.request_1_1.service_context))
    return TRUE;

  buf->cur = ALIGN_ADDRESS(buf->cur, 4);

  if((buf->cur + 12) > buf->end)
    return TRUE;

  if(do_bswap)
    buf->msg.u.request_1_1.request_id = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
  else
    buf->msg.u.request_1_1.request_id = *((guint32 *)buf->cur);
  buf->cur += 4;
  buf->msg.u.request_1_1.response_expected = *buf->cur;
  buf->cur += 4;
  if(do_bswap)
    buf->msg.u.request_1_1.object_key._length = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
  else
    buf->msg.u.request_1_1.object_key._length = *((guint32 *)buf->cur);
  buf->cur += 4;
  
  if((buf->cur + buf->msg.u.request_1_1.object_key._length) > buf->end
     || (buf->cur + buf->msg.u.request_1_1.object_key._length) < buf->cur)
    return TRUE;

  buf->msg.u.request_1_1.object_key._buffer = buf->cur;
  buf->msg.u.request_1_1.object_key._release = CORBA_FALSE;

  buf->cur += buf->msg.u.request_1_1.object_key._length;
  buf->cur = ALIGN_ADDRESS(buf->cur, 4);
  if((buf->cur + 4) > buf->end)
    return TRUE;
  if(do_bswap)
    oplen = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
  else
    oplen = *((guint32 *)buf->cur);
  buf->cur += 4;

  if((buf->cur + oplen) > buf->end
     || (buf->cur + oplen) < buf->cur)
    return TRUE;

  buf->msg.u.request_1_1.operation = (CORBA_char *) buf->cur;
  buf->cur += oplen;
  buf->cur = ALIGN_ADDRESS(buf->cur, 4);
  if((buf->cur + 4) > buf->end)
    return TRUE;

  if(do_bswap)
    buf->msg.u.request_1_1.requesting_principal._length = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
  else
    buf->msg.u.request_1_1.requesting_principal._length = *((guint32 *)buf->cur);

  buf->cur += 4;
  if((buf->cur + buf->msg.u.request_1_1.requesting_principal._length) > buf->end
     || (buf->cur + buf->msg.u.request_1_1.requesting_principal._length) < buf->cur)
    return TRUE;

  buf->msg.u.request_1_1.requesting_principal._buffer = buf->cur;
  buf->msg.u.request_1_1.requesting_principal._release = CORBA_FALSE;
  buf->cur += buf->msg.u.request_1_1.requesting_principal._length;

  return FALSE;
}

static gboolean
giop_recv_buffer_demarshal_request_1_2(GIOPRecvBuffer *buf)
{
  gboolean do_bswap = giop_msg_conversion_needed(buf);
  CORBA_unsigned_long oplen;

  buf->cur = ALIGN_ADDRESS(buf->cur, 4);

  if((buf->cur + 8) > buf->end)
    return TRUE;

  if(do_bswap)
    buf->msg.u.request_1_2.request_id = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
  else
    buf->msg.u.request_1_2.request_id = *((guint32 *) buf->cur);
  buf->cur += 4;
  buf->msg.u.request_1_2.response_flags = *buf->cur;
  buf->cur += 4;

  if(giop_GIOP_TargetAddress_demarshal(buf, &buf->msg.u.request_1_2.target))
    return TRUE;

  buf->cur = ALIGN_ADDRESS(buf->cur, 4);
  if((buf->cur + 4) > buf->end)
    return TRUE;

  if(do_bswap)
    oplen = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
  else
    oplen = *((guint32 *)buf->cur);
  buf->cur += 4;

  if((buf->cur + oplen) > buf->end
     || (buf->cur + oplen) < buf->cur)
    return TRUE;

  buf->msg.u.request_1_2.operation = (CORBA_char *) buf->cur;
  buf->cur += oplen;

  buf->msg.u.request_1_2.service_context._buffer = NULL;
  if(giop_IOP_ServiceContextList_demarshal(buf, &buf->msg.u.request_1_2.service_context))
    return TRUE;
  buf->cur = ALIGN_ADDRESS(buf->cur, 8);

  return FALSE;
}

static gboolean
giop_recv_buffer_demarshal_reply_1_1(GIOPRecvBuffer *buf)
{
  gboolean do_bswap = giop_msg_conversion_needed(buf);

  buf->msg.u.reply_1_1.service_context._buffer = NULL;
  if(giop_IOP_ServiceContextList_demarshal(buf, &buf->msg.u.reply_1_1.service_context))
    return TRUE;
  buf->cur = ALIGN_ADDRESS(buf->cur, 4);
  if((buf->cur + 8) > buf->end)
    return TRUE;
  if(do_bswap)
    {
      buf->msg.u.reply_1_1.request_id = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
      buf->cur += 4;
      buf->msg.u.reply_1_1.reply_status = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
    }
  else
    {
      buf->msg.u.reply_1_1.request_id = *((guint32 *)buf->cur);
      buf->cur += 4;
      buf->msg.u.reply_1_1.reply_status = *((guint32 *)buf->cur);
    }
  buf->cur += 4;

 return FALSE;
}

static gboolean
giop_recv_buffer_demarshal_reply_1_2(GIOPRecvBuffer *buf)
{
  gboolean do_bswap = giop_msg_conversion_needed(buf);

  buf->cur = ALIGN_ADDRESS(buf->cur, 4);
  if((buf->cur + 8) > buf->end)
    return TRUE;
  if(do_bswap)
    {
      buf->msg.u.reply_1_2.request_id = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
      buf->cur += 4;
      buf->msg.u.reply_1_2.reply_status = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
    }
  else
    {
      buf->msg.u.reply_1_2.request_id = *((guint32 *)buf->cur);
      buf->cur += 4;
      buf->msg.u.reply_1_2.reply_status = *((guint32 *)buf->cur);
    }
  buf->cur += 4;

  buf->msg.u.reply_1_2.service_context._buffer = NULL;
  if(giop_IOP_ServiceContextList_demarshal(buf, &buf->msg.u.reply_1_2.service_context))
    return TRUE;

  buf->cur = ALIGN_ADDRESS(buf->cur, 8);
  return FALSE;
}

static gboolean
giop_recv_buffer_demarshal_cancel(GIOPRecvBuffer *buf)
{
	gboolean do_bswap = giop_msg_conversion_needed (buf);

	buf->cur = ALIGN_ADDRESS (buf->cur, 4);
	if ((buf->cur + 4) > buf->end)
		return TRUE;
	if (do_bswap)
		buf->msg.u.cancel_request.request_id = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
	else
		buf->msg.u.cancel_request.request_id = *((guint32 *)buf->cur);
	buf->cur += 4;

	return FALSE;
}

static gboolean
giop_recv_buffer_demarshal_locate_request_1_1(GIOPRecvBuffer *buf)
{
	gboolean do_bswap = giop_msg_conversion_needed (buf);

	buf->cur = ALIGN_ADDRESS (buf->cur, 4);

	if ((buf->cur + 8) > buf->end)
		return TRUE;

	if (do_bswap)
		buf->msg.u.locate_request_1_1.request_id = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
	else
		buf->msg.u.locate_request_1_1.request_id = *((guint32 *)buf->cur);
	buf->cur += 4;
	if (do_bswap)
		buf->msg.u.locate_request_1_1.object_key._length = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
	else
		buf->msg.u.locate_request_1_1.object_key._length = *((guint32 *)buf->cur);
	buf->cur += 4;

	if ((buf->cur + buf->msg.u.locate_request_1_1.object_key._length) > buf->end ||
	    (buf->cur + buf->msg.u.locate_request_1_1.object_key._length) < buf->cur)
		return TRUE;

	buf->msg.u.locate_request_1_1.object_key._buffer = buf->cur;
	buf->msg.u.locate_request_1_1.object_key._release = CORBA_FALSE;
	buf->cur += buf->msg.u.locate_request_1_1.object_key._length;

	return FALSE;
}

static gboolean
giop_recv_buffer_demarshal_locate_request_1_2(GIOPRecvBuffer *buf)
{
	gboolean do_bswap = giop_msg_conversion_needed(buf);

	buf->cur = ALIGN_ADDRESS(buf->cur, 4);

	if((buf->cur + 4) > buf->end)
		return TRUE;

	if(do_bswap)
		buf->msg.u.locate_request_1_2.request_id = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
	else
		buf->msg.u.locate_request_1_2.request_id = *((guint32 *)buf->cur);
	buf->cur += 4;

	return giop_GIOP_TargetAddress_demarshal (buf, &buf->msg.u.locate_request_1_2.target);
}

static gboolean
giop_recv_buffer_demarshal_locate_reply_1_1(GIOPRecvBuffer *buf)
{
	gboolean do_bswap = giop_msg_conversion_needed(buf);

	buf->cur = ALIGN_ADDRESS (buf->cur, 4);

	if ((buf->cur + 8) > buf->end)
		return TRUE;

	if (do_bswap) {
		buf->msg.u.locate_reply_1_1.request_id = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
		buf->cur += 4;
		buf->msg.u.locate_reply_1_1.locate_status = GUINT32_SWAP_LE_BE(*((guint32 *)buf->cur));
	} else {
		buf->msg.u.locate_reply_1_1.request_id = *((guint32 *)buf->cur);
		buf->cur += 4;
		buf->msg.u.locate_reply_1_1.locate_status = *((guint32 *)buf->cur);
	}
	buf->cur += 4;
  
	return FALSE;
}

static gboolean
giop_recv_buffer_demarshal_locate_reply_1_2 (GIOPRecvBuffer *buf)
{
	return giop_recv_buffer_demarshal_locate_reply_1_1 (buf);
}

typedef gboolean (*GIOPDecodeFunc) (GIOPRecvBuffer *buf);

static gboolean
giop_recv_buffer_demarshal (GIOPRecvBuffer *buf)
{
	GIOPDecodeFunc              decode_func;
	static const GIOPDecodeFunc decode_funcs [GIOP_NUM_MSG_TYPES] [GIOP_NUM_VERSIONS] = {
		/* request */
		{ giop_recv_buffer_demarshal_request_1_1,
		  giop_recv_buffer_demarshal_request_1_1,
		  giop_recv_buffer_demarshal_request_1_2},
		/* reply */
		{ giop_recv_buffer_demarshal_reply_1_1,
		  giop_recv_buffer_demarshal_reply_1_1,
		  giop_recv_buffer_demarshal_reply_1_2},
		/* cancel request */
		{ giop_recv_buffer_demarshal_cancel,
		  giop_recv_buffer_demarshal_cancel,
		  giop_recv_buffer_demarshal_cancel},
		/* locate request */
		{ giop_recv_buffer_demarshal_locate_request_1_1,
		  giop_recv_buffer_demarshal_locate_request_1_1,
		  giop_recv_buffer_demarshal_locate_request_1_2},
		/* locate reply */
		{ giop_recv_buffer_demarshal_locate_reply_1_1,
		  giop_recv_buffer_demarshal_locate_reply_1_1,
		  giop_recv_buffer_demarshal_locate_reply_1_2},
		/* close connection */
		{NULL, NULL, NULL},
		/* message error */
		{NULL, NULL, NULL},
		/* fragment */
		{NULL, NULL, NULL}
	};

	if (buf->msg.header.message_type >= GIOP_NUM_MSG_TYPES)
		return TRUE;

	if (buf->giop_version >= GIOP_NUM_VERSIONS)
		return TRUE;

	decode_func = decode_funcs [buf->msg.header.message_type] [buf->giop_version];

	if (decode_func)
		return decode_func (buf);

	return FALSE;
}

GIOPRecvBuffer *
giop_recv_buffer_use_encaps (guchar *mem, gulong len)
{
	GIOPRecvBuffer *buf = giop_recv_buffer_use_buf (NULL);

	buf->cur = buf->message_body = mem;
	buf->end = buf->cur + len;
	buf->msg.header.message_size = len;
	buf->msg.header.flags = *(buf->cur++);
	buf->giop_version = GIOP_LATEST;
	buf->left_to_read = 0;
	buf->state = GIOP_MSG_READY;
	buf->free_body = FALSE;

	return buf;
}

GIOPRecvBuffer *
giop_recv_buffer_use_encaps_buf (GIOPRecvBuffer *buf)
{
	guchar             *ptr;
	CORBA_unsigned_long len;

	buf->cur = ALIGN_ADDRESS (buf->cur, 4);

	if ((buf->cur + 4) > buf->end)
		return NULL;
	len = *(CORBA_unsigned_long *) buf->cur;

	if (giop_msg_conversion_needed (buf))
		len = GUINT32_SWAP_LE_BE (len);

	buf->cur += 4;
	if ((buf->cur + len) > buf->end ||
	    (buf->cur + len) < buf->cur)
		return NULL;

	ptr = buf->cur;
	buf->cur += len;

	return giop_recv_buffer_use_encaps (ptr, len);
}

void
giop_recv_buffer_unuse (GIOPRecvBuffer *buf)
{
	if (!buf)
		return;

	if (buf->free_body) {
		g_free (buf->message_body);
		buf->message_body = NULL;
	}

	switch (buf->giop_version) {
	case GIOP_1_0:
	case GIOP_1_1:
		switch (buf->msg.header.message_type) {
		case GIOP_REPLY:
			giop_IOP_ServiceContextList_free (
				&buf->msg.u.reply_1_1.service_context);
			break;
		case GIOP_REQUEST:
			giop_IOP_ServiceContextList_free (
				&buf->msg.u.request_1_1.service_context);
			break;
		default:
			break;
		}
		break;
	case GIOP_1_2:
		switch (buf->msg.header.message_type) {
		case GIOP_REPLY:
			giop_IOP_ServiceContextList_free (
				&buf->msg.u.reply_1_2.service_context);
			break;
		case GIOP_REQUEST:
			giop_IOP_ServiceContextList_free (
				&buf->msg.u.request_1_2.service_context);
			break;
		default:
			break;
		}
		break;
	default:
		break;
	}
	if(buf->connection)
		giop_connection_unref (buf->connection);
	g_free (buf);
}

static void
ent_lock (GIOPMessageQueueEntry *ent)
{
	if (ent->src_thread)
		g_mutex_lock (ent->src_thread->lock);
}

static void
ent_unlock (GIOPMessageQueueEntry *ent)
{
	if (ent->src_thread)
		g_mutex_unlock (ent->src_thread->lock);
}

static void
giop_recv_destroy_queue_entry_T (GIOPMessageQueueEntry *ent)
{
	if (ent->cnx) {
		giop_connection_unref (ent->cnx);
		ent->cnx = NULL;
	}
}

void
giop_recv_list_destroy_queue_entry (GIOPMessageQueueEntry *ent)
{
	LINK_MUTEX_LOCK (giop_queued_messages_lock);
#ifdef DEBUG
	g_warning ("Remove XX:%p:(%p) - %d", ent, ent->async_cb,
		   g_list_length (giop_queued_messages));
#endif
	giop_queued_messages = g_list_remove (giop_queued_messages, ent);
	LINK_MUTEX_UNLOCK (giop_queued_messages_lock);

	giop_recv_destroy_queue_entry_T (ent);
}

void
giop_recv_list_setup_queue_entry (GIOPMessageQueueEntry *ent,
				  GIOPConnection        *cnx,
				  CORBA_unsigned_long    msg_type,
				  CORBA_unsigned_long    request_id)
{
	ent->src_thread = giop_thread_self ();
	ent->async_cb = NULL;

	ent->cnx = giop_connection_ref (cnx);
	ent->msg_type = msg_type;
	ent->request_id = request_id;
	ent->buffer = NULL;

	LINK_MUTEX_LOCK   (giop_queued_messages_lock);
#ifdef DEBUG
	g_warning ("Push XX:%p:(%p) - %d", ent, ent->async_cb,
		   g_list_length (giop_queued_messages));
#endif
	giop_queued_messages = g_list_prepend (giop_queued_messages, ent);
	LINK_MUTEX_UNLOCK (giop_queued_messages_lock);
}

void
giop_recv_list_setup_queue_entry_async (GIOPMessageQueueEntry *ent,
					GIOPAsyncCallback      cb)
{
	g_return_if_fail (ent != NULL);

	ent->async_cb = cb;
}

void
giop_recv_list_zap (GIOPConnection *cnx)
{
	GList  *l, *next;
	GSList *sl, *notify = NULL;

	LINK_MUTEX_LOCK (giop_queued_messages_lock);

	for (l = giop_queued_messages; l; l = next) {
		GIOPMessageQueueEntry *ent = l->data;

		next = l->next;

		if (ent->cnx == cnx) {
			ent_lock (ent);

			dprintf (ERRORS, "Zap listener on dead cnx with buffer %p\n",
				 ent->buffer);

			giop_recv_buffer_unuse (ent->buffer);
			ent->buffer = NULL;

			giop_recv_destroy_queue_entry_T (ent);

			if (giop_thread_io () && !ent->async_cb)
				giop_incoming_signal_T (ent->src_thread, GIOP_CLOSECONNECTION);
			ent_unlock (ent);

			if (ent->async_cb)
				notify = g_slist_prepend (notify, ent);
			giop_queued_messages = g_list_delete_link (
				giop_queued_messages, l);
		}
	}

	LINK_MUTEX_UNLOCK (giop_queued_messages_lock);

	for (sl = notify; sl; sl = sl->next) {
		GIOPMessageQueueEntry *ent = sl->data;
		
		if (!ent->async_cb) {
			/* This should never happen */
			g_warning ("Extraordinary recv list re-enterancy");
			continue;
		}

		giop_invoke_async (ent);
	}
	g_slist_free (notify);
}

CORBA_unsigned_long
giop_recv_buffer_get_request_id (GIOPRecvBuffer *buf)
{
	static const glong reqid_offsets [GIOP_NUM_MSG_TYPES] [GIOP_NUM_VERSIONS] = {
		/* GIOP_REQUEST */
		{ G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.request_1_0.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.request_1_1.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.request_1_2.request_id)},
		/* GIOP_REPLY */
		{ G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.reply_1_0.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.reply_1_1.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.reply_1_2.request_id)},
		/* GIOP_CANCELREQUEST */
		{ G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.cancel_request.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.cancel_request.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.cancel_request.request_id)},
		/* GIOP_LOCATEREQUEST */
		{ G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.locate_request_1_0.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.locate_request_1_1.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.locate_request_1_2.request_id)},
		/* GIOP_LOCATEREPLY */
		{ G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.locate_reply_1_0.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.locate_reply_1_1.request_id),
		  G_STRUCT_OFFSET(GIOPRecvBuffer,
				  msg.u.locate_reply_1_2.request_id)},
		{0,0,0}, /* GIOP_CLOSECONNECTION */
		{0,0,0}, /* GIOP_MESSAGEERROR */
		{0,0,0} /* GIOP_FRAGMENT */
	};
	gulong offset;

	offset = reqid_offsets [buf->msg.header.message_type] [buf->giop_version];
	if (!offset)
		return 0;

	return G_STRUCT_MEMBER (CORBA_unsigned_long, buf, offset);
}

static inline gboolean
check_got (GIOPMessageQueueEntry *ent)
{
	return (ent->buffer || 
		!ent->cnx ||
		(ent->cnx->parent.status == LINK_DISCONNECTED) ||
		(ent->cnx->parent.status == LINK_TIMEOUT));
}

GIOPRecvBuffer *
giop_recv_buffer_get (GIOPMessageQueueEntry *ent,
		      gboolean *timeout)
{
	GIOPThread *tdata = NULL;

	*timeout = FALSE;
	tdata = giop_thread_self ();

 thread_switch:
	if (giop_thread_io ()) {
		ent_lock (ent);

		for (; !check_got (ent); ) {
			if (!giop_thread_queue_empty_T (tdata)) {
				ent_unlock (ent);
				giop_thread_queue_process (tdata);
				ent_lock (ent);
			} else
				g_cond_wait (tdata->incoming, tdata->lock);
		}
		
		ent_unlock (ent);

	} else { /* non-threaded */

		while (!ent->buffer && ent->cnx &&
		       (ent->cnx->parent.status != LINK_DISCONNECTED) &&
		       (ent->cnx->parent.status != LINK_TIMEOUT) &&
		       !giop_thread_io())
			link_main_iteration (TRUE);

		if (giop_thread_io())
			goto thread_switch;
	}

	if (giop_thread_io() 
	    && ent 
	    && ent->cnx 
	    && ent->cnx->parent.timeout_mutex) {
		g_mutex_lock (ent->cnx->parent.timeout_mutex);
		if (ent->cnx->parent.timeout_status == LINK_TIMEOUT_UNKNOWN) {
			link_io_thread_remove_timeout (ent->cnx->parent.timeout_source_id);
			ent->cnx->parent.timeout_source_id = 0;
			ent->cnx->parent.timeout_status = LINK_TIMEOUT_NO;
			giop_connection_unref (ent->cnx); // we remove the source so we must unref the connection
		} else if (ent->cnx->parent.timeout_status == LINK_TIMEOUT_YES)
			*timeout = TRUE;
		g_mutex_unlock (ent->cnx->parent.timeout_mutex);
	}

	giop_thread_queue_tail_wakeup (tdata);
	giop_recv_list_destroy_queue_entry (ent);

	return ent->buffer;
}

MateCORBA_ObjectKey*
giop_recv_buffer_get_objkey (GIOPRecvBuffer *buf)
{
	switch (buf->msg.header.message_type) {
	case GIOP_REQUEST:
		switch (buf->msg.header.version [1]) {
		case 0:
			return &buf->msg.u.request_1_0.object_key;
			break;
		case 1:
			return &buf->msg.u.request_1_1.object_key;
			break;
		case 2:
			g_assert (buf->msg.u.request_1_2.target._d == GIOP_KeyAddr);
			return &buf->msg.u.request_1_2.target._u.object_key;
			break;
		}
		break;

	case GIOP_LOCATEREQUEST:
		switch (buf->msg.header.version [1]) {
		case 0:
			return &buf->msg.u.locate_request_1_0.object_key;
			break;
		case 1:
			return &buf->msg.u.locate_request_1_1.object_key;
			break;
		case 2:
			g_assert (buf->msg.u.locate_request_1_2.target._d == GIOP_KeyAddr);
			return &buf->msg.u.locate_request_1_2.target._u.object_key;
			break;
		}
		break;

	default:
		g_assert_not_reached ();
	}

	return NULL;
}

char *
giop_recv_buffer_get_opname (GIOPRecvBuffer *buf)
{
	switch(buf->msg.header.version [1]) {
	case 0:
		return buf->msg.u.request_1_0.operation;
		break;
	case 1:
		return buf->msg.u.request_1_1.operation;
		break;
	case 2:
		return buf->msg.u.request_1_2.operation;
		break;
	}

	return NULL;
}

void
giop_recv_buffer_init (void)
{
	giop_queued_messages_lock = link_mutex_new ();
}

static void
giop_connection_add_frag (GIOPConnection *cnx,
			  GIOPRecvBuffer *buf)
{
	cnx->incoming_frags = g_list_prepend (cnx->incoming_frags,
					      g_list_prepend (NULL, buf));
}

static GList *
giop_connection_get_frag (GIOPConnection     *cnx,
			  CORBA_unsigned_long request_id,
			  gboolean            return_first_if_none)
{
	GList *l;

	for (l = cnx->incoming_frags; l; l = l->next) {
		GList *frags = l->data;

		if (giop_recv_buffer_get_request_id (frags->data) == request_id)
			return l->data;
	}

	/* This sucks, but it's prolly a GIOP-1.1 spec issue */
	if (return_first_if_none && cnx->incoming_frags) {
		static int warned = 0;
		if (!warned++)
			dprintf (MESSAGES, "GIOP-1.1 1 fragment set per cnx (?)");

		return cnx->incoming_frags->data;
	}

	return NULL;
}

static void
giop_connection_remove_frag (GIOPConnection *cnx, GList *frags)
{
	GList *l;

	g_return_if_fail (frags != NULL);

	for (l = frags->next; l; l = l->next)
		giop_recv_buffer_unuse (l->data);

	cnx->incoming_frags = g_list_remove (cnx->incoming_frags, frags);
	g_list_free (frags);
}

void
giop_connection_destroy_frags (GIOPConnection *cnx)
{
	GList *l;

	for (l = cnx->incoming_frags; l; l = l->next) {
		GList *l2;

		for (l2 = l->data; l2; l2 = l2->next)
			giop_recv_buffer_unuse (l2->data);

		g_list_free (l->data);
	}
	g_list_free (cnx->incoming_frags);
	cnx->incoming_frags = NULL;
}

static gboolean
alloc_buffer (GIOPRecvBuffer *buf, gpointer old_alloc, gulong body_size)
{
	buf->message_body = g_try_realloc (old_alloc, body_size + 12);

	if (!buf->message_body)
		return TRUE;

	/*
	 *   We assume that this is 8 byte aligned, for efficiency -
	 * so we can align to the memory address rather than the offset
	 * into the buffer.
	 */
	g_assert (((gulong)buf->message_body & 0x3) == 0);
	buf->free_body = TRUE;
	buf->cur = buf->message_body + 12;
	buf->end = buf->cur + body_size;
	buf->left_to_read = body_size;

	return FALSE;
}

static gboolean
concat_frags (GList *list)
{
	GList *l;
	guchar *ptr;
	gulong length = 0;
	gulong initial_length;
	gulong initial_offset;
	GIOPRecvBuffer *head;

	head = list->data;

	length = head->msg.header.message_size;
	initial_offset = (head->cur - head->message_body);
	initial_length = (head->end - head->cur);

	length += initial_offset - 12; /* include what we read of the header */

	g_assert (head->free_body);

	if (alloc_buffer (head, head->message_body, length)) {
		dprintf (ERRORS, "failed to allocate fragment collation buffer");
		return TRUE;
	}

	head->left_to_read = 0;
	head->cur = head->message_body + initial_offset;

	ptr = head->cur + initial_length;

	for (l = list->next; l; l = l->next) {
		gulong len;
		GIOPRecvBuffer *buf = l->data;
			
		len = buf->end - buf->cur;
		memcpy (ptr, buf->cur, len);
		ptr+= len;
	}

	head->end = ptr;

	return FALSE;
}

static glong giop_initial_msg_size_limit = GIOP_INITIAL_MSG_SIZE_LIMIT;

void
giop_recv_set_limit (glong limit)
{
	if (limit > 256) /* Something slightly sensible ? */
		giop_initial_msg_size_limit = limit;
}

glong
giop_recv_get_limit (void)
{
	return giop_initial_msg_size_limit;
}

/**
 * giop_recv_buffer_handle_fragmented:
 * @buf: pointer to recv buffer pointer
 * @cnx: current connection.
 * 
 *   This will append @buf to the right list of buffers
 * on the connection, forming a complete message, and
 * re-write *@buf to the first buffer in the chain.
 * 
 * Return value: TRUE on error else FALSE
 **/
static gboolean
giop_recv_buffer_handle_fragmented (GIOPRecvBuffer **ret_buf,
				    GIOPConnection  *cnx)
{
	GList *list;
	gboolean giop_1_1;
	gboolean error = FALSE;
	CORBA_long message_id;
	GIOPRecvBuffer *buf = *ret_buf;

	giop_1_1 = (buf->giop_version == GIOP_1_1);

	switch (buf->msg.header.message_type) {
	case GIOP_REPLY:
	case GIOP_LOCATEREPLY:
	case GIOP_REQUEST:
	case GIOP_LOCATEREQUEST:
		message_id = giop_recv_buffer_get_request_id (buf);
		break;
	case GIOP_FRAGMENT:
		if (!giop_1_1) {
			buf->cur = ALIGN_ADDRESS (buf->cur, 4);

			if ((buf->cur + 4) > buf->end) {
				dprintf (ERRORS, "incoming bogus fragment length");
				return TRUE;
			}
			if (giop_msg_conversion_needed (buf))
				message_id = GUINT32_SWAP_LE_BE (*((guint32 *)buf->cur));
			else
				message_id = *(guint32 *) buf->cur;
			buf->cur += 4;
		} else
			message_id = 0;
		break;
	default:
		dprintf (ERRORS, "Bogus fragment packet type %d",
			 buf->msg.header.message_type);
		return TRUE;
	}

	if (!(list = giop_connection_get_frag (cnx, message_id, giop_1_1))) {
		if (!MORE_FRAGMENTS_FOLLOW (buf))
			return TRUE;

		giop_connection_add_frag (cnx, buf);

	} else {
		GIOPRecvBuffer *head = list->data;

		*ret_buf = head;
		g_assert (head->msg.header.message_type != GIOP_FRAGMENT);

		/* track total length on head node */
		/* (end - cur) to account for fragment (msg id) header */
		head->msg.header.message_size += (buf->end - buf->cur);

		list = g_list_append (list, buf);

		if (!cnx->parent.is_auth &&
		    buf->msg.header.message_size > giop_initial_msg_size_limit) {
			dprintf (ERRORS, "Message exceeded initial size limit\n");
			error = TRUE;
			giop_connection_remove_frag (cnx, list);
		}
			
		if (!MORE_FRAGMENTS_FOLLOW (buf)) {
			g_assert (buf->msg.header.message_type == GIOP_FRAGMENT);

			/* concat all fragments - re-write & continue */
			error = concat_frags (list);

			giop_connection_remove_frag (cnx, list);
		}
	}

	return error;
}

static gboolean
handle_reply (GIOPRecvBuffer *buf)
{
	GList                 *l;
	gboolean               error;
	GIOPMessageQueueEntry *ent;
	CORBA_unsigned_long    request_id;

	request_id = giop_recv_buffer_get_request_id (buf);

	error = FALSE;

	LINK_MUTEX_LOCK (giop_queued_messages_lock);

	for (l = giop_queued_messages; l; l = l->next) {
		ent = l->data;

		if (ent->request_id == request_id &&
		    ent->msg_type == buf->msg.header.message_type)
			break;
	}

	ent = l ? l->data : NULL;

	if (!ent) {
		if (giop_recv_buffer_reply_status (buf) ==
		    CORBA_SYSTEM_EXCEPTION) {
			/*
			 * Unexpected - but sometimes a oneway
			 * method invocation on a de-activated
			 * object results in us getting a bogus
			 * system exception in reply.
			 */
 		} else {
#ifdef G_ENABLE_DEBUG
			if (giop_debug_hook_unexpected_reply)
				giop_debug_hook_unexpected_reply (buf);
			else
				dprintf (ERRORS, "We received an unexpected reply\n");
#endif /* G_ENABLE_DEBUG */
			error = TRUE;
		}

	} else if (ent->cnx != buf->connection) {
#ifdef G_ENABLE_DEBUG
		if (giop_debug_hook_spoofed_reply)
			giop_debug_hook_spoofed_reply (buf, ent);
#endif
		dprintf (ERRORS, "We received a bogus reply\n");

		error = TRUE;

	} else {
#ifdef DEBUG
		g_warning ("Pop XX:%p:%p - %d",
			   ent, ent->async_cb,
			   g_list_length (giop_queued_messages));
#endif
		giop_queued_messages = g_list_delete_link
			(giop_queued_messages, l);
	}

	LINK_MUTEX_UNLOCK (giop_queued_messages_lock);

	if (ent && !error) {
		gboolean async = FALSE;

		ent_lock (ent);
		ent->buffer = buf;

		if (giop_thread_io () && !ent->async_cb)
			giop_incoming_signal_T (ent->src_thread,
						GIOP_REPLY);

		else if (ent->async_cb)
			async = TRUE;

		ent_unlock (ent);

		if (async)
			giop_invoke_async (ent);

		buf = NULL;
	}
	
	giop_recv_buffer_unuse (buf);

	return error;
}

static gboolean
giop_recv_msg_reading_body (GIOPRecvBuffer *buf,
			    gboolean        is_auth)
{
	dprintf (GIOP, "Incoming IIOP header:\n");

	do_giop_dump (stderr, (guint8 *) &buf->msg.header, 12, 0);

	/* Check the header */
	if (memcmp (buf->msg.header.magic, "GIOP", 4))
		return TRUE;
	
	if (buf->msg.header.message_type >= GIOP_NUM_MSG_TYPES)
		return TRUE;
	
	switch (buf->msg.header.version [0]) {
	case 1:
		switch (buf->msg.header.version [1]) {
		case 0:
			buf->giop_version = GIOP_1_0;
			break;
		case 1:
			buf->giop_version = GIOP_1_1;
			break;
		case 2:
			buf->giop_version = GIOP_1_2;
			break;
		default:
			return TRUE;
			break;
		}
		break;
	default:
		return TRUE;
		break;
	}

	if ((buf->msg.header.flags & GIOP_FLAG_LITTLE_ENDIAN) != GIOP_FLAG_ENDIANNESS)
		buf->msg.header.message_size = GUINT32_SWAP_LE_BE (buf->msg.header.message_size);

	/* NB. at least CLOSECONNECTION has 0 length message_size */

	if (!is_auth && buf->msg.header.message_size > giop_initial_msg_size_limit) {
		dprintf (ERRORS, "Message exceeded unauthorized size limit\n");
		return TRUE;
	}

	if (alloc_buffer (buf, NULL, buf->msg.header.message_size))
		return TRUE;

	return FALSE;
}

/*
 * FIXME: we should definately handle things more asynchronously,
 * perhaps even at the expense of having to go to the GSource
 * twice in order to get fresh input (?)
 * or should we poll ourselves on the source to see what's up?
 *
 * The whole locking concept here looks broken to me,
 * especially since 'read' can flag the connection disconnected
 * giving a nice deadlock.
 */
gboolean
giop_connection_handle_input (LinkConnection *lcnx)
{
	GIOPRecvBuffer *buf;
	GIOPConnection *cnx = (GIOPConnection *) lcnx;

	do {
		int n;

		if (!cnx->incoming_msg)
			cnx->incoming_msg = giop_recv_buffer_use_buf (cnx);

		buf = cnx->incoming_msg;

		n = link_connection_read (
			lcnx, buf->cur, buf->left_to_read, FALSE);

		if (n == 0) /* We'll be back */
			return TRUE;

		if (n < 0 || !buf->left_to_read) /* HUP */
			goto msg_error;

/*		fprintf (stderr, "Read %d\n", n);
		giop_dump (stderr, buf->cur, n, 0); */

		buf->left_to_read -= n;
		buf->cur += n;

		if (buf->left_to_read == 0) {

#ifdef G_ENABLE_DEBUG
			if (giop_debug_hook_incoming_mangler)
				giop_debug_hook_incoming_mangler (buf);
#endif

			switch (buf->state) {

			case GIOP_MSG_READING_HEADER:
				if (giop_recv_msg_reading_body (buf, cnx->parent.is_auth)) {
					dprintf (ERRORS, "OOB incoming msg header data\n");
					goto msg_error;
				}
				buf->state = GIOP_MSG_READING_BODY;
				break;

			case GIOP_MSG_READING_BODY: {

				dprintf (GIOP, "Incoming IIOP body:\n");

				buf->cur = buf->message_body + 12;
				if ((buf->cur + buf->msg.header.message_size) > buf->end) {
					dprintf (ERRORS, "broken incoming length data\n");
					goto msg_error;
				}
				do_giop_dump (stderr, buf->cur, buf->msg.header.message_size, 12);

				buf->state = GIOP_MSG_READY;

				if (giop_recv_buffer_demarshal (buf)) {
					dprintf (ERRORS, "broken incoming header data\n");
					goto msg_error;
				}

				if (MORE_FRAGMENTS_FOLLOW (buf)) {
					if (giop_recv_buffer_handle_fragmented (&buf, cnx))
						goto msg_error;

					else {
						cnx->incoming_msg = NULL;
						goto frag_out;
					}

				} else if (buf->msg.header.message_type == GIOP_FRAGMENT) {
					if (giop_recv_buffer_handle_fragmented (&buf, cnx))
						goto msg_error;
					/* else last fragment */
				}
				break;
			}

			case GIOP_MSG_AWAITING_FRAGMENTS:
			case GIOP_MSG_READY:
				g_assert_not_reached ();
				break;
			}
		}

	} while (cnx->incoming_msg &&
		 buf->left_to_read > 0 &&
		 buf->state != GIOP_MSG_READY);

	cnx->incoming_msg = NULL;

	switch (buf->msg.header.message_type) {
	case GIOP_REPLY:
	case GIOP_LOCATEREPLY:
		dprintf (MESSAGES, "handling reply\n");
		if (handle_reply (buf)) /* dodgy inbound data, pull the cnx */
			link_connection_state_changed (lcnx, LINK_DISCONNECTED);
		break;

	case GIOP_REQUEST:
		dprintf (MESSAGES, "handling request\n");
		MateCORBA_handle_request (cnx->orb_data, buf);
		break;

	case GIOP_LOCATEREQUEST:
		dprintf (MESSAGES, "handling locate request\n");
		MateCORBA_handle_locate_request (cnx->orb_data, buf);
		break;

	case GIOP_CANCELREQUEST:
	case GIOP_MESSAGEERROR:
		dprintf (ERRORS, "dropping an unusual & unhandled input buffer 0x%x",
			 buf->msg.header.message_type);
		giop_recv_buffer_unuse (buf);
		break;

	case GIOP_CLOSECONNECTION:
		dprintf (MESSAGES, "received close connection\n");
		giop_recv_buffer_unuse (buf);
		link_connection_state_changed (lcnx, LINK_DISCONNECTED);
		break;

	case GIOP_FRAGMENT:
		dprintf (ERRORS, "Fragment got in the wrong channel\n");
	default:
		dprintf (ERRORS, "dropping an out of bound input buffer "
			 "on the floor 0x%x\n", buf->msg.header.message_type);
		goto msg_error;
		break;
	}

 frag_out:	
	return TRUE;

 msg_error:
	cnx->incoming_msg = NULL;

	buf->msg.header.message_type = GIOP_MESSAGEERROR;
	buf->msg.header.message_size = 0;

	giop_recv_buffer_unuse (buf);

	/* Zap it for badness.
	 * XXX We should probably handle oversized
	 * messages more graciously XXX */
	link_connection_state_changed (LINK_CONNECTION (cnx),
				       LINK_DISCONNECTED);

	return TRUE;
}

static gboolean
giop_timeout (gpointer data)
{
	gboolean retv = FALSE;
	GIOPConnection *cnx = (GIOPConnection*)data;
	LinkConnection *lcnx = LINK_CONNECTION (cnx);
	GIOPThread *tdata = (GIOPThread *)lcnx->tdata;

	g_assert (lcnx->timeout_mutex);

	if (lcnx->status == LINK_DISCONNECTED) {
		giop_connection_unref (cnx); // we remove the source so we must unref cnx
		goto out;
	}
		
	g_mutex_lock (lcnx->timeout_mutex);
	if (lcnx->timeout_status == LINK_TIMEOUT_UNKNOWN) {
		lcnx->timeout_source_id = 0;
		lcnx->timeout_status = LINK_TIMEOUT_YES;
	} else {
		g_mutex_unlock (lcnx->timeout_mutex);
		retv = TRUE; // do not remove the source - the one who sets timeout_status will do that
		goto out;
	}
	g_mutex_unlock (lcnx->timeout_mutex);

	link_connection_state_changed (lcnx, LINK_TIMEOUT);

	g_mutex_lock (tdata->lock); /* ent_lock */
	giop_incoming_signal_T (tdata, GIOP_CLOSECONNECTION);
	g_mutex_unlock (tdata->lock); /* ent_lock */
	
	giop_connection_unref (cnx); // we remove the source so we must unref cnx

out:
	return retv;
}

void
giop_timeout_add (GIOPConnection *cnx)
{
	static GStaticMutex static_mutex = G_STATIC_MUTEX_INIT;
	LinkConnection *lcnx = LINK_CONNECTION (cnx);

	if (!giop_thread_io ())
		return;
	if (!lcnx->timeout_msec) 
		return;

	g_static_mutex_lock (&static_mutex);
	if (lcnx->timeout_source_id)
		goto out;

	giop_connection_ref (cnx); // to be unref'ed by the one who removes the timeout source
	
	if (!lcnx->timeout_mutex)
		lcnx->timeout_mutex = g_mutex_new ();

	g_mutex_lock (lcnx->timeout_mutex);
	lcnx->timeout_status = LINK_TIMEOUT_UNKNOWN;
	g_mutex_unlock (lcnx->timeout_mutex);

	lcnx->tdata = giop_thread_self ();

	lcnx->timeout_source_id = link_io_thread_add_timeout (lcnx->timeout_msec, giop_timeout, (gpointer)cnx);

out:
	g_static_mutex_unlock (&static_mutex);
}

GIOPRecvBuffer *
giop_recv_buffer_use_buf (GIOPConnection *cnx)
{
	GIOPRecvBuffer *buf = NULL;
	
	if(cnx)
		giop_connection_ref (cnx);
	
	buf = g_new0 (GIOPRecvBuffer, 1);

	buf->state = GIOP_MSG_READING_HEADER;
	buf->cur = (guchar *)&buf->msg.header;
	buf->left_to_read = 12;
	buf->connection = cnx;

	return buf;
}
