/*
 * 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:
 *
 *
 * ndmis_connect_status transitions
 *
 *   Event relative to	------ before ------	------ after -------
 *   "mine" end point	MINE	PEER	REMO	MINE	PEER	REMO
 *   ====================================================================
 *   LISTEN/LOCAL	IDLE	IDLE	IDLE	LISTEN	IDLE	EXCLUDE
 *   LISTEN/TCP		IDLE	IDLE	IDLE	LISTEN	REMOTE	LISTEN
 *
 *   CONNECT/LOCAL	IDLE	LISTEN	EXCLUDE	CONN'ED	ACC'ED	EXCLUDE
 *   CONNECT/TCP	IDLE	IDLE	IDLE	CONN'ED	REMOTE	CONN'ED
 *
 *   tcp_accept()	LISTEN	REMOTE	LISTEN	ACC'ED	REMOTE	ACC'ED
 *
 */


#if 0
	DATA					TAPE
	END	========== LOCAL ============	END
	POINT					POINT
			REMOTE
#endif




#include "ndmagents.h"


int
ndmis_reinit_remote (struct ndm_session *sess)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;

	NDMOS_MACRO_ZEROFILL(&is->remote);

	ndmchan_initialize (&is->remote.listen_chan, "image-stream-listen");
	ndmchan_initialize (&is->remote.sanity_chan, "image-stream-sanity");
	ndmchan_initialize (&is->chan, "image-stream");
	ndmchan_setbuf (&is->chan, is->buf, sizeof is->buf);

	return 0;
}


/*
 * Initialization and Cleanup
 ****************************************************************
 */

/* Initialize -- Set data structure to know value, ignore current value */
int
ndmis_initialize (struct ndm_session *sess)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;

	NDMOS_MACRO_ZEROFILL(is);
	NDMOS_MACRO_ZEROFILL (&is->chan);

	ndmis_reinit_remote (sess);

	is->data_ep.name = "DATA";
	is->tape_ep.name = "TAPE";

	return 0;
}


/* Commission -- Get agent ready. Entire session has been initialize()d */
int
ndmis_commission (struct ndm_session *sess)
{
	return 0;
}

/* Decommission -- Discard agent */
int
ndmis_decommission (struct ndm_session *sess)
{
	return 0;
}

/* Belay -- Cancel partially issued activation/start */
int
ndmis_belay (struct ndm_session *sess)
{
	return 0;
}




/*
 * Semantic actions -- called from ndma_dispatch()
 ****************************************************************
 */

ndmp9_error
ndmis_audit_data_listen (struct ndm_session *sess,
  ndmp9_addr_type addr_type, char *reason)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->data_ep;
	struct ndmis_end_point	*peer_ep  = &is->tape_ep;

	return ndmis_audit_ep_listen (sess, addr_type, reason,
						mine_ep, peer_ep);
}

ndmp9_error
ndmis_audit_tape_listen (struct ndm_session *sess,
  ndmp9_addr_type addr_type, char *reason)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->tape_ep;
	struct ndmis_end_point	*peer_ep  = &is->data_ep;

	return ndmis_audit_ep_listen (sess, addr_type, reason,
						mine_ep, peer_ep);
}

ndmp9_error
ndmis_audit_data_connect (struct ndm_session *sess,
  ndmp9_addr_type addr_type, char *reason)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->data_ep;
	struct ndmis_end_point	*peer_ep  = &is->tape_ep;

	return ndmis_audit_ep_connect (sess, addr_type, reason,
						mine_ep, peer_ep);
}

ndmp9_error
ndmis_audit_tape_connect (struct ndm_session *sess,
  ndmp9_addr_type addr_type, char *reason)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->tape_ep;
	struct ndmis_end_point	*peer_ep  = &is->data_ep;

	return ndmis_audit_ep_connect (sess, addr_type, reason,
						mine_ep, peer_ep);
}

ndmp9_error
ndmis_data_listen (struct ndm_session *sess,
  ndmp9_addr_type addr_type, ndmp9_addr *ret_addr, char *reason)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->data_ep;
	struct ndmis_end_point	*peer_ep  = &is->tape_ep;

	return ndmis_ep_listen (sess, addr_type, ret_addr, reason,
						mine_ep, peer_ep);
}

ndmp9_error
ndmis_tape_listen (struct ndm_session *sess,
  ndmp9_addr_type addr_type, ndmp9_addr *ret_addr, char *reason)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->tape_ep;
	struct ndmis_end_point	*peer_ep  = &is->data_ep;

	return ndmis_ep_listen (sess, addr_type, ret_addr, reason,
						mine_ep, peer_ep);
}

ndmp9_error
ndmis_data_connect (struct ndm_session *sess,
  ndmp9_addr *addr, char *reason)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->data_ep;
	struct ndmis_end_point	*peer_ep  = &is->tape_ep;
	ndmp9_error		error;

	error = ndmis_ep_connect (sess, addr, reason, mine_ep, peer_ep);
#ifndef NDMOS_OPTION_NO_TAPE_AGENT
	if (error == NDMP9_NO_ERR) {
		if (peer_ep->connect_status == NDMIS_CONN_ACCEPTED
		 && peer_ep->addr_type == NDMP9_ADDR_LOCAL) {
			ndmta_quantum (sess);
		}
	}
#endif /* !NDMOS_OPTION_NO_TAPE_AGENT */
	return error;
}

ndmp9_error
ndmis_tape_connect (struct ndm_session *sess,
  ndmp9_addr *addr, char *reason)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->tape_ep;
	struct ndmis_end_point	*peer_ep  = &is->data_ep;

	return ndmis_ep_connect (sess, addr, reason, mine_ep, peer_ep);
}

int
ndmis_ep_start (struct ndm_session *sess, int chan_mode,
  struct ndmis_end_point *mine_ep, struct ndmis_end_point *peer_ep)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;

	if (mine_ep->connect_status != NDMIS_CONN_CONNECTED
	 && mine_ep->connect_status != NDMIS_CONN_ACCEPTED) {
		return -1;
	}

	if (mine_ep->transfer_mode != NDMCHAN_MODE_IDLE) {
		return -2;
	}

	if (mine_ep->addr_type == NDMP9_ADDR_LOCAL) {
		ndmchan_start_resident (&is->chan);
		if (chan_mode == NDMCHAN_MODE_WRITE) {
			peer_ep->transfer_mode = NDMCHAN_MODE_READ;
		} else {
			peer_ep->transfer_mode = NDMCHAN_MODE_WRITE;
		}
	} else if (chan_mode == NDMCHAN_MODE_WRITE) {
		ndmchan_pending_to_write (&is->chan);
	} else if (chan_mode == NDMCHAN_MODE_READ) {
		ndmchan_pending_to_read (&is->chan);
	} else {
		return -3;
	}

	mine_ep->transfer_mode = chan_mode;

	return 0;
}

int
ndmis_data_start (struct ndm_session *sess, int chan_mode)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->data_ep;
	struct ndmis_end_point	*peer_ep  = &is->tape_ep;

	return ndmis_ep_start (sess, chan_mode, mine_ep, peer_ep);
}

int
ndmis_tape_start (struct ndm_session *sess, int chan_mode)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->tape_ep;
	struct ndmis_end_point	*peer_ep  = &is->data_ep;

	return ndmis_ep_start (sess, chan_mode, mine_ep, peer_ep);
}

int
ndmis_data_close (struct ndm_session *sess)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->data_ep;
	struct ndmis_end_point	*peer_ep  = &is->tape_ep;

	return ndmis_ep_close (sess, mine_ep, peer_ep);
}

int
ndmis_tape_close (struct ndm_session *sess)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point	*mine_ep = &is->tape_ep;
	struct ndmis_end_point	*peer_ep  = &is->data_ep;

	return ndmis_ep_close (sess, mine_ep, peer_ep);
}




/*
 * Quantum -- get a bit of work done
 ****************************************************************
 */

int
ndmis_quantum (struct ndm_session *sess)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	struct ndmis_end_point *mine_ep;
	int			rc;

	if (is->remote.connect_status != NDMIS_CONN_LISTEN)
		return 0;	/* did nothing */

	if (!is->remote.listen_chan.ready)
		return 0;	/* did nothing */

	/* now this is going to get hard */

	if (is->data_ep.connect_status == NDMIS_CONN_LISTEN) {
		mine_ep = &is->data_ep;
		/* assert (is->tape_ep.connect_status == NDMIS_CONN_REMOTE); */
	} else if (is->tape_ep.connect_status == NDMIS_CONN_LISTEN) {
		mine_ep = &is->tape_ep;
		/* assert (is->data_ep.connect_status == NDMIS_CONN_REMOTE); */
	} else {
		assert(0);
		return -1;
	}

	rc = ndmis_tcp_accept (sess);
	if (rc == 0) {
		mine_ep->connect_status = NDMIS_CONN_ACCEPTED;
		is->remote.connect_status = NDMIS_CONN_ACCEPTED;
	} else {
		mine_ep->connect_status = NDMIS_CONN_BOTCHED;
		is->remote.connect_status = NDMIS_CONN_BOTCHED;
	}

	return 1;	/* did something */
}




/*
 * ndmis_end_point oriented helper routines
 ****************************************************************
 */

ndmp9_error
ndmis_audit_ep_listen (
  struct ndm_session *sess,
  ndmp9_addr_type addr_type,
  char *reason,
  struct ndmis_end_point *mine_ep,
  struct ndmis_end_point *peer_ep)
{
	ndmp9_error		error = NDMP9_NO_ERR;
	char *			reason_end;

	sprintf (reason, "IS %s_LISTEN: ", mine_ep->name);
	reason_end = reason;
	while (*reason_end) reason_end++;

	if (mine_ep->connect_status != NDMIS_CONN_IDLE) {
		sprintf (reason_end, "%s not idle", mine_ep->name);
		error = NDMP9_ILLEGAL_STATE_ERR;
		goto out;
	}
	if (peer_ep->connect_status != NDMIS_CONN_IDLE) {
		sprintf (reason_end, "%s not idle", peer_ep->name);
		error = NDMP9_ILLEGAL_STATE_ERR;
		goto out;
	}

	switch (addr_type) {
	case NDMP9_ADDR_LOCAL:
		break;

	case NDMP9_ADDR_TCP:
		break;

	default:
		strcpy (reason_end, "unknown addr_type");
		error = NDMP9_ILLEGAL_ARGS_ERR;
		goto out;
	}

  out:
	if (error == NDMP9_NO_ERR)
		strcpy (reason_end, "OK");
	else
		ndmalogf (sess, 0, 2, "listen %s messy mcs=%d pcs=%d",
			mine_ep->name,
			mine_ep->connect_status,
			peer_ep->connect_status);

	return error;
}

ndmp9_error
ndmis_audit_ep_connect (
  struct ndm_session *sess,
  ndmp9_addr_type addr_type,
  char *reason,
  struct ndmis_end_point *mine_ep,
  struct ndmis_end_point *peer_ep)
{
	ndmp9_error		error = NDMP9_NO_ERR;
	char *			reason_end;

	sprintf (reason, "IS %s_CONNECT: ", mine_ep->name);
	reason_end = reason;
	while (*reason_end) reason_end++;

	if (mine_ep->connect_status != NDMIS_CONN_IDLE) {
		sprintf (reason_end, "%s not idle", mine_ep->name);
		error = NDMP9_ILLEGAL_STATE_ERR;
		goto out;
	}

	switch (addr_type) {
	case NDMP9_ADDR_LOCAL:
		if (peer_ep->connect_status != NDMIS_CONN_LISTEN) {
			sprintf (reason_end, "LOCAL %s not LISTEN",
					peer_ep->name);
			error = NDMP9_ILLEGAL_STATE_ERR;
			goto out;
		}
		if (peer_ep->addr_type != NDMP9_ADDR_LOCAL) {
			sprintf (reason_end, "LOCAL %s not LOCAL",
					peer_ep->name);
			error = NDMP9_ILLEGAL_STATE_ERR;
			goto out;
		}
		break;

	case NDMP9_ADDR_TCP:
		if (peer_ep->connect_status != NDMIS_CONN_IDLE) {
			sprintf (reason_end, "LOCAL %s not IDLE",
					peer_ep->name);
			error = NDMP9_ILLEGAL_STATE_ERR;
			goto out;
		}
		break;

	default:
		strcpy (reason_end, "unknown addr_type");
		error = NDMP9_ILLEGAL_ARGS_ERR;
		goto out;
	}

  out:
	if (error == NDMP9_NO_ERR)
		strcpy (reason_end, "OK");

	return error;
}


ndmp9_error
ndmis_ep_listen (
  struct ndm_session *sess,
  ndmp9_addr_type addr_type,
  ndmp9_addr *ret_addr,
  char *reason,
  struct ndmis_end_point *mine_ep,
  struct ndmis_end_point *peer_ep)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	char *			reason_end;
	ndmp9_error		error;

	error = ndmis_audit_ep_listen (sess, addr_type, reason,
						mine_ep, peer_ep);
	if (error != NDMP9_NO_ERR)
		return error;

	reason_end = reason;
	while (*reason_end && *reason_end != ':') reason_end++;
	*reason_end++ = ':'; *reason_end++ = ' '; *reason_end = 0;

	NDMOS_MACRO_ZEROFILL (ret_addr);
	ret_addr->addr_type = addr_type;

	switch (addr_type) {
	case NDMP9_ADDR_LOCAL:
		mine_ep->addr_type = NDMP9_ADDR_LOCAL;
		mine_ep->connect_status = NDMIS_CONN_LISTEN;
		is->remote.connect_status = NDMIS_CONN_EXCLUDE;
		break;

	case NDMP9_ADDR_TCP:
		if (ndmis_tcp_listen (sess, ret_addr) != 0) {
			strcpy (reason_end, "TCP listen() failed");
			error = NDMP9_CONNECT_ERR;
			goto out;
		}
		mine_ep->addr_type = NDMP9_ADDR_TCP;
		mine_ep->connect_status = NDMIS_CONN_LISTEN;
		peer_ep->connect_status = NDMIS_CONN_REMOTE;
		break;

	default:
		reason = "unknown addr_type (bad)";
		error = NDMP9_ILLEGAL_ARGS_ERR;
		goto out;
	}

  out:
	if (error == NDMP9_NO_ERR)
		strcpy (reason_end, "OK");

	return error;
}

ndmp9_error
ndmis_ep_connect (
  struct ndm_session *sess,
  ndmp9_addr *addr,
  char *reason,
  struct ndmis_end_point *mine_ep,
  struct ndmis_end_point *peer_ep)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	ndmp9_addr_type		addr_type = addr->addr_type;
	char *			reason_end;
	ndmp9_error		error;

	error = ndmis_audit_ep_connect (sess, addr_type, reason,
						mine_ep, peer_ep);
	if (error != NDMP9_NO_ERR)
		return error;

	reason_end = reason;
	while (*reason_end && *reason_end != ':') reason_end++;
	*reason_end++ = ':'; *reason_end++ = ' '; *reason_end = 0;

	switch (addr_type) {
	case NDMP9_ADDR_LOCAL:
		mine_ep->addr_type = NDMP9_ADDR_LOCAL;
		mine_ep->connect_status = NDMIS_CONN_CONNECTED;
		peer_ep->connect_status = NDMIS_CONN_ACCEPTED;
		is->remote.connect_status = NDMIS_CONN_EXCLUDE;
		break;

	case NDMP9_ADDR_TCP:
		if (ndmis_tcp_connect (sess, addr) != 0) {
			strcpy (reason_end, "TCP connect() failed");
			error = NDMP9_CONNECT_ERR;
			goto out;
		}
		mine_ep->addr_type = NDMP9_ADDR_TCP;
		mine_ep->connect_status = NDMIS_CONN_CONNECTED;
		peer_ep->connect_status = NDMIS_CONN_REMOTE;
		break;

	default:
		reason = "unknown addr_type (bad)";
		error = NDMP9_ILLEGAL_ARGS_ERR;
		goto out;
	}

  out:
	return error;
}

int
ndmis_ep_close (struct ndm_session *sess,
  struct ndmis_end_point *mine_ep, struct ndmis_end_point *peer_ep)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	char *			save_name = mine_ep->name;

	switch (mine_ep->connect_status) {
	case NDMIS_CONN_IDLE:
		return 0;

	case NDMIS_CONN_BOTCHED:
	case NDMIS_CONN_REMOTE:
	case NDMIS_CONN_EXCLUDE:
		goto messy;

	case NDMIS_CONN_LISTEN:
		switch (mine_ep->addr_type) {
		default:
			goto messy;

		case NDMP9_ADDR_LOCAL:
			ndmis_reinit_remote (sess);
			if (peer_ep->connect_status != NDMIS_CONN_IDLE)
				goto messy;
			break;

		case NDMP9_ADDR_TCP:
			ndmis_tcp_close (sess);
			if (peer_ep->connect_status != NDMIS_CONN_REMOTE)
				goto messy;
			peer_ep->connect_status = NDMIS_CONN_IDLE;
			break;
		}
		break;

	case NDMIS_CONN_ACCEPTED:
		switch (mine_ep->addr_type) {
		default:
			goto messy;

		case NDMP9_ADDR_LOCAL:
			if (peer_ep->connect_status != NDMIS_CONN_CONNECTED)
				goto messy;
			peer_ep->connect_status = NDMIS_CONN_DISCONNECTED;
			is->chan.eof = 1;
			if (mine_ep->transfer_mode == NDMCHAN_MODE_READ)
				is->chan.error = 1; /* EPIPE */
			break;

		case NDMP9_ADDR_TCP:
			ndmis_tcp_close (sess);
			if (peer_ep->connect_status != NDMIS_CONN_REMOTE)
				goto messy;
			peer_ep->connect_status = NDMIS_CONN_IDLE;
			break;
		}
		break;

	case NDMIS_CONN_CONNECTED:
		switch (mine_ep->addr_type) {
		default:
			goto messy;

		case NDMP9_ADDR_LOCAL:
			if (peer_ep->connect_status != NDMIS_CONN_ACCEPTED)
				goto messy;
			peer_ep->connect_status = NDMIS_CONN_DISCONNECTED;
			is->chan.eof = 1;
			if (mine_ep->transfer_mode == NDMCHAN_MODE_READ)
				is->chan.error = 1; /* EPIPE */
			break;

		case NDMP9_ADDR_TCP:
			ndmis_tcp_close (sess);
			if (peer_ep->connect_status != NDMIS_CONN_REMOTE)
				goto messy;
			peer_ep->connect_status = NDMIS_CONN_IDLE;
			break;
		}
		break;

	case NDMIS_CONN_DISCONNECTED:	/* peer close()d first */
		ndmis_reinit_remote (sess);
		break;

	case NDMIS_CONN_CLOSED:
		goto messy;
	}

	NDMOS_MACRO_ZEROFILL (mine_ep);
	mine_ep->name = save_name;

	return 0;

  messy:
	ndmalogf (sess, 0, 2, "close %s messy mcs=%d pcs=%d",
			mine_ep->name,
			mine_ep->connect_status,
			peer_ep->connect_status);
	NDMOS_MACRO_ZEROFILL (mine_ep);
	mine_ep->name = save_name;
	return -1;
}

#if 0
ndmp9_error
ndmis_ep_could_start (
  struct ndm_session *sess,
  int want_transfer_mode,	/* NDMCHAN_MODE_{IDLE|READ|WRITE} */
  char *reason,
  struct ndmis_end_point *mine_ep,
  struct ndmis_end_point *peer_ep)
{
	ndmp9_error		error = NDMP9_NO_ERR;
	char *			reason_end;

	sprintf (reason, "IS %s_START: ", mine_ep->name);
	reason_end = reason;
	while (*reason_end) reason_end++;

	if (want_transfer_mode == NDMCHAN_MODE_IDLE)
		want_transfer_mode = mine_ep->transfer_mode;

	switch (want_transfer_mode) {
	case NDMCHAN_MODE_IDLE:
		/* can't check much  */
		break;

	case NDMCHAN_MODE_READ:
		/* what to check? */
		break;

	case NDMCHAN_MODE_WRITE:
		/* what to check? */
		break;

	default:
		strcpy (reason_end, "unknown chan_mode");
		error = NDMP9_ILLEGAL_ARGS_ERR;
		goto out;
	}

  out:
	if (error == NDMP9_NO_ERR)
		strcpy (reason_end, "OK");

	return error;
}
#endif /* 0 */




/*
 * ADDR_TCP helper routines
 ****************************************************************
 */


/*
 * ndmis_tcp_listen()
 *
 * The tricky part of listen()ing is determining the IP
 * address to offer, which ultimately will be used by
 * the other (peer) side for connect()ing.
 *
 * We can't just bind() with INADDR_ANY (0's) because
 * that results in a local socket with INADDR_ANY, and
 * any inbound connection to the right port will be
 * accept()ed by the networking system. That doesn't
 * help us here, though, because we have to have a
 * real IP address to offer. INADDR_ANY ain't sufficient.
 *
 * There is also the issue of systems with multiple
 * network connections. We of course would like to
 * use the network data link that is most advantageous
 * on both sides. This may vary from job run to job
 * run, and so any method of specifying just one
 * is IP address ain't sufficient.
 *
 * The approach here uses the existing control connections,
 * which normally precede the image stream connection,
 * as cues for which IP address to use. So, for example,
 * if a TAPE or DATA host has four network connections,
 * the CONTROL agent can coax them to use a specific one
 * of the four by connecting to the IP address of the
 * network wanted for the image stream.
 *
 * If the clever rules don't work out, the fallback is to
 * look up the host name. Right now we use ndmhost_lookup()
 * of sess->local_info.host_name because both must work
 * before things would progress to this point.
 */

int
ndmis_tcp_listen (struct ndm_session *sess, struct ndmp9_addr *listen_addr)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	ndmp9_tcp_addr *	tcp_addr = &listen_addr->ndmp9_addr_u.tcp_addr;
	struct ndmconn *	conn;
	struct sockaddr		c_sa;
	struct sockaddr		l_sa;
	struct sockaddr_in *	sin;
	socklen_t		len;
	int			listen_sock = -1;
	char *			what = "???";

	/*
	 * Get the IP address thru which the CONTROL agent connected
	 * to this session. The CONTROL agent may influence the
	 * network used for the image-stream on multi-homed hosts
	 * simply by connecting to the prefered IP address.
	 */
	what = "determine-conn";
	conn = sess->plumb.control;
	if (!conn || conn->conn_type != NDMCONN_TYPE_REMOTE) {
		/*
		 * If CONTROL is resident, try the other
		 * control connections in hopes of finding
		 * a clue about what IP address to offer.
		 */
		conn = sess->plumb.data;
		if (!conn || conn->conn_type != NDMCONN_TYPE_REMOTE) {
			conn = sess->plumb.tape;
			if (!conn || conn->conn_type != NDMCONN_TYPE_REMOTE) {
				conn = 0;
			}
		}
	}

	if (conn) {
		/*
		 * We found a connection to use for determining
		 * what IP address to offer.
		 */
		what = "getsockname-ctrl";
		len = sizeof c_sa;
		if (getsockname (ndmconn_fileno(conn), &c_sa, &len) < 0) {
			/* we'll try the fallback rules */
			conn = 0;
		}
	}

	if (!conn) {
		/*
		 * For whatever reason, we can't determine a good
		 * IP address from the connections. Try the boring
		 * fallback rules.
		 */
		ndmos_sync_config_info (sess);

		sin = (struct sockaddr_in *) &c_sa;

		what = "ndmhost_lookup";
		if (ndmhost_lookup (sess->config_info.hostname, sin) != 0)
			goto fail;
	}

	/* c_sa is a sockaddr_in for the IP address to use */

	what = "socket";
	listen_sock = socket (AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0) goto fail;

	/* could bind() to more restrictive addr based on c_sa */
	NDMOS_MACRO_SET_SOCKADDR(&l_sa, 0, 0);
	what = "bind";
	if (bind (listen_sock, &l_sa, sizeof l_sa) < 0) goto fail;

	what = "listen";
	if (listen (listen_sock, 1) < 0) goto fail;

	ndmos_condition_listen_socket (sess, listen_sock);

	/* Get the port */
	what = "getsockname-listen";
	len = sizeof l_sa;
	if (getsockname (listen_sock, &l_sa, &len) < 0) goto fail;

	/*
	 * Fill in the return address
	 */

	listen_addr->addr_type = NDMP9_ADDR_TCP;
	tcp_addr = &listen_addr->ndmp9_addr_u.tcp_addr;

	/* IP addr from CONTROL connection, or where ever c_sa came from */
	sin = (struct sockaddr_in *) &c_sa;
	tcp_addr->ip_addr = ntohl (sin->sin_addr.s_addr);

	/* port from the bind() and getsockname() above */
	sin = (struct sockaddr_in *) &l_sa;
	tcp_addr->port = ntohs (sin->sin_port);

	/*
	 * Start the listen channel
	 */

	ndmchan_start_listen (&is->remote.listen_chan, listen_sock);

	is->remote.connect_status = NDMIS_CONN_LISTEN;
	is->remote.listen_addr = *listen_addr;

	return 0;

  fail:
	ndmalogf (sess, 0, 2, "ndmis_tcp_listen(): %s failed", what);
	if (listen_sock >= 0) close (listen_sock);

	return -1;
}

int
ndmis_tcp_accept (struct ndm_session *sess)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	char *			what = "???";
	ndmp9_tcp_addr *	tcp_addr;
	struct sockaddr		sa;
	struct sockaddr_in *	sin = (struct sockaddr_in *) &sa;
	socklen_t		len;
	int			accept_sock = -1;

	what = "remote-conn-stat";
	if (is->remote.connect_status != NDMIS_CONN_LISTEN)
		goto fail;

	what = "remote-list-ready";
	if (!is->remote.listen_chan.ready)
		goto fail;

	what = "accept";
	len = sizeof sa;
	accept_sock = accept (is->remote.listen_chan.fd, &sa, &len);

	ndmchan_cleanup (&is->remote.listen_chan);

	if (accept_sock < 0) {
		is->remote.connect_status = NDMIS_CONN_BOTCHED;
		goto fail;
	}

	/* write what we know, ndmis...addrs() will update if possible */
	is->remote.peer_addr.addr_type = NDMP9_ADDR_TCP;
	tcp_addr = &is->remote.peer_addr.ndmp9_addr_u.tcp_addr;
	tcp_addr->ip_addr = ntohl (sin->sin_addr.s_addr);
	tcp_addr->port = ntohs (sin->sin_port);

	ndmis_tcp_green_light (sess, accept_sock, NDMIS_CONN_ACCEPTED);

	return 0;

  fail:
	ndmalogf (sess, 0, 2, "ndmis_tcp_accept(): %s failed", what);
	if (accept_sock >= 0) close (accept_sock);

	return -1;
}

int
ndmis_tcp_connect (struct ndm_session *sess, struct ndmp9_addr *connect_addr)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	ndmp9_tcp_addr *	tcp_addr=&connect_addr->ndmp9_addr_u.tcp_addr;
	char *			what = "???";
	struct sockaddr		sa;
	int			connect_sock;

	NDMOS_MACRO_SET_SOCKADDR (&sa, tcp_addr->ip_addr, tcp_addr->port);

	what = "socket";
	connect_sock = socket (AF_INET, SOCK_STREAM, 0);
	if (connect_sock < 0)
		goto fail;

	what = "connect";
	if (connect (connect_sock, &sa, sizeof sa) < 0)
		goto fail;


	/* write what we know, ndmis...addrs() will update if possible */
	is->remote.peer_addr = *connect_addr;

	ndmis_tcp_green_light (sess, connect_sock, NDMIS_CONN_CONNECTED);

	return 0;

  fail:
	ndmalogf (sess, 0, 2, "ndmis_tcp_connect(): %s failed", what);
	if (connect_sock >= 0) close (connect_sock);

	return -1;
}

int
ndmis_tcp_green_light (struct ndm_session *sess, int sock,
  ndmis_connect_status new_status)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;

	ndmos_condition_image_stream_socket (sess, sock);

	ndmchan_start_pending (&is->chan, sock);

	is->remote.connect_status = new_status;

	ndmis_tcp_get_local_and_peer_addrs (sess);

	return 0;
}

int
ndmis_tcp_close (struct ndm_session *sess)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;

	switch (is->remote.connect_status) {
	case NDMIS_CONN_LISTEN:
		ndmchan_cleanup (&is->remote.listen_chan);
		break;

	case NDMIS_CONN_CONNECTED:
	case NDMIS_CONN_ACCEPTED:
		ndmchan_cleanup (&is->chan);
		break;

	default:
		break;
	}

	ndmis_reinit_remote (sess);

	return 0;
}

int
ndmis_tcp_get_local_and_peer_addrs (struct ndm_session *sess)
{
	struct ndm_image_stream *is = &sess->plumb.image_stream;
	char *			what = "???";
	struct sockaddr		sa;
	struct sockaddr_in *	sin = (struct sockaddr_in *) &sa;
	ndmp9_tcp_addr *	tcp_addr;
	socklen_t		len;
	int			rc = 0;

	len = sizeof sa;
	what = "getpeername";
	if (getpeername (is->chan.fd, &sa, &len) < 0) {
		/* this is best effort */
		ndmalogf (sess, 0, 2, "ndmis_tcp..._addrs(): %s failed", what);
		rc = -1;
	} else {
		is->remote.peer_addr.addr_type = NDMP9_ADDR_TCP;
		tcp_addr = &is->remote.peer_addr.ndmp9_addr_u.tcp_addr;
		tcp_addr->ip_addr = ntohl (sin->sin_addr.s_addr);
		tcp_addr->port = ntohs (sin->sin_port);
	}

	len = sizeof sa;
	what = "getsockname";
	if (getsockname (is->chan.fd, &sa, &len) < 0) {
		/* this is best effort */
		ndmalogf (sess, 0, 2, "ndmis_tcp..._addrs(): %s failed", what);
		rc = -1;
	} else {
		is->remote.local_addr.addr_type = NDMP9_ADDR_TCP;
		tcp_addr = &is->remote.peer_addr.ndmp9_addr_u.tcp_addr;
		tcp_addr->ip_addr = ntohl (sin->sin_addr.s_addr);
		tcp_addr->port = ntohs (sin->sin_port);
	}

	return rc;
}

