/*
 * Copyright (c) 1998,1999,2000
 *	Traakan, Inc., Los Altos, CA
 *	All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Project:  NDMJOB
 * Ident:    $Id: $
 *
 * Description:
 *
 */


#include "ndmjob.h"
#include "ndmlib.h"


#ifndef NDMOS_OPTION_NO_NDMP4
#define MAX_PROTOCOL_VERSION	NDMP4VER
#else /* !NDMOS_OPTION_NO_NDMP4 */
#ifndef NDMOS_OPTION_NO_NDMP3
#define MAX_PROTOCOL_VERSION	NDMP3VER
#else /* !NDMOS_OPTION_NO_NDMP3 */
#ifndef NDMOS_OPTION_NO_NDMP2
#define MAX_PROTOCOL_VERSION	NDMP2VER
#else /* !NDMOS_OPTION_NO_NDMP2 */
#define MAX_PROTOCOL_VERSION	0
#endif /* !NDMOS_OPTION_NO_NDM2 */
#endif /* !NDMOS_OPTION_NO_NDMP3 */
#endif /* !NDMOS_OPTION_NO_NDMP4 */



/*
 * INITIALIZE AND DESTRUCT
 ****************************************************************
 *
 * Initialize an ndmconn. This pretty much amounts to
 * initializing the underlying ndmchan and stuffing
 * the function pointers.
 */

struct ndmconn *
ndmconn_initialize (struct ndmconn *aconn, char *name)
{
	struct ndmconn *	conn = aconn;

	if (!conn) {
		conn = NDMOS_MACRO_NEW(struct ndmconn);
		if (!conn)
			return 0;
	}

	NDMOS_MACRO_ZEROFILL(conn);

	if (!name) name = "#?";				/* default */

	ndmchan_initialize (&conn->chan, name);
	conn->was_allocated = aconn == 0;
	conn->next_sequence = 1;
	xdrrec_create (&conn->xdrs, 0, 0, (void*) conn,
				(void*)ndmconn_readit,
				(void*)ndmconn_writeit);
	conn->unexpected = ndmconn_unexpected;

	conn->call = ndmconn_call;

	conn->time_limit = 0;

	return conn;
}

/*
 * Get rid of an ndmconn.
 */
void
ndmconn_destruct (struct ndmconn *conn)
{
	if (conn->chan.fd >= 0) {
		close (conn->chan.fd);
		conn->chan.fd = -1;
	}

	xdr_destroy (&conn->xdrs);

	if (conn->was_allocated) {
		NDMOS_API_FREE (conn);
		conn = 0;
	}
}

/*
 * ESTABLISH CONNECTION
 ****************************************************************
 *
 * The following four routines establish the TCP/IP connection
 * between agents.
 *
 * ndmconn_connect_agent()
 *     make a connection per an ndmagent, uses ..._host_port()
 * ndmconn_connect_host_port ()
 *     make a connection per a hostname and port, uses ..._sockaddr_in()
 * ndmconn_connect_sockaddr_in()
 *     make a connection per sockaddr_in, performs NDMP_CONNECT_
 *     sequences, but no authentication
 * ndmconn_accept()
 *     make a connection (receive it really) from a file descriptor
 *     already accept()ed.
 */

int
ndmconn_connect_agent (struct ndmconn *conn, struct ndmagent *agent)
{
	if (agent->conn_type == NDMCONN_TYPE_RESIDENT) {
		conn->conn_type = NDMCONN_TYPE_RESIDENT;
		conn->protocol_version = agent->protocol_version;
		if (conn->protocol_version == 0) {
			/* Let's negotiate......MAX */
			conn->protocol_version = MAX_PROTOCOL_VERSION;
		}
		ndmchan_start_resident (&conn->chan);
		return 0;
	}

	if (agent->port == 0) agent->port = NDMPPORT;

	return ndmconn_connect_host_port (conn,
			agent->host, agent->port, agent->protocol_version);
}


int
ndmconn_connect_host_port (struct ndmconn *conn,
  char * hostname, int port, unsigned want_protocol_version)
{
	struct sockaddr_in	sin;
	char *			err = "???";

	if (conn->chan.fd >= 0) {
		err = "already-connected";
		return ndmconn_set_err_msg (conn, err);
	}

	if (ndmhost_lookup (hostname, &sin) != 0) {
		err = "bad-host-name";
		return ndmconn_set_err_msg (conn, err);
	}

	if (port == 0) port = NDMPPORT;

	sin.sin_port = htons(port);

	return ndmconn_connect_sockaddr_in (conn, &sin, want_protocol_version);
}

int
ndmconn_connect_sockaddr_in (struct ndmconn *conn,
  struct sockaddr_in *sin, unsigned want_protocol_version)
{
	int			fd = -1;
	int			rc;
	char *			err = "???";
	unsigned		max_protocol_version = MAX_PROTOCOL_VERSION;

	if (conn->chan.fd >= 0) {
		err = "already-connected";
		return ndmconn_set_err_msg (conn, err);
	}

	fd = socket (AF_INET, SOCK_STREAM, 0);
	if (fd < 0) {
		err = malloc(1024);
		snprintf(err, 1023, "open a socket failed: %s", strerror(errno));
		goto error_out;
	}

	/* reserved port? */
	if (connect (fd, (struct sockaddr *)sin, sizeof *sin) < 0) {
		// leak memory
		err = malloc(1024);
		snprintf(err, 1023, "connect failed: %s", strerror(errno));
		goto error_out;
	}

	ndmchan_start_readchk (&conn->chan, fd);
	conn->conn_type = NDMCONN_TYPE_REMOTE;

	/*
	 * Await the NDMP_NOTIFY_CONNECTED request (no reply)
	 * Don't get confused that this client-side is awaiting
	 * a "request" from the server.
	 */
	NDMC_WITH_NO_REPLY(ndmp0_notify_connected,0)
		rc = ndmconn_recv_nmb(conn, &xa->request);
		if (rc != 0) {
			err = "recv-notify-connected";
			goto error_out;
		}
		if (xa->request.header.message_type != NDMP0_MESSAGE_REQUEST
		 || xa->request.header.message != NDMP0_NOTIFY_CONNECTED) {
			err = "msg-not-notify-connected";
			goto error_out;
		}

		if (request->reason != NDMP0_CONNECTED) {
			err = "notify-connected-not-connected";
			goto error_out;
		}

		if (max_protocol_version > request->protocol_version) {
			max_protocol_version = request->protocol_version;
		}
	NDMC_ENDWITH

	if (want_protocol_version == 0) {
		want_protocol_version = max_protocol_version;
	} else if (want_protocol_version > max_protocol_version) {
		err = "connect-want/max-version-mismatch";
		goto error_out;
	}

	/*
	 * Send the OPEN request
	 */
	NDMC_WITH(ndmp0_connect_open,0)
		request->protocol_version = want_protocol_version;
		rc = NDMC_CALL(conn);
		if (rc) {
			err = "connect-open-failed";
			goto error_out;
		}
	NDMC_ENDWITH

	/* GOOD! */

	conn->protocol_version = want_protocol_version;

	return 0;

  error_out:
	if (fd >= 0) {
		close (fd);
		fd = -1;
	}
	conn->chan.fd = -1;
	conn->chan.mode = NDMCHAN_MODE_IDLE;
	conn->conn_type = NDMCONN_TYPE_NONE;

	return ndmconn_set_err_msg (conn, err);
}

int
ndmconn_try_open (struct ndmconn *conn, unsigned protocol_version)
{
	int		rc;

	/*
	 * Send the OPEN request
	 */
	NDMC_WITH(ndmp0_connect_open,0)
		request->protocol_version = protocol_version;
		rc = NDMC_CALL(conn);
		if (rc) {
			ndmconn_set_err_msg (conn, "connect-open-failed");
		}
	NDMC_ENDWITH

	return rc;
}

int
ndmconn_accept (struct ndmconn *conn, int sock)
{
	char *			err = "???";

	if (conn->chan.fd >= 0) {
		err = "already-connected";
		return ndmconn_set_err_msg (conn, err);
	}

	ndmchan_start_readchk (&conn->chan, sock);
	conn->conn_type = NDMCONN_TYPE_REMOTE;

	/*
	 * Send the NDMP_NOTIFY_CONNECTED message, no reply
	 * The connect()er is waiting for it.
	 */
	NDMC_WITH_NO_REPLY(ndmp0_notify_connected,0)
		request->reason = NDMP0_CONNECTED;
		request->protocol_version = MAX_PROTOCOL_VERSION;
		request->text_reason = "Hello";
		NDMC_SEND(conn);
	NDMC_ENDWITH

	/* assume connection is running in offered protocol_version */
	conn->protocol_version = MAX_PROTOCOL_VERSION;

	return 0;
}




/*
 * TERMINATE CONNECTION
 ****************************************************************
 *
 * These two routines are about terminating a connection.
 * They are incomplete.
 */

/* hangup */
int
ndmconn_abort (struct ndmconn *conn)
{
	return 0;
}

/* orderly close */
int
ndmconn_close (struct ndmconn *conn)
{
	return 0;
}


/*
 * Return the underlying fd of the ndmconn.
 * This is no longer used since the ndmchan stuff was done.
 */

int
ndmconn_fileno (struct ndmconn *conn)
{
	return conn->chan.fd;
}





/*
 * AUTHENTICATION
 *
 * The following three routines do the NDMP_CONNECT_AUTH sequences.
 */

int
ndmconn_auth_agent (struct ndmconn *conn, struct ndmagent *agent)
{
	int		rc;

	if (conn->conn_type == NDMCONN_TYPE_RESIDENT)
		return 0;

	switch (agent->auth_type) {
	case 'n':		/* NDMP_AUTH_NONE */
		rc = ndmconn_auth_none (conn);
		break;

	case 't':		/* NDMP_AUTH_TEXT */
		rc = ndmconn_auth_text (conn, agent->account, agent->password);
		break;

	case 'm':		/* NDMP_AUTH_MD5 */
		rc = ndmconn_auth_md5 (conn, agent->account, agent->password);
		break;

	case 'v':		/* void (don't auth) */
		rc = 0;
		break;

	default:
		ndmconn_set_err_msg (conn, "connect-auth-unknown");
		rc = -1;
		break;
	}

	return rc;
}

int
ndmconn_auth_none (struct ndmconn *conn)
{
	int		rc;

	switch (conn->protocol_version) {
	default:
		ndmconn_set_err_msg (conn, "connect-auth-none-vers-botch");
		return -1;

#ifndef NDMOS_OPTION_NO_NDMP2
	case NDMP2VER:
	    NDMC_WITH(ndmp2_connect_client_auth, NDMP2VER)
		request->auth_data.auth_type = NDMP2_AUTH_NONE;
		rc = NDMC_CALL(conn);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP2 */

#ifndef NDMOS_OPTION_NO_NDMP3
	case NDMP3VER:
	    NDMC_WITH(ndmp3_connect_client_auth, NDMP3VER)
		request->auth_data.auth_type = NDMP3_AUTH_NONE;
		rc = NDMC_CALL(conn);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP3 */

#ifndef NDMOS_OPTION_NO_NDMP4
	case NDMP4VER:
	    NDMC_WITH(ndmp4_connect_client_auth, NDMP4VER)
		request->auth_data.auth_type = NDMP4_AUTH_NONE;
		rc = NDMC_CALL(conn);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP4 */
	}

	if (rc) {
		ndmconn_set_err_msg (conn, "connect-auth-none-failed");
		return -1;
	}

	return 0;
}

int
ndmconn_auth_text (struct ndmconn *conn, char *id, char *pw)
{
	int		rc;

	switch (conn->protocol_version) {
	default:
		ndmconn_set_err_msg (conn, "connect-auth-text-vers-botch");
		return -1;

#ifndef NDMOS_OPTION_NO_NDMP2
	case NDMP2VER:
	    NDMC_WITH(ndmp2_connect_client_auth, NDMP2VER)
		struct ndmp2_auth_text *at;

		request->auth_data.auth_type = NDMP2_AUTH_TEXT;
		at = &request->auth_data.ndmp2_auth_data_u.auth_text;
		at->auth_id = id;
		at->auth_password = pw;
		rc = NDMC_CALL(conn);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP2 */

#ifndef NDMOS_OPTION_NO_NDMP3
	case NDMP3VER:
	    NDMC_WITH(ndmp3_connect_client_auth, NDMP3VER)
		struct ndmp3_auth_text *at;

		request->auth_data.auth_type = NDMP3_AUTH_TEXT;
		at = &request->auth_data.ndmp3_auth_data_u.auth_text;
		at->auth_id = id;
		at->auth_password = pw;
		rc = NDMC_CALL(conn);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP3 */

#ifndef NDMOS_OPTION_NO_NDMP4
	case NDMP4VER:
	    NDMC_WITH(ndmp4_connect_client_auth, NDMP4VER)
		struct ndmp4_auth_text *at;

		request->auth_data.auth_type = NDMP4_AUTH_TEXT;
		at = &request->auth_data.ndmp4_auth_data_u.auth_text;
		at->auth_id = id;
		at->auth_password = pw;
		rc = NDMC_CALL(conn);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP4 */
	}

	if (rc) {
		ndmconn_set_err_msg (conn, "connect-auth-text-failed");
		return -1;
	}

	return 0;
}

int
ndmconn_auth_md5 (struct ndmconn *conn, char *id, char *pw)
{
	int		rc;
	char		challenge[NDMP_MD5_CHALLENGE_LENGTH];
	char		digest[NDMP_MD5_DIGEST_LENGTH];

	switch (conn->protocol_version) {
	default:
		ndmconn_set_err_msg (conn, "connect-auth-md5-vers-botch");
		return -1;

#ifndef NDMOS_OPTION_NO_NDMP2
	case NDMP2VER:
	    NDMC_WITH(ndmp2_config_get_auth_attr, NDMP2VER)
		request->auth_type = NDMP2_AUTH_MD5;
		rc = NDMC_CALL(conn);
		if (rc == 0) {
			if (reply->server_attr.auth_type != NDMP2_AUTH_MD5) {
				ndmconn_set_err_msg (conn,
					"connect-auth-md5-attr-type-botch");
				return -1;
			}
			NDMOS_API_BCOPY (
				reply->server_attr.ndmp2_auth_attr_u.challenge,
				challenge, sizeof challenge);
		}
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP2 */

#ifndef NDMOS_OPTION_NO_NDMP3
	case NDMP3VER:
	    NDMC_WITH(ndmp3_config_get_auth_attr, NDMP3VER)
		request->auth_type = NDMP3_AUTH_MD5;
		rc = NDMC_CALL(conn);
		if (rc == 0) {
			if (reply->server_attr.auth_type != NDMP3_AUTH_MD5) {
				ndmconn_set_err_msg (conn,
					"connect-auth-md5-attr-type-botch");
				return -1;
			}
			NDMOS_API_BCOPY (
				reply->server_attr.ndmp3_auth_attr_u.challenge,
				challenge, sizeof challenge);
		}
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP3 */

#ifndef NDMOS_OPTION_NO_NDMP4
	case NDMP4VER:
	    NDMC_WITH(ndmp4_config_get_auth_attr, NDMP4VER)
		request->auth_type = NDMP4_AUTH_MD5;
		rc = NDMC_CALL(conn);
		if (rc == 0) {
			if (reply->server_attr.auth_type != NDMP4_AUTH_MD5) {
				ndmconn_set_err_msg (conn,
					"connect-auth-md5-attr-type-botch");
				return -1;
			}
			NDMOS_API_BCOPY (
				reply->server_attr.ndmp4_auth_attr_u.challenge,
				challenge, sizeof challenge);
		}
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP4 */
	}

	if (rc) {
		ndmconn_set_err_msg (conn, "connect-auth-md5-attr-failed");
		return -1;
	}

	ndmmd5_digest (challenge, pw, digest);

	switch (conn->protocol_version) {
	default:
		ndmconn_set_err_msg (conn, "connect-auth-text-vers-botch");
		return -1;

#ifndef NDMOS_OPTION_NO_NDMP2
	case NDMP2VER:
	    NDMC_WITH(ndmp2_connect_client_auth, NDMP2VER)
		struct ndmp2_auth_md5 *am;

		request->auth_data.auth_type = NDMP2_AUTH_MD5;
		am = &request->auth_data.ndmp2_auth_data_u.auth_md5;
		am->auth_id = id;
		NDMOS_API_BCOPY (digest, am->auth_digest, sizeof digest);
		rc = NDMC_CALL(conn);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP2 */

#ifndef NDMOS_OPTION_NO_NDMP3
	case NDMP3VER:
	    NDMC_WITH(ndmp3_connect_client_auth, NDMP3VER)
		struct ndmp3_auth_md5 *am;

		request->auth_data.auth_type = NDMP3_AUTH_MD5;
		am = &request->auth_data.ndmp3_auth_data_u.auth_md5;
		am->auth_id = id;
		NDMOS_API_BCOPY (digest, am->auth_digest, sizeof digest);
		rc = NDMC_CALL(conn);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP3 */

#ifndef NDMOS_OPTION_NO_NDMP4
	case NDMP4VER:
	    NDMC_WITH(ndmp4_connect_client_auth, NDMP4VER)
		struct ndmp4_auth_md5 *am;

		request->auth_data.auth_type = NDMP4_AUTH_MD5;
		am = &request->auth_data.ndmp4_auth_data_u.auth_md5;
		am->auth_id = id;
		NDMOS_API_BCOPY (digest, am->auth_digest, sizeof digest);
		rc = NDMC_CALL(conn);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP4 */
	}

	if (rc) {
		ndmconn_set_err_msg (conn, "connect-auth-md5-failed");
		return -1;
	}

	return 0;
}




/*
 * CALL (REQUEST/REPLY), SEND, and RECEIVE
 ****************************************************************
 */

int
ndmconn_call (struct ndmconn *conn, struct ndmp_xa_buf *xa)
{
	unsigned		protocol_version = conn->protocol_version;
	unsigned		msg = xa->request.header.message;
	int			rc;
	struct ndmp_xdr_message_table *	xmte;

	conn->last_message = msg;
	conn->last_call_status = NDMCONN_CALL_STATUS_BOTCH;
	conn->last_header_error = -1;	/* invalid */
	conn->last_reply_error = -1;	/* invalid */

	if (protocol_version != xa->request.protocol_version) {
		ndmconn_set_err_msg (conn, "protocol-version-mismatch");
		return NDMCONN_CALL_STATUS_BOTCH;
	}

	xmte = ndmp_xmt_lookup (protocol_version, msg);
	if (!xmte) {
		ndmconn_set_err_msg (conn, "no-xdr-found");
		return NDMCONN_CALL_STATUS_BOTCH;
	}

	xa->request.header.message_type = NDMP0_MESSAGE_REQUEST;

	if (!xmte->xdr_reply) {
		/* no reply expected, just a send (eg NOTIFY) */
		return ndmconn_send_nmb (conn, &xa->request);
	}

	rc = ndmconn_exchange_nmb (conn, &xa->request, &xa->reply);
	if (rc) {
		ndmconn_set_err_msg (conn, "exchange-failed");
		return NDMCONN_CALL_STATUS_BOTCH;
	}

	if (xa->reply.header.message != msg) {
		ndmconn_set_err_msg (conn, "msg-mismatch");
		return NDMCONN_CALL_STATUS_BOTCH;
	}

	/* TODO: this should be converted ndmp_xto9_error(....) */
	conn->last_header_error = xa->reply.header.error;

	if (xa->reply.header.error) {
		conn->last_call_status = NDMCONN_CALL_STATUS_HDR_ERROR;
		ndmconn_set_err_msg (conn, "reply-error-hdr");
		return NDMCONN_CALL_STATUS_HDR_ERROR;
	}

	conn->last_reply_error = ndmnmb_get_reply_error (&xa->reply);

	if (conn->last_reply_error != NDMP9_NO_ERR) {
		conn->last_call_status = NDMCONN_CALL_STATUS_REPLY_ERROR;
		ndmconn_set_err_msg (conn, "reply-error");
		return NDMCONN_CALL_STATUS_REPLY_ERROR;
	}

	return NDMCONN_CALL_STATUS_OK;
}

int
ndmconn_exchange_nmb (struct ndmconn *conn,
  struct ndmp_msg_buf *request_nmb,
  struct ndmp_msg_buf *reply_nmb)
{
	int			rc;

	if ((rc = ndmconn_send_nmb (conn, request_nmb)) != 0)
		return rc;
	conn->received_time = 0;
	conn->sent_time = time(0);

	for (;;) {
		if ((rc = ndmconn_recv_nmb (conn, reply_nmb)) != 0)
			return rc;

		if (reply_nmb->header.message_type == NDMP0_MESSAGE_REPLY
		 && reply_nmb->header.reply_sequence
		    == request_nmb->header.sequence) {
			conn->received_time = time(0);
			return 0;
		}

		(*conn->unexpected)(conn, reply_nmb);
	}
}

int
ndmconn_send_nmb (struct ndmconn *conn, struct ndmp_msg_buf *nmb)
{
	return ndmconn_xdr_nmb (conn, nmb, XDR_ENCODE);
}

int
ndmconn_recv_nmb (struct ndmconn *conn, struct ndmp_msg_buf *nmb)
{
	NDMOS_MACRO_ZEROFILL (nmb);
	nmb->protocol_version = conn->protocol_version;

	return ndmconn_xdr_nmb (conn, nmb, XDR_DECODE);
}

void
ndmconn_free_nmb (struct ndmconn *conn, struct ndmp_msg_buf *nmb)
{
	ndmnmb_free (nmb);
}


int
ndmconn_xdr_nmb (struct ndmconn *conn,
  struct ndmp_msg_buf *nmb,
  enum xdr_op x_op)
{
	xdrproc_t xdr_body = 0;

	assert (conn->conn_type == NDMCONN_TYPE_REMOTE);

	if (conn->chan.fd < 0) {
		return ndmconn_set_err_msg (conn, "not-open");
	}

	conn->xdrs.x_op = x_op;

	if (x_op == XDR_ENCODE) {
		xdr_body = ndmnmb_find_xdrproc (nmb);

		if (nmb->header.error == NDMP0_NO_ERR && !xdr_body) {
			return ndmconn_set_err_msg (conn, "unknown-body");
		}
		nmb->header.sequence = conn->next_sequence++;
		nmb->header.time_stamp = time(0);
		ndmconn_snoop_nmb (conn, nmb, "Send");
	}
	if (x_op == XDR_DECODE) {
		if (!xdrrec_skiprecord (&conn->xdrs)) {
			return ndmconn_set_err_msg (conn, "xdr-get-next");
		}
	}

	if (!xdr_ndmp0_header (&conn->xdrs, &nmb->header)) {
		ndmconn_abort (conn);
		if (x_op == XDR_DECODE
		 && conn->chan.eof && !conn->chan.error) {
			return ndmconn_set_err_msg (conn, "EOF");
		} else {
			return ndmconn_set_err_msg (conn, "xdr-hdr");
		}
	}

	if (x_op == XDR_DECODE) {
		xdr_body = ndmnmb_find_xdrproc (nmb);

		if (nmb->header.error == NDMP0_NO_ERR && !xdr_body) {
			return ndmconn_set_err_msg (conn, "unknown-body");
		}
	}
	if (nmb->header.error == NDMP0_NO_ERR) {
		if (!(*xdr_body) (&conn->xdrs, &nmb->body)) {
			ndmconn_abort (conn);
			return ndmconn_set_err_msg (conn, "xdr-body");
		}
	}

	if (x_op == XDR_ENCODE) {
		if (!xdrrec_endofrecord(&conn->xdrs, 1)) {
			ndmconn_abort (conn);
			return ndmconn_set_err_msg (conn, "xdr-send");
		}
	}
	if (x_op == XDR_DECODE) {
		ndmconn_snoop_nmb (conn, nmb, "Recv");
	}

	return 0;
}




/*
 * XDR READ/WRITE CALLBACKS
 ****************************************************************
 *
 * ndmconn_readit() and ndmconn_writeit() are the XDR callbacks
 * used by xdrrec_create(). They are fundamentally wrappers
 * around read() and write(), and have very similar parameters.
 * See the xdr(3) manual page (or try "man xdrrec_create").
 *
 * ndmconn_readit() tracks the XDR record marks, and never
 * reads across a record boundary. This keeps select() an
 * indicator of when there is a (single) request pending.
 * Otherwise, we have to check buffers internal to XDR
 * as well as the file descriptor (via select) to determine
 * if a request is pending.
 */

int
ndmconn_readit (void *a_conn, char *buf, int len)
{
	struct ndmconn *conn = (struct ndmconn *)a_conn;
	int		rc, i, c;

	/* could impose timeout here */
	if (conn->chan.fd < 0 || conn->chan.eof)
		return -1;

	ndmconn_snoop (conn, 8,
		"frag_resid=%d fhb_off=%d", conn->frag_resid, conn->fhb_off);

	if (conn->frag_resid == 0) {
		i = 0;
		while (i < 4) {
			c = 4 - i;

			rc = ndmconn_sys_read (conn, (void *)(conn->frag_hdr_buf+i), c);
			if (rc <= 0) {
				return rc;
			}
			i += rc;
		}
		conn->frag_resid = conn->frag_hdr_buf[0] << 24;
		conn->frag_resid |= conn->frag_hdr_buf[1] << 16;
		conn->frag_resid |= conn->frag_hdr_buf[2] << 8;
		conn->frag_resid |= conn->frag_hdr_buf[3];
		conn->frag_resid &= 0xFFFFFF;
		conn->fhb_off = 0;
	}
	if (conn->fhb_off < 4) {
		i = 0;
		while (conn->fhb_off < 4 && len > 0) {
			buf[i++] = conn->frag_hdr_buf[conn->fhb_off++];
			len--;
		}
		return i;
	}

	if ((unsigned int)len > conn->frag_resid)
		len = (unsigned int)conn->frag_resid;

	rc = ndmconn_sys_read (conn, buf, len);

	if (rc > 0) {
		conn->frag_resid -= rc;
	}

	return rc;
}

int
ndmconn_writeit (void *a_conn, char *buf, int len)
{
	struct ndmconn *conn = (struct ndmconn *)a_conn;

	/* could impose timeout here */
	if (conn->chan.fd < 0)
		return -1;

	return ndmconn_sys_write (conn, buf, len);
}

/*
 * ndmconn_sys_read() and ndmconn_sys_write() are simply
 * wrappers around read() and write(). They implement
 * the low-level snooping.
 */

int
ndmconn_sys_read (struct ndmconn *conn, char *buf, unsigned len)
{
	int		rc;

	ndmconn_snoop (conn, 9, "reading %d ...", len);

	rc = read (conn->chan.fd, buf, len);

	ndmconn_snoop (conn, 8, "read=%d len=%d", rc, len);
	ndmconn_hex_dump (conn, buf, rc);

	if (rc <= 0) {
		conn->chan.eof = 1;
		if (rc < 0)
			conn->chan.error = 1;
	}

	return rc;
}

int
ndmconn_sys_write (struct ndmconn *conn, char *buf, unsigned len)
{
	int		rc;

	ndmconn_snoop (conn, 9, "writing %d ...", len);
	ndmconn_hex_dump (conn, buf, len);

	rc = write (conn->chan.fd, buf, len);

	ndmconn_snoop (conn, 8, "write=%d len=%d", rc, len);

	if (rc !=(int)len) {
		conn->chan.eof = 1;
		conn->chan.error = 1;
	}

	return rc;
}




/*
 * UNEXPECTED
 ****************************************************************
 *
 * The default unexpected() handler for a connection. It is
 * called when ndmconn_exchange_nmb() receives something
 * other than the reply for which it is waiting.
 * This default routine silently dumps the message.
 */

void
ndmconn_unexpected (struct ndmconn *conn, struct ndmp_msg_buf *nmb)
{
	xdrproc_t xdr_body = ndmnmb_find_xdrproc (nmb);

	if (xdr_body) {
		xdr_free (xdr_body, (void*) &nmb->body);
	}
}




/*
 * SNOOP
 ****************************************************************
 *
 * The ndmconn snoop stuff. The cool part. This pretty prints
 * NDMP messages as they go flying by this end-point.
 */

int
ndmconn_set_snoop (struct ndmconn *conn, struct ndmlog *log, int level)
{
	conn->snoop_log = log;
	conn->snoop_level = level;
	return 0;
}

void
ndmconn_clear_snoop (struct ndmconn *conn)
{
	conn->snoop_log = 0;
	conn->snoop_level = 0;
}

void
ndmconn_snoop_nmb (struct ndmconn *conn,
  struct ndmp_msg_buf *nmb,
  char *whence)
{
	if (!conn->snoop_log) {
		return;
	}

	ndmnmb_snoop (conn->snoop_log, conn->chan.name, conn->snoop_level,
				nmb, whence);
}

void
ndmconn_snoop (struct ndmconn *conn, int level, char *fmt, ...)
{
	va_list		ap;

	if (conn->snoop_log && conn->snoop_level >= level) {
		va_start (ap, fmt);
		ndmlogfv (conn->snoop_log, conn->chan.name, level, fmt, ap);
		va_end (ap);
	}
}

/* used by ndmconn_sys_read() and ndmconn_sys_write() to show low-level */
void
ndmconn_hex_dump (struct ndmconn *conn, char *buf, unsigned len)
{
	struct ndmlog *	log = conn->snoop_log;
	char *		tag = conn->chan.name;
	char		linebuf[16*3+3];
	char *		p = linebuf;
	int		b;
	unsigned	i;

	if (log && conn->snoop_level > 8) {
		for (i = 0; i < len; i++) {
			b = buf[i] & 0xFF;
			sprintf (p, " %02x", b);
			while (*p) p++;
			if ((i&0xF) == 0xF) {
				ndmlogf (log,tag,9,"%s",linebuf+1);
				p = linebuf;
			}
		}
		if (p > linebuf) {
			ndmlogf (log,tag,9,"%s",linebuf+1);
		}
	}
}




/*
 * ERRORS
 ****************************************************************
 *
 * Possible errors for ndmconn are not enumerated.
 * Instead, errors are indicated by a -1 return, and
 * a simple string error message is available for details.
 * Appologies for the english-centric design, but it
 * is quick and easy, and better than using printf().
 */

int
ndmconn_set_err_msg (struct ndmconn *conn, char *err_msg)
{
	conn->last_err_msg = err_msg;
	ndmconn_snoop (conn, 4, "ERR=%s", err_msg);
	return -1;
}

char *
ndmconn_get_err_msg (struct ndmconn *conn)
{
	if (!conn->last_err_msg)
		return "-no-error-";
	else
		return conn->last_err_msg;
}
