/*
 *
 * ===================================
 * HARP  |  Host ATM Research Platform
 * ===================================
 *
 *
 * This Host ATM Research Platform ("HARP") file (the "Software") is
 * made available by Network Computing Services, Inc. ("NetworkCS")
 * "AS IS".  NetworkCS does not provide maintenance, improvements or
 * support of any kind.
 *
 * NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
 * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
 * SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
 * In no event shall NetworkCS be responsible for any damages, including
 * but not limited to consequential damages, arising from or relating to
 * any use of the Software or related support.
 *
 * Copyright 1994-1998 Network Computing Services, Inc.
 *
 * Copies of this Software may be made, however, the above copyright
 * notice must be reproduced on all copies.
 *
 *	@(#) $FreeBSD: src/sys/netatm/spans/spans_subr.c,v 1.4 1999/08/28 00:48:52 peter Exp $
 *
 */

/*
 * SPANS Signalling Manager
 * ---------------------------
 *
 * SPANS-related subroutines.
 *
 */

#include <netatm/kern_include.h>

#include "spans_xdr.h"
#include <netatm/spans/spans_var.h>

#ifndef lint
__RCSID("@(#) $FreeBSD: src/sys/netatm/spans/spans_subr.c,v 1.4 1999/08/28 00:48:52 peter Exp $");
#endif


/*
 * Open a SPANS VCC
 *
 * Called when a user wants to open a VC.  This function will construct
 * a VCCB, create the stack requested by the user, and, if we are
 * opening an SVC, start the SPANS signalling message exchange.  The
 * user will have to wait for a notify event to be sure the SVC is fully
 * open.
 *
 * Must be called at splnet.
 *
 * Arguments:
 *	spp	pointer to SPANS protocol instance
 *	acp	pointer to PVC's connection parameters
 *
 * Returns:
 *	0	VCC creation successful
 *	errno	VCC setup failed - reason indicated
 *
 */
int
spans_open_vcc(spp, cvp)
	struct spans	*spp;
	Atm_connvc	*cvp;

{
	struct atm_pif		*pip = spp->sp_pif;
	struct spans_vccb	*svp;
	Atm_addr_pvc		*pvp;
	spans_aal		aal;
	int			err, pvc, vpi, vci;

	ATM_DEBUG2("spans_open_vcc: spp=%p, cvp=%p\n", spp, cvp);

	/*
	 * Validate user parameters. AAL and encapsulation are
	 * checked by the connection manager.
	 */

	/*
	 * Check called party address(es)
	 */
	if (cvp->cvc_attr.called.tag != T_ATM_PRESENT ||
			cvp->cvc_attr.called.addr.address_format ==
				T_ATM_ABSENT ||
			cvp->cvc_attr.called.subaddr.address_format !=
				T_ATM_ABSENT) {
		return(EINVAL);
	}
	switch (cvp->cvc_attr.called.addr.address_format) {
	case T_ATM_PVC_ADDR:
		/*
		 * Make sure VPI/VCI is valid
		 */
		pvc = 1;
		pvp = (Atm_addr_pvc *)cvp->cvc_attr.called.addr.address;
		vpi = ATM_PVC_GET_VPI(pvp);
		vci = ATM_PVC_GET_VCI(pvp);
		if ((vpi > pip->pif_maxvpi) ||
				(vci == 0) ||
				(vci > pip->pif_maxvci)) {
			return(ERANGE);
		}

		/*
		 * Make sure VPI/VCI is not already in use
		 */
		if (spans_find_vpvc(spp, vpi, vci, 0)) {
			return(EADDRINUSE);
		}
		ATM_DEBUG2("spans_open_vcc: VPI.VCI=%d.%d\n",
				vpi, vci);
		break;

	case T_ATM_SPANS_ADDR:
		pvc = 0;
		vpi = vci = 0;

		/*
		 * Check signalling state
		 */
		if (spp->sp_state != SPANS_ACTIVE) {
			return(ENETDOWN);
		}

		/*
		 *Check destination address length
		 */
		if (cvp->cvc_attr.called.addr.address_length !=
				sizeof(spans_addr)) {
			return(EINVAL);
		}
		break;

	default:
		return(EINVAL);
	}

	/*
	 * Check that this is for the same interface SPANS uses
         */
	if (!cvp->cvc_attr.nif ||
			cvp->cvc_attr.nif->nif_pif != spp->sp_pif) {
		return(EINVAL);
	}

	/*
	 * Check AAL
	 */
	if (!spans_get_spans_aal(cvp->cvc_attr.aal.type, &aal)) {
		return(EINVAL);
	}

#ifdef NOTDEF
	/*
	 * Check encapsulation
	 */
	/* XXX -- How do we check encapsulation? */
	if (cvp->ac_encaps != ATM_ENC_NULL) {
		return(EINVAL);
	}
#endif

	/*
	 * Allocate control block for VCC
	 */
	svp = (struct spans_vccb *)atm_allocate(&spans_vcpool);
	if (svp == NULL) {
		return(ENOMEM);
	}

	/*
	 * Fill in VCCB
	 */
	if (pvc) {
		svp->sv_type = VCC_PVC | VCC_IN | VCC_OUT;
		svp->sv_vpi = vpi;
		svp->sv_vci = vci;
		svp->sv_sstate = (spp->sp_state == SPANS_ACTIVE ?
				SPANS_VC_ACTIVE : SPANS_VC_ACT_DOWN);
		svp->sv_ustate = VCCU_OPEN;
	} else {
		svp->sv_type = VCC_SVC | VCC_OUT;
		spans_addr_copy(cvp->cvc_attr.called.addr.address,
				&svp->sv_conn.con_dst);
		spans_addr_copy(spp->sp_addr.address,
				&svp->sv_conn.con_src);
		svp->sv_conn.con_dsap = SPANS_SAP_IP;
		svp->sv_conn.con_ssap = spans_ephemeral_sap(spp);
		svp->sv_sstate = SPANS_VC_POPEN;
		svp->sv_ustate = VCCU_POPEN;
	}
	svp->sv_proto = ATM_SIG_SPANS;
	svp->sv_pif = spp->sp_pif;
	svp->sv_nif = cvp->cvc_attr.nif;
	svp->sv_connvc = cvp;
	svp->sv_spans_aal = aal;
	svp->sv_tstamp = time_second;

	/*
	 * Put VCCB on SPANS queue
	 */
	ENQUEUE(svp, struct spans_vccb, sv_sigelem, spp->sp_vccq);

	/*
	 * Link VCCB to VCC connection block
	 */
	cvp->cvc_vcc = (struct vccb *) svp;

	/*
	 * Start the SPANS message exchange if this is an SVC
	 */
	if (!pvc) {
		svp->sv_retry = 0;
		svp->sv_spans_qos.rsc_peak = 1;
		svp->sv_spans_qos.rsc_mean = 1;
		svp->sv_spans_qos.rsc_burst = 1;
		err = spans_send_open_req(spp, svp);
		if (err) {
			/*
			 * On error, delete the VCCB
			 */
			DEQUEUE(svp, struct spans_vccb, sv_sigelem,
					spp->sp_vccq);
			cvp->cvc_vcc = (struct vccb *)0;
			atm_free((caddr_t)svp);
			return(err);
		} else {
			/*
			 * VCCB is opening--set the retransmit timer
			 */
			SPANS_VC_TIMER((struct vccb *) svp, SV_TIMEOUT);
		}
	}

	return(0);
}


/*
 * Close a SPANS VCC
 *
 * Called when a user wants to close a VCC.  This function will clean
 * up the VCCB and, for an SVC, send a close request.
 *
 * Must be called at splnet.
 *
 * Arguments:
 *	spp	pointer to SPANS protocol instance
 *	svp	pointer to VCCB for the VCC to be closed
 *
 * Returns:
 *	0	VCC is now closed
 *	errno	error encountered
 */
int
spans_close_vcc(spp, svp, force)
	struct spans		*spp;
	struct spans_vccb	*svp;
	int			force;

{
	int		err = 0;

	ATM_DEBUG2("spans_close_vcc: svp=%p, state=%d\n", svp,
			svp->sv_sstate);

	/*
	 * Check that this is for the same interface SPANS uses
         */
	if (svp->sv_pif != spp->sp_pif) {
		return (EINVAL);
	}

	/*
	 * Kill any possible timer
	 */
	SPANS_VC_CANCEL((struct vccb *) svp);

	/*
	 * Mark the close time.
	 */
	svp->sv_tstamp = time_second;

	/*
	 * Process based on the connection type
	 */
	if (svp->sv_type & VCC_PVC) {
		svp->sv_sstate = SPANS_VC_FREE;
		svp->sv_ustate = VCCU_CLOSED;
	} else if (svp->sv_type & VCC_SVC) {
		/*
		 * Update VCCB states
		 */
		svp->sv_ustate = VCCU_CLOSED;

		/*
		 * Send the appropriate SPANS close message
		 */
		switch (svp->sv_sstate) {
		case SPANS_VC_R_POPEN:
			err = spans_send_open_rsp(spp, svp, SPANS_FAIL);
			svp->sv_sstate = SPANS_VC_FREE;
			break;
		case SPANS_VC_OPEN:
		case SPANS_VC_POPEN:
		case SPANS_VC_ABORT:
			svp->sv_retry = 0;
			err = spans_send_close_req(spp, svp);
			if (force) {
				svp->sv_sstate = SPANS_VC_FREE;
			} else {
				svp->sv_sstate = SPANS_VC_CLOSE;
				SPANS_VC_TIMER((struct vccb *) svp,
						SV_TIMEOUT);
			}
			break;
		case SPANS_VC_CLOSE:
			if (force) {
				svp->sv_sstate = SPANS_VC_FREE;
			}
			break;
		}
	}

	/*
	 * Wait for user to free resources
	 */
	return(err);
}


/*
 * Clear a SPANS VCC
 *
 * Called when the signalling manager wants to close a VCC immediately.
 * This function will clean up the VCCB and notify the owner.
 *
 * Must be called at splnet.
 *
 * Arguments:
 *	spp	pointer to SPANS protocol instance
 *	svp	pointer to VCCB for the VCC to be closed
 *
 * Returns:
 *	0	VCC is now closed
 *	errno	error encountered
 */
int
spans_clear_vcc(spp, svp)
	struct spans		*spp;
	struct spans_vccb	*svp;

{
	u_char	outstate;

	ATM_DEBUG2("spans_clear_vcc: svp=%p, state=%d\n", svp,
			svp->sv_sstate);

	/*
	 * Check that this is for the same interface SPANS uses
         */
	if (svp->sv_pif != spp->sp_pif) {
		return (EINVAL);
	}

	/*
	 * Kill any possible timer
	 */
	SPANS_VC_CANCEL((struct vccb *) svp);

	/*
	 * Mark the close time
	 */
	svp->sv_tstamp = time_second;

	/*
	 * Mark the VCCB closed
	 */
	outstate = svp->sv_sstate;
	svp->sv_sstate = SPANS_VC_FREE;
	svp->sv_ustate = VCCU_CLOSED;

	/*
	 * Notify the user if old state indicates.
	 */
	switch (outstate) {
	case SPANS_VC_ACTIVE:
	case SPANS_VC_ACT_DOWN:
	case SPANS_VC_POPEN:
	case SPANS_VC_OPEN:
	case SPANS_VC_CLOSE:
	case SPANS_VC_ABORT:
		/* XXX -- set cause */
		atm_cm_cleared(svp->sv_connvc);
		break;
	case SPANS_VC_NULL:
	case SPANS_VC_R_POPEN:
	case SPANS_VC_FREE:
		break;
	}

	/*
	 * Wait for user to free resources
	 */
	return(0);
}


/*
 * Reset the switch state
 *
 * Called when the switch or host at the far end of the ATM link has
 * gone away.  This can be deteched either by a number of SPANS_STAT_REQ
 * messages going unanswered or by the host epoch changing in a SPANS
 * SPANS_STAT_IND or SPANS_STAT_REQ message.
 *
 * Arguments:
 *	spp	pointer to SPANS protocol instance
 *
 * Returns:
 *	none
 *
 */
void
spans_switch_reset(spp, cause)
	struct spans	*spp;
	int		cause;

{
	int		s;
	struct vccb	*vcp, *vnext;

	ATM_DEBUG2("spans_switch_reset: spp=%p, cause=%d\n",
			spp, cause);

	/*
	 * Log the event
	 */
	log(LOG_INFO, "spans: signalling %s on interface %s%d\n",
			(cause == SPANS_UNI_DOWN ? "down" : "up"),
			spp->sp_pif->pif_name,
			spp->sp_pif->pif_unit);

	/*
	 * Terminate all of our VCCs
	 */
	s = splnet();
	for (vcp = Q_HEAD(spp->sp_vccq, struct vccb); vcp;
			vcp = vnext) {

		u_char  outstate;

		vnext = Q_NEXT(vcp, struct vccb, vc_sigelem);

		if (vcp->vc_type & VCC_SVC) {
			/*
			 * Close the SVC and notify the owner
			 */
			outstate = vcp->vc_sstate;
			SPANS_VC_CANCEL((struct vccb *) vcp);
			vcp->vc_ustate = VCCU_CLOSED;
			vcp->vc_sstate = SPANS_VC_FREE;
			if (outstate == SPANS_VC_OPEN ||
					outstate == SPANS_VC_POPEN) {
				/* XXX -- set cause */
				atm_cm_cleared(vcp->vc_connvc);
			}
		} else if (vcp->vc_type & VCC_PVC) {
			/*
			 * Note new state
			 */
			switch(cause) {
			case SPANS_UNI_DOWN:
				vcp->vc_sstate = SPANS_VC_ACT_DOWN;
				break;
			case SPANS_UNI_UP:
				vcp->vc_sstate = SPANS_VC_ACTIVE;
				break;
			}
		} else {
			log(LOG_ERR, "spans: invalid VCC type: vccb=%p, type=%d\n",
					vcp, vcp->vc_type);
		}
	}
	(void) splx(s);
}
