/*
 *
 * ===================================
 * 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.
 *
 *	@(#) $Id: unisig_subr.c,v 1.4 1998/10/31 20:08:03 phk Exp $
 *
 */

/*
 * ATM Forum UNI 3.0/3.1 Signalling Manager
 * ----------------------------------------
 *
 * Subroutines
 *
 */

#include <netatm/kern_include.h>

#include <netatm/uni/unisig.h>
#include <netatm/uni/unisig_var.h>
#include <netatm/uni/unisig_msg.h>

#ifndef lint
__RCSID("@(#) $Id: unisig_subr.c,v 1.4 1998/10/31 20:08:03 phk Exp $");
#endif


/*
 * External variables
 */
extern struct ie_aalp  ie_aalp_absent;
extern struct ie_clrt  ie_clrt_absent;
extern struct ie_bbcp  ie_bbcp_absent;
extern struct ie_bhli  ie_bhli_absent;
extern struct ie_blli  ie_blli_absent;
extern struct ie_clst  ie_clst_absent;
extern struct ie_cdad  ie_cdad_absent;
extern struct ie_cdsa  ie_cdsa_absent;
extern struct ie_cgad  ie_cgad_absent;
extern struct ie_cgsa  ie_cgsa_absent;
extern struct ie_caus  ie_caus_absent;
extern struct ie_cnid  ie_cnid_absent;
extern struct ie_qosp  ie_qosp_absent;
extern struct ie_brpi  ie_brpi_absent;
extern struct ie_rsti  ie_rsti_absent;
extern struct ie_blsh  ie_blsh_absent;
extern struct ie_bnsh  ie_bnsh_absent;
extern struct ie_bsdc  ie_bsdc_absent;
extern struct ie_trnt  ie_trnt_absent;
extern struct ie_eprf  ie_eprf_absent;
extern struct ie_epst  ie_epst_absent;


/*
 * Set a cause code in an ATM attribute block
 *
 * Arguments:
 *	aap	pointer to attribute block
 *	cause	cause code
 *
 * Returns:
 *	none
 *
 */
void
unisig_set_cause_attr(aap, cause)
	Atm_attributes	*aap;
	int		cause;
{
	/*
	 * Set the fields in the attribute block
	 */
	aap->cause.tag = T_ATM_PRESENT;
	aap->cause.v.coding_standard = T_ATM_ITU_CODING;
	aap->cause.v.location = T_ATM_LOC_USER;
	aap->cause.v.cause_value = cause;
	KM_ZERO(aap->cause.v.diagnostics,
			sizeof(aap->cause.v.diagnostics));
}


/*
 * Open a UNI VCC
 *
 * Called when a user wants to open a VC.  This function will construct
 * a VCCB and, if we are opening an SVC, call the Q.2931 VC state
 * machine.  The user will have to wait for a notify event to be sure
 * the SVC is fully open.
 *
 * Must be called at splnet.
 *
 * Arguments:
 *	usp	pointer to UNISIG protocol instance
 *	cvp	pointer to connection parameters for the VCC
 *
 * Returns:
 *	0	VCC creation successful
 *	errno	VCC setup failed - reason indicated
 *
 */
int
unisig_open_vcc(usp, cvp)
	struct unisig	*usp;
	Atm_connvc	*cvp;
{
	struct atm_pif		*pip = usp->us_pif;
	struct unisig_vccb	*uvp;
	Atm_addr_pvc		*pvp;
	int			err, pvc;

	ATM_DEBUG2("unisig_open_vcc: usp=%p, cvp=%p\n", usp, 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) {
		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;
		if ((ATM_PVC_GET_VPI(pvp) > pip->pif_maxvpi) ||
				(ATM_PVC_GET_VCI(pvp) == 0) ||
				(ATM_PVC_GET_VCI(pvp) > pip->pif_maxvci)) {
			return(ERANGE);
		}

		/*
		 * Make sure VPI/VCI is not already in use
		 */
		if (unisig_find_vpvc(usp,
				ATM_PVC_GET_VPI(pvp),
				ATM_PVC_GET_VCI(pvp), 0)) {
			return(EEXIST);
		}
		ATM_DEBUG2("unisig_open_vcc: VPI.VCI=%d.%d\n",
				ATM_PVC_GET_VPI(pvp),
				ATM_PVC_GET_VCI(pvp));
		break;

	case T_ATM_ENDSYS_ADDR:
		/*
		 * Check signalling state
		 */
		pvc = 0;
		pvp = NULL;
		if (usp->us_state != UNISIG_ACTIVE) {
			return(ENETDOWN);
		}

		/*
		 * Make sure there's no subaddress
		 */
		if (cvp->cvc_attr.called.subaddr.address_format !=
				T_ATM_ABSENT) {
			return(EINVAL);
		}
		break;

	case T_ATM_E164_ADDR:
		/*
		 * Check signalling state
		 */
		pvc = 0;
		pvp = NULL;
		if (usp->us_state != UNISIG_ACTIVE) {
			return(ENETDOWN);
		}

		/*
		 * Check destination address format
		 */
		if (cvp->cvc_attr.called.subaddr.address_format !=
					T_ATM_ENDSYS_ADDR &&
				cvp->cvc_attr.called.subaddr.address_format !=
					T_ATM_ABSENT) {
			return(EINVAL);
		}
		break;

	default:
		return(EPROTONOSUPPORT);
	}

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

	/*
	 * Allocate control block for VCC
	 */
	uvp = (struct unisig_vccb *)atm_allocate(&unisig_vcpool);
	if (uvp == NULL) {
		return(ENOMEM);
	}

	/*
	 * Fill in VCCB
	 */
	if (pvc) {
		uvp->uv_type = VCC_PVC | VCC_IN | VCC_OUT;
		uvp->uv_vpi = ATM_PVC_GET_VPI(pvp);
		uvp->uv_vci = ATM_PVC_GET_VCI(pvp);
		uvp->uv_sstate = (usp->us_state == UNISIG_ACTIVE ?
				UNI_PVC_ACTIVE : UNI_PVC_ACT_DOWN);
		uvp->uv_ustate = VCCU_OPEN;
	} else {
		uvp->uv_type = VCC_SVC | VCC_IN | VCC_OUT;
		uvp->uv_sstate = UNI_NULL;
		uvp->uv_ustate = VCCU_POPEN;
	}
	uvp->uv_proto = usp->us_pif->pif_sigmgr->sm_proto;
	uvp->uv_pif = usp->us_pif;
	uvp->uv_nif = cvp->cvc_attr.nif;
	uvp->uv_connvc = cvp;
	uvp->uv_tstamp = time_second;

	/*
	 * Put VCCB on UNISIG queue
	 */
	ENQUEUE(uvp, struct unisig_vccb, uv_sigelem, usp->us_vccq);

	/*
	 * Call the VC state machine if this is an SVC
	 */
	if (!pvc) {
		err = unisig_vc_state(usp, uvp, UNI_VC_SETUP_CALL,
				(struct unisig_msg *) 0);
		if (err) {
			/*
			 * On error, delete the VCCB
			 */
			DEQUEUE(uvp, struct unisig_vccb, uv_sigelem,
					usp->us_vccq);
			atm_free((caddr_t)uvp);
			return(err);
		}
	}

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

	return(0);
}


/*
 * Close a UNISIG 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:
 *	usp	pointer to UNISIG protocol instance
 *	uvp	pointer to VCCB for the VCC to be closed
 *
 * Returns:
 *	0	VCC is now closed
 *	errno	error encountered
 */
int
unisig_close_vcc(usp, uvp)
	struct unisig		*usp;
	struct unisig_vccb	*uvp;
{
	int		err = 0;

	ATM_DEBUG2("unisig_close_vcc: uvp=%p, state=%d\n", uvp,
			uvp->uv_sstate);

	/*
	 * Check that this is for the same interface UNISIG uses
	 */
	if (uvp->uv_pif != usp->us_pif) {
		return (EINVAL);
	}

	/*
	 * Mark the close time.
	 */
	uvp->uv_tstamp = time_second;

	/*
	 * Process based on the connection type
	 */
	if (uvp->uv_type & VCC_PVC) {
		uvp->uv_sstate = UNI_FREE;
		uvp->uv_ustate = VCCU_CLOSED;
	} else if (uvp->uv_type & VCC_SVC) {
		/*
		 * Call the VC state machine
		 */
		uvp->uv_ustate = VCCU_CLOSED;
		err = unisig_vc_state(usp, uvp, UNI_VC_RELEASE_CALL,
				(struct unisig_msg *) 0);
	}

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


/*
 * Clear a UNISIG VCC
 *
 * Called to internally clear a VCC.  No external protocol is
 * initiated, the VCC is just closed and the owner is notified.
 *
 * Must be called at splnet.
 *
 * Arguments:
 *	usp	pointer to UNISIG protocol instance
 *	uvp	pointer to VCCB for the VCC to be closed
 *	cause	cause code giving the reason for the close
 *
 * Returns:
 *	0	VCC is closed
 *	errno	error encountered
 */
int
unisig_clear_vcc(usp, uvp, cause)
	struct unisig		*usp;
	struct unisig_vccb	*uvp;
	int			cause;
{
	u_char	outstate;

	ATM_DEBUG3("unisig_clear_vcc: uvp=%p, state=%d, cause=%d\n",
			uvp, uvp->uv_sstate, cause);

	/*
	 * Check that this is for the same interface UNISIG uses
	 */
	if (uvp->uv_pif != usp->us_pif) {
		return (EINVAL);
	}

	/*
	 * Kill any possible timer
	 */
	UNISIG_VC_CANCEL((struct vccb *) uvp);

	/*
	 * Mark the close time.
	 */
	uvp->uv_tstamp = time_second;

	/*
	 * Close the VCC and notify the user
	 */
	outstate = uvp->uv_sstate;
	uvp->uv_sstate = UNI_FREE;
	uvp->uv_ustate = VCCU_CLOSED;
	if (outstate == UNI_ACTIVE ||
			outstate == UNI_CALL_INITIATED ||
			outstate == UNI_CALL_OUT_PROC ||
			outstate == UNI_CONNECT_REQUEST ||
			outstate == UNI_RELEASE_REQUEST ||
			outstate == UNI_RELEASE_IND ||
			outstate == UNI_SSCF_RECOV ||
			outstate == UNI_PVC_ACT_DOWN ||
			outstate == UNI_PVC_ACTIVE) {
		unisig_set_cause_attr(&uvp->uv_connvc->cvc_attr, cause);
		atm_cm_cleared(uvp->uv_connvc);
	}

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


#ifdef NOTDEF
/*
 * Reset the switch state
 *
 * Arguments:
 *	usp	pointer to UNISIG protocol instance
 *
 * Returns:
 *	none
 *
 */
void
unisig_switch_reset(usp, cause)
	struct unisig	*usp;
	int		cause;
{
	int			s;
	struct unisig_vccb	*uvp, *vnext;

	ATM_DEBUG2("unisig_switch_reset: usp=%p, cause=%d\n",
			usp, cause);

	/*
	 * Terminate all of our VCCs
	 */
	s = splnet();
	for (uvp = Q_HEAD(usp->us_vccq, struct unisig_vccb); uvp;
			uvp = vnext) {
		vnext = Q_NEXT(uvp, struct unisig_vccb, uv_sigelem);

		if (uvp->uv_type & VCC_SVC) {
			/*
			 * Close the SVC and notify the owner
			 */
			(void)unisig_clear_vcc(usp, uvp,
					T_ATM_CAUSE_NORMAL_CALL_CLEARING);
		} else if (uvp->uv_type & VCC_PVC) {
			/*
			 * Notify PVC owner of the state change
			 */
			switch(cause) {
			case UNI_DOWN:
				uvp->uv_sstate = UNI_PVC_ACT_DOWN;
				break;
			case UNI_UP:
				uvp->uv_sstate = UNI_PVC_ACTIVE;
				break;
			}
			atm_cm_cleared(uvp->uv_connvc, cause);
		} else {
			log(LOG_ERR, "unisig: invalid VCC type: vccb=%p, type=%d\n",
					uvp, uvp->uv_type);
		}
	}
	(void) splx(s);
}
#endif


/*
 * Copy connection parameters from UNI 3.0 message IEs into
 * an attribute block
 *
 * Arguments:
 *	usp	pointer to UNISIG protocol instance
 *	msg	pointer to the SETUP message
 *	ap	pointer to the attribute block
 *
 * Returns:
 *	none
 *
 */
void
unisig_save_attrs(usp, msg, ap)
	struct unisig		*usp;
	struct unisig_msg	*msg;
	Atm_attributes		*ap;
{
	/*
	 * Sanity check
	 */
	if (!msg || !ap)
		return;

	/*
	 * Save the AAL parameters (AAL 3/4 and AAL 5 only)
	 */
	if (msg->msg_ie_aalp) {
		struct ie_generic	*aalp = msg->msg_ie_aalp;

		switch(msg->msg_ie_aalp->ie_aalp_aal_type) {
		case UNI_IE_AALP_AT_AAL3:
			ap->aal.tag = T_ATM_PRESENT;
			ap->aal.type =
				msg->msg_ie_aalp->ie_aalp_aal_type;
			ap->aal.v.aal4.forward_max_SDU_size =
				msg->msg_ie_aalp->ie_aalp_4_fwd_max_sdu;
			ap->aal.v.aal4.backward_max_SDU_size =
				msg->msg_ie_aalp->ie_aalp_4_bkwd_max_sdu;
			ap->aal.v.aal4.SSCS_type =
				msg->msg_ie_aalp->ie_aalp_4_sscs_type;
			if (aalp->ie_aalp_4_mid_range == T_ATM_ABSENT) {
				ap->aal.v.aal4.mid_low = T_ATM_ABSENT;
				ap->aal.v.aal4.mid_high = T_ATM_ABSENT;
			} else {
				if (usp->us_proto == ATM_SIG_UNI30) {
					ap->aal.v.aal4.mid_low = 0;
					ap->aal.v.aal4.mid_high =
						aalp->ie_aalp_4_mid_range
							& UNI_IE_AALP_A3_R_MASK;
				} else {
					ap->aal.v.aal4.mid_low =
						(aalp->ie_aalp_4_mid_range >>
							UNI_IE_AALP_A3_R_SHIFT)
							& UNI_IE_AALP_A3_R_MASK;
					ap->aal.v.aal4.mid_high =
						aalp->ie_aalp_4_mid_range
							& UNI_IE_AALP_A3_R_MASK;
				}
			}
			break;
		case UNI_IE_AALP_AT_AAL5:
			ap->aal.tag = T_ATM_PRESENT;
			ap->aal.type =
				msg->msg_ie_aalp->ie_aalp_aal_type;
			ap->aal.v.aal5.forward_max_SDU_size =
				msg->msg_ie_aalp->ie_aalp_5_fwd_max_sdu;
			ap->aal.v.aal5.backward_max_SDU_size =
				msg->msg_ie_aalp->ie_aalp_5_bkwd_max_sdu;
			ap->aal.v.aal5.SSCS_type =
				msg->msg_ie_aalp->ie_aalp_5_sscs_type;
			break;
		}
	}

	/*
	 * Save traffic descriptor attributes
	 */
	if (msg->msg_ie_clrt) {
		ap->traffic.tag = T_ATM_PRESENT;
		ap->traffic.v.forward.PCR_high_priority =
				msg->msg_ie_clrt->ie_clrt_fwd_peak;
		ap->traffic.v.forward.PCR_all_traffic =
				msg->msg_ie_clrt->ie_clrt_fwd_peak_01;
		ap->traffic.v.forward.SCR_high_priority =
				msg->msg_ie_clrt->ie_clrt_fwd_sust;
		ap->traffic.v.forward.SCR_all_traffic =
				msg->msg_ie_clrt->ie_clrt_fwd_sust_01;
		ap->traffic.v.forward.MBS_high_priority =
				msg->msg_ie_clrt->ie_clrt_fwd_burst;
		ap->traffic.v.forward.MBS_all_traffic =
				msg->msg_ie_clrt->ie_clrt_fwd_burst_01;
		ap->traffic.v.backward.PCR_high_priority =
				msg->msg_ie_clrt->ie_clrt_bkwd_peak;
		ap->traffic.v.backward.PCR_all_traffic =
				msg->msg_ie_clrt->ie_clrt_bkwd_peak_01;
		ap->traffic.v.backward.SCR_high_priority =
				msg->msg_ie_clrt->ie_clrt_bkwd_sust;
		ap->traffic.v.backward.SCR_all_traffic =
				msg->msg_ie_clrt->ie_clrt_bkwd_sust_01;
		ap->traffic.v.backward.MBS_high_priority =
				msg->msg_ie_clrt->ie_clrt_bkwd_burst;
		ap->traffic.v.backward.MBS_all_traffic =
				msg->msg_ie_clrt->ie_clrt_bkwd_burst_01;
		ap->traffic.v.best_effort =
				msg->msg_ie_clrt->ie_clrt_best_effort;
		if (msg->msg_ie_clrt->ie_clrt_tm_options ==
				T_ATM_ABSENT) {
			ap->traffic.v.forward.tagging = T_NO;
			ap->traffic.v.backward.tagging = T_NO;
		} else {
			ap->traffic.v.forward.tagging =
					(msg->msg_ie_clrt->ie_clrt_tm_options &
					UNI_IE_CLRT_TM_FWD_TAG) != 0;
			ap->traffic.v.backward.tagging =
					(msg->msg_ie_clrt->ie_clrt_tm_options &
					UNI_IE_CLRT_TM_BKWD_TAG) != 0;
		}
	}

	/*
	 * Save broadband bearer attributes
	 */
	if (msg->msg_ie_bbcp) {
		ap->bearer.tag = T_ATM_PRESENT;
		ap->bearer.v.bearer_class =
				msg->msg_ie_bbcp->ie_bbcp_bearer_class;
		ap->bearer.v.traffic_type =
				msg->msg_ie_bbcp->ie_bbcp_traffic_type;
		ap->bearer.v.timing_requirements =
				msg->msg_ie_bbcp->ie_bbcp_timing_req;
		ap->bearer.v.clipping_susceptibility =
				msg->msg_ie_bbcp->ie_bbcp_clipping;
		ap->bearer.v.connection_configuration =
				msg->msg_ie_bbcp->ie_bbcp_conn_config;
	}

	/*
	 * Save broadband high layer attributes
	 */
	if (msg->msg_ie_bhli) {
		ap->bhli.tag = T_ATM_PRESENT;
		ap->bhli.v.ID_type = msg->msg_ie_bhli->ie_bhli_type;
		switch(ap->bhli.v.ID_type) {
		case T_ATM_ISO_APP_ID:
			KM_COPY(msg->msg_ie_bhli->ie_bhli_info,
					ap->bhli.v.ID.ISO_ID,
					sizeof(ap->bhli.v.ID.ISO_ID));
			break;
		case T_ATM_USER_APP_ID:
			KM_COPY(msg->msg_ie_bhli->ie_bhli_info,
					ap->bhli.v.ID.user_defined_ID,
					sizeof(ap->bhli.v.ID.user_defined_ID));
			break;
		case T_ATM_VENDOR_APP_ID:
			KM_COPY(msg->msg_ie_bhli->ie_bhli_info,
					ap->bhli.v.ID.vendor_ID.OUI,
					sizeof(ap->bhli.v.ID.vendor_ID.OUI));
			KM_COPY(&msg->msg_ie_bhli->ie_bhli_info[sizeof(ap->bhli.v.ID.vendor_ID.OUI)-1],
					ap->bhli.v.ID.vendor_ID.app_ID,
					sizeof(ap->bhli.v.ID.vendor_ID.app_ID));
			break;
		}
	}

	/*
	 * Save Broadband low layer, user layer 2 and 3 attributes
	 */
	if (msg->msg_ie_blli) {
		/*
		 * Layer 2 parameters
		 */
		switch(msg->msg_ie_blli->ie_blli_l2_id) {
		case UNI_IE_BLLI_L2P_ISO1745:
		case UNI_IE_BLLI_L2P_Q921:
		case UNI_IE_BLLI_L2P_X25L:
		case UNI_IE_BLLI_L2P_X25M:
		case UNI_IE_BLLI_L2P_LAPB:
		case UNI_IE_BLLI_L2P_HDLC1:
		case UNI_IE_BLLI_L2P_HDLC2:
		case UNI_IE_BLLI_L2P_HDLC3:
		case UNI_IE_BLLI_L2P_LLC:
		case UNI_IE_BLLI_L2P_X75:
		case UNI_IE_BLLI_L2P_Q922:
		case UNI_IE_BLLI_L2P_ISO7776:
			ap->blli.tag_l2 = T_ATM_PRESENT;
			ap->blli.v.layer_2_protocol.ID_type =
					T_ATM_SIMPLE_ID;
			ap->blli.v.layer_2_protocol.ID.simple_ID =
					msg->msg_ie_blli->ie_blli_l2_id;
			break;
		case UNI_IE_BLLI_L2P_USER:
			ap->blli.tag_l2 = T_ATM_PRESENT;
			ap->blli.v.layer_2_protocol.ID_type =
					T_ATM_USER_ID;
			ap->blli.v.layer_2_protocol.ID.user_defined_ID =
					msg->msg_ie_blli->ie_blli_l2_user_proto;
			break;
		default:
			ap->blli.tag_l2 = T_ATM_ABSENT;
		}
		if (ap->blli.tag_l2 == T_ATM_PRESENT) {
			ap->blli.v.layer_2_protocol.mode =
					msg->msg_ie_blli->ie_blli_l2_mode;
			ap->blli.v.layer_2_protocol.window_size =
					msg->msg_ie_blli->ie_blli_l2_window;
		}

		/*
		 * Layer 3 parameters
		 */
		switch(msg->msg_ie_blli->ie_blli_l3_id) {
		case UNI_IE_BLLI_L3P_X25:
		case UNI_IE_BLLI_L3P_ISO8208:
		case UNI_IE_BLLI_L3P_ISO8878:
		case UNI_IE_BLLI_L3P_ISO8473:
		case UNI_IE_BLLI_L3P_T70:
			ap->blli.tag_l3 = T_ATM_PRESENT;
			ap->blli.v.layer_3_protocol.ID_type =
					T_ATM_SIMPLE_ID;
			ap->blli.v.layer_3_protocol.ID.simple_ID =
					msg->msg_ie_blli->ie_blli_l3_id;
			break;
		case UNI_IE_BLLI_L3P_ISO9577:
			ap->blli.tag_l3 = T_ATM_PRESENT;
			ap->blli.v.layer_3_protocol.ID_type =
					T_ATM_SIMPLE_ID;
			ap->blli.v.layer_3_protocol.ID.simple_ID =
					msg->msg_ie_blli->ie_blli_l3_id;
			if (msg->msg_ie_blli->ie_blli_l3_ipi ==
					UNI_IE_BLLI_L3IPI_SNAP) {
				KM_COPY(msg->msg_ie_blli->ie_blli_l3_oui,
						ap->blli.v.layer_3_protocol.ID.SNAP_ID.OUI,
						sizeof(ap->blli.v.layer_3_protocol.ID.SNAP_ID.OUI));
				KM_COPY(msg->msg_ie_blli->ie_blli_l3_pid,
						ap->blli.v.layer_3_protocol.ID.SNAP_ID.PID,
						sizeof(ap->blli.v.layer_3_protocol.ID.SNAP_ID.PID));
			} else {
				ap->blli.v.layer_3_protocol.ID.IPI_ID =
						msg->msg_ie_blli->ie_blli_l3_ipi;
			}
			break;
		case UNI_IE_BLLI_L3P_USER:
			ap->blli.tag_l3 = T_ATM_PRESENT;
			ap->blli.v.layer_3_protocol.ID_type =
					T_ATM_USER_ID;
			ap->blli.v.layer_3_protocol.ID.user_defined_ID =
					msg->msg_ie_blli->ie_blli_l3_user_proto;
			break;
		default:
			ap->blli.tag_l3 = T_ATM_ABSENT;
		}
		if (ap->blli.tag_l3 == T_ATM_PRESENT) {
			ap->blli.v.layer_3_protocol.mode =
					msg->msg_ie_blli->ie_blli_l3_mode;
			ap->blli.v.layer_3_protocol.packet_size =
					msg->msg_ie_blli->ie_blli_l3_packet_size;
			ap->blli.v.layer_3_protocol.window_size =
					msg->msg_ie_blli->ie_blli_l3_window;
		}
	}

	/*
	 * Save the called party address and subaddress
	 */
	if (msg->msg_ie_cdad) {
		ap->called.tag = T_ATM_PRESENT;
		ATM_ADDR_COPY(&msg->msg_ie_cdad->ie_cdad_addr,
				&ap->called.addr);
		ap->called.subaddr.address_format = T_ATM_ABSENT;
		ap->called.subaddr.address_length = 0;
	}
	if (msg->msg_ie_cdsa) {
		ATM_ADDR_COPY(&msg->msg_ie_cdsa->ie_cdsa_addr,
				&ap->called.subaddr);
	}

	/*
	 * Save the calling party address and subaddress
	 */
	if (msg->msg_ie_cgad) {
		ap->calling.tag = T_ATM_PRESENT;
		ATM_ADDR_COPY(&msg->msg_ie_cgad->ie_cgad_addr,
				&ap->calling.addr);
		ap->calling.subaddr.address_format = T_ATM_ABSENT;
		ap->calling.subaddr.address_length = 0;
	}

	if (msg->msg_ie_cgsa) {
		ATM_ADDR_COPY(&msg->msg_ie_cgsa->ie_cgsa_addr,
				&ap->calling.subaddr);
	}

	/*
	 * Save quality of service attributes
	 */
	if (msg->msg_ie_qosp) {
		ap->qos.tag = T_ATM_PRESENT;
		ap->qos.v.coding_standard = msg->msg_ie_qosp->ie_coding;
		ap->qos.v.forward.qos_class = msg->msg_ie_qosp->ie_qosp_fwd_class;
		ap->qos.v.forward.qos_class =
				msg->msg_ie_qosp->ie_qosp_bkwd_class;
	}

	/*
	 * Save transit network attributes
	 */
	if (msg->msg_ie_trnt) {
		ap->transit.tag = T_ATM_PRESENT;
		ap->transit.v.length = 
				MIN(msg->msg_ie_trnt->ie_trnt_id_len,
				sizeof(ap->transit.v.network_id));
		KM_COPY(msg->msg_ie_trnt->ie_trnt_id,
				ap->transit.v.network_id,
				ap->transit.v.length);
	}

	/*
	 * Save cause code
	 */
	if (msg->msg_ie_caus) {
		ap->cause.tag = T_ATM_PRESENT;
		ap->cause.v.coding_standard =
				msg->msg_ie_caus->ie_coding;
		ap->cause.v.location = 
				msg->msg_ie_caus->ie_caus_loc;
		ap->cause.v.cause_value = 
				msg->msg_ie_caus->ie_caus_cause;
		KM_ZERO(ap->cause.v.diagnostics,
				sizeof(ap->cause.v.diagnostics));
#ifdef NOTDEF
		KM_COPY(msg->msg_ie_caus->ie_caus_diagnostic,
				ap->transit.v.diagnostics,
				MIN(sizeof(ap->transit.v.diagnostics),
				msg->msg_ie_caus->ie_caus_diag_len));
#endif
	}
}


/*
 * Copy connection parameters from an attribute block into
 * UNI 3.0 message IEs
 *
 * Arguments:
 *	usp	pointer to UNISIG protocol instance
 *	msg	pointer to the SETUP message
 *	ap	pointer to the attribute block
 *
 * Returns:
 *	0	everything OK
 *	else	error encountered
 *
 */
int
unisig_set_attrs(usp, msg, ap)
	struct unisig		*usp;
	struct unisig_msg	*msg;
	Atm_attributes		*ap;
{
	int			err = 0;

	/*
	 * Sanity check
	 */
	if (!msg || !ap)
		return(EINVAL);

	/*
	 * Set the AAL parameters (AAL 3/4 and AAL 5 only)
	 */
	if (ap->aal.tag == T_ATM_PRESENT) {
		if (!msg->msg_ie_aalp) {
			msg->msg_ie_aalp = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_aalp == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_aalp_absent,
				&msg->msg_ie_aalp->ie_u.ie_aalp,
				sizeof(ie_aalp_absent));
		msg->msg_ie_aalp->ie_ident = UNI_IE_AALP;
		msg->msg_ie_aalp->ie_aalp_aal_type = ap->aal.type;
		switch(ap->aal.type) {
		case ATM_AAL3_4:
			msg->msg_ie_aalp->ie_aalp_4_fwd_max_sdu =
					ap->aal.v.aal4.forward_max_SDU_size;
			msg->msg_ie_aalp->ie_aalp_4_bkwd_max_sdu =
					ap->aal.v.aal4.backward_max_SDU_size;
			msg->msg_ie_aalp->ie_aalp_4_mode = UNI_IE_AALP_A5_M_MSG;
			msg->msg_ie_aalp->ie_aalp_4_sscs_type =
					ap->aal.v.aal4.SSCS_type;
			if (ap->aal.v.aal4.mid_low == T_ATM_ABSENT) {
				msg->msg_ie_aalp->ie_aalp_4_mid_range =
					T_ATM_ABSENT;
			} else {
				if (usp->us_proto == ATM_SIG_UNI30) {
					msg->msg_ie_aalp->ie_aalp_4_mid_range =
						ap->aal.v.aal4.mid_high &
							UNI_IE_AALP_A3_R_MASK;
				} else {
					msg->msg_ie_aalp->ie_aalp_4_mid_range =
						((ap->aal.v.aal4.mid_low &
							UNI_IE_AALP_A3_R_MASK)
						    << UNI_IE_AALP_A3_R_SHIFT)
						  |
						 (ap->aal.v.aal4.mid_high &
							UNI_IE_AALP_A3_R_MASK);
				}
			}
			break;
		case ATM_AAL5:
			msg->msg_ie_aalp->ie_aalp_5_fwd_max_sdu =
					ap->aal.v.aal5.forward_max_SDU_size;
			msg->msg_ie_aalp->ie_aalp_5_bkwd_max_sdu =
					ap->aal.v.aal5.backward_max_SDU_size;
			msg->msg_ie_aalp->ie_aalp_5_mode =
					UNI_IE_AALP_A5_M_MSG;
			msg->msg_ie_aalp->ie_aalp_5_sscs_type =
					ap->aal.v.aal5.SSCS_type;
			break;
		}
	}

	/*
	 * Set traffic descriptor attributes
	 */
	if (ap->traffic.tag == T_ATM_PRESENT) {
		if (!msg->msg_ie_clrt) {
			msg->msg_ie_clrt = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_clrt == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_clrt_absent,
				&msg->msg_ie_clrt->ie_u.ie_clrt,
				sizeof(ie_clrt_absent));
		msg->msg_ie_clrt->ie_ident = UNI_IE_CLRT;
		msg->msg_ie_clrt->ie_clrt_fwd_peak =
				ap->traffic.v.forward.PCR_high_priority;
		msg->msg_ie_clrt->ie_clrt_fwd_peak_01 =
				ap->traffic.v.forward.PCR_all_traffic;
		msg->msg_ie_clrt->ie_clrt_fwd_sust =
				ap->traffic.v.forward.SCR_high_priority;
		msg->msg_ie_clrt->ie_clrt_fwd_sust_01 =
				ap->traffic.v.forward.SCR_all_traffic;
		msg->msg_ie_clrt->ie_clrt_fwd_burst =
				ap->traffic.v.forward.MBS_high_priority;
		msg->msg_ie_clrt->ie_clrt_fwd_burst_01 =
				ap->traffic.v.forward.MBS_all_traffic;
		msg->msg_ie_clrt->ie_clrt_bkwd_peak =
				ap->traffic.v.backward.PCR_high_priority;
		msg->msg_ie_clrt->ie_clrt_bkwd_peak_01 =
				ap->traffic.v.backward.PCR_all_traffic;
		msg->msg_ie_clrt->ie_clrt_bkwd_sust =
				ap->traffic.v.backward.SCR_high_priority;
		msg->msg_ie_clrt->ie_clrt_bkwd_sust_01 =
				ap->traffic.v.backward.SCR_all_traffic;
		msg->msg_ie_clrt->ie_clrt_bkwd_burst =
				ap->traffic.v.backward.MBS_high_priority;
		msg->msg_ie_clrt->ie_clrt_bkwd_burst_01 =
				ap->traffic.v.backward.MBS_all_traffic;
		msg->msg_ie_clrt->ie_clrt_best_effort =
				ap->traffic.v.best_effort;
		msg->msg_ie_clrt->ie_clrt_tm_options = 0;
		if (ap->traffic.v.forward.tagging) {
			msg->msg_ie_clrt->ie_clrt_tm_options |=
					UNI_IE_CLRT_TM_FWD_TAG;
		}
		if (ap->traffic.v.backward.tagging) {
			msg->msg_ie_clrt->ie_clrt_tm_options |=
					UNI_IE_CLRT_TM_BKWD_TAG;
		}
		if (msg->msg_ie_clrt->ie_clrt_tm_options == 0) {
			msg->msg_ie_clrt->ie_clrt_tm_options =
					T_ATM_ABSENT;
		}
	}

	/*
	 * Set broadband bearer attributes
	 */
	if (ap->bearer.tag == T_ATM_PRESENT) {
		if (!msg->msg_ie_bbcp) {
			msg->msg_ie_bbcp = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_bbcp == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_bbcp_absent,
				&msg->msg_ie_bbcp->ie_u.ie_bbcp,
				sizeof(ie_bbcp_absent));
		msg->msg_ie_bbcp->ie_ident = UNI_IE_BBCP;
		msg->msg_ie_bbcp->ie_bbcp_bearer_class =
				ap->bearer.v.bearer_class;
		msg->msg_ie_bbcp->ie_bbcp_traffic_type =
				ap->bearer.v.traffic_type;
		msg->msg_ie_bbcp->ie_bbcp_timing_req =
				ap->bearer.v.timing_requirements;
		msg->msg_ie_bbcp->ie_bbcp_clipping =
				ap->bearer.v.clipping_susceptibility;
		msg->msg_ie_bbcp->ie_bbcp_conn_config =
				ap->bearer.v.connection_configuration;
	}

	/*
	 * Set broadband high layer attributes
	 */
	if (ap->bhli.tag == T_ATM_PRESENT) {
		if (!msg->msg_ie_bhli) {
			msg->msg_ie_bhli = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_bhli == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_bhli_absent,
				&msg->msg_ie_bhli->ie_u.ie_bhli,
				sizeof(ie_bhli_absent));
		msg->msg_ie_bhli->ie_ident = UNI_IE_BHLI;
		msg->msg_ie_bhli->ie_bhli_type = ap->bhli.v.ID_type;
		switch (ap->bhli.v.ID_type) {
		case T_ATM_ISO_APP_ID:
			KM_COPY(ap->bhli.v.ID.ISO_ID,
					msg->msg_ie_bhli->ie_bhli_info,
					sizeof(ap->bhli.v.ID.ISO_ID));
			break;
		case T_ATM_USER_APP_ID:
			KM_COPY(ap->bhli.v.ID.user_defined_ID,
					msg->msg_ie_bhli->ie_bhli_info,
					sizeof(ap->bhli.v.ID.user_defined_ID));
			break;
		case T_ATM_VENDOR_APP_ID:
			KM_COPY(ap->bhli.v.ID.vendor_ID.OUI,
					msg->msg_ie_bhli->ie_bhli_info,
					sizeof(ap->bhli.v.ID.vendor_ID.OUI));
			KM_COPY(ap->bhli.v.ID.vendor_ID.app_ID,
					&msg->msg_ie_bhli->ie_bhli_info[sizeof(ap->bhli.v.ID.vendor_ID.OUI)-1],
					sizeof(ap->bhli.v.ID.vendor_ID.app_ID));
			break;
		}
	}

	/*
	 * Set Broadband low layer, user layer 2 and 3 attributes
	 */
	if (ap->blli.tag_l2 == T_ATM_PRESENT ||
			ap->blli.tag_l3 == T_ATM_PRESENT) {
		if (!msg->msg_ie_blli) {
			msg->msg_ie_blli = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_blli == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_blli_absent,
				&msg->msg_ie_blli->ie_u.ie_blli,
				sizeof(ie_blli_absent));
		msg->msg_ie_blli->ie_ident = UNI_IE_BLLI;

		if (ap->blli.tag_l2 == T_ATM_PRESENT) {
			switch(ap->blli.v.layer_2_protocol.ID_type) {
			case T_ATM_SIMPLE_ID:
				msg->msg_ie_blli->ie_blli_l2_id =
						ap->blli.v.layer_2_protocol.ID.simple_ID;
				break;
			case T_ATM_USER_ID:
				msg->msg_ie_blli->ie_blli_l2_id =
						UNI_IE_BLLI_L2P_USER;
				msg->msg_ie_blli->ie_blli_l2_user_proto =
						ap->blli.v.layer_2_protocol.ID.user_defined_ID;
				break;
			}
			if (ap->blli.v.layer_2_protocol.ID_type !=
					T_ATM_ABSENT) {
				msg->msg_ie_blli->ie_blli_l2_mode =
						ap->blli.v.layer_2_protocol.mode;
				msg->msg_ie_blli->ie_blli_l2_window =
						ap->blli.v.layer_2_protocol.window_size;
			}
		}

		if (ap->blli.tag_l3 == T_ATM_PRESENT) {
			switch (ap->blli.v.layer_3_protocol.ID_type) {
			case T_ATM_SIMPLE_ID:
				msg->msg_ie_blli->ie_blli_l3_id =
						ap->blli.v.layer_3_protocol.ID.simple_ID;
				break;

			case T_ATM_IPI_ID:
				msg->msg_ie_blli->ie_blli_l3_id =
						UNI_IE_BLLI_L3P_ISO9577;
				msg->msg_ie_blli->ie_blli_l3_ipi =
						ap->blli.v.layer_3_protocol.ID.IPI_ID;
				break;

			case T_ATM_SNAP_ID:
				msg->msg_ie_blli->ie_blli_l3_id =
						UNI_IE_BLLI_L3P_ISO9577;
				msg->msg_ie_blli->ie_blli_l3_ipi =
						UNI_IE_BLLI_L3IPI_SNAP;
				KM_COPY(ap->blli.v.layer_3_protocol.ID.SNAP_ID.OUI,
						msg->msg_ie_blli->ie_blli_l3_oui,
						sizeof(msg->msg_ie_blli->ie_blli_l3_oui));
				KM_COPY(ap->blli.v.layer_3_protocol.ID.SNAP_ID.PID,
						msg->msg_ie_blli->ie_blli_l3_pid,
						sizeof(msg->msg_ie_blli->ie_blli_l3_pid));
				break;

			case T_ATM_USER_ID:
				msg->msg_ie_blli->ie_blli_l3_id =
						UNI_IE_BLLI_L3P_USER;
				msg->msg_ie_blli->ie_blli_l3_user_proto =
						ap->blli.v.layer_3_protocol.ID.user_defined_ID;
				break;
			}
			if (ap->blli.v.layer_3_protocol.ID_type
					!= T_ATM_ABSENT) {
				msg->msg_ie_blli->ie_blli_l3_mode =
						ap->blli.v.layer_3_protocol.mode;
				msg->msg_ie_blli->ie_blli_l3_packet_size =
						ap->blli.v.layer_3_protocol.packet_size;
				msg->msg_ie_blli->ie_blli_l3_window =
						ap->blli.v.layer_3_protocol.window_size;
			}
		}
	}

	/*
	 * Set the called party address and subaddress
	 */
	if (ap->called.tag == T_ATM_PRESENT) {
		if (!msg->msg_ie_cdad) {
			msg->msg_ie_cdad = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_cdad == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_cdad_absent,
				&msg->msg_ie_cdad->ie_u.ie_cdad,
				sizeof(ie_cdad_absent));
		msg->msg_ie_cdad->ie_ident = UNI_IE_CDAD;
		ATM_ADDR_COPY(&ap->called.addr,
				&msg->msg_ie_cdad->ie_cdad_addr);

		if (ap->called.subaddr.address_format != T_ATM_ABSENT) {
			if (!msg->msg_ie_cdsa) {
				msg->msg_ie_cdsa = (struct ie_generic *)
						atm_allocate(&unisig_iepool);
				if (msg->msg_ie_cdsa == NULL) {
					err = ENOMEM;
					goto done;
				}
			}
			KM_COPY(&ie_cdsa_absent,
					&msg->msg_ie_cdsa->ie_u.ie_cdsa,
					sizeof(ie_cdsa_absent));
			msg->msg_ie_cdsa->ie_ident = UNI_IE_CDSA;
			ATM_ADDR_COPY(&ap->called.subaddr,
					&msg->msg_ie_cdsa->ie_cdsa_addr);
		}
	}

	/*
	 * Set the calling party address and subaddress
	 */

	if (ap->calling.tag == T_ATM_PRESENT) {
		if (!msg->msg_ie_cgad) {
			msg->msg_ie_cgad = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_cgad == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_cgad_absent,
				&msg->msg_ie_cgad->ie_u.ie_cgad,
				sizeof(ie_cgad_absent));
		msg->msg_ie_cgsa->ie_ident = UNI_IE_CGSA;
		ATM_ADDR_COPY(&ap->calling.addr,
				&msg->msg_ie_cgad->ie_cgad_addr);

		if (ap->calling.subaddr.address_format !=
				T_ATM_ABSENT) {
			if (!msg->msg_ie_cgsa) {
				msg->msg_ie_cgsa = (struct ie_generic *)
						atm_allocate(&unisig_iepool);
				if (msg->msg_ie_cgsa == NULL) {
					err = ENOMEM;
					goto done;
				}
			}
			KM_COPY(&ie_cgsa_absent,
					&msg->msg_ie_cgsa->ie_u.ie_cgsa,
					sizeof(ie_cgsa_absent));
			msg->msg_ie_cgsa->ie_ident = UNI_IE_CGSA;
			ATM_ADDR_COPY(&ap->calling.subaddr,
					&msg->msg_ie_cgsa->ie_cgsa_addr);
		}
	}

	/*
	 * Set quality of service attributes
	 */
	if (ap->qos.tag == T_ATM_PRESENT) {
		if (!msg->msg_ie_qosp) {
			msg->msg_ie_qosp = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_qosp == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_qosp_absent,
				&msg->msg_ie_qosp->ie_u.ie_qosp,
				sizeof(ie_qosp_absent));
		msg->msg_ie_qosp->ie_ident = UNI_IE_QOSP;
		if (usp->us_proto == ATM_SIG_UNI30)
			msg->msg_ie_qosp->ie_coding = UNI_IE_CODE_STD;
		else if ((ap->qos.v.forward.qos_class == 
					T_ATM_QOS_CLASS_0) || 
			   (ap->qos.v.backward.qos_class == 
					T_ATM_QOS_CLASS_0))
			msg->msg_ie_qosp->ie_coding = UNI_IE_CODE_CCITT;
		else
			msg->msg_ie_qosp->ie_coding = ap->qos.v.coding_standard;
		msg->msg_ie_qosp->ie_qosp_fwd_class =
				ap->qos.v.forward.qos_class;
		msg->msg_ie_qosp->ie_qosp_bkwd_class =
				ap->qos.v.backward.qos_class;
	}

	/*
	 * Set transit network attributes
	 */
	if (ap->transit.tag == T_ATM_PRESENT &&
				ap->transit.v.length != 0) {
		if (!msg->msg_ie_trnt) {
			msg->msg_ie_trnt = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_trnt == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_trnt_absent,
				&msg->msg_ie_trnt->ie_u.ie_trnt,
				sizeof(ie_trnt_absent));
		msg->msg_ie_trnt->ie_ident = UNI_IE_TRNT;
		msg->msg_ie_trnt->ie_trnt_id_type =
				UNI_IE_TRNT_IDT_NATL;
		msg->msg_ie_trnt->ie_trnt_id_plan =
				UNI_IE_TRNT_IDP_CIC;
		KM_COPY(ap->transit.v.network_id,
				msg->msg_ie_trnt->ie_trnt_id,
				ap->transit.v.length);
	}

	/*
	 * Set cause code
	 */
	if (ap->cause.tag == T_ATM_PRESENT) {
		if (!msg->msg_ie_caus) {
			msg->msg_ie_caus = (struct ie_generic *)
					atm_allocate(&unisig_iepool);
			if (msg->msg_ie_caus == NULL) {
				err = ENOMEM;
				goto done;
			}
		}
		KM_COPY(&ie_caus_absent,
				&msg->msg_ie_caus->ie_u.ie_caus,
				sizeof(ie_caus_absent));
		msg->msg_ie_caus->ie_ident = UNI_IE_CAUS;
		msg->msg_ie_caus->ie_coding =
				ap->cause.v.coding_standard;
		msg->msg_ie_caus->ie_caus_loc =
				ap->cause.v.location;
		msg->msg_ie_caus->ie_caus_cause =
				ap->cause.v.cause_value;

		/*
		 * Don't copy the diagnostics from the attribute
		 * block, as there's no way to tell how much of
		 * the diagnostic field is relevant
		 */
		msg->msg_ie_caus->ie_caus_diag_len = 0;
	}

done:
	return(err);
}
