/*
 * Copyright (c) 2003-2004
 *	Hartmut Brandt
 *	All rights reserved.
 *
 * Copyright (c) 2001-2002
 *	Fraunhofer Institute for Open Communication Systems (FhG Fokus).
 *	All rights reserved.
 *
 * Author: Harti Brandt <harti@freebsd.org>
 *
 * Redistribution of this software and documentation 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 or documentation must retain the above
 *    copyright notice, 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 AND DOCUMENTATION IS PROVIDED BY THE AUTHOR
 * AND ITS 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 ITS 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.
 *
 * $Begemot: libunimsg/netnatm/api/cc_conn.c,v 1.2 2004/07/16 18:45:11 brandt Exp $
 *
 * ATM API as defined per af-saa-0108
 *
 * Lower half - connection handling
 */
#include <netnatm/unimsg.h>
#include <netnatm/msg/unistruct.h>
#include <netnatm/msg/unimsglib.h>
#include <netnatm/api/unisap.h>
#include <netnatm/sig/unidef.h>
#include <netnatm/api/atmapi.h>
#include <netnatm/api/ccatm.h>
#include <netnatm/api/ccpriv.h>

static const char *stab[] = {
#define DEF(N) [N] = #N,
	CONN_STATES
#undef DEF
};

static const char *ptab[] = {
#define DEF(N) [PARTY_##N] = #N,
	PARTY_STATES
#undef DEF
};

const char *
cc_conn_state2str(u_int s)
{
	if (s >= sizeof(stab) / sizeof(stab[0]) || stab[s] == NULL)
		return ("?");
	return (stab[s]);
}

__inline void
cc_conn_set_state(struct ccconn *conn, enum conn_state ns)
{
	if (conn->state != ns) {
		if (conn->cc->log & CCLOG_CONN_STATE)
			cc_conn_log(conn, "%s -> %s",
			    stab[conn->state], stab[ns]);
		conn->state = ns;
	}
}

const char *
cc_party_state2str(u_int s)
{
	if (s >= sizeof(ptab) / sizeof(ptab[0]) || ptab[s] == NULL)
		return ("?");
	return (ptab[s]);
}

__inline void
cc_party_set_state(struct ccparty *party, enum party_state ns)
{

	if (party->state != ns) {
		if (party->conn->cc->log & CCLOG_PARTY_STATE)
			cc_party_log(party, "%s -> %s",
			    ptab[party->state], ptab[ns]);
		party->state = ns;
	}
}

/*
 * Remove connection from its user's queue
 */
__inline void
cc_disconnect_from_user(struct ccconn *conn)
{

	if (conn->user == NULL)
		cc_conn_log(conn, "no %s", "user");
	else {
		TAILQ_REMOVE(&conn->user->connq, conn, connq_link);
		conn->user->queue_act--;
		conn->user = NULL;
	}
}

/*
 * Put connection on user queue
 */
__inline void
cc_connect_to_user(struct ccconn *conn, struct ccuser *user)
{

	if (conn->user != NULL)
		cc_conn_log(conn, "still connected to %p", conn->user);
	conn->user = user;
	TAILQ_INSERT_TAIL(&user->connq, conn, connq_link);
	conn->user->queue_act++;
}

/*
 * Send a signal to the UNI stack for this connection
 */
static void
cc_send_uni(struct ccconn *conn, u_int op, struct uni_msg *msg)
{
	struct ccreq *r;

	r = CCZALLOC(sizeof(*r));
	if (r == NULL) {
		if (msg != NULL)
			uni_msg_destroy(msg);
		cc_conn_log(conn, "no memory for cookie op=%u", op);
		return;
	}

	if ((r->cookie = ++conn->port->cc->cookie) == 0)
		r->cookie = ++conn->port->cc->cookie;
	r->req = op;
	r->conn = conn;

	TAILQ_INSERT_TAIL(&conn->port->cookies, r, link);

	conn->port->cc->funcs->send_uni(conn, conn->port->uarg, op,
	    r->cookie, msg);
}

/*
 * Send a RELEASE.request for this connection.
 */
static void
do_release_request(struct ccconn *conn, const struct uni_ie_cause cause[2])
{
	struct uni_msg *u;
	struct uniapi_release_request *req;

	if ((u = uni_msg_alloc(sizeof(*req))) == NULL)
		return;
	req = uni_msg_wptr(u, struct uniapi_release_request *);
	memset(req, 0, sizeof(*req));
	u->b_wptr += sizeof(struct uniapi_release_request);

	req->release.hdr.cref = conn->cref;
	req->release.hdr.act = UNI_MSGACT_DEFAULT;

	if (cause == NULL) {
		IE_SETPRESENT(req->release.cause[0]);
		req->release.cause[0].h.act = UNI_IEACT_DEFAULT;
		req->release.cause[0].loc = UNI_CAUSE_LOC_USER;
		req->release.cause[0].cause = UNI_CAUSE_UNSPEC;
	} else {
		req->release.cause[0] = cause[0];
		req->release.cause[1] = cause[1];
	}

	cc_send_uni(conn, UNIAPI_RELEASE_request, u);
}

/*
 * Make a RELEASE.response for this connection
 */
static void
do_release_response(struct ccconn *conn, uint8_t cause, struct uni_ie_cause *ie)
{
	struct uni_msg *u;
	struct uniapi_release_response *resp;

	if ((u = uni_msg_alloc(sizeof(*resp))) == NULL)
		return;
	resp = uni_msg_wptr(u, struct uniapi_release_response *);
	memset(resp, 0, sizeof(*resp));
	u->b_wptr += sizeof(struct uniapi_release_response);

	resp->release_compl.hdr.cref = conn->cref;
	resp->release_compl.hdr.act = UNI_MSGACT_DEFAULT;

	if (ie != NULL)
		resp->release_compl.cause[0] = *ie;

	if (cause != 0) {
		IE_SETPRESENT(resp->release_compl.cause[0]);
		resp->release_compl.cause[0].h.act = UNI_IEACT_DEFAULT;
		resp->release_compl.cause[0].loc = UNI_CAUSE_LOC_USER;
		resp->release_compl.cause[0].cause = cause;
	}

	cc_send_uni(conn, UNIAPI_RELEASE_response, u);
}

/**********************************************************************
 *
 * INSTANCE handling
 */
struct ccconn *
cc_conn_create(struct ccdata *cc)
{
	struct ccconn *conn;

	conn = CCZALLOC(sizeof(*conn));
	if (conn == NULL)
		return (NULL);

	conn->state = CONN_NULL;
	conn->port = NULL;
	conn->cc = cc;
	LIST_INIT(&conn->parties);

	LIST_INSERT_HEAD(&cc->orphaned_conns, conn, port_link);

	if (conn->cc->log & CCLOG_CONN_INST)
		cc_conn_log(conn, "created %s", "orphaned");

	return (conn);
}

/*
 * assign to port
 */
void
cc_conn_ins_port(struct ccconn *conn, struct ccport *port)
{

	if (conn->port != NULL) {
		cc_conn_log(conn, "conn is already on port %u",
		    conn->port->param.port);
		cc_conn_rem_port(conn);
	}
	LIST_REMOVE(conn, port_link);

	conn->port = port;
	LIST_INSERT_HEAD(&port->conn_list, conn, port_link);

}

/*
 * remove from port
 */
void
cc_conn_rem_port(struct ccconn *conn)
{

	if (conn->port == NULL) {
		cc_conn_log(conn, "conn not on any %s", "port");
		return;
	}
	LIST_REMOVE(conn, port_link);
	conn->port = NULL;
	LIST_INSERT_HEAD(&conn->cc->orphaned_conns, conn, port_link);
}

static void
cc_conn_flush_cookies(struct ccconn *conn)
{
	struct ccreq *r, *r1;

	if (conn->port == NULL)
		return;
	TAILQ_FOREACH_SAFE(r, &conn->port->cookies, link, r1) {
		if (r->conn == conn) {
			TAILQ_REMOVE(&conn->port->cookies, r, link);
			CCFREE(r);
		}
	}
}

__inline void
cc_conn_reset_acceptor(struct ccconn *conn)
{
	if (conn->acceptor != NULL) {
		conn->acceptor->accepted = NULL;
		conn->acceptor = NULL;
	}
}

/*
 * Destroy a connection
 */
void
cc_conn_destroy(struct ccconn *conn)
{
	struct ccparty *p;

	if (conn->cc->log & CCLOG_CONN_INST)
		cc_conn_log(conn, "destroy%s", "");

	if (conn->user != NULL) {
		cc_conn_log(conn, "still connected to user %p\n", conn->user);
		conn->user->queue_act--;
		TAILQ_REMOVE(&conn->user->connq, conn, connq_link);
	}

	if (conn->acceptor != NULL)
		conn->acceptor->accepted = NULL;

	cc_conn_flush_cookies(conn);
	cc_conn_sig_flush(conn);

	LIST_REMOVE(conn, port_link);
	while ((p = LIST_FIRST(&conn->parties)) != NULL) {
		LIST_REMOVE(p, link);
		CCFREE(p);
	}

	CCFREE(conn);
}

struct ccparty *
cc_party_create(struct ccconn *conn, u_int ident, u_int flag)
{
	struct ccparty *party;

	party = CCZALLOC(sizeof(*party));
	if (party == NULL)
		return (NULL);

	party->conn = conn;
	party->state = PARTY_NULL;
	IE_SETPRESENT(party->epref);
	party->epref.flag = flag;
	party->epref.epref = ident;
	LIST_INSERT_HEAD(&conn->parties, party, link);

	if (party->conn->cc->log & CCLOG_PARTY_INST)
		cc_party_log(party, "created %u.%u", flag, ident);

	return (party);
}

static void
cc_party_destroy(struct ccparty *party)
{

	if (party->conn->cc->log & CCLOG_PARTY_INST)
		cc_party_log(party, "destroyed %u.%u", party->epref.flag,
		    party->epref.epref);

	LIST_REMOVE(party, link);
	CCFREE(party);
}

static struct ccparty *
cc_party_find(struct ccconn *conn, u_int ident)
{
	struct ccparty *party;

	LIST_FOREACH(party, &conn->parties, link)
		if (party->epref.epref == ident)
			return (party);
	return (NULL);
}
/*
 * Abort connection from down stream (because of the UNI hook beeing
 * disconnected). This is called from two places:
 *  1) the shutdown code.
 *	In this case the connections should be already dissociated from
 *	users and be only in states waiting for the UNI stack.
 *  2) from the disconnect code.
 */
void
cc_conn_abort(struct ccconn *conn, int shutdown)
{
	struct ccuser *u = conn->user;
	struct ccparty *p, *p1;

	if (shutdown) {
		CCASSERT(u == NULL, ("still in use"));
		CCASSERT(conn->acceptor == NULL, ("still in use"));
		cc_conn_destroy(conn);
		return;
	}

	/*
	 * Look whether any parties are blocked waiting for a response
	 * from the stack. We don't use extra party states to handle
	 * user aborts, so check that there is a user before using it.
	 */
	if (u == NULL) {
		while ((p = LIST_FIRST(&conn->parties)) != NULL)
			cc_party_destroy(p);
	} else {
		LIST_FOREACH_SAFE(p, &conn->parties, link, p1) {
			switch (p->state) {

			  case PARTY_NULL:		/* P0 */
				/* should not happen */
				goto dpty;

			  case PARTY_ACTIVE:		/* P1 */
				/* don't send a drop - user'll get a rel */
				goto dpty;

			  case PARTY_ADD_WAIT_CREATE:	/* P2 */
			  case PARTY_ADD_WAIT_OK:	/* P3 */
				/* we're adding - synthesise an error */
				cc_user_sig(u, USER_SIG_ADD_PARTY_ERR,
				    NULL, ATMERR_BAD_PORT);
				goto dpty;

			  case PARTY_ADD_WAIT_ACK:	/* P4 */
				/* don't send a drop - user'll get a rel */
				goto dpty;

			  case PARTY_DROP_WAIT_OK:	/* P5 */
			  case PARTY_DROP_WAIT_ACK:	/* P6 */
			  case PARTY_ADD_DROP_WAIT_OK:	/* P11 */
				/* we're dropping - synthesis an ok */
				cc_user_sig(u, USER_SIG_DROP_PARTY_OK,
				    NULL, p->epref.epref);
				goto dpty;

			  case PARTY_WAIT_DESTROY:	/* P7 */
				goto dpty;

			  case PARTY_WAIT_SETUP_COMPL:	/* P8 */
			  case PARTY_WAIT_SETUP_CONF:	/* P10 */
				/* first party - nothing to do */
				goto dpty;

			  case PARTY_WAIT_DROP_ACK_OK:	/* P9 */
			  case PARTY_ADD_DROPACK_WAIT_OK:/* P12 */
				/* we're dropping - nothing to do */
				goto dpty;
			}
			cc_party_log(p, "bad uabort for party in state %s",
			    ptab[p->state]);
    dpty:
			cc_party_destroy(p);
		}
	}

	/*
	 * Now do what the connection needs
	 */
	switch (conn->state) {

	  case CONN_NULL:		/* 0 */
	  case CONN_OUT_PREPARING: 	/* 1 */
		/* may not happen because we're not associated with
		 * aport yet */
		break;

	  case CONN_OUT_WAIT_CREATE: 	/* 2 */
	  case CONN_OUT_WAIT_OK:	/* 3 */
	  case CONN_OUT_WAIT_DESTROY:	/* 37 */
		/* return an error to the user, go back to C1/U1
		 * reset cref (for C37, C3) and cookie */
		conn->cref.flag = 0;
		conn->cref.cref = 0;
		cc_conn_flush_cookies(conn);
		cc_conn_set_state(conn, CONN_OUT_PREPARING);
		cc_conn_rem_port(conn);
		cc_user_sig(u, USER_SIG_CONNECT_OUTGOING_ERR,
		    NULL, ATMERR_BAD_PORT);
		return;

	  case CONN_OUT_WAIT_CONF:	/* 4 */
	  case CONN_ACTIVE:		/* 5 */
	  case CONN_IN_WAIT_COMPL:	/* 13 */
		/* emulate a RELEASE.confirm */
		memset(&u->cause, 0, sizeof(u->cause));
		cc_user_sig(u, USER_SIG_RELEASE_CONFIRM, NULL, 0);
		cc_disconnect_from_user(conn);
		cc_conn_destroy(conn);
		return;

	  case CONN_IN_PREPARING:	/* 10 */
	  case CONN_AB_WAIT_REQ_OK:	/* 33 */
	  case CONN_AB_WAIT_RESP_OK:	/* 34 */
	  case CONN_AB_FLUSH_IND:	/* 35 */
		/* no user - destroy */
		cc_conn_destroy(conn);
		return;

	  case CONN_IN_ARRIVED:		/* 11 */
		u->aborted = 1;
		cc_disconnect_from_user(conn);
		cc_conn_destroy(conn);
		return;

	  case CONN_IN_WAIT_ACCEPT_OK:	/* 12 */
		/* return ACCEPT error */
		cc_disconnect_from_user(conn);
		cc_conn_reset_acceptor(conn);
		cc_user_sig(u, USER_SIG_ACCEPT_ERR,
		    u, ATMERR_PREVIOUSLY_ABORTED);
		cc_conn_destroy(conn);
		return;

	  case CONN_REJ_WAIT_OK:	/* 14 */
		/* return REJECT ok */
		cc_disconnect_from_user(conn);
		cc_conn_destroy(conn);
		cc_user_sig(u, USER_SIG_REJECT_OK, NULL, 0);
		return;

	  case CONN_REL_IN_WAIT_OK:	/* 15 */
	  case CONN_REL_WAIT_OK:	/* 20 */
		/* confirm destroy */
		if (u != NULL) {
			/* connection not aborted */
			memset(&u->cause, 0, sizeof(u->cause));
			cc_user_sig(u, USER_SIG_RELEASE_CONFIRM, NULL, 0);
			cc_disconnect_from_user(conn);
		}
		cc_conn_destroy(conn);
		return;

	  case CONN_IN_WAITING:		/* 21 */
		/* user has not seen the connection - destroy */
		cc_disconnect_from_user(conn);
		cc_conn_destroy(conn);
		return;
	}
	cc_conn_log(conn, "bad state %s", stab[conn->state]);
}

#ifdef DEBUG_MATCH
static void
print_sap(const struct uni_sap *sap)
{
	static const char *const tags[] = {
		[UNISVE_ABSENT] "absent",
		[UNISVE_PRESENT]"present",
		[UNISVE_ANY]	"any",
	};
	u_int i;

	printf("addr={%s", tags[sap->addr.tag]);
	if (sap->addr.tag == UNISVE_PRESENT) {
		printf(",%d-%d", sap->addr.type, sap->addr.plan);
		for (i = 0; i < sap->addr.len; i++)
			printf("%c%02x", ",:"[i!=0], sap->addr.addr[i]);
	}
	printf("}\n");

	printf("selector={%s", tags[sap->selector.tag]);
	if (sap->selector.tag == UNISVE_PRESENT)
		printf(",%02x", sap->selector.selector);
	printf("}\n");

	printf("blli_id2={%s", tags[sap->blli_id2.tag]);
	if (sap->blli_id2.tag == UNISVE_PRESENT)
		printf(",%02x,%02x", sap->blli_id2.proto, sap->blli_id2.user);
	printf("}\n");

	printf("blli_id3={%s", tags[sap->blli_id3.tag]);
	if (sap->blli_id3.tag == UNISVE_PRESENT)
		printf(",%02x,%02x,%02x,%06x,%04x,%d",
		    sap->blli_id3.proto, sap->blli_id3.user,
		    sap->blli_id3.ipi, sap->blli_id3.oui,
		    sap->blli_id3.pid, sap->blli_id3.noipi);
	printf("}\n");

	printf("bhli={%s", tags[sap->bhli.tag]);
	if (sap->bhli.tag == UNISVE_PRESENT) {
		printf(",%d", sap->bhli.type);
		for (i = 0; i < sap->bhli.len; i++)
			printf("%c%02x", ",:"[i!=0], sap->bhli.info[i]);
	}
	printf("}\n");
}
#endif

/*********************************************************************
 *
 * DISPATCH incoming call
 */
void
cc_conn_dispatch(struct ccconn *conn)
{
	struct ccdata *priv = conn->port->cc;
	struct ccuser *user;
	u_int blli_index;

#ifdef DEBUG_MATCH
	static char buf[1000];
	static struct unicx cx;
	static int init = 1;

	if (init) {
		uni_initcx(&cx);
		init = 0;
	}
#endif

	/*
	 * Do call dispatching according to 4.6
	 */
#ifdef DEBUG_MATCH
	printf("+++++ DISPATCH++++++\n");
#endif
	for (blli_index = 0; blli_index < UNI_NUM_IE_BLLI; blli_index++) {
		if (blli_index > 0 && !IE_ISGOOD(conn->blli[blli_index]))
			break;
#ifdef DEBUG_MATCH
		if (IE_ISPRESENT(conn->called)) {
			uni_print_ie(buf, sizeof(buf), UNI_IE_CALLED,
			    (union uni_ieall *)&conn->called, &cx);
			printf("called=%s\n", buf);
		}
		if (IE_ISPRESENT(conn->bhli)) {
			uni_print_ie(buf, sizeof(buf), UNI_IE_BHLI,
			    (union uni_ieall *)&conn->bhli, &cx);
			printf("bhli=%s\n", buf);
		}
		if (IE_ISPRESENT(conn->blli[blli_index])) {
			uni_print_ie(buf, sizeof(buf), UNI_IE_BLLI,
			    (union uni_ieall *)&conn->blli[blli_index], &cx);
			printf("%s\n", buf);
		}
#endif
		LIST_FOREACH(user, &priv->user_list, node_link) {
			if ((user->state == USER_IN_WAITING ||
			    user->state == USER_IN_ARRIVED ||
			    user->state == USER_IN_WAIT_ACC ||
			    user->state == USER_IN_WAIT_REJ) &&
			    !unisve_is_catchall(user->sap)) {
#ifdef DEBUG_MATCH
				printf("TRYING user=%p\n", user);
				print_sap(user->sap);
#endif
				if (unisve_match(user->sap, &conn->called,
				    &conn->blli[blli_index], &conn->bhli))
					goto found;
			}
		}
	}
#ifdef DEBUG_MATCH
	printf("TRYING CATCHALL\n");
#endif
	blli_index = 0;
	LIST_FOREACH(user, &priv->user_list, node_link) {
		if ((user->state == USER_IN_WAITING ||
		    user->state == USER_IN_ARRIVED ||
		    user->state == USER_IN_WAIT_ACC ||
		    user->state == USER_IN_WAIT_REJ) &&
		    unisve_is_catchall(user->sap))
			goto found;
	}
#ifdef DEBUG_MATCH
	printf("SORRY\n");
#endif

	/*
	 * No application found - reject call.
	 */
	do_release_response(conn, UNI_CAUSE_INCOMP, NULL);
	cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
	return;

  found:
#ifdef DEBUG_MATCH
	printf("MATCH\n");
#endif
	if (user->queue_max == user->queue_act) {
		do_release_response(conn, UNI_CAUSE_BUSY, NULL);
		cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
		return;
	}

	if (blli_index == 0 && !IE_ISGOOD(conn->blli[blli_index]))
		conn->blli_selector = 0;
	else
		conn->blli_selector = blli_index + 1;

	cc_conn_set_state(conn, CONN_IN_WAITING);
	cc_connect_to_user(conn, user);

	cc_user_sig(user, USER_SIG_SETUP_IND, NULL, 0);
}

static void
cc_party_setup_conf(struct ccconn *conn)
{
	struct ccparty *party;

	party = cc_party_find(conn, conn->epref.epref);
	if (party == NULL) {
		cc_party_log(party, "no party for %s",
		    cc_conn_sigtab[CONN_SIG_SETUP_CONFIRM]);
		return;
	}
	if (party->state != PARTY_WAIT_SETUP_CONF) {
		cc_party_log(party, "bad state=%s for signal=%s",
		    ptab[party->state], cc_conn_sigtab[CONN_SIG_SETUP_CONFIRM]);
		return;
	}
	cc_party_set_state(party, PARTY_ACTIVE);
}

static void
cc_party_add_ack_ind(struct ccconn *conn, const struct uni_ie_epref *epref)
{
	struct ccparty *party;

	party = cc_party_find(conn, epref->epref);
	if (party == NULL) {
		cc_party_log(party, "no party for %s",
		    cc_conn_sigtab[CONN_SIG_PARTY_ADD_ACK_IND]);
	}
	if (party->state != PARTY_ADD_WAIT_ACK) {
		cc_party_log(party, "bad state=%s for signal=%s",
		    ptab[party->state],
		    cc_conn_sigtab[CONN_SIG_PARTY_ADD_ACK_IND]);
		return;
	}
	cc_party_set_state(party, PARTY_ACTIVE);
	cc_user_sig(conn->user, USER_SIG_ADD_PARTY_ACK,
	    NULL, epref->epref);
}

static void
cc_party_add_rej_ind(struct ccconn *conn, const struct uni_ie_epref *epref)
{
	struct ccparty *party;

	party = cc_party_find(conn, epref->epref);
	if (party == NULL) {
		cc_party_log(party, "no party for %s",
		    cc_conn_sigtab[CONN_SIG_PARTY_ADD_REJ_IND]);
		return;
	}
	if (party->state != PARTY_ADD_WAIT_ACK) {
		cc_party_log(party, "bad state=%s for signal=%s",
		    ptab[party->state],
		    cc_conn_sigtab[CONN_SIG_PARTY_ADD_REJ_IND]);
		return;
	}
	cc_party_set_state(party, PARTY_WAIT_DESTROY);
	cc_user_sig(conn->user, USER_SIG_ADD_PARTY_REJ, NULL, epref->epref);
}

static void
cc_party_drop_ack_ind(struct ccconn *conn,
    const struct uni_drop_party *drop)
{
	struct ccparty *party;

	party = cc_party_find(conn, drop->epref.epref);
	if (party == NULL) {
		cc_party_log(party, "no party for %s",
		    ptab[CONN_SIG_DROP_PARTY_ACK_IND]);
		return;
	}
	switch (party->state) {

	  case PARTY_ACTIVE:			/* P1 */
		memset(&conn->user->cause[1], 0, sizeof(conn->user->cause[1]));
		conn->user->cause[0] = drop->cause;
		cc_party_set_state(party, PARTY_WAIT_DESTROY);
		cc_user_sig(conn->user, USER_SIG_DROP_PARTY_IND,
		    NULL, party->epref.epref);
		break;

	  case PARTY_ADD_WAIT_ACK:		/* P4 */
		memset(&conn->user->cause[1], 0, sizeof(conn->user->cause[1]));
		conn->user->cause[0] = drop->cause;
		cc_party_set_state(party, PARTY_WAIT_DESTROY);
		cc_user_sig(conn->user, USER_SIG_ADD_PARTY_REJ,
		    NULL, party->epref.epref);
		break;

	  case PARTY_DROP_WAIT_ACK:		/* P6 */
		cc_party_set_state(party, PARTY_WAIT_DESTROY);
		cc_user_sig(conn->user, USER_SIG_DROP_PARTY_OK, NULL, 0);
		break;

	  case PARTY_WAIT_SETUP_COMPL:		/* P8 */
	  case PARTY_WAIT_SETUP_CONF:		/* P10 */
		cc_party_set_state(party, PARTY_WAIT_DESTROY);
		break;

	  default:
		cc_party_log(party, "bad state=%s for signal=%s",
		    ptab[party->state],
		    cc_conn_sigtab[CONN_SIG_DROP_PARTY_ACK_IND]);
		break;
	}
}

/*
 * Handle a signal to this connection
 */
void
cc_conn_sig_handle(struct ccconn *conn, enum conn_sig sig,
    void *arg, u_int iarg)
{
	struct ccparty *party;

	if (conn->cc->log & CCLOG_CONN_SIG)
		cc_conn_log(conn, "signal %s in state %s", cc_conn_sigtab[sig],
		    stab[conn->state]);

	switch (sig) {

	  case CONN_SIG_CONNECT_OUTGOING:
		/* Do SETUP */
	    {
		struct uni_msg *u;
		struct uniapi_setup_request *setup;

		if (conn->state != CONN_OUT_PREPARING)
			goto bad_state;

		if (IE_ISGOOD(conn->bearer) &&
		    conn->bearer.cfg == UNI_BEARER_MP) {
			IE_SETPRESENT(conn->epref);
			conn->epref.flag = 0;
			conn->epref.epref = 0;
		}

		/*
		 * Construct message to UNI.
		 */
		u = uni_msg_alloc(sizeof(struct uniapi_setup_request));
		if (u == NULL) {
			cc_user_sig(conn->user, USER_SIG_CONNECT_OUTGOING_ERR,
			    NULL, ATMERR_NOMEM);
			return;
		}
		setup = uni_msg_wptr(u, struct uniapi_setup_request *);
		memset(setup, 0, sizeof(*setup));
		u->b_wptr += sizeof(struct uniapi_setup_request);

		setup->setup.hdr.act = UNI_MSGACT_DEFAULT;
		memcpy(setup->setup.blli, conn->blli, sizeof(conn->blli));
		setup->setup.bearer = conn->bearer;
		setup->setup.traffic = conn->traffic;
		setup->setup.qos = conn->qos;
		setup->setup.exqos = conn->exqos;
		setup->setup.called = conn->called;
		setup->setup.calledsub[0] = conn->calledsub;
		setup->setup.aal = conn->aal;
		setup->setup.epref = conn->epref;
		setup->setup.eetd = conn->eetd;
		setup->setup.abrsetup = conn->abrsetup;
		setup->setup.abradd = conn->abradd;
		setup->setup.calling = conn->calling;
		setup->setup.callingsub[0] = conn->callingsub;
		setup->setup.connid = conn->connid;
		memcpy(setup->setup.tns, conn->tns, sizeof(conn->tns));
		setup->setup.atraffic = conn->atraffic;
		setup->setup.mintraffic = conn->mintraffic;
		setup->setup.cscope = conn->cscope;
		setup->setup.bhli = conn->bhli;
		setup->setup.mdcr = conn->mdcr;

		cc_conn_set_state(conn, CONN_OUT_WAIT_CREATE);
		cc_send_uni(conn, UNIAPI_SETUP_request, u);

		break;
	    }


	  case CONN_SIG_ARRIVAL:
		/* user informed of arrival of this call */
		if (conn->state != CONN_IN_WAITING)
			goto bad_state;
		cc_conn_set_state(conn, CONN_IN_ARRIVED);
		break;


	  case CONN_SIG_RELEASE:
	    {
		/* Release this call */
		struct uni_msg *u;
		struct uniapi_release_request *req;

		if (conn->state != CONN_ACTIVE &&
		    conn->state != CONN_IN_WAIT_COMPL)
			goto bad_state;

		if ((u = uni_msg_alloc(sizeof(*req))) == NULL)
			return;

		req = uni_msg_wptr(u, struct uniapi_release_request *);
		memset(req, 0, sizeof(*req));
		u->b_wptr += sizeof(struct uniapi_release_request);

		req->release.hdr.cref = conn->cref;
		req->release.hdr.act = UNI_MSGACT_DEFAULT;

		req->release.cause[0] = conn->cause[0];
		req->release.cause[1] = conn->cause[1];

		if (conn->state == CONN_ACTIVE)
			cc_conn_set_state(conn, CONN_REL_WAIT_OK);
		else
			cc_conn_set_state(conn, CONN_REL_IN_WAIT_OK);

		cc_send_uni(conn, UNIAPI_RELEASE_request, u);
		break;
	    }

	  case CONN_SIG_REJECT:
	    {
		/* reject from user */
		struct ccuser *user = conn->user;

		if (conn->state != CONN_IN_ARRIVED) {
			cc_user_sig(user, USER_SIG_REJECT_ERR,
			    NULL, ATMERR_BAD_STATE);
			break;
		}
		cc_conn_set_state(conn, CONN_REJ_WAIT_OK);
		do_release_response(conn, 0, conn->cause);
		break;
	    }


	  case CONN_SIG_ACCEPT:
	    {
		 /* User accepts. */
    		struct ccuser *newep = arg;
		struct uni_msg *u;
		struct uniapi_setup_response *resp;
		struct ccuser *user = conn->user;

		if (conn->state != CONN_IN_ARRIVED) {
			cc_user_sig(user, USER_SIG_ACCEPT_ERR,
			    NULL, ATMERR_PREVIOUSLY_ABORTED);
			break;
		}

		u = uni_msg_alloc(sizeof(struct uniapi_setup_response));
		if (u == NULL) {
			cc_user_sig(user, USER_SIG_ACCEPT_ERR,
			    NULL, ATMERR_NOMEM);
			return;
		}

		/*
		 * Link to the new endpoint
		 */
		conn->acceptor = newep;
		newep->accepted = conn;

		/*
		 * Construct connect message
		 */
		resp = uni_msg_wptr(u, struct uniapi_setup_response *);
		memset(resp, 0, sizeof(*resp));
		u->b_wptr += sizeof(*resp);

		resp->connect.hdr.act = UNI_MSGACT_DEFAULT;
		resp->connect.hdr.cref = conn->cref;

		/*
		 * attributes
		 */
		if (conn->dirty_attr && CCDIRTY_AAL)
			resp->connect.aal = conn->aal;
		if (conn->dirty_attr && CCDIRTY_BLLI)
			resp->connect.blli =
			    conn->blli[conn->blli_selector - 1];
		if (conn->dirty_attr && CCDIRTY_CONNID)
			resp->connect.connid = conn->connid;
		/* XXX NOTIFY */
		if (conn->dirty_attr && CCDIRTY_EETD)
			resp->connect.eetd = conn->eetd;
		/* XXX GIT */
		/* XXX UU */
		if (conn->dirty_attr && CCDIRTY_TRAFFIC)
			resp->connect.traffic = conn->traffic;
		if (conn->dirty_attr && CCDIRTY_EXQOS)
			resp->connect.exqos = conn->exqos;
		if (conn->dirty_attr && CCDIRTY_ABRSETUP)
			resp->connect.abrsetup = conn->abrsetup;
		if (conn->dirty_attr && CCDIRTY_ABRADD)
			resp->connect.abradd = conn->abradd;

		/*
		 * If the SETUP had an endpoint reference - echo it back
		 */
		if (IE_ISPRESENT(conn->epref)) {
			resp->connect.epref = conn->epref;
			resp->connect.epref.flag = !resp->connect.epref.flag;
		}

		cc_conn_set_state(conn, CONN_IN_WAIT_ACCEPT_OK);
		cc_send_uni(conn, UNIAPI_SETUP_response, u);
		break;
	    }


	  case CONN_SIG_ADD_PARTY:
	    {
		/* request to add party from user */
		struct uni_msg *u;
		struct uniapi_add_party_request *req;

		if (conn->state != CONN_ACTIVE)
			goto bad_state;

		/* create the party */
		party = cc_party_create(conn, (u_int)(uintptr_t)arg, 0);
		if (party == NULL) {
			cc_user_sig(conn->user, USER_SIG_ADD_PARTY_ERR,
			    NULL, ATMERR_NOMEM);
			return;
		}
		party->called = conn->called;

		/* Construct message to UNI. */
		u = uni_msg_alloc(sizeof(struct uniapi_setup_request));
		if (u == NULL) {
			cc_party_destroy(party);
			cc_user_sig(conn->user, USER_SIG_ADD_PARTY_ERR,
			    NULL, ATMERR_NOMEM);
			return;
		}

		req = uni_msg_wptr(u, struct uniapi_add_party_request *);
		memset(req, 0, sizeof(*req));
		u->b_wptr += sizeof(struct uniapi_add_party_request);

		req->add.hdr.act = UNI_MSGACT_DEFAULT;
		req->add.hdr.cref = conn->cref;
		req->add.epref = party->epref;
		req->add.called = party->called;

		cc_party_set_state(party, PARTY_ADD_WAIT_CREATE);
		cc_send_uni(conn, UNIAPI_ADD_PARTY_request, u);
		break;
	    }


	  case CONN_SIG_DROP_PARTY:
	    {
		/* user request to drop a party */
		struct uni_msg *u;
		struct uniapi_drop_party_request *req;

		if (conn->state != CONN_ACTIVE)
			goto bad_state;

		party = cc_party_find(conn, (u_int)(uintptr_t)arg);
		if (party == NULL) {
			cc_user_sig(conn->user, USER_SIG_DROP_PARTY_ERR,
			    NULL, ATMERR_BAD_PARTY);
			return;
		}

		switch (party->state) {

		  case PARTY_ACTIVE:
		  case PARTY_ADD_WAIT_ACK:
			break;

		  default:
			cc_user_sig(conn->user, USER_SIG_DROP_PARTY_ERR,
			    NULL, ATMERR_BAD_STATE);
			return;

		}
		/*
		 * Construct message to UNI.
		 */
		u = uni_msg_alloc(sizeof(*req));
		if (u == NULL) {
			cc_user_sig(conn->user, USER_SIG_DROP_PARTY_ERR,
			    NULL, ATMERR_NOMEM);
			return;
		}

		req = uni_msg_wptr(u, struct uniapi_drop_party_request *);
		memset(req, 0, sizeof(*req));
		u->b_wptr += sizeof(struct uniapi_drop_party_request);

		req->drop.hdr.act = UNI_MSGACT_DEFAULT;
		req->drop.hdr.cref = conn->cref;
		req->drop.epref = party->epref;
		req->drop.cause = conn->cause[0];

		if (party->state == PARTY_ACTIVE)
			cc_party_set_state(party, PARTY_DROP_WAIT_OK);
		else
			cc_party_set_state(party, PARTY_ADD_DROP_WAIT_OK);
		cc_send_uni(conn, UNIAPI_DROP_PARTY_request, u);
		break;
	    }

	  case CONN_SIG_DROP_PARTY_ACK_IND:
	    {
		struct uni_msg *msg = arg;
		struct uniapi_drop_party_ack_indication *ind = uni_msg_rptr(msg,
		    struct uniapi_drop_party_ack_indication *);

		cc_party_drop_ack_ind(conn, &ind->drop);
		break;
	    }


	  case CONN_SIG_USER_ABORT:
		/*
		 * Aborting a connection. This is callable in all states.
		 * The connection is already disconnected from the user.
		 * The cause is in cause[].
		 */
		switch (conn->state) {

		  case CONN_NULL:		/* C0 */
		  case CONN_OUT_PREPARING:	/* C1 */
			cc_conn_destroy(conn);
			break;

		  case CONN_OUT_WAIT_CONF:	/* C4 */
		  case CONN_ACTIVE:		/* C5 */
			do_release_request(conn, conn->cause);
			cc_conn_set_state(conn, CONN_AB_WAIT_REQ_OK);
			break;

		  case CONN_IN_WAITING:		/* C21 */
			/* that should not happen */
			goto bad_state;
			break;

		  case CONN_IN_ARRIVED:		/* C11 */
			/*
			 * This is called only for the first connection
			 * of the user - the others are re-dispatched.
			 */
			do_release_response(conn, 0, conn->cause);
			cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
			break;

		  case CONN_IN_WAIT_COMPL:	/* C13 */
			do_release_request(conn, conn->cause);
			cc_conn_set_state(conn, CONN_AB_WAIT_REQ_OK);
			break;

		  case CONN_OUT_WAIT_DESTROY:	/* C20 */
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			break;

		  case CONN_IN_WAIT_ACCEPT_OK:	/* C12 */
		  case CONN_AB_WAIT_REQ_OK:	/* C33 */
		  case CONN_AB_WAIT_RESP_OK:	/* C34 */
		  case CONN_AB_FLUSH_IND:	/* C35 */
			/* just ignore */
			break;

		/*
		 * The following states may not happen, because
		 * we're waiting for a response from the UNI stack.
		 * As soon as the response comes the ABORT is undefered
		 * and will hit us (but in another state).
		 */
		  case CONN_OUT_WAIT_CREATE:	/* C2 */
		  case CONN_OUT_WAIT_OK:	/* C3 */
		  case CONN_IN_PREPARING:	/* C10 */
		  case CONN_REJ_WAIT_OK:	/* C14 */
		  case CONN_REL_IN_WAIT_OK:	/* C15 */
		  case CONN_REL_WAIT_OK:	/* C20 */
			goto bad_state;
		}
		break;


	  case CONN_SIG_CREATED:
	    {
		/*
		 * CALL_CREATED message from UNI. This can happen for either
		 * incoming or outgoing connections.
		 */
		struct uni_msg *msg = arg;
		struct uniapi_call_created *cr = uni_msg_rptr(msg,
		    struct uniapi_call_created *);

		switch (conn->state) {

		  case CONN_OUT_WAIT_CREATE:
			conn->cref = cr->cref;
			cc_conn_set_state(conn, CONN_OUT_WAIT_OK);
			break;

		  case CONN_NULL:
			conn->cref = cr->cref;
			cc_conn_set_state(conn, CONN_IN_PREPARING);
			break;

		  default:
			goto bad_state;
		}
		break;
	    }

	  case CONN_SIG_DESTROYED:
		/*
		 * CALL_DESTROYED message from UNI.
		 */
		switch (conn->state) {

		  case CONN_OUT_WAIT_DESTROY:
			cc_conn_rem_port(conn);
			cc_conn_set_state(conn, CONN_OUT_PREPARING);
			if (conn->user != NULL)
				cc_user_sig(conn->user,
				    USER_SIG_CONNECT_OUTGOING_ERR,
				    NULL, ATM_MKUNIERR(conn->reason));
			break;

		  case CONN_AB_FLUSH_IND:
			cc_conn_destroy(conn);
			break;

		  case CONN_IN_PREPARING:
			cc_conn_destroy(conn);
			break;

		  default:
			goto bad_state;
		}
		break;


	  case CONN_SIG_SETUP_CONFIRM:
		/* Setup confirm from the UNI. */
	    {
		struct uni_msg *msg = arg;
		struct uniapi_setup_confirm *conf = uni_msg_rptr(msg,
		    struct uniapi_setup_confirm *);

		switch (conn->state) {

		  case CONN_OUT_WAIT_CONF:
			/*
			 * Shuffle attributes and inform the user.
			 * Negotiable attributes are condititionally shuffled,
			 * because not returning it means accepting it
			 * (in case of blli the first instance of it).
			 * All others are shuffled unconditionally.
			 * Here we should also open the VCI in the driver. (XXX)
			 */
#define SHUFFLE(ATTR)	conn->ATTR = conf->connect.ATTR
#define COND_SHUFFLE(ATTR) if (IE_ISPRESENT(conf->connect.ATTR)) SHUFFLE(ATTR)

			COND_SHUFFLE(aal);
			(void)memset(conn->blli + 1, 0,
			    sizeof(conn->blli) - sizeof(conn->blli[0]));
			if (IE_ISPRESENT(conf->connect.blli))
				conn->blli[0] = conf->connect.blli;
			conn->blli_selector = 1;
			COND_SHUFFLE(epref);
			SHUFFLE(conned);
			SHUFFLE(connedsub);
			SHUFFLE(eetd);
			COND_SHUFFLE(traffic);
			COND_SHUFFLE(exqos);
			COND_SHUFFLE(abrsetup);
			COND_SHUFFLE(abradd);
			COND_SHUFFLE(connid);
#undef SHUFFLE
#undef COND_SHUFFLE
			if (IE_ISGOOD(conn->epref))
				cc_party_setup_conf(conn);

			cc_conn_set_state(conn, CONN_ACTIVE);
			cc_user_sig(conn->user, USER_SIG_SETUP_CONFIRM,
			    NULL, 0);
			break;

		  case CONN_AB_FLUSH_IND:
		  case CONN_AB_WAIT_RESP_OK:
			break;

		  default:
			goto bad_state;
		}
		break;
	    }

	  case CONN_SIG_SETUP_IND:
	    {
		/* SETUP indication */
		struct uni_msg *msg = arg;
		struct uniapi_setup_indication *ind = uni_msg_rptr(msg,
		    struct uniapi_setup_indication *);
		u_int i;

		if (conn->state != CONN_IN_PREPARING)
			goto bad_state;

		/*
		 * Shuffle information elements.
		 */
		for (i = 0; i < UNI_NUM_IE_BLLI; i++)
			conn->blli[i] = ind->setup.blli[i];
		conn->bearer = ind->setup.bearer;
		conn->traffic = ind->setup.traffic;
		conn->qos = ind->setup.qos;
		conn->exqos = ind->setup.exqos;
		conn->called = ind->setup.called;
		conn->calledsub = ind->setup.calledsub[0];
		conn->aal = ind->setup.aal;
		conn->epref = ind->setup.epref;
		conn->eetd = ind->setup.eetd;
		conn->abrsetup = ind->setup.abrsetup;
		conn->abradd = ind->setup.abradd;
		conn->calling = ind->setup.calling;
		conn->callingsub = ind->setup.callingsub[0];
		conn->connid = ind->setup.connid;
		for (i = 0; i < UNI_NUM_IE_TNS; i++)
			conn->tns[i] = ind->setup.tns[i];
		conn->atraffic = ind->setup.atraffic;
		conn->mintraffic = ind->setup.mintraffic;
		conn->cscope = ind->setup.cscope;
		conn->bhli = ind->setup.bhli;
		conn->mdcr = ind->setup.mdcr;

		cc_conn_dispatch(conn);
		break;
	    }


	  case CONN_SIG_SETUP_COMPL:
	    {
		struct uni_msg *msg = arg;
		struct uniapi_setup_indication *ind __unused =
		    uni_msg_rptr(msg, struct uniapi_setup_indication *);

		/* SETUP_COMPLETE.indication from UNI */
		if (conn->state == CONN_AB_FLUSH_IND ||
		    conn->state == CONN_AB_WAIT_RESP_OK)
			break;

		if (conn->state != CONN_IN_WAIT_COMPL)
			goto bad_state;

		cc_conn_set_state(conn, CONN_ACTIVE);

		LIST_FOREACH(party, &conn->parties, link) {
			if (party->state == PARTY_WAIT_SETUP_COMPL)
				cc_party_set_state(party, PARTY_ACTIVE);
			else
				cc_party_log(party, "bad state=%s for sig=%s",
				    ptab[party->state],
				    cc_conn_sigtab[CONN_SIG_SETUP_COMPL]);
		}

		cc_user_sig(conn->user, USER_SIG_SETUP_COMPL, NULL, 0);
		break;
	    }


	  case CONN_SIG_PROC_IND:
	    {
		/*
		 * ALERTING.indication and PROCEEDING.indication are entirly
		 * ignored by the specification. We need to at least save the
		 * connid information element.
		 */
		struct uni_msg *msg = arg;
		struct uniapi_proceeding_indication *ind = uni_msg_rptr(msg,
		    struct uniapi_proceeding_indication *);

		switch (conn->state) {

		  case CONN_OUT_WAIT_CONF:
			if (IE_ISGOOD(ind->call_proc.connid))
				conn->connid = ind->call_proc.connid;
			break;

		  case CONN_AB_FLUSH_IND:
		  case CONN_AB_WAIT_RESP_OK:
			break;

		  default:
			goto bad_state;
		}
		break;
	    }

	  case CONN_SIG_ALERTING_IND:
	    {
		struct uni_msg *msg = arg;
		struct uniapi_alerting_indication *ind = uni_msg_rptr(msg,
		    struct uniapi_alerting_indication *);

		switch (conn->state) {

		  case CONN_OUT_WAIT_CONF:
			if (IE_ISGOOD(ind->alerting.connid))
				conn->connid = ind->alerting.connid;
			break;

		  case CONN_AB_FLUSH_IND:
		  case CONN_AB_WAIT_RESP_OK:
			break;

		  default:
			goto bad_state;
		}
		break;
	  }

	  case CONN_SIG_REL_CONF:
	    {
		/* RELEASE.confirm from UNI */
		struct uni_msg *msg = arg;
		struct uniapi_release_confirm *conf = uni_msg_rptr(msg,
		    struct uniapi_release_confirm *);

		switch (conn->state) {

		  case CONN_OUT_WAIT_CONF:
		  case CONN_ACTIVE:
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			memcpy(conn->user->cause, conf->release.cause,
			    sizeof(conn->user->cause));
			/*
			 * If any party is in P6, ok the user
			 */
			LIST_FOREACH(party, &conn->parties, link) {
				if (party->state == PARTY_DROP_WAIT_ACK) {
					cc_party_set_state(party,
					    PARTY_WAIT_DESTROY);
					cc_user_sig(conn->user,
					    USER_SIG_DROP_PARTY_OK,
					    NULL, party->epref.epref);
				}
			}
			cc_user_sig(conn->user, USER_SIG_RELEASE_CONFIRM,
			    NULL, 0);
			cc_disconnect_from_user(conn);
			break;

		  case CONN_AB_FLUSH_IND:
		  case CONN_AB_WAIT_RESP_OK:
			break;

		  case CONN_IN_WAITING:
			cc_disconnect_from_user(conn);
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			break;

		  case CONN_IN_ARRIVED:
			conn->user->aborted = 1;
			memcpy(conn->user->cause, conf->release.cause,
			    sizeof(conn->user->cause));
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			cc_disconnect_from_user(conn);
			break;

		  case CONN_IN_WAIT_COMPL:
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			memcpy(conn->user->cause, conf->release.cause,
			    sizeof(conn->user->cause));
			cc_user_sig(conn->user, USER_SIG_RELEASE_CONFIRM,
			    NULL, 0);
			cc_disconnect_from_user(conn);
			break;
			
		  default:
			goto bad_state;
		}
		break;
	    }

	  case CONN_SIG_REL_IND:
	    {
		/* RELEASE.ind from UNI */
		struct uni_msg *msg = arg;
		struct uniapi_release_indication *conf = uni_msg_rptr(msg,
		    struct uniapi_release_indication *);

		switch (conn->state) {

		  case CONN_OUT_WAIT_CONF:
		  case CONN_ACTIVE:
			do_release_response(conn, 0, NULL);
			cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
			memcpy(conn->user->cause, conf->release.cause,
			    sizeof(conn->user->cause));
			/*
			 * If any party is in P6, ok the user
			 */
			LIST_FOREACH(party, &conn->parties, link) {
				if (party->state == PARTY_DROP_WAIT_ACK) {
					cc_party_set_state(party,
					    PARTY_WAIT_DESTROY);
					cc_user_sig(conn->user,
					    USER_SIG_DROP_PARTY_OK,
					    NULL, party->epref.epref);
				}
			}
			cc_user_sig(conn->user, USER_SIG_RELEASE_CONFIRM,
			    NULL, 0);
			cc_disconnect_from_user(conn);
			break;

		  case CONN_AB_FLUSH_IND:
		  case CONN_AB_WAIT_RESP_OK:
			break;

		  case CONN_IN_WAITING:
			cc_disconnect_from_user(conn);
			do_release_response(conn, 0, NULL);
			cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
			break;

		  case CONN_IN_ARRIVED:
			conn->user->aborted = 1;
			cc_disconnect_from_user(conn);
			do_release_response(conn, 0, NULL);
			cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
			break;

		  case CONN_IN_WAIT_COMPL:
			do_release_response(conn, 0, NULL);
			cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
			memcpy(conn->user->cause, conf->release.cause,
			    sizeof(conn->user->cause));
			cc_user_sig(conn->user, USER_SIG_RELEASE_CONFIRM,
			    NULL, 0);
			cc_disconnect_from_user(conn);
			break;
		  default:
			goto bad_state;
			break;
		}
		break;
	    }

	  case CONN_SIG_PARTY_ALERTING_IND:
		/* party alerting from UNI */
		if (conn->state == CONN_AB_FLUSH_IND)
			break;
		if (conn->state != CONN_ACTIVE)
			goto bad_state;
		/* ignore */
		break;

	  case CONN_SIG_PARTY_ADD_ACK_IND:
	    {
		/* ADD PARTY ACKNOWLEDGE from UNI */
		struct uni_msg *msg = arg;
		struct uniapi_add_party_ack_indication *ind = uni_msg_rptr(msg,
		    struct uniapi_add_party_ack_indication *);

		if (conn->state == CONN_AB_FLUSH_IND)
			break;
		if (conn->state != CONN_ACTIVE)
			goto bad_state;

		cc_party_add_ack_ind(conn, &ind->ack.epref);
		break;
	    }


	 case CONN_SIG_PARTY_ADD_REJ_IND:
	    {
		/* ADD PARTY REJECT indication */
		struct uni_msg *msg = arg;
		struct uniapi_add_party_rej_indication *ind = uni_msg_rptr(msg,
		    struct uniapi_add_party_rej_indication *);

		if (conn->state == CONN_AB_FLUSH_IND)
			break;
		if (conn->state != CONN_ACTIVE)
			goto bad_state;

		memset(&conn->user->cause[1], 0, sizeof(conn->user->cause[1]));
		conn->user->cause[0] = ind->rej.cause;

		cc_party_add_rej_ind(conn, &ind->rej.epref);
		break;
	    }


	  case CONN_SIG_DROP_PARTY_IND:
	    {
		/* DROP_PARTY.indication from UNI */
		struct uni_msg *msg = arg;
		struct uniapi_drop_party_indication *ind = uni_msg_rptr(msg,
		    struct uniapi_drop_party_indication *);
		struct uniapi_drop_party_ack_request *req;
		struct uni_msg *u;

		if (conn->state == CONN_AB_FLUSH_IND)
			break;
		if (conn->state != CONN_ACTIVE)
			goto bad_state;

		party = cc_party_find(conn, ind->drop.epref.epref);
		if (party == NULL) {
			cc_party_log(party, "no party for %s",
			    cc_conn_sigtab[sig]);
			break;
		}

		u = uni_msg_alloc(sizeof(*req));
		if (u == NULL)
			return;

		memset(&conn->user->cause[1], 0, sizeof(conn->user->cause[1]));
		conn->user->cause[0] = ind->drop.cause;

		switch (party->state) {

		  default:
			cc_party_log(party, "bad state %s for DROP.ind",
			    ptab[party->state]);
			/* FALLTHRU */

		  case PARTY_ACTIVE:		/* P1 -> P9 */
			cc_party_set_state(party, PARTY_WAIT_DROP_ACK_OK);
			break;

		  case PARTY_ADD_WAIT_ACK:	/* P4 -> P12 */
			cc_party_set_state(party, PARTY_ADD_DROPACK_WAIT_OK);
			break;
		}

		/*
		 * Construct message to UNI.
		 */
		req = uni_msg_wptr(u, struct uniapi_drop_party_ack_request *);
		memset(req, 0, sizeof(*req));
		u->b_wptr += sizeof(*req);

		IE_SETPRESENT(req->ack.epref);
		req->ack.hdr.act = UNI_MSGACT_DEFAULT;
		req->ack.hdr.cref = conn->cref;

		req->ack.epref.flag = 0;
		req->ack.epref.epref = ind->drop.epref.epref;

		cc_send_uni(conn, UNIAPI_DROP_PARTY_ACK_request, u);
		break;
	    }

	  case CONN_SIG_OK:
	    {
 		/* OK response from UNI */
		struct ccuser *user = conn->user;

		switch (conn->state) {

		  case CONN_OUT_WAIT_OK:		/* C3 */
			cc_conn_set_state(conn, CONN_OUT_WAIT_CONF);
			if (conn->user != NULL)
				cc_user_sig(conn->user,
				    USER_SIG_CONNECT_OUTGOING_OK, NULL, 0);
			break;

		  case CONN_AB_WAIT_RESP_OK:		/* C33 */
		  case CONN_AB_WAIT_REQ_OK:		/* C34 */
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			break;

		  case CONN_REL_WAIT_OK:		/* C20 */
		  case CONN_REL_IN_WAIT_OK:		/* C15 */
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			if (conn->user != NULL) {
				/* connection has not been aborted */
				memset(&conn->user->cause, 0,
				    sizeof(conn->user->cause));
				cc_user_sig(conn->user,
				    USER_SIG_RELEASE_CONFIRM, NULL, 0);
				cc_disconnect_from_user(conn);
			}
			break;

		  case CONN_IN_WAIT_ACCEPT_OK:		/* C12 */
			if (user == NULL) {
				/* has been aborted */
				do_release_request(conn, NULL);
				cc_conn_set_state(conn, CONN_AB_WAIT_REQ_OK);
				break;
			}
			cc_conn_set_state(conn, CONN_IN_WAIT_COMPL);
			cc_disconnect_from_user(conn);
			cc_user_sig(user, USER_SIG_ACCEPT_OK, NULL, 0);
			if (conn->acceptor == NULL) {
				do_release_request(conn, NULL);
				cc_conn_set_state(conn, CONN_AB_WAIT_REQ_OK);
				break;
			}
			cc_connect_to_user(conn, conn->acceptor);
			cc_conn_reset_acceptor(conn);
			cc_user_sig(conn->user, USER_SIG_ACCEPTING, NULL, 0);
			break;

		  case CONN_REJ_WAIT_OK:		/* C14 */
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			if (user != NULL) {
				cc_disconnect_from_user(conn);
				cc_user_sig(user, USER_SIG_REJECT_OK, NULL, 0);
			}
			break;

		  default:
			/* maybe it's for a party */
			LIST_FOREACH(party, &conn->parties, link) {
				switch (party->state) {

				  case PARTY_ADD_WAIT_OK:	/* P3 */
					if (user != NULL)
						cc_user_sig(user,
						    USER_SIG_ADD_PARTY_OK,
						    NULL, 0);
					cc_party_set_state(party,
					    PARTY_ADD_WAIT_ACK);
					goto ex_party_ok;

				  case PARTY_DROP_WAIT_OK:	/* P5 */
					cc_party_set_state(party,
					    PARTY_DROP_WAIT_ACK);
					goto ex_party_ok;

				  case PARTY_WAIT_DROP_ACK_OK:	/* P9 */
				  case PARTY_ADD_DROPACK_WAIT_OK:/* P12 */
				     {
					struct ccparty *p1;

					cc_party_set_state(party,
					    PARTY_WAIT_DESTROY);
					/* signal to user only if there are any other parties */
					LIST_FOREACH(p1, &conn->parties, link)
						if (p1 != party)
							break;
					if (p1 != NULL && user != NULL)
						cc_user_sig(user,
						    USER_SIG_DROP_PARTY_IND,
						    NULL,
						    party->epref.epref);

					goto ex_party_ok;
				    }

				  case PARTY_ADD_DROP_WAIT_OK:	/* P11 */
					cc_party_set_state(party,
					    PARTY_DROP_WAIT_ACK);
					goto ex_party_ok;

				  default:
					break;
				}
			}
			goto bad_state;
		    ex_party_ok:
			break;
		}
		break;
	    }

	  case CONN_SIG_ERROR:
	    {
		/* error response from UNI */
		u_int reason = (iarg >> 16) & 0xffff;
		u_int state = iarg & 0xffff;
		struct ccuser *user = conn->user;

		switch (conn->state) {

		  case CONN_OUT_WAIT_CREATE:		/* C2 */
			cc_conn_rem_port(conn);
			cc_conn_set_state(conn, CONN_OUT_PREPARING);
			if (conn->user != NULL)
				cc_user_sig(conn->user,
				    USER_SIG_CONNECT_OUTGOING_ERR,
				    NULL, ATM_MKUNIERR(reason));
			break;

		  case CONN_OUT_WAIT_OK:		/* C3 */
			cc_conn_set_state(conn, CONN_OUT_WAIT_DESTROY);
			conn->reason = reason;
			break;

		  case CONN_AB_WAIT_REQ_OK:		/* C33 */
			if (state == UNI_CALLSTATE_U12) {
				do_release_response(conn, 0, conn->cause);
				cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
				break;
			}
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			break;

		  case CONN_AB_WAIT_RESP_OK:		/* C34 */
			cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			break;

		  case CONN_REL_WAIT_OK:		/* C20 */
			if (user == NULL) {
				/* connection has been aborted. */
				if (state == UNI_CALLSTATE_U10) {
					/* do what we can */
					do_release_request(conn, conn->cause);
					cc_conn_set_state(conn,
					    CONN_AB_WAIT_REQ_OK);
				} else if (state == UNI_CALLSTATE_U12) {
					do_release_response(conn, 0, NULL);
					cc_conn_set_state(conn,
					    CONN_AB_WAIT_RESP_OK);
				} else {
					cc_conn_set_state(conn,
					    CONN_AB_FLUSH_IND);
				}
				break;
			}
			if (state == UNI_CALLSTATE_U10) {
				cc_conn_set_state(conn, CONN_ACTIVE);
				cc_user_sig(conn->user, USER_SIG_RELEASE_ERR,
				    NULL, reason);
			} else if (state == UNI_CALLSTATE_U12) {
				do_release_response(conn, 0, NULL);
				cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
				memset(&conn->user->cause, 0,
				    sizeof(conn->user->cause));
				cc_user_sig(conn->user,
				    USER_SIG_RELEASE_CONFIRM, NULL, 0);
				cc_disconnect_from_user(conn);
			} else {
				cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
				memset(&conn->user->cause, 0,
				    sizeof(conn->user->cause));
				cc_user_sig(conn->user,
				    USER_SIG_RELEASE_CONFIRM, NULL, 0);
				cc_disconnect_from_user(conn);
			}
			break;

		  case CONN_IN_WAIT_ACCEPT_OK:		/* C12 */
			if (user == NULL) {
				/* connection was aborted */
				if (state == UNI_CALLSTATE_U6 ||
				    state == UNI_CALLSTATE_U7 ||
				    state == UNI_CALLSTATE_U9 ||
				    state == UNI_CALLSTATE_U12) {
					do_release_response(conn, 0, NULL);
					cc_conn_set_state(conn,
					    CONN_AB_WAIT_RESP_OK);
				} else {
					cc_conn_set_state(conn,
					    CONN_AB_FLUSH_IND);
				}
				break;
			}
			cc_conn_reset_acceptor(conn);
			if (state == UNI_CALLSTATE_U6 ||
			    state == UNI_CALLSTATE_U9 ||
			    state == UNI_CALLSTATE_U7) {
				cc_user_sig(user, USER_SIG_ACCEPT_ERR,
				    NULL, ATM_MKUNIERR(reason));
				cc_conn_set_state(conn, CONN_IN_ARRIVED);
			} else if (state == UNI_CALLSTATE_U12) {
				do_release_response(conn, 0, NULL);
				cc_disconnect_from_user(conn);
				cc_user_sig(user, USER_SIG_ACCEPT_ERR,
				    user, ATMERR_PREVIOUSLY_ABORTED);
				cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
			} else {
				cc_disconnect_from_user(conn);
				cc_user_sig(user, USER_SIG_ACCEPT_ERR,
				    user, ATMERR_PREVIOUSLY_ABORTED);
				cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			}
			break;

		  case CONN_REJ_WAIT_OK:		/* C14 */
			if (user == NULL) {
				/* connection has been aborted. */
				if (state == UNI_CALLSTATE_U6 ||
				    state == UNI_CALLSTATE_U7 ||
				    state == UNI_CALLSTATE_U9 ||
				    state == UNI_CALLSTATE_U12) {
					/* do what we can */
					do_release_response(conn, 0, NULL);
					cc_conn_set_state(conn,
					    CONN_AB_WAIT_RESP_OK);
				} else {
					cc_conn_set_state(conn,
					    CONN_AB_FLUSH_IND);
				}
				break;
			}
			if (state == UNI_CALLSTATE_U6 ||
			    state == UNI_CALLSTATE_U9 ||
			    state == UNI_CALLSTATE_U7) {
				cc_user_sig(user, USER_SIG_REJECT_ERR,
				    NULL, ATM_MKUNIERR(reason));
				cc_conn_set_state(conn, CONN_IN_ARRIVED);
			} else {
				cc_disconnect_from_user(conn);
				cc_user_sig(user, USER_SIG_REJECT_OK, NULL, 0);
				cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
			}
			break;

		  case CONN_REL_IN_WAIT_OK:		/* C15 */
			if (user == NULL) {
				/* connection has been aborted. */
				if (state == UNI_CALLSTATE_U8) {
					/* do what we can */
					do_release_request(conn, conn->cause);
					cc_conn_set_state(conn,
					    CONN_AB_WAIT_REQ_OK);
				} else if (state == UNI_CALLSTATE_U12) {
					do_release_response(conn, 0, NULL);
					cc_conn_set_state(conn,
					    CONN_AB_WAIT_RESP_OK);
				} else {
					cc_conn_set_state(conn,
					    CONN_AB_FLUSH_IND);
				}
				break;
			}
			if (state == UNI_CALLSTATE_U8) {
				cc_conn_set_state(conn, CONN_IN_WAIT_COMPL);
				cc_user_sig(conn->user, USER_SIG_RELEASE_ERR,
				    NULL, reason);
			} else if (state == UNI_CALLSTATE_U12) {
				do_release_response(conn, 0, NULL);
				cc_conn_set_state(conn, CONN_AB_WAIT_RESP_OK);
				memset(&conn->user->cause, 0,
				    sizeof(conn->user->cause));
				cc_user_sig(conn->user,
				    USER_SIG_RELEASE_CONFIRM, NULL, 0);
				cc_disconnect_from_user(conn);
			} else {
				cc_conn_set_state(conn, CONN_AB_FLUSH_IND);
				memset(&conn->user->cause, 0,
				    sizeof(conn->user->cause));
				cc_user_sig(conn->user,
				    USER_SIG_RELEASE_CONFIRM, NULL, 0);
				cc_disconnect_from_user(conn);
			}
			break;

		  default:
			/* maybe it's for a party */
			LIST_FOREACH(party, &conn->parties, link) {
				switch (party->state) {

				  case PARTY_ADD_WAIT_CREATE:	/* P2 */
					cc_party_destroy(party);
					if (user != NULL)
						cc_user_sig(user,
						    USER_SIG_ADD_PARTY_ERR,
						    NULL, ATM_MKUNIERR(reason));
					goto ex_party_err;

				  case PARTY_ADD_WAIT_OK:	/* P3 */
					cc_party_set_state(party,
					    PARTY_WAIT_DESTROY);
					if (user != NULL)
						cc_user_sig(user,
						    USER_SIG_ADD_PARTY_ERR,
						    NULL, ATM_MKUNIERR(reason));
					goto ex_party_err;

				  case PARTY_DROP_WAIT_OK:	/* P5 */
					cc_party_set_state(party,
					    PARTY_ACTIVE);
					if (user != NULL)
						cc_user_sig(user,
						    USER_SIG_DROP_PARTY_ERR,
						    NULL, ATM_MKUNIERR(reason));
					goto ex_party_err;

				  case PARTY_WAIT_DROP_ACK_OK:	/* P9 */
					cc_party_set_state(party,
					    PARTY_ACTIVE);
					goto ex_party_err;

				  case PARTY_ADD_DROP_WAIT_OK:	/* P11 */
					cc_party_set_state(party,
					    PARTY_ADD_WAIT_ACK);
					if (user != NULL)
						cc_user_sig(user,
						    USER_SIG_DROP_PARTY_ERR,
						    NULL, ATM_MKUNIERR(reason));
					goto ex_party_err;

				  case PARTY_ADD_DROPACK_WAIT_OK:/* P12 */
					cc_party_set_state(party,
					    PARTY_ADD_WAIT_ACK);
					goto ex_party_err;

				  default:
					break;
				}
			}
			cc_conn_log(conn, "unexpected reason=%u ustate=%u "
			    "state=%s\n", reason, state, stab[conn->state]);
		  ex_party_err:
			break;
		}
		break;
	    }

	  case CONN_SIG_PARTY_CREATED:
	    {
		struct uni_msg *msg = arg;
		struct uniapi_party_created *pcr = uni_msg_rptr(msg,
		    struct uniapi_party_created *);

		party = cc_party_find(conn, pcr->epref.epref);
		if (party == NULL) {
			/* for incoming connections we see the party-created
			 * immediately after the call-create so that we
			 * must be in C10 */
			switch (conn->state) {

			  case CONN_IN_PREPARING:
				party = cc_party_create(conn,
				    pcr->epref.epref, 1);
				if (party == NULL)
					break;
				cc_party_set_state(party,
				    PARTY_WAIT_SETUP_COMPL);
				break;

			  case CONN_OUT_WAIT_OK:
				party = cc_party_create(conn,
				    pcr->epref.epref, 0);
				if (party == NULL)
					break;
				cc_party_set_state(party,
				    PARTY_WAIT_SETUP_CONF);
				break;

			  default:
				goto bad_state;
			}
			break;
		}
		/* this is for an ADD-PARTY */
		if (conn->state != CONN_ACTIVE)
			goto bad_state;
		if (party->state != PARTY_ADD_WAIT_CREATE)
			goto bad_party_state;
		cc_party_set_state(party, PARTY_ADD_WAIT_OK);
		break;
	    }

	  case CONN_SIG_PARTY_DESTROYED:
	    {
		struct uni_msg *msg = arg;
		struct uniapi_party_destroyed *pcr = uni_msg_rptr(msg,
		    struct uniapi_party_destroyed *);

		party = cc_party_find(conn, pcr->epref.epref);
		if (party == NULL) {
			cc_conn_log(conn, "no party to destroy %u/%u",
			    pcr->epref.flag, pcr->epref.epref);
			break;
		}
		cc_party_destroy(party);
		break;
	    }

	}

	return;

  bad_state:
	cc_conn_log(conn, "bad state=%s for signal=%s",
	    stab[conn->state], cc_conn_sigtab[sig]);
	return;

  bad_party_state:
	cc_conn_log(conn, "bad party state=%s for signal=%s",
	    ptab[party->state], cc_conn_sigtab[sig]);
	return;
}
