/*
 * 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:
 *
 ********************************************************************
 *
 * NDMP Elements of a test-data session
 *
 *                   +-----+     ###########
 *                   | Job |----># CONTROL #
 *                   +-----+     #  Agent  #
 *                               #         #
 *                               ###########
 *                                 |      #
 *                +----------------+      #
 *                | control connection    #     CONTROL
 *                V                       #     impersonates
 *           ############                 #     TAPE side of
 *           #  DATA    #                 #     image stream
 *           #  Agent   #                 #
 *  +-----+  # +------+ # image           #
 *  |FILES|====|butype|===================#
 *  +-----+  # +------+ # stream
 *           ############
 *
 *
 ********************************************************************
 *
 */


#include "ndmagents.h"


#ifndef NDMOS_OPTION_NO_CONTROL_AGENT


extern int	ndmca_td_wrapper (struct ndm_session *sess,
				int (*func)(struct ndm_session *sess));


extern int	ndmca_op_test_data (struct ndm_session *sess);
extern int	ndmca_td_idle (struct ndm_session *sess);

extern int	ndmca_td_listen (struct ndm_session *sess);
extern int	ndmca_td_listen_subr (struct ndm_session *sess,
				      ndmp9_error expect_err,
				      ndmp9_addr_type addr_type);
extern int	ndmca_test_check_data_state  (struct ndm_session *sess,
			ndmp9_data_state expected, int reason);
extern int	ndmca_test_data_get_state (struct ndm_session *sess,
			ndmp9_error expect_err);
extern int	ndmca_test_data_abort (struct ndm_session *sess,
			ndmp9_error expect_err);
extern int	ndmca_test_data_stop (struct ndm_session *sess,
			ndmp9_error expect_err);



int
ndmca_op_test_data (struct ndm_session *sess)
{
	struct ndm_control_agent *ca = &sess->control_acb;
	struct ndmconn *	conn;
	int			(*save_call) (struct ndmconn *conn,
						struct ndmp_xa_buf *xa);
	int			rc;

	rc = ndmca_connect_data_agent(sess);
	if (rc) {
		ndmconn_destruct (sess->plumb.data);
		return rc;
	}

	conn = sess->plumb.data;
	save_call = conn->call;
	conn->call = ndma_call_no_tattle;

	/* perform query to find out about TCP and LOCAL support */
	rc = ndmca_test_query_conn_types (sess, conn);
	if (rc) return rc;

	rc = ndmca_td_wrapper (sess, ndmca_td_idle);
	if (sess->plumb.data->protocol_version >= 3) {
	    // version 3 and later adds LISTEN
	    rc = ndmca_td_wrapper (sess, ndmca_td_listen);
	}

	ndmca_test_done_series (sess, "test-data");

	ca = &sess->control_acb;
	if (ca->has_tcp_addr && ca->has_local_addr) {
	    ndmalogf (sess, "TEST", 0, "LOCAL and TCP addressing tested.");
	} else if (ca->has_tcp_addr) {
	    ndmalogf (sess, "TEST", 0, "TCP addressing ONLY tested.");
	} else if (ca->has_local_addr) {
	    ndmalogf (sess, "TEST", 0, "LOCAL addressing ONLY tested.");
	} else {
	    ndmalogf (sess, "TEST", 0, "Neither TCP or LOCAL addressing tested.");
	}

	return 0;
}

int
ndmca_td_wrapper (struct ndm_session *sess,
  int (*func)(struct ndm_session *sess))
{
	int		rc;

	rc = (*func)(sess);

	if (rc != 0) {
		ndmalogf (sess, "Test", 1, "Failure");
	}

	ndmca_test_done_phase (sess);

	/* clean up mess */
	ndmca_test_log_note (sess, 2, "Cleaning up...");

	rc = 0;

	return rc;
}

int
ndmca_td_idle (struct ndm_session *sess)
{
	int		rc;

	ndmca_test_phase (sess, "D-IDLE", "Data IDLE State Series");

	rc = ndmca_test_check_data_state  (sess, NDMP9_DATA_STATE_IDLE, 0);
	if (rc) return rc;

	rc = ndmca_test_data_abort (sess, NDMP9_ILLEGAL_STATE_ERR);
	if (rc) return rc;

	rc = ndmca_test_data_stop (sess, NDMP9_ILLEGAL_STATE_ERR);
	if (rc) return rc;

	return 0;	/* pass */
}



#define NDMTEST_CALL(CONN) ndmca_test_call(CONN, xa, expect_err);



int
ndmca_test_data_listen (struct ndm_session *sess, ndmp9_error expect_err,
			ndmp9_addr_type addr_type)
{
	struct ndmconn *	conn = sess->plumb.data;
	struct ndm_control_agent *ca = &sess->control_acb;
	int			rc;

	/* close previous test if there is one */
	ndmca_test_close (sess);

	switch (conn->protocol_version) {
	default:	return -1234;

#ifndef NDMOS_OPTION_NO_NDMP3
	case NDMP3VER:
	    NDMC_WITH(ndmp3_data_listen, NDMP3VER)
		request->addr_type = addr_type;

		rc = NDMTEST_CALL(conn);
		if (rc) return rc;

		if (expect_err == NDMP9_NO_ERR
		 && request->addr_type
		    != reply->data_connection_addr.addr_type) {
			/* TODO: use proper test format */
			ndmalogf (sess, "Test", 1,
				"DATA_LISTEN addr_type mismatch");
			return -1;
		}
		ndmp_3to9_addr (&reply->data_connection_addr, &ca->data_addr);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP3 */
#ifndef NDMOS_OPTION_NO_NDMP4
	case NDMP4VER:
	    NDMC_WITH(ndmp4_data_listen, NDMP4VER)
		request->addr_type = addr_type;

		rc = NDMTEST_CALL(conn);
		if (rc) return rc;

		if (expect_err == NDMP9_NO_ERR
		 && request->addr_type
		    != reply->connect_addr.addr_type) {
			/* TODO: use proper test format */
			ndmalogf (sess, "Test", 1,
				"DATA_LISTEN addr_type mismatch");
			return -1;
		}
		ndmp_4to9_addr (&reply->connect_addr, &ca->data_addr);
	    NDMC_ENDWITH
	    break;
#endif /* !NDMOS_OPTION_NO_NDMP4 */
	}

	return 0;
}


int
ndmca_td_listen (struct ndm_session *sess)
{
        struct ndm_control_agent *ca = &sess->control_acb;
	int		rc;

	ndmca_test_phase (sess, "D-LISTEN", "Data LISTEN State Series");

	rc = ndmca_test_check_data_state  (sess, NDMP9_DATA_STATE_IDLE, 0);
	if (rc) return rc;

	if (ca->has_tcp_addr) {
	    rc = ndmca_td_listen_subr (sess, NDMP9_NO_ERR, NDMP9_ADDR_TCP);
	    if (rc) return rc;
	}

	if (ca->has_local_addr) {
	    rc = ndmca_td_listen_subr (sess, NDMP9_NO_ERR, NDMP9_ADDR_LOCAL);
	    if (rc) return rc;
	}

	ndmca_test_done_phase (sess);

	/*
	 * Bogus arguments
	 */
	ndmca_test_phase (sess, "D-LISTEN/bogus-args",
				"Data LISTEN State Series w/ bogus args");

	rc = ndmca_test_data_listen (sess, NDMP9_ILLEGAL_ARGS_ERR,
				     123);
	if (rc) return rc;

	ndmca_test_done_phase (sess);


	return 0;	/* pass */
}

int
ndmca_td_listen_subr (struct ndm_session *sess,
		      ndmp9_error expect_err,
		      ndmp9_addr_type addr_type)
{
	int		rc;

	rc = ndmca_test_check_data_state  (sess, NDMP9_DATA_STATE_IDLE, 0);
	if (rc) return rc;

	rc = ndmca_test_data_listen (sess, expect_err, addr_type);
	if (rc) return rc;

	if (expect_err != NDMP9_NO_ERR)
		return 0;		/* got expected error */

	rc = ndmca_test_check_data_state  (sess, NDMP9_DATA_STATE_LISTEN, 0);
	if (rc) return rc;

	rc = ndmca_test_data_listen (sess, NDMP9_ILLEGAL_STATE_ERR,
		addr_type);
	if (rc) return rc;

	rc = ndmca_test_data_stop (sess, NDMP9_ILLEGAL_STATE_ERR);
	if (rc) return rc;

	rc = ndmca_test_data_abort (sess, NDMP9_NO_ERR);
	if (rc) return rc;

	rc = ndmca_test_check_data_state  (sess,
		NDMP9_DATA_STATE_HALTED, NDMP9_DATA_HALT_ABORTED);
	if (rc) return rc;

	rc = ndmca_test_data_stop (sess, NDMP9_NO_ERR);
	if (rc) return rc;

	rc = ndmca_test_check_data_state  (sess, NDMP9_DATA_STATE_IDLE, 0);
	if (rc) return rc;

	return 0;
}

int
ndmca_test_check_data_state  (struct ndm_session *sess,
  ndmp9_data_state expected, int reason)
{
	struct ndm_control_agent *	ca = &sess->control_acb;
	ndmp9_data_get_state_reply *	ds = &ca->data_state;
	int				rc;
	char *				what;
	char				errbuf[100];
	char				tmpbuf[256];

	/* close previous test if there is one */
	ndmca_test_close (sess);

	/* open new test */
	ndmca_test_open (sess,
			 "data check",
			 ndmp9_data_state_to_str (expected));

	strcpy (errbuf, "???");

	what = "get_state";
	rc = ndmca_data_get_state (sess);
	if (rc) goto fail;

	what = "state self-consistent";
	/* make sure the sensed state is self consistent */
	switch (ds->state) {
	case NDMP9_DATA_STATE_IDLE:
	case NDMP9_DATA_STATE_ACTIVE:
	case NDMP9_DATA_STATE_LISTEN:
	case NDMP9_DATA_STATE_CONNECTED:
		if (ds->halt_reason != NDMP9_DATA_HALT_NA) {
			strcpy (errbuf, "reason != NA");
			goto fail;
		}
		break;

	case NDMP9_DATA_STATE_HALTED:
		break;

	default:
		strcpy (errbuf, "bogus state");
		goto fail;
	}

	what = "state";
	if (ds->state != expected) {
		sprintf (errbuf, "expected %s got %s",
			ndmp9_data_state_to_str (expected),
			ndmp9_data_state_to_str (ds->state));
		goto fail;
	}

	what = "reason";
	switch (ds->state) {
	case NDMP9_DATA_STATE_HALTED:
		if (ds->halt_reason != (ndmp9_data_halt_reason)reason) {
			sprintf (errbuf, "expected %s got %s",
			    ndmp9_data_halt_reason_to_str (reason),
			    ndmp9_data_halt_reason_to_str (ds->halt_reason));
			goto fail;
		}
		break;

	default:
		break;
	}

	/* test passed */
	ndmca_test_close (sess);

	return 0;

  fail:
	/* test failed */
	sprintf(tmpbuf, "%s: %s", what, errbuf);
	ndmca_test_fail(sess, tmpbuf);

	ndmca_test_close (sess);

	return -1;
}



int
ndmca_test_data_get_state (struct ndm_session *sess, ndmp9_error expect_err)
{
	struct ndmconn *	conn = sess->plumb.data;
	int			rc;

	/* close previous test if there is one */
	ndmca_test_close (sess);

	rc = ndmca_data_get_state (sess);

	rc = ndmca_test_check_expect (conn, rc, expect_err);

	return rc;
}

int
ndmca_test_data_abort (struct ndm_session *sess, ndmp9_error expect_err)
{
	struct ndmconn *	conn = sess->plumb.data;
	int			rc;

	/* close previous test if there is one */
	ndmca_test_close (sess);

	rc = ndmca_data_abort (sess);

	rc = ndmca_test_check_expect (conn, rc, expect_err);

	return rc;
}

int
ndmca_test_data_stop (struct ndm_session *sess, ndmp9_error expect_err)
{
	struct ndmconn *	conn = sess->plumb.data;
	int			rc;

	/* close previous test if there is one */
	ndmca_test_close (sess);

	rc = ndmca_data_stop (sess);

	rc = ndmca_test_check_expect (conn, rc, expect_err);

	return rc;
}






#endif /* !NDMOS_OPTION_NO_CONTROL_AGENT */
