/*
 * Copyright (C) 1996-2001  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* $Id: connection.c,v 1.36.4.3 2001/03/27 00:14:52 bwelling Exp $ */

/* Principal Author: DCL */

/*
 * Subroutines for dealing with connections.
 */

#include <config.h>

#include <isc/buffer.h>
#include <isc/bufferlist.h>
#include <isc/netdb.h>
#include <isc/string.h>		/* Required for HP/UX (and others?) */
#include <isc/task.h>
#include <isc/util.h>

#include <omapi/private.h>
#include <omapi/result.h>

/*
 * Swiped from bin/tests/sdig.c.
 */
static isc_result_t
get_address(const char *hostname, in_port_t port, isc_sockaddr_t *sockaddr) {
	struct in_addr in4;
	struct in6_addr in6;
	struct hostent *he;

	/*
	 * Is this an IPv6 numeric address?
	 */
	if (isc_net_probeipv6() == ISC_R_SUCCESS &&
	    inet_pton(AF_INET6, hostname, &in6) == 1)
		isc_sockaddr_fromin6(sockaddr, &in6, port);

	/*
	 * What about an IPv4 numeric address?
	 */
	else if (inet_pton(AF_INET, hostname, &in4) == 1)
		isc_sockaddr_fromin(sockaddr, &in4, port);

	else {
		/*
		 * Look up the host name.
		 */
		he = gethostbyname(hostname);
		if (he == NULL)
			return (ISC_R_NOTFOUND);

		INSIST(he->h_addrtype == AF_INET);
		isc_sockaddr_fromin(sockaddr,
				    (struct in_addr *)(he->h_addr_list[0]),
				    port);
	}

	return (ISC_R_SUCCESS);
}

/*
 * Called when there are no more events are pending on the socket.
 * It can be detached and data for the connection object freed.
 */
static void
free_connection(omapi_connection_t *connection) {
	isc_buffer_t *buffer;

	connection->state = omapi_connection_disconnecting;

	while ((buffer = ISC_LIST_HEAD(connection->input_buffers)) != NULL) {
		ISC_LIST_UNLINK(connection->input_buffers, buffer, link);
		isc_buffer_free(&buffer);
	}

	while ((buffer = ISC_LIST_HEAD(connection->output_buffers)) != NULL) {
		ISC_LIST_UNLINK(connection->output_buffers, buffer, link);
		isc_buffer_free(&buffer);
	}

	if (connection->task != NULL)
		isc_task_destroy(&connection->task);

	if (connection->socket != NULL)
		isc_socket_detach(&connection->socket);

	if (connection->is_client) {
		DESTROYLOCK(&connection->wait_lock);
		RUNTIME_CHECK(isc_condition_destroy(&connection->waiter) ==
			      ISC_R_SUCCESS);

		/*
		 * Break the link between the protocol object and its parent
		 * (usually a generic object); this is done so the client's
		 * reference to its managing object does not prevent the
		 * connection object and protocol object from being destroyed.
		 */
		INSIST(connection->inner->type == omapi_type_protocol &&
		       connection->inner->inner != NULL);
		OBJECT_DEREF(&connection->inner->inner->outer);
		OBJECT_DEREF(&connection->inner->inner);

	} else {
		/*
		 * Ensure that the protocol object has no parent, and
		 * signal the listener that the connection is ended.
		 */
		INSIST(connection->inner->inner == NULL);

		object_signal(connection->listener, "disconnect", connection);
		OBJECT_DEREF(&connection->listener);
	}

	/*
	 * Finally, free the object itself.
	 */
	OBJECT_DEREF(&connection);
}

static void
end_connection(omapi_connection_t *connection) {
	connection->state = omapi_connection_disconnecting;

	if (connection->events_pending == 0) {
		connection->state = omapi_connection_closed;

		/*
		 * The client connection will be waiting if the error was
		 * triggered in one of the socket event handlers.  It will
		 * not be waiting an error happened in omapi_message_send
		 * or send_intro.
		 *
		 * The server connection will never be waiting.
		 */
		if (connection->waiting) {
			/*
			 * Signal connection_wait and have it do the cleanup.
			 * free_connection can't be called directly here
			 * because it can't be sure that the mutex has been
			 * finished being touched by connection_wait even if
			 * free_connection has signaled it.  (Nasty little race
			 * condition with the lock.)
			 */
			SIGNAL(&connection->waiter);
		} else
			free_connection(connection);

		return;
	}

	/*
	 * This function is only expected to be called by the client side
	 * when events_pending is 0.  On the server side, this function
	 * could possibly be called with an event pending if
	 * omapi_listener_shutdown was called and had to resort to
	 * forced disconnects to blow away outstanding connections.
	 */
	INSIST(! connection->is_client);

	/*
	 * It is also expected that the library only has one event
	 * outstanding at any given time.
	 */
	INSIST(connection->events_pending == 1);

	/*
	 * Cancel the outstanding event.  It will generate an ISC_R_CANCELED
	 * result for either recv_done or send_done, which will decrement
	 * events_pending to 0 and call end_connection again.
	 */
	isc_socket_cancel(connection->socket, NULL, ISC_SOCKCANCEL_ALL);
}

/*
 * Pause the client until it has received a message from the server, either the
 * introductory message or a response to a message it has sent.  This is
 * necessary because the underlying socket library is multithreaded, and
 * it is possible that reading incoming data would trigger an error
 * that causes the connection to be destroyed --- while the client program
 * is still trying to use it.
 *
 * This problem does not exist in the server, because everything in the
 * server happens in the socket event functions, and as soon as one
 * detects an error the connection is destroyed and no further attempt
 * is made to use it.  The server has its own mechanism for making sure
 * destroyed connections are gone via omapi_listener_shutdown.
 */
static isc_result_t
connection_wait(omapi_connection_t *connection_handle) {
	omapi_connection_t *connection;
	isc_result_t result = ISC_R_SUCCESS;

	REQUIRE(connection_handle != NULL &&
		connection_handle->type == omapi_type_connection);

	connection = (omapi_connection_t *)connection_handle;
	/*
	 * This routine is not valid for server connections.
	 */
	INSIST(connection->is_client);

	INSIST(connection->state == omapi_connection_connecting ||
	       connection->state == omapi_connection_connected);

	connection->waiting = ISC_TRUE;

	while (connection->events_pending > 0)
		WAIT(&connection->waiter, &connection->wait_lock);

	connection->waiting = ISC_FALSE;
	UNLOCK(&connection->wait_lock);

	if (connection->state == omapi_connection_closed) {
		/*
		 * An error occurred and end_connection needs to have
		 * free_connection called now that we're done looking
		 * at connection->events_pending.
		 */
		result = connection->result;

		free_connection(connection);
	}

	return (result);
}

/*
 * This is the function that is called when a connect event is posted on
 * the socket as a result of isc_socket_connect.  It is only called
 * on the client side.
 */
static void
connect_done(isc_task_t *task, isc_event_t *event) {
	isc_result_t result;
	isc_socket_t *sock;
	omapi_connection_t *connection;

	UNUSED(task);

	sock = event->ev_sender;
	connection = event->ev_arg;
	result = ((isc_socket_connev_t *)event)->result;

	isc_event_free(&event);

	INSIST(sock == connection->socket && task == connection->task);

	/*
	 * Acquire the wait_lock before proceeding, to guarantee that
	 * connection_wait was entered in connection_toserver.
	 */
	LOCK(&connection->wait_lock);
	UNLOCK(&connection->wait_lock);

	INSIST(connection->events_pending == 1);
	connection->events_pending--;

	if (result == ISC_R_SUCCESS)
		result = isc_socket_getpeername(connection->socket,
						&connection->remote_addr);

	if (result == ISC_R_SUCCESS)
		result = isc_socket_getsockname(connection->socket,
						&connection->local_addr);

	if (result == ISC_R_SUCCESS) {
		connection->state = omapi_connection_connected;

		/*
		 * Unblock omapi_protocol_connect so it can send the intro.
		 */
		SIGNAL(&connection->waiter);
	} else {
		/*
		 * Set the state to disconnecting and unblock connection_wait
		 * to free the connection.
		 */
		connection->result = result;

		end_connection(connection);
	}
}

/*
 * This is the function that is called when a recv event is posted on
 * the socket as a result of isc_socket_recv*.  It is called by both the
 * client and the server.
 */
static void
recv_done(isc_task_t *task, isc_event_t *event) {
	isc_buffer_t *buffer;
	isc_bufferlist_t bufferlist;
	isc_result_t result;
	isc_socket_t *sock;
	isc_socketevent_t *socketevent;
	omapi_connection_t *connection;
	unsigned int bytes_read;

	UNUSED(task);

	sock = event->ev_sender;
	connection = event->ev_arg;
	socketevent = (isc_socketevent_t *)event;
	bufferlist = socketevent->bufferlist;
	bytes_read = socketevent->n;
	result = socketevent->result;

	isc_event_free(&event);

	INSIST(sock == connection->socket && task == connection->task);

	/*
	 * Acquire the wait_lock before proceeding, to guarantee that
	 * connection_wait was entered in connection_send.
	 */
	if (connection->is_client) {
		LOCK(&connection->wait_lock);
		UNLOCK(&connection->wait_lock);
	}

	INSIST(connection->events_pending == 1);
	connection->events_pending--;

	/*
	 * Restore the input buffers to the connection object.
	 */
	while ((buffer = ISC_LIST_HEAD(bufferlist)) != NULL) {
		ISC_LIST_UNLINK(bufferlist, buffer, link);
		ISC_LIST_APPEND(connection->input_buffers, buffer, link);
	}

	if (result == ISC_R_SUCCESS) {
		connection->in_bytes += bytes_read;

		/*
		 * Signal protocol_signalhandler that the bytes it requested
		 * are present.  Though this is set up as a loop, it should
		 * only execute once because protocol_signalhandler will
		 * loop calling dispatch_messages as long as there is
		 * input available.
		 */
		while (connection->bytes_needed <= connection->in_bytes &&
		       connection->bytes_needed > 0) {
			result = object_signal((omapi_object_t *)connection,
					       "ready", connection);

			if (result != ISC_R_SUCCESS)
				break;
		}
	}

	if (result == ISC_R_SUCCESS) {
		if (connection->is_client)
			/*
			 * Attempt to unblock connection_send.  It might
			 * be the case that not all the bytes the client
			 * needs have yet been read, and so it would
			 * have had connection_require queue another recv,
			 * so events_pending will be 1 and connection_wait
			 * will not yet continue.
			 */
			SIGNAL(&connection->waiter);
	} else {
		/*
		 * Set the state to disconnecting and unblock connection_wait
		 * to free the connection.
		 */
		connection->result = result;
		end_connection(connection);
	}
}

/*
 * This is the function that is called when a send event is posted on
 * the socket as a result of isc_socket_send*.  It is called by both the
 * client and the server.
 */
static void
send_done(isc_task_t *task, isc_event_t *event) {
	isc_buffer_t *buffer;
	isc_bufferlist_t bufferlist;
	isc_socket_t *sock;
	isc_socketevent_t *socketevent;
	omapi_connection_t *connection;
	unsigned int sent_bytes;

	UNUSED(task);

	sock = event->ev_sender;
	connection = event->ev_arg;
	socketevent = (isc_socketevent_t *)event;
	sent_bytes = socketevent->n;
	bufferlist = socketevent->bufferlist;

	isc_event_free(&event);

	INSIST(sock == connection->socket && task == connection->task);

	/*
	 * Check the validity of the assumption that partial
	 * writes are not done.
	 */
	if (socketevent->result == ISC_R_SUCCESS)
		INSIST(sent_bytes == connection->out_bytes &&
		       sent_bytes == isc_bufferlist_usedcount(&bufferlist));

	/*
	 * Acquire the wait_lock before proceeding, to guarantee that
	 * connection_wait was entered in connection_send.
	 */
	if (connection->is_client) {
		LOCK(&connection->wait_lock);
		UNLOCK(&connection->wait_lock);
	}

	INSIST(connection->events_pending == 1);
	connection->events_pending--;

	/*
	 * Restore the head of bufferlist into the connection object, resetting
	 * it to have zero used space, and free the remaining buffers.
	 * This is done before the test of the socketevent's result so that
	 * end_connection can free the buffer, if it is called below.
	 */
	buffer = ISC_LIST_HEAD(bufferlist);
	ISC_LIST_UNLINK(bufferlist, buffer, link);
	ISC_LIST_APPEND(connection->output_buffers, buffer, link);
	isc_buffer_clear(buffer);

	while ((buffer = ISC_LIST_NEXT(buffer, link)) != NULL) {
		ISC_LIST_UNLINK(bufferlist, buffer, link);
		isc_buffer_free(&buffer);
	}

	if (connection->result == ISC_R_SUCCESS) {
		connection->out_bytes -= sent_bytes;

		/*
		 * Both the server and client are allowed to have only
		 * one event outstanding on a client at a time.  Each has
		 * already set up the number of bytes it expects to read
		 * next, but not queued the isc_socket_recv yet.  Calling
		 * connection_require for 0 bytes will enable the recv.
		 */
		connection_require(connection, 0);

	} else
		/*
		 * Set the state to disconnecting and unblock connection_wait
		 * to free the connection.
		 */
		end_connection(connection);
}

isc_result_t
connection_send(omapi_connection_t *connection) {
	isc_result_t result;

	REQUIRE(connection != NULL &&
		connection->type == omapi_type_connection);

	REQUIRE(connection->state == omapi_connection_connected);
	REQUIRE(connection->out_bytes > 0);

	INSIST(!ISC_LIST_EMPTY(connection->output_buffers));
	/*
	 * This does not need to be locked, because the only thing that
	 * decrements events_pending is the socket event handlers, and the
	 * design is to have only one event outstanding at a time.
	 */
	INSIST(connection->events_pending == 0);
	connection->events_pending++;

	/*
	 * Block the send event from posting before the wait is established.
	 */
	if (connection->is_client)
		LOCK(&connection->wait_lock);

	isc_socket_sendv(connection->socket, &connection->output_buffers,
			 connection->task, send_done, connection);

	if (connection->is_client)
		/*
		 * Wait for the server's response to be processed.  If
		 * the result is not ISC_R_SUCCESS, connection_wait
		 * has freed the connection.
		 */
		result = connection_wait(connection);
	else
		result = ISC_R_SUCCESS;

	return (result);
}

/*
 * Make an outgoing connection to an OMAPI server.
 */
isc_result_t
connect_toserver(omapi_object_t *protocol, const char *server_name,
		 in_port_t port)
{
	isc_result_t result;
	isc_sockaddr_t sockaddr;
	isc_buffer_t *ibuffer = NULL, *obuffer = NULL;
	isc_task_t *task = NULL;
	omapi_connection_t *connection = NULL;

	result = get_address(server_name, port, &sockaddr);
	if (result != ISC_R_SUCCESS)
		return (result);

	/* XXXDCL Make cleanup better */
	/*
	 * Prepare the task that will wait for the connection to be made.
	 */
	result = isc_task_create(omapi_taskmgr, 0, &task);
	if (result != ISC_R_SUCCESS)
		return (result);

	result = isc_buffer_allocate(omapi_mctx, &ibuffer, OMAPI_BUFFER_SIZE);
	if (result != ISC_R_SUCCESS)
		goto free_task;

	result = isc_buffer_allocate(omapi_mctx, &obuffer, OMAPI_BUFFER_SIZE);
	if (result != ISC_R_SUCCESS)
		goto free_ibuffer;

	/*
	 * Create a new connection object.
	 */
	result = omapi_object_create((omapi_object_t **)&connection,
				     omapi_type_connection,
				     sizeof(*connection));
	if (result != ISC_R_SUCCESS)
		goto free_obuffer;

	connection->is_client = ISC_TRUE;
	connection->waiting = ISC_FALSE;
	connection->state = omapi_connection_connecting;
	connection->task = task;

	ISC_LIST_INIT(connection->input_buffers);
	ISC_LIST_APPEND(connection->input_buffers, ibuffer, link);
	ISC_LIST_INIT(connection->output_buffers);
	ISC_LIST_APPEND(connection->output_buffers, obuffer, link);

	RUNTIME_CHECK(isc_mutex_init(&connection->wait_lock) == ISC_R_SUCCESS);
	RUNTIME_CHECK(isc_condition_init(&connection->waiter) ==
		      ISC_R_SUCCESS);

	/*
	 * Tie the new connection object to the protocol object.
	 */
	OBJECT_REF(&protocol->outer, connection);
	OBJECT_REF(&connection->inner, protocol);

	/*
	 * Create a socket on which to communicate.
	 */
	result = isc_socket_create(omapi_socketmgr, isc_sockaddr_pf(&sockaddr),
				   isc_sockettype_tcp, &connection->socket);
	if (result == ISC_R_SUCCESS) {
		/*
		 * Lock before requesting the connection; this way
		 * connection_wait can safely block on connection->waiter
		 * before some connect error comes in and blows away the
		 * connection structure.
		 */
		LOCK(&connection->wait_lock);

		connection->events_pending = 1;
		result = isc_socket_connect(connection->socket, &sockaddr,
					    task, connect_done, connection);
	}

	if (result == ISC_R_SUCCESS)
		/*
		 * Wait for the connection event.  If result != ISC_R_SUCCESS,
		 * the connection was already abandoned via connect_done, so
		 * it does not need to be freed.
		 */
		result = connection_wait(connection);

	else
		/*
		 * There was an error calling isc_socket_create or
		 * isc_socket_connect.  Tear down the connection.
		 */
		end_connection(connection);

	return (result);

free_obuffer:
	isc_buffer_free(&obuffer);
free_ibuffer:
	isc_buffer_free(&ibuffer);
free_task:
	isc_task_destroy(&task);

	return (result);
}

/*
 * Put some bytes into the output buffer for a connection.
 */
isc_result_t
omapi_connection_putmem(omapi_object_t *c, const unsigned char *src,
			unsigned int len)
{
	omapi_connection_t *connection;
	omapi_protocol_t *protocol;
	isc_buffer_t *buffer;
	isc_bufferlist_t bufferlist;
	isc_constregion_t region;
	isc_result_t result;
	unsigned int space_available;

	REQUIRE(c != NULL && c->type == omapi_type_connection);

	connection = (omapi_connection_t *)c;

	protocol = (omapi_protocol_t *)connection->inner;

	REQUIRE(protocol != NULL && protocol->type == omapi_type_protocol);

	/*
	 * XXX make the auth stuff a part of the connection object instead?
	 */
	if (protocol->dst_update) {
		region.base = src;
		region.length = len;
		result = dst_context_adddata(protocol->dstctx,
					     (isc_region_t *)&region);
		if (result != ISC_R_SUCCESS)
			return (result);
	}

	/*
	 * Check for enough space in the output buffers.
	 */
	bufferlist = connection->output_buffers;
	space_available = isc_bufferlist_availablecount(&bufferlist);

	while (space_available < len) {
		/*
		 * Add new buffers until there is sufficient space.
		 */
		buffer = NULL;
		result = isc_buffer_allocate(omapi_mctx, &buffer,
					     OMAPI_BUFFER_SIZE);
		if (result != ISC_R_SUCCESS)
			return (result);

		space_available += OMAPI_BUFFER_SIZE;
		ISC_LIST_APPEND(bufferlist, buffer, link);
	}

	connection->out_bytes += len;

	/*
	 * Copy the data into the buffers, splitting across buffers
	 * as necessary.
	 */
	for (buffer = ISC_LIST_HEAD(bufferlist); len > 0;
	     buffer = ISC_LIST_NEXT(buffer, link)) {

		space_available = isc_buffer_availablelength(buffer);
		if (space_available > len)
			space_available = len;

		isc_buffer_putmem(buffer, src, space_available);

		src += space_available;
		len -= space_available;
	}

	return (ISC_R_SUCCESS);
}

/*
 * Copy some bytes from the input buffer, and advance the input buffer
 * pointer beyond the bytes copied out.
 */
void
connection_copyout(unsigned char *dst, omapi_connection_t *connection,
		   unsigned int size)
{
	isc_buffer_t *buffer;
	isc_region_t region;
	unsigned int copy_bytes;
	omapi_protocol_t *protocol;

	REQUIRE(connection != NULL &&
		connection->type == omapi_type_connection);

	protocol = (omapi_protocol_t *)connection->inner;

	REQUIRE(protocol != NULL && protocol->type == omapi_type_protocol);

	INSIST(size <= connection->in_bytes);

	connection->bytes_needed -= size;

	buffer = ISC_LIST_HEAD(connection->input_buffers);

	/*
	 * The data could potentially be split across multiple buffers,
	 * so rather than a simple memcpy, a loop is needed.
	 */
	while (size > 0) {
		copy_bytes = buffer->used - buffer->current;
		if (copy_bytes > size)
			copy_bytes = size;

		region.base = (unsigned char *)buffer->base + buffer->current;
		region.length = copy_bytes;

		/*
		 * When dst == NULL, this function is being used to skip
		 * over uninteresting input.
		 */
		if (dst != NULL)
			(void)memcpy(dst, region.base, copy_bytes);

		if (protocol->dst_update &&
		    protocol->verify_result == ISC_R_SUCCESS)
			protocol->verify_result =
				dst_context_adddata(protocol->dstctx, &region);

		isc_buffer_forward(buffer, copy_bytes);

		size -= copy_bytes;
		connection->in_bytes -= copy_bytes;

		buffer = ISC_LIST_NEXT(buffer, link);
	}
}

/*
 * Disconnect a connection object from the remote end.   If force is true,
 * close the connection immediately.   Otherwise, shut down the receiving end
 * but allow any unsent data to be sent before actually closing the socket.
 *
 * This routine is called in the following situations:
 *
 *   The client wants to exit normally after all its transactions are
 *   processed.  Closing the connection causes an ISC_R_EOF event result
 *   to be given to the server's recv_done, which then causes the
 *   server's recv_done to close its side of the connection.
 *
 *   The client got some sort of error it could not handle gracefully, so
 *   it wants to just tear down the connection.  This can be caused either
 *   internally in the omapi library, or by the calling program.
 *
 *   The server is dropping the connection.  This is always asynchronous;
 *   the server will never block waiting for a connection to be completed
 *   because it never initiates a "normal" close of the connection.
 *   (Receipt of ISC_R_EOF is always treated as though it were an error,
 *   no matter what the client had been intending; it's the nature of
 *   the protocol.)
 */
void
omapi_connection_disconnect(omapi_object_t *generic, isc_boolean_t force) {
	omapi_connection_t *connection;

	REQUIRE(generic != NULL);

	connection = (omapi_connection_t *)generic;

	REQUIRE(connection->type == omapi_type_connection);

	/*
	 * Only the client can request an unforced disconnection.  The server's
	 * "normal" (non-error) disconnection will always happen when the
	 * client goes away, and the only time it calls this function
	 * is to forcibly blow away a connection while trying to shut down.
	 * XXXDCL ... hmm, can timeouts of the client on the server be handled?
	 */
	REQUIRE(force || connection->is_client);

	/*
	 * XXXDCL this has to be fixed up when isc_socket_shutdown is
	 * available, because then the shutdown can be done asynchronously.
	 * It is currently done synchronously.
	 */

	if (! force) {
		/*
		 * Client wants a clean disconnect.
		 *
		 * Since this *must* have been called from the client driving
		 * thread, and the client never gets control back until all
		 * outstanding events have been posted, and the connection must
		 * still be valid for it to have been passed here, the
		 * following *must* be true.
		 */
		INSIST(connection->state == omapi_connection_connected);
		INSIST(connection->events_pending == 0);

		/*
		 * Fall through.
		 */
	}

	end_connection(connection);
}

/*
 * The caller wants a specific amount of bytes to be read.  Queue up a
 * recv for the socket.
 */
isc_result_t
connection_require(omapi_connection_t *connection, unsigned int bytes) {
	REQUIRE(connection != NULL &&
		connection->type == omapi_type_connection);

	INSIST(connection->state == omapi_connection_connected);

	connection->bytes_needed += bytes;

	if (connection->bytes_needed <= connection->in_bytes)
		return (ISC_R_SUCCESS);

	if (connection->bytes_needed >
	    isc_bufferlist_availablecount(&connection->input_buffers)) {
		/*
		 * Not enough space to put the required volume of information.
		 * See if the space can be attained by getting rid of the
		 * used buffer space.
		 *
		 * This could be made more efficient by not freeing
		 * the completely used buffers, but honestly the free/allocate
		 * code will probably *never* be used in practice; to even test
		 * the free/allocate stuff OMAPI_BUFFER_SIZE has to be set to
		 * an absurdly low value (like 4).
		 */
		isc_bufferlist_t bufferlist = connection->input_buffers;
		isc_buffer_t *buffer;
		isc_result_t result;

		buffer = ISC_LIST_HEAD(bufferlist);

		/*
		 * Lop off any completely used buffers, except the last one.
		 */
		while (isc_buffer_availablelength(buffer) == 0 &&
		       buffer != ISC_LIST_TAIL(bufferlist)) {

			ISC_LIST_UNLINK(bufferlist, buffer, link);
			isc_buffer_free(&buffer);

			buffer = ISC_LIST_HEAD(bufferlist);
		}

		/*
		 * Reclaim any used space.  (Any buffers after this one,
		 * if they exist at all, will be empty.)
		 */
		isc_buffer_compact(buffer);

		/*
		 * Create as many new buffers as necessary to fit the
		 * entire size requirement.
		 */
		while (connection->bytes_needed >
		       isc_bufferlist_availablecount(&bufferlist)) {

			buffer = NULL;
			result = isc_buffer_allocate(omapi_mctx, &buffer,
						     OMAPI_BUFFER_SIZE);
			if (result != ISC_R_SUCCESS)
				return (result);

			ISC_LIST_APPEND(bufferlist, buffer, link);
		}
	}


	/*
	 * Queue the receive task.
	 */
	INSIST(connection->events_pending == 0);
	connection->events_pending++;

	/*
	 * The client should already be waiting.
	 */
	if (connection->is_client)
		INSIST(connection->waiting);

	/*
	 * XXXDCL The "minimum" arg has not been fully thought out.
	 */
	isc_socket_recvv(connection->socket, &connection->input_buffers,
			 connection->bytes_needed - connection->in_bytes,
			 connection->task, recv_done, connection);

	return (OMAPI_R_NOTYET);
}

void
connection_getuint32(omapi_connection_t *connection,
		     isc_uint32_t *value)
{
	isc_uint32_t inbuf;

	REQUIRE(connection != NULL &&
		connection->type == omapi_type_connection);

	connection_copyout((unsigned char *)&inbuf, connection, sizeof(inbuf));

	*value = ntohl(inbuf);
}

void
connection_getuint16(omapi_connection_t *connection,
		     isc_uint16_t *value) {
	isc_uint16_t inbuf;

	REQUIRE(connection != NULL &&
		connection->type == omapi_type_connection);

	connection_copyout((unsigned char *)&inbuf, connection, sizeof(inbuf));

	*value = ntohs(inbuf);
}

isc_result_t
omapi_connection_putuint32(omapi_object_t *c, isc_uint32_t value) {
	isc_uint32_t inbuf;

	inbuf = htonl(value);

	return (omapi_connection_putmem(c, (unsigned char *)&inbuf,
					sizeof(inbuf)));
}

isc_result_t
omapi_connection_putuint16(omapi_object_t *c, isc_uint32_t value) {
	isc_uint16_t inbuf;

	REQUIRE(value < 65536);

	inbuf = htons((isc_uint16_t)value);

	return (omapi_connection_putmem(c, (unsigned char *)&inbuf,
					sizeof(inbuf)));
}

isc_result_t
omapi_connection_putdata(omapi_object_t *c, omapi_data_t *data) {
	isc_result_t result;
	omapi_handle_t handle;

	REQUIRE(data != NULL &&
		(data->type == omapi_datatype_int    ||
		 data->type == omapi_datatype_data   ||
		 data->type == omapi_datatype_string ||
		 data->type == omapi_datatype_object));

	switch (data->type) {
	      case omapi_datatype_int:
		result = omapi_connection_putuint32(c, sizeof(isc_uint32_t));
		if (result != ISC_R_SUCCESS)
			return (result);
		return (omapi_connection_putuint32(c, ((isc_uint32_t)
							(data->u.integer))));

	      case omapi_datatype_string:
	      case omapi_datatype_data:
		result = omapi_connection_putuint32(c, data->u.buffer.len);
		if (result != ISC_R_SUCCESS)
			return (result);
		if (data->u.buffer.len > 0)
			return (omapi_connection_putmem(c,
							data->u.buffer.value,
							data->u.buffer.len));
		return (ISC_R_SUCCESS);

	      case omapi_datatype_object:
		if (data->u.object != NULL) {
			result = object_gethandle(&handle, data->u.object);
			if (result != ISC_R_SUCCESS)
				return (result);
		} else
			handle = 0;
		result = omapi_connection_putuint32(c, sizeof(handle));
		if (result != ISC_R_SUCCESS)
			return (result);
		return (omapi_connection_putuint32(c, handle));
	}

	UNEXPECTED_ERROR(__FILE__, __LINE__,
			 "unknown type in omapi_connection_putdata: "
			 "%d", data->type);
	return (ISC_R_UNEXPECTED);
}

isc_result_t
omapi_connection_putname(omapi_object_t *c, const char *name) {
	isc_result_t result;
	unsigned int len = strlen(name);

	if (len > 65535)
		/* XXXDCL better error? */
		return (ISC_R_FAILURE);

	result = omapi_connection_putuint16(c, len);
	if (result != ISC_R_SUCCESS)
		return (result);

	return (omapi_connection_putmem(c, (const unsigned char *)name, len));
}

isc_result_t
omapi_connection_putstring(omapi_object_t *c, const char *string) {
	isc_result_t result;
	unsigned int len;

	if (string != NULL)
		len = strlen(string);
	else
		len = 0;

	result = omapi_connection_putuint32(c, len);

	if (result == ISC_R_SUCCESS && len > 0)
		result = omapi_connection_putmem(c,
						 (const unsigned char *)string,
						 len);
	return (result);
}

isc_result_t
omapi_connection_puthandle(omapi_object_t *c, omapi_object_t *h) {
	isc_result_t result;
	omapi_handle_t handle;

	if (h != NULL) {
		result = object_gethandle(&handle, h);
		if (result != ISC_R_SUCCESS)
			return (result);
	} else
		handle = 0;	/* The null handle. */

	result = omapi_connection_putuint32(c, sizeof(handle));

	if (result == ISC_R_SUCCESS)
		result = omapi_connection_putuint32(c, handle);

	return (result);
}

static isc_result_t
connection_setvalue(omapi_object_t *connection, omapi_string_t *name,
		    omapi_data_t *value)
{
	REQUIRE(connection != NULL &&
		connection->type == omapi_type_connection);

	return (omapi_object_passsetvalue(connection, name, value));
}

static isc_result_t
connection_getvalue(omapi_object_t *connection, omapi_string_t *name,
		    omapi_value_t **value)
{
	REQUIRE(connection != NULL &&
		connection->type == omapi_type_connection);

	return (omapi_object_passgetvalue(connection, name, value));
}

static void
connection_destroy(omapi_object_t *handle) {
	omapi_connection_t *connection;

	REQUIRE(handle != NULL && handle->type == omapi_type_connection);

	connection = (omapi_connection_t *)handle;

	/*
	 * end_connection is the proper entry point for removing a
	 * connection, so it should have been called to do all the cleanup.
	 */

	/*
	 * XXXDCL somehow, not all memory is being destroyed with abnormal
	 * drops.  run the omapi_test program listener.  then run the
	 * omapi_test as a client, and break at the end of omapi_auth_use.
	 * when the debugger stops, exit the debugger.  only two blocks
	 * of memory are freed, but i suspect there are more than those
	 * associated with the connection.
	 */

	if (connection->state == omapi_connection_connected) {
		UNEXPECTED_ERROR(__FILE__, __LINE__,
				 "Unexpected path to connection_destroy - "
				 "the connection object was dereferenced "
				 "without a previous disconnect");
		omapi_connection_disconnect(handle, OMAPI_FORCE_DISCONNECT);
	}
}

static isc_result_t
connection_signalhandler(omapi_object_t *connection, const char *name,
			 va_list ap)
{
	REQUIRE(connection != NULL &&
		connection->type == omapi_type_connection);

	return (omapi_object_passsignal(connection, name, ap));
}

/*
 * Write all the published values associated with the object through the
 * specified connection.
 */
static isc_result_t
connection_stuffvalues(omapi_object_t *connection, omapi_object_t *handle)
{
	REQUIRE(connection != NULL &&
		connection->type == omapi_type_connection);

	return (omapi_object_passstuffvalues(connection, handle));
}

isc_result_t
connection_init(void) {
	return (omapi_object_register(&omapi_type_connection, "connection",
				      connection_setvalue,
				      connection_getvalue,
				      connection_destroy,
				      connection_signalhandler,
				      connection_stuffvalues,
				      NULL, NULL, NULL));
}
